Unstable 0.1300.0.3

This commit is contained in:
Markus Isberg
2021-03-25 15:40:24 +02:00
parent 874616027b
commit 58c50a235d
136 changed files with 2486 additions and 1008 deletions
@@ -50,6 +50,11 @@ namespace Barotrauma
float reduction = IsPriority ? 1 : isSelected ? 2 : 3;
float max = AIObjectiveManager.LowestOrderPriority - reduction;
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
if (decontainObjective == null)
{
// Halve the priority until there's a decontain objective (a valid container was found).
Priority /= 2;
}
}
return Priority;
}
@@ -126,5 +131,13 @@ namespace Barotrauma
itemIndex = 0;
decontainObjective = null;
}
public void DropTarget()
{
if (item != null && character.HasItem(item))
{
item.Drop(character);
}
}
}
}
@@ -2,6 +2,7 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Barotrauma
{
@@ -29,7 +30,21 @@ namespace Barotrauma
this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null));
}
protected override float TargetEvaluation() => Targets.Any() ? (objectiveManager.IsOrder(this) ? objectiveManager.GetOrderPriority(this) : AIObjectiveManager.RunPriority - 1) : 0;
protected override float TargetEvaluation()
{
if (Targets.None()) { return 0; }
if (objectiveManager.IsOrder(this))
{
float prio = objectiveManager.GetOrderPriority(this);
if (subObjectives.All(so => so.SubObjectives.None()))
{
// If none of the subobjectives have subobjectives, no valid container was found. In this case, let's reduce the priority below the run threshold.
prio = Math.Min(prio, AIObjectiveManager.RunPriority - 1);
}
return prio;
}
return AIObjectiveManager.RunPriority - 0.5f;
}
protected override bool Filter(Item target)
{
@@ -65,10 +80,10 @@ namespace Barotrauma
return true;
}
public static bool IsValidContainer(Item item, Character character) =>
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
public static bool IsValidContainer(Item item, Character character, bool allowUnloading = true) =>
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && allowUnloading && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
public static bool IsValidTarget(Item item, Character character, bool checkInventory)
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
{
if (item == null) { return false; }
if (item.IgnoreByAI) { return false; }
@@ -76,7 +91,7 @@ namespace Barotrauma
if (item.SpawnedInOutpost) { return false; }
if (item.ParentInventory != null)
{
if (item.Container == null || !IsValidContainer(item.Container, character)) { return false; }
if (item.Container == null || !IsValidContainer(item.Container, character, allowUnloading)) { return false; }
}
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
var pickable = item.GetComponent<Pickable>();
@@ -127,5 +142,17 @@ namespace Barotrauma
}
return canEquip;
}
public override void OnDeselected()
{
base.OnDeselected();
foreach (var subObjective in SubObjectives)
{
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
{
cleanUpObjective.DropTarget();
}
}
}
}
}
@@ -457,7 +457,7 @@ namespace Barotrauma
priority /= 2;
}
}
if (Enemy.Stun > 1)
if (Enemy.IsKnockedDown)
{
// Enemy is stunned, reduce the priority of stunner weapons.
Attack attack = GetAttackDefinition(weapon);
@@ -730,11 +730,12 @@ namespace Barotrauma
{
IgnoreIfTargetDead = true,
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = Enemy.DisplayName
TargetName = Enemy.DisplayName,
AlwaysUseEuclideanDistance = false
},
onAbandon: () => Abandon = true);
if (followTargetObjective == null) { return; }
if (Mode == CombatMode.Arrest && Enemy.Stun > 2)
if (Mode == CombatMode.Arrest && (Enemy.Stun > 1 || Enemy.IsKnockedDown))
{
if (HumanAIController.HasItem(character, "handlocker", out _))
{
@@ -742,8 +743,8 @@ namespace Barotrauma
{
arrestingRegistered = true;
followTargetObjective.Completed += OnArrestTargetReached;
followTargetObjective.CloseEnough = 100;
}
followTargetObjective.CloseEnough = 100;
}
else
{
@@ -759,7 +760,7 @@ namespace Barotrauma
SteeringManager.Reset();
}
}
if (followTargetObjective != null)
if (!arrestingRegistered && followTargetObjective != null)
{
followTargetObjective.CloseEnough =
WeaponComponent is RangedWeapon ? 1000 :
@@ -782,7 +783,7 @@ namespace Barotrauma
private void OnArrestTargetReached()
{
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
{
var handCuffs = matchingItems.First();
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
@@ -802,8 +803,8 @@ namespace Barotrauma
}
}
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
IsCompleted = true;
}
IsCompleted = true;
}
/// <summary>
@@ -937,7 +938,14 @@ namespace Barotrauma
return;
}
if (reloadTimer > 0) { return; }
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
if (Mode == CombatMode.Arrest)
{
// If the target is arrested or if it's stunned and we can't lock the target up, consider the objective done.
if (Enemy.IsKnockedDown && !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false) || HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true))
{
IsCompleted = true;
}
}
if (holdFireCondition != null && holdFireCondition()) { return; }
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
if (WeaponComponent is MeleeWeapon meleeWeapon)
@@ -1017,6 +1025,8 @@ namespace Barotrauma
private void UseWeapon(float deltaTime)
{
// Never allow to attack characters with deadly weapons while trying to arrest.
if (Mode == CombatMode.Arrest && isLethalWeapon) { return; }
float reloadTime = 0;
if (WeaponComponent is RangedWeapon rangedWeapon)
{
@@ -1038,10 +1048,16 @@ namespace Barotrauma
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
}
private bool ShouldUnequipWeapon =>
Weapon != null &&
character.Submarine != null &&
character.Submarine.TeamID == character.TeamID &&
Character.CharacterList.None(c => c.Submarine == character.Submarine && HumanAIController.IsActive(c) && !HumanAIController.IsFriendly(character, c) && HumanAIController.VisibleHulls.Contains(c.CurrentHull));
protected override void OnCompleted()
{
base.OnCompleted();
if (Weapon != null)
if (ShouldUnequipWeapon)
{
Unequip();
}
@@ -1051,7 +1067,7 @@ namespace Barotrauma
protected override void OnAbandon()
{
base.OnAbandon();
if (Weapon != null)
if (ShouldUnequipWeapon)
{
Unequip();
}
@@ -38,7 +38,7 @@ namespace Barotrauma
Priority = 0;
Abandon = true;
}
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
{
Priority = 0;
Abandon = true;
@@ -10,6 +10,8 @@ namespace Barotrauma
{
public override string DebugTag => "get item";
public override bool AbandonWhenCannotCompleteSubjectives => false;
private readonly bool equip;
public HashSet<Item> ignoredItems = new HashSet<Item>();
@@ -225,18 +227,8 @@ namespace Barotrauma
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
{
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
abortCondition = obj =>
{
bool abort = targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget;
if (abort)
{
// Fail silently if someone takes the suit.
obj.speakIfFails = false;
}
return abort;
},
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
abortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
SpeakIfFails = false
};
},
onAbandon: () =>
@@ -263,13 +255,18 @@ namespace Barotrauma
if (targetItem == null)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item was defined.", Color.Red);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
#endif
Abandon = true;
}
return;
}
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.followControlledCharacter);
bool hasCalledPathFinder = false;
int itemsPerFrame = (int)priority;
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
{
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
@@ -310,8 +307,18 @@ namespace Barotrauma
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist));
itemPriority *= distanceFactor;
itemPriority *= item.Condition / item.MaxCondition;
//ignore if the item has a lower priority than the currently selected one
// Ignore if the item has a lower priority than the currently selected one
if (itemPriority < currItemPriority) { continue; }
if (!hasCalledPathFinder && PathSteering != null && checkPath)
{
// While following the player, let's ensure that there's a valid path to the target before accepting it.
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
// Only allow one path find call per frame.
hasCalledPathFinder = true;
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
if (path.Unreachable) { continue; }
}
currItemPriority = itemPriority;
targetItem = item;
moveToTarget = rootInventoryOwner ?? item;
@@ -326,7 +333,7 @@ namespace Barotrauma
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
#endif
Abandon = true;
}
@@ -345,7 +352,7 @@ namespace Barotrauma
else
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
#endif
Abandon = true;
}
@@ -393,11 +400,30 @@ namespace Barotrauma
/// </summary>
private void ResetInternal()
{
goToObjective = null;
RemoveSubObjective(ref goToObjective);
targetItem = originalTarget;
moveToTarget = targetItem?.GetRootInventoryOwner();
isDoneSeeking = false;
currSearchIndex = 0;
currItemPriority = 0;
}
protected override void OnAbandon()
{
base.OnAbandon();
if (moveToTarget == null) { return; }
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
#endif
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null)
{
string TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString();
string msg = TargetName == null ? TextManager.Get("dialogcannotreachtarget", true) : TextManager.GetWithVariable("dialogcannotreachtarget", "[name]", TargetName, formatCapitals: !(moveToTarget is Character));
if (msg != null)
{
character.Speak(msg, identifier: "dialogcannotreachtarget", minDurationBetweenSimilar: 20.0f);
}
}
}
}
}
@@ -30,7 +30,7 @@ namespace Barotrauma
public bool followControlledCharacter;
public bool mimic;
public bool speakIfFails = true;
public bool SpeakIfFails { get; set; } = true;
public float extraDistanceWhileSwimming;
public float extraDistanceOutsideSub;
@@ -67,6 +67,8 @@ namespace Barotrauma
public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; }
public bool AlwaysUseEuclideanDistance { get; set; } = true;
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
public override bool AllowOutsideSubmarine => AllowGoingOutside;
@@ -88,12 +90,7 @@ namespace Barotrauma
Abandon = !isOrder;
return Priority;
}
if (followControlledCharacter && Character.Controlled == null)
{
Priority = 0;
Abandon = !isOrder;
}
if (Target is Entity e && e.Removed)
if (Target == null || Target is Entity e && e.Removed)
{
Priority = 0;
Abandon = !isOrder;
@@ -147,11 +144,10 @@ namespace Barotrauma
private void SpeakCannotReach()
{
if (!character.IsOnPlayerTeam) { return; }
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
#endif
if (objectiveManager.HasOrders() && DialogueIdentifier != null && speakIfFails)
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null && DialogueIdentifier != null && SpeakIfFails)
{
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
if (msg != null)
@@ -165,12 +161,15 @@ namespace Barotrauma
{
if (followControlledCharacter)
{
if (Character.Controlled == null || !HumanAIController.IsFriendly(Character.Controlled))
if (Character.Controlled != null && HumanAIController.IsFriendly(Character.Controlled))
{
Target = Character.Controlled;
}
if (Target == null)
{
Abandon = true;
return;
}
Target = Character.Controlled;
}
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
{
@@ -260,6 +259,7 @@ namespace Barotrauma
}
if (needsEquipment)
{
SteeringManager.Reset();
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
@@ -288,9 +288,14 @@ namespace Barotrauma
}
}
}
float maxGapDistance = 500;
Character targetCharacter = Target as Character;
if (character.AnimController.InWater)
{
if (character.CurrentHull == null)
if (character.CurrentHull == null ||
followControlledCharacter &&
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
{
if (seekGapsTimer > 0)
{
@@ -298,7 +303,7 @@ namespace Barotrauma
}
else
{
SeekGaps(maxDistance: 500);
SeekGaps(maxGapDistance);
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
if (TargetGap != null)
{
@@ -327,7 +332,7 @@ namespace Barotrauma
}
if (TargetGap != null)
{
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, TargetGap.FlowTargetHull.WorldPosition, deltaTime))
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, followControlledCharacter ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return;
@@ -347,7 +352,7 @@ namespace Barotrauma
float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
(targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{
// Currently equipped scooter
@@ -528,17 +533,24 @@ namespace Barotrauma
{
Gap selectedGap = null;
float selectedDistance = -1;
Vector2 toTargetNormalized = Vector2.Normalize(Target.WorldPosition - character.WorldPosition);
foreach (Gap gap in Gap.GapList)
{
if (gap.Open < 1) { continue; }
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
float distance = Vector2.DistanceSquared(character.WorldPosition, gap.WorldPosition);
if (distance > maxDistance * maxDistance) { continue; }
if (selectedGap == null || distance < selectedDistance)
if (gap.Submarine == null) { continue; }
if (!followControlledCharacter)
{
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
}
Vector2 toGap = gap.WorldPosition - character.WorldPosition;
if (Vector2.Dot(Vector2.Normalize(toGap), toTargetNormalized) < 0) { continue; }
float squaredDistance = toGap.LengthSquared();
if (squaredDistance > maxDistance * maxDistance) { continue; }
if (selectedGap == null || squaredDistance < selectedDistance)
{
selectedGap = gap;
selectedDistance = distance;
selectedDistance = squaredDistance;
}
}
TargetGap = selectedGap;
@@ -555,7 +567,7 @@ namespace Barotrauma
//otherwise characters can let go of the ladders too soon once they're close enough to the target
if (PathSteering.CurrentPath.NextNode != null) { return false; }
}
if (!character.AnimController.InWater)
if (!AlwaysUseEuclideanDistance && !character.AnimController.InWater)
{
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
if (yDiff > CloseEnough) { return false; }
@@ -495,7 +495,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true) && !ignoredItems.Contains(item))
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
{
itemsToClean.Add(item);
}
@@ -540,5 +540,17 @@ namespace Barotrauma
ignoredItems.Clear();
autonomousObjectiveRetryTimer = 10;
}
public override void OnDeselected()
{
base.OnDeselected();
foreach (var subObjective in SubObjectives)
{
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
{
cleanUpObjective.DropTarget();
}
}
}
}
}
@@ -445,7 +445,7 @@ namespace Barotrauma
extraDistanceWhileSwimming = 100,
AllowGoingOutside = true,
IgnoreIfTargetDead = true,
followControlledCharacter = orderGiver == character,
followControlledCharacter = true,
mimic = true,
DialogueIdentifier = "dialogcannotreachplace"
};
@@ -69,10 +69,9 @@ namespace Barotrauma
{
if (!isOrder)
{
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC ||
HumanAIController.IsTrueForAnyCrewMember(c =>
c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target))
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC)
{
// The reactor was previously operated by a player -> ignore.
Priority = 0;
return Priority;
}
@@ -89,7 +88,6 @@ namespace Barotrauma
case "powerup":
// Check that we don't already have another order that is targeting the same item.
// Without this the autonomous objective will tell the bot to turn the reactor on again.
if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective)))
{
Priority = 0;
@@ -177,9 +175,9 @@ namespace Barotrauma
}
if (operateTarget != null)
{
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
{
// Another crew member is already targeting this entity.
// Another crew member is already targeting this entity (leak).
Abandon = true;
return;
}