(89fea1963) Refactoring and fixing: - Remove relatively expensive HasAccessToPath calls. - Fix finding diving gear subobjective of goto main objective not checking if we need the gear. - Move the abandon checks from CanBeCompleted property under Act method and use the inherited property. Fixes incorrect results when the goto objective is not the active objective. - Improve the support for concurrent sub objectives.

This commit is contained in:
Joonas Rikkonen
2019-05-16 05:38:02 +03:00
parent 96eb27140e
commit 4695e6f92b
16 changed files with 290 additions and 297 deletions
@@ -432,7 +432,7 @@ namespace Barotrauma
/// <summary>
/// Check whether the character has a diving mask in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource");
public static bool HasDivingMask(Character character) => HasItem(character, "diving", "oxygensource");
public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0)
{
@@ -528,7 +528,7 @@ namespace Barotrauma
if (hull == null) { return 0; }
bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(Character);
bool ignoreOxygen = ignoreWater || HasDivingGear(Character);
bool ignoreOxygen = ignoreWater || HasDivingMask(Character);
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
}
@@ -38,7 +38,6 @@ namespace Barotrauma
/// </summary>
public virtual bool IsLoop { get; set; }
public IEnumerable<AIObjective> SubObjectives => subObjectives;
public AIObjective CurrentSubObjective { get; private set; }
public event Action Completed;
@@ -90,11 +89,6 @@ namespace Barotrauma
}
}
if (!subObjectives.Contains(CurrentSubObjective))
{
CurrentSubObjective = null;
}
foreach (AIObjective objective in subObjectives)
{
objective.TryComplete(deltaTime);
@@ -123,8 +117,14 @@ namespace Barotrauma
{
if (subObjectives.None()) { return; }
subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
CurrentSubObjective = SubObjectives.First();
CurrentSubObjective.SortSubObjectives();
if (ConcurrentObjectives)
{
subObjectives.ForEach(so => SortSubObjectives());
}
else
{
subObjectives.First().SortSubObjectives();
}
}
public virtual float GetPriority() => Priority * PriorityModifier;
@@ -137,10 +137,12 @@ namespace Barotrauma
}
else if (objectiveManager.WaitTimer <= 0)
{
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
if ((objectiveManager.CurrentObjective == this || subObjective == this))
if (objectiveManager.CurrentObjective != null)
{
Priority += Devotion * PriorityModifier * deltaTime;
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this))
{
Priority += Devotion * PriorityModifier * deltaTime;
}
}
Priority = MathHelper.Clamp(Priority, 0, 100);
subObjectives.ForEach(so => so.Update(deltaTime));
@@ -202,7 +204,11 @@ namespace Barotrauma
public virtual void OnSelected()
{
//SteeringManager.Reset();
// Should we reset steering here?
//if (!ConcurrentObjectives)
//{
// SteeringManager.Reset();
//}
}
public virtual void Reset() { }
@@ -27,7 +27,6 @@ namespace Barotrauma
public override float GetPriority()
{
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; }
if (Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c))) { return 0; }
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f;
@@ -80,7 +80,7 @@ namespace Barotrauma
}
else if (needsDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character);
needsEquipment = !HumanAIController.HasDivingMask(character);
}
if (needsEquipment)
{
@@ -601,6 +601,49 @@ namespace Barotrauma
{
character.AIController.SteeringManager.Reset();
}
if (goToObjective != null) { return; }
if (currentHull == null) { return; }
//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
Vector2 escapeVel = Vector2.Zero;
foreach (FireSource fireSource in currentHull.FireSources)
{
Vector2 dir = character.Position - fireSource.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
foreach (Character enemy in Character.CharacterList)
{
//don't run from friendly NPCs
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{
Vector2 dir = character.Position - enemy.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
}
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();
}
}
}
@@ -634,7 +677,6 @@ namespace Barotrauma
unreachable.Add(hull);
continue;
}
if (!PathSteering.HasAccessToPath(path)) { continue; }
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
@@ -346,6 +346,31 @@ namespace Barotrauma
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
#endif
abandon = true;
return;
}
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
// TODO: use the collider size/reach?
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
{
HumanAIController.AnimController.Crouching = true;
}
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
if (canOperate)
{
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
}
else
{
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
}
var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null)
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
#endif
abandon = true;
return;
@@ -9,9 +9,6 @@ namespace Barotrauma
{
public override string DebugTag => "go to";
const float checkAccessInterval = 1;
private float accessCheckTimer;
private AIObjectiveFindDivingGear findDivingGear;
private Vector2 targetPos;
private bool repeat;
@@ -36,43 +33,6 @@ namespace Barotrauma
return 1.0f;
}
public override bool CanBeCompleted
{
get
{
if (!abandon)
{
if (FollowControlledCharacter && Character.Controlled == null)
{
abandon = true;
}
else if (Target != null && Target.Removed)
{
abandon = true;
}
else if (!repeat && waitUntilPathUnreachable < 0)
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
{
abandon = PathSteering.CurrentPath.Unreachable;
}
}
}
if (abandon)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
#endif
if (objectiveManager.CurrentOrder != null)
{
character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
}
character.AIController.SteeringManager.Reset();
}
return base.CanBeCompleted;
}
}
public Entity Target { get; private set; }
public Vector2 TargetPos => Target != null ? Target.SimPosition : targetPos;
@@ -106,12 +66,17 @@ namespace Barotrauma
{
if (FollowControlledCharacter)
{
if (Character.Controlled == null) { return; }
if (Character.Controlled == null)
{
abandon = true;
return;
}
Target = Character.Controlled;
}
if (Target == character)
{
character.AIController.SteeringManager.Reset();
abandon = true;
return;
}
waitUntilPathUnreachable -= deltaTime;
@@ -119,7 +84,17 @@ namespace Barotrauma
{
character.SelectedConstruction = null;
}
if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); }
if (Target != null)
{
if (Target.Removed)
{
abandon = true;
}
else
{
character.AIController.SelectTarget(Target.AiTarget);
}
}
Vector2 currTargetPos = Vector2.Zero;
if (Target == null)
{
@@ -153,30 +128,46 @@ namespace Barotrauma
if (isInside && targetIsOutside && !AllowGoingOutside)
{
abandon = true;
return;
}
if (accessCheckTimer <= 0)
else if (!repeat && waitUntilPathUnreachable < 0)
{
accessCheckTimer = checkAccessInterval;
if (insideSteering && !PathSteering.HasAccessToPath(PathSteering.CurrentPath))
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
{
abandon = true;
return;
abandon = PathSteering.CurrentPath.Unreachable;
}
}
if (abandon)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
#endif
if (objectiveManager.CurrentOrder != null)
{
character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
}
character.AIController.SteeringManager.Reset();
}
else
{
accessCheckTimer -= deltaTime;
}
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
if (getDivingGearIfNeeded)
{
if (targetIsOutside ||
Target is Hull h && HumanAIController.NeedsDivingGear(h) ||
Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) ||
Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull))
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
if (getDivingGearIfNeeded)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull;
bool needsDivingGear = HumanAIController.NeedsDivingGear(targetHull);
bool needsDivingSuit = needsDivingGear && (targetIsOutside || targetHull.WaterPercentage > 90);
bool needsEquipment = false;
if (needsDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character);
}
else if (needsDivingGear)
{
needsEquipment = !HumanAIController.HasDivingMask(character);
}
if (needsEquipment)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager));
}
}
}
}
@@ -74,6 +74,21 @@ namespace Barotrauma
}
}
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
{
if (randomTimer > 0)
{
randomTimer -= deltaTime;
}
else
{
SetRandom();
}
}
}
public override bool IsCompleted() => false;
public override bool CanBeCompleted => true;
@@ -142,10 +157,9 @@ namespace Barotrauma
// 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 would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition);
if (path.Unreachable ||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) ||
IsForbidden(n.CurrentHull)) ||
!PathSteering.HasAccessToPath(path))
if (path.Unreachable ||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) ||
IsForbidden(n.CurrentHull)))
{
//can't go to this room, remove it from the list and try another room next frame
int index = targetHulls.IndexOf(randomHull);
@@ -25,7 +25,6 @@ namespace Barotrauma
public override float GetPriority()
{
if (goToObjective != null && !goToObjective.CanBeCompleted) { return 0; }
if (component.Item.ConditionPercentage <= 0) { return 0; }
if (objectiveManager.CurrentOrder == this)
{
@@ -140,6 +139,10 @@ namespace Barotrauma
{
isCompleted = true;
}
if (component.AIOperate(deltaTime, character, this))
{
isCompleted = true;
}
}
else
{
@@ -620,6 +620,25 @@ namespace Barotrauma.Items.Components
}
}
if (targetItem.Prefab.DeconstructItems.Any())
{
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
}
else
{
if (outputContainer.Inventory.Items.All(i => i != null))
{
targetItem.Drop(dropper: null);
}
else
{
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
}
if (targetItem.Prefab.DeconstructItems.Any())
{
inputContainer.Inventory.RemoveItem(targetItem);
@@ -212,6 +212,33 @@ namespace Barotrauma.Items.Components
}
}
public Vector2? PosToMaintain
{
get { return posToMaintain; }
set { posToMaintain = value; }
}
struct ObstacleDebugInfo
{
public Vector2 Point1;
public Vector2 Point2;
public Vector2? Intersection;
public float Dot;
public Vector2 AvoidStrength;
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
{
Point1 = edge.Point1;
Point2 = edge.Point2;
Intersection = intersection;
Dot = dot;
AvoidStrength = avoidStrength;
}
}
//edge point 1, edge point 2, avoid strength
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
@@ -1532,6 +1532,10 @@ namespace Barotrauma
{
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
}
if (!broken)
{
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
}
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
@@ -341,6 +341,25 @@ namespace Barotrauma
}
}
public string DisplayName
{
get;
private set;
}
private string roomName;
[Editable, Serialize("", true, translationTextTag: "RoomName.")]
public string RoomName
{
get { return roomName; }
set
{
if (roomName == value) { return; }
roomName = value;
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
}
}
public override Rectangle Rect
{
get