251 lines
10 KiB
C#
251 lines
10 KiB
C#
using FarseerPhysics;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class AIObjectiveIdle : AIObjective
|
|
{
|
|
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;
|
|
|
|
private float standStillTimer;
|
|
private float walkDuration;
|
|
|
|
public AIObjectiveIdle(Character character) : base(character, "")
|
|
{
|
|
standStillTimer = Rand.Range(-10.0f, 10.0f);
|
|
walkDuration = Rand.Range(0.0f, 10.0f);
|
|
}
|
|
|
|
public override bool IsCompleted() => false;
|
|
public override bool CanBeCompleted => true;
|
|
|
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
protected override void Act(float deltaTime)
|
|
{
|
|
if (PathSteering == null) return;
|
|
|
|
//don't keep dragging others when idling
|
|
if (character.SelectedCharacter != null)
|
|
{
|
|
character.DeselectCharacter();
|
|
}
|
|
if (!character.IsClimbing)
|
|
{
|
|
character.SelectedConstruction = null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (character.AnimController.InWater || character.IsClimbing)
|
|
{
|
|
standStillTimer = 0;
|
|
}
|
|
if (newTargetTimer <= 0.0f)
|
|
{
|
|
currentTarget = FindRandomHull();
|
|
|
|
if (currentTarget != null)
|
|
{
|
|
string errorMsg = null;
|
|
#if DEBUG
|
|
bool isRoomNameFound = currentTarget.RoomName != null;
|
|
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")";
|
|
#endif
|
|
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg);
|
|
PathSteering.SetPath(path);
|
|
}
|
|
|
|
newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax);
|
|
}
|
|
|
|
newTargetTimer -= deltaTime;
|
|
|
|
//wander randomly
|
|
// - if reached the end of the path
|
|
// - if the target is unreachable
|
|
// - if the path requires going outside
|
|
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(walkDurationMin, walkDurationMax);
|
|
PathSteering.Reset();
|
|
return;
|
|
}
|
|
if (standStillTimer < -walkDuration)
|
|
{
|
|
standStillTimer = Rand.Range(standStillMin, standStillMax);
|
|
}
|
|
|
|
Wander(deltaTime);
|
|
return;
|
|
}
|
|
|
|
if (currentTarget != null)
|
|
{
|
|
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition);
|
|
}
|
|
}
|
|
|
|
public void Wander(float deltaTime)
|
|
{
|
|
//steer away from edges of the hull
|
|
if (character.AnimController.CurrentHull != null && !character.IsClimbing)
|
|
{
|
|
float leftDist = character.Position.X - character.AnimController.CurrentHull.Rect.X;
|
|
float rightDist = character.AnimController.CurrentHull.Rect.Right - character.Position.X;
|
|
if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance)
|
|
{
|
|
if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2)
|
|
{
|
|
PathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist));
|
|
}
|
|
else
|
|
{
|
|
PathSteering.Reset();
|
|
}
|
|
}
|
|
else if (leftDist < WallAvoidDistance)
|
|
{
|
|
//PathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance - leftDist) / WallAvoidDistance);
|
|
PathSteering.SteeringManual(deltaTime, Vector2.UnitX);
|
|
PathSteering.WanderAngle = 0.0f;
|
|
}
|
|
else if (rightDist < WallAvoidDistance)
|
|
{
|
|
//PathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance - rightDist) / WallAvoidDistance);
|
|
PathSteering.SteeringManual(deltaTime, -Vector2.UnitX);
|
|
PathSteering.WanderAngle = MathHelper.Pi;
|
|
}
|
|
else
|
|
{
|
|
SteeringManager.SteeringWander();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SteeringManager.SteeringWander();
|
|
}
|
|
if (!character.IsClimbing && !character.AnimController.InWater)
|
|
{
|
|
//reset vertical steering to prevent dropping down from platforms etc
|
|
character.AIController.SteeringManager.ResetY();
|
|
}
|
|
}
|
|
|
|
private readonly List<Hull> targetHulls = new List<Hull>(20);
|
|
private readonly List<float> hullWeights = new List<float>(20);
|
|
|
|
private Hull FindRandomHull()
|
|
{
|
|
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)
|
|
{
|
|
foreach (WayPoint wp in WayPoint.WayPointList)
|
|
{
|
|
if (wp.SpawnType != SpawnType.Human || wp.CurrentHull == null) { continue; }
|
|
|
|
foreach (string tag in wp.IdCardTags)
|
|
{
|
|
if (idCard.HasTag(tag))
|
|
{
|
|
targetHull = wp.CurrentHull;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (targetHull == null)
|
|
{
|
|
targetHulls.Clear();
|
|
hullWeights.Clear();
|
|
foreach (var hull in Hull.hullList)
|
|
{
|
|
if (HumanAIController.UnsafeHulls.Contains(hull)) { continue; }
|
|
if (hull.Submarine == null) { continue; }
|
|
if (hull.Submarine.TeamID != character.TeamID) { continue; }
|
|
// If the character is inside, only take connected hulls into account.
|
|
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
|
if (IsForbidden(hull)) { continue; }
|
|
// Ignore hulls that are too low to stand inside
|
|
if (character.AnimController is HumanoidAnimController animController)
|
|
{
|
|
if (hull.CeilingHeight < ConvertUnits.ToDisplayUnits(animController.HeadPosition.Value))
|
|
{
|
|
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.Unreachable) { continue; }
|
|
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; }
|
|
|
|
if (!targetHulls.Contains(hull))
|
|
{
|
|
targetHulls.Add(hull);
|
|
hullWeights.Add(hull.Volume);
|
|
}
|
|
}
|
|
return ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
|
}
|
|
return targetHull;
|
|
}
|
|
|
|
private bool IsForbidden(Hull hull)
|
|
{
|
|
if (hull == null) { return true; }
|
|
string hullName = hull.RoomName?.ToLowerInvariant();
|
|
bool isForbidden = hullName == "ballast" || hullName == "airlock";
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
if (item.CurrentHull == hull && (item.HasTag("ballast") || item.HasTag("airlock")))
|
|
{
|
|
isForbidden = true;
|
|
break;
|
|
}
|
|
}
|
|
return isForbidden;
|
|
}
|
|
|
|
public override bool IsDuplicate(AIObjective otherObjective)
|
|
{
|
|
return (otherObjective is AIObjectiveIdle);
|
|
}
|
|
}
|
|
}
|