Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs
Eero 046483b9da Revert "OBT1.1.0 Merge branch 'dev_pte' into dev"
This reverts commit 177cf89756, reversing
changes made to 42ba733cd4.
2025-12-29 11:18:11 +08:00

861 lines
33 KiB
C#

using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma.Items.Components
{
partial class Steering : Powered, IServerSerializable, IClientSerializable
{
public const float AutopilotMinDistToPathNode = 30.0f;
private const float AutopilotRayCastInterval = 0.5f;
private const float RecalculatePathInterval = 5.0f;
private const float AutoPilotSteeringLerp = 0.1f;
private const float AutoPilotMaxSpeed = 0.5f;
private const float AIPilotMaxSpeed = 1.0f;
/// <summary>
/// How many units before crush depth the pressure warning is shown
/// </summary>
public const float PressureWarningThreshold = 500.0f;
/// <summary>
/// How fast the steering vector adjusts when the nav terminal is operated by something else than a character (= signals)
/// </summary>
const float DefaultSteeringAdjustSpeed = 0.2f;
private Vector2 targetVelocity;
private Vector2 steeringInput;
private bool autoPilot;
private Vector2? posToMaintain;
private SteeringPath steeringPath;
private PathFinder pathFinder;
private float networkUpdateTimer;
private bool unsentChanges;
private float autopilotRayCastTimer;
private float autopilotRecalculatePathTimer;
private Vector2 avoidStrength;
private float neutralBallastLevel;
private float steeringAdjustSpeed = 1.0f;
private Character user;
private Sonar sonar;
private Submarine controlledSub;
public Submarine ControlledSub => controlledSub;
// AI interfacing
public Vector2 AITacticalTarget { get; set; }
public float AIRamTimer { get; set; }
bool navigateTactically; // this will be removed after rewriting steering to use an enum
private bool showIceSpireWarning;
private List<Submarine> connectedSubs = new List<Submarine>();
private const float ConnectedSubUpdateInterval = 1.0f;
float connectedSubUpdateTimer;
private double lastReceivedSteeringSignalTime;
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, description: "Is autopilot currently on or not?", AlwaysUseInstanceValues = true)]
public bool AutoPilot
{
get { return autoPilot; }
set
{
if (value == autoPilot) { return; }
autoPilot = value;
#if CLIENT
UpdateGUIElements();
#endif
if (autoPilot)
{
MaintainPos = true;
if (posToMaintain == null)
{
RefreshPosToMaintain();
}
}
else
{
PosToMaintain = null;
MaintainPos = false;
LevelEndSelected = false;
LevelStartSelected = false;
}
}
}
[Editable(0.0f, 1.0f, decimals: 4),
Serialize(0.5f, IsPropertySaveable.Yes, description: "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
+ " Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine.")]
public float NeutralBallastLevel
{
get { return neutralBallastLevel; }
set
{
neutralBallastLevel = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
[Serialize(1000.0f, IsPropertySaveable.Yes, description: "How close the docking port has to be to another docking port for the docking mode to become active.")]
public float DockingAssistThreshold
{
get;
set;
}
public Vector2 TargetVelocity
{
get { return targetVelocity; }
set
{
if (!MathUtils.IsValid(value))
{
if (!MathUtils.IsValid(targetVelocity))
{
targetVelocity = Vector2.Zero;
}
return;
}
targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
public float TargetVelocityLengthSquared
{
get => TargetVelocity.LengthSquared();
}
public Vector2 SteeringInput
{
get { return steeringInput; }
set
{
if (!MathUtils.IsValid(value)) return;
steeringInput.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
steeringInput.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
public SteeringPath SteeringPath
{
get { return steeringPath; }
}
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, Vector2 translation)
{
Point1 = edge.Point1 + translation;
Point2 = edge.Point2 + translation;
Intersection = intersection;
Dot = dot;
AvoidStrength = avoidStrength;
}
}
//edge point 1, edge point 2, avoid strength
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
#region Docking
public List<DockingPort> DockingSources = new List<DockingPort>();
private bool searchedConnectedDockingPort;
private bool dockingModeEnabled;
public bool DockingModeEnabled
{
get { return UseAutoDocking && dockingModeEnabled; }
set { dockingModeEnabled = value; }
}
public bool UseAutoDocking
{
get;
set;
} = true;
private void FindConnectedDockingPort()
{
searchedConnectedDockingPort = true;
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item item)
{
var port = item.GetComponent<DockingPort>();
if (port != null)
{
DockingSources.Add(port);
}
}
}
var dockingConnection = item.Connections.FirstOrDefault(c => c.Name == "toggle_docking");
if (dockingConnection != null)
{
var connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection, allowTraversingBackwards: false);
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.Info.IsOutpost));
}
}
#endregion
public Steering(Item item, ContentXElement element)
: base(item, element)
{
IsActive = true;
InitProjSpecific(element);
}
partial void InitProjSpecific(ContentXElement element);
public override void OnItemLoaded()
{
base.OnItemLoaded();
sonar = item.GetComponent<Sonar>();
}
public override bool Select(Character character)
{
if (!CanBeSelected) return false;
user = character;
return true;
}
/// <summary>
/// Sets the position the autopilot tries to maintain to the current position of the sub.
/// </summary>
public void RefreshPosToMaintain()
{
posToMaintain = controlledSub != null ?
controlledSub.WorldPosition :
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
}
public override void OnMapLoaded()
{
if (MaintainPos)
{
RefreshPosToMaintain();
}
}
public override void Update(float deltaTime, Camera cam)
{
if (!searchedConnectedDockingPort)
{
FindConnectedDockingPort();
}
networkUpdateTimer -= deltaTime;
if (unsentChanges)
{
if (networkUpdateTimer <= 0.0f)
{
#if CLIENT
if (GameMain.Client != null)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
#endif
#if SERVER
item.CreateServerEvent(this);
#endif
networkUpdateTimer = 0.1f;
unsentChanges = false;
}
}
controlledSub = item.Submarine;
var sonar = item.GetComponent<Sonar>();
if (sonar != null && sonar.UseTransducers)
{
controlledSub = sonar.ConnectedTransducers.Any() ? sonar.ConnectedTransducers.First().Item.Submarine : null;
}
if (!HasPower) { return; }
if (user != null && user.Removed)
{
user = null;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime);
float userSkill = 0.0f;
if (user != null && controlledSub != null &&
(user.SelectedItem == item || item.linkedTo.Contains(user.SelectedItem)))
{
userSkill = user.GetSkillLevel(Tags.HelmSkill) / 100.0f;
}
// override autopilot pathing while the AI rams, and go full speed ahead
if (AIRamTimer > 0f && controlledSub != null)
{
AIRamTimer -= deltaTime;
TargetVelocity = GetSteeringVelocity(AITacticalTarget, 0f);
}
else if (AutoPilot)
{
//signals override autopilot for a duration of one second
if (lastReceivedSteeringSignalTime < Timing.TotalTime - 1)
{
UpdateAutoPilot(deltaTime);
float throttle = 1.0f;
if (controlledSub != null)
{
//if the sub is heading in the correct direction, throttle the speed according to the user's skill
//if it's e.g. sinking due to extra water, don't throttle, but allow emptying up the ballast completely
throttle = MathHelper.Clamp(Vector2.Dot(controlledSub.Velocity, TargetVelocity) / 100.0f, 0.0f, 1.0f);
}
float maxSpeed = MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f;
TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(100.0f, maxSpeed, throttle));
}
}
else
{
showIceSpireWarning = false;
if (user != null && user.Info != null &&
user.SelectedItem == item)
{
IncreaseSkillLevel(user, deltaTime);
}
Vector2 velocityDiff = steeringInput - targetVelocity;
if (velocityDiff != Vector2.Zero)
{
if (steeringAdjustSpeed >= 0.99f)
{
TargetVelocity = steeringInput;
}
else
{
float steeringChange = 1.0f / (1.0f - steeringAdjustSpeed);
steeringChange *= steeringChange * 10.0f;
TargetVelocity += Vector2.Normalize(velocityDiff) *
Math.Min(steeringChange * deltaTime, velocityDiff.Length());
}
}
}
float velX = targetVelocity.X;
if (controlledSub != null && controlledSub.FlippedX) { velX *= -1; }
item.SendSignal(new Signal(velX.ToString(CultureInfo.InvariantCulture), sender: user), "velocity_x_out");
float velY = MathHelper.Lerp((neutralBallastLevel * 100 - 50) * 2, -100 * Math.Sign(targetVelocity.Y), Math.Abs(targetVelocity.Y) / 100.0f);
item.SendSignal(new Signal(velY.ToString(CultureInfo.InvariantCulture), sender: user), "velocity_y_out");
// converts the controlled sub's velocity to km/h and sends it.
if (controlledSub is { } sub)
{
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x");
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y");
Vector2 pos = new Vector2(sub.WorldPosition.X * Physics.DisplayToRealWorldRatio, sub.RealWorldDepth);
if (sonar != null && sonar.UseTransducers && sonar.CenterOnTransducers && sonar.ConnectedTransducers.Any())
{
pos = Vector2.Zero;
foreach (var connectedTransducer in sonar.ConnectedTransducers)
{
pos += connectedTransducer.Item.WorldPosition;
}
pos /= sonar.ConnectedTransducers.Count();
pos = new Vector2(
pos.X * Physics.DisplayToRealWorldRatio,
Level.Loaded?.GetRealWorldDepth(pos.Y) ?? (-pos.Y * Physics.DisplayToRealWorldRatio));
}
item.SendSignal(new Signal(pos.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal(pos.Y.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
}
// if our tactical AI pilot has left, revert back to maintaining position
if (navigateTactically && (user == null || user.SelectedItem != item))
{
navigateTactically = false;
AIRamTimer = 0f;
SetMaintainPosition();
}
}
private void IncreaseSkillLevel(Character user, float deltaTime)
{
if (controlledSub == null) { return; }
if (controlledSub.Velocity.LengthSquared() < 0.01f) { return; }
if (user?.Info == null) { return; }
// Do not increase the helm skill when "steering" the sub while docked into something static (e.g. outpost or wreck)
if (GameMain.GameSession?.Campaign != null&& controlledSub.DockedTo.Any(d => d.PhysicsBody.BodyType == BodyType.Static)) { return; }
float speedMultiplier = MathHelper.Clamp(TargetVelocity.Length() / 100.0f, 0.0f, 1.0f);
user.Info.ApplySkillGain(Tags.HelmSkill,
SkillSettings.Current.SkillIncreasePerSecondWhenSteering * speedMultiplier * deltaTime);
}
private void UpdateAutoPilot(float deltaTime)
{
if (controlledSub == null) { return; }
if (posToMaintain != null)
{
Vector2 steeringVel = GetSteeringVelocity((Vector2)posToMaintain, 10.0f);
TargetVelocity = Vector2.Lerp(TargetVelocity, steeringVel, AutoPilotSteeringLerp);
showIceSpireWarning = false;
return;
}
autopilotRayCastTimer -= deltaTime;
autopilotRecalculatePathTimer -= deltaTime;
if (autopilotRecalculatePathTimer <= 0.0f)
{
//periodically recalculate the path in case the sub ends up to a position
//where it can't keep traversing the initially calculated path
UpdatePath();
autopilotRecalculatePathTimer = RecalculatePathInterval;
}
if (steeringPath == null)
{
showIceSpireWarning = false;
return;
}
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
connectedSubUpdateTimer -= deltaTime;
if (connectedSubUpdateTimer <= 0.0f)
{
connectedSubs.Clear();
connectedSubs.AddRange(controlledSub.GetConnectedSubs());
connectedSubUpdateTimer = ConnectedSubUpdateInterval;
}
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
{
Vector2 diff = ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - controlledSub.WorldPosition);
//if the node is close enough, check if it's visible
float lengthSqr = diff.LengthSquared();
if (lengthSqr > 0.001f && lengthSqr < AutopilotMinDistToPathNode * AutopilotMinDistToPathNode)
{
diff = Vector2.Normalize(diff);
//check if the next waypoint is visible from all corners of the sub
//(i.e. if we can navigate directly towards it or if there's obstacles in the way)
bool nextVisible = true;
for (int x = -1; x < 2; x += 2)
{
for (int y = -1; y < 2; y += 2)
{
Vector2 cornerPos =
new Vector2(controlledSub.Borders.Width * x, controlledSub.Borders.Height * y) / 2.0f;
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.1f + controlledSub.WorldPosition);
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) { continue; }
nextVisible = false;
x = 2;
y = 2;
}
}
if (nextVisible) steeringPath.SkipToNextNode();
}
autopilotRayCastTimer = AutopilotRayCastInterval;
}
Vector2 newVelocity = Vector2.Zero;
if (steeringPath.CurrentNode != null)
{
newVelocity = GetSteeringVelocity(steeringPath.CurrentNode.WorldPosition, 2.0f);
}
Vector2 avoidDist = new Vector2(
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.X), controlledSub.Borders.Width * 0.75f),
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 0.75f));
float avoidRadius = avoidDist.Length();
float damagingWallAvoidRadius = MathHelper.Clamp(avoidRadius * 1.5f, 5000.0f, 10000.0f);
Vector2 newAvoidStrength = Vector2.Zero;
debugDrawObstacles.Clear();
//steer away from nearby walls
showIceSpireWarning = false;
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
if (cell.DoesDamage || cell.Body is { BodyType: BodyType.Dynamic })
{
foreach (GraphEdge edge in cell.Edges)
{
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition);
Vector2 diff = closestPoint - controlledSub.WorldPosition;
float dist = diff.Length() - Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height) / 2;
if (dist > damagingWallAvoidRadius) { continue; }
Vector2 normalizedDiff = Vector2.Normalize(diff);
float dot = Vector2.Dot(normalizedDiff, controlledSub.Velocity);
float avoidStrength = MathHelper.Clamp(MathHelper.Lerp(1.0f, 0.0f, dist / damagingWallAvoidRadius - dot), 0.0f, 1.0f);
Vector2 avoid = -normalizedDiff * avoidStrength;
newAvoidStrength += avoid;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, edge.Center, 1.0f, avoid, cell.Translation));
if (dot > 0.0f && cell.DoesDamage)
{
showIceSpireWarning = true;
}
}
continue;
}
foreach (GraphEdge edge in cell.Edges)
{
if (MathUtils.GetLineSegmentIntersection(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
{
Vector2 diff = controlledSub.WorldPosition - intersection;
//far enough -> ignore
if (Math.Abs(diff.X) > avoidDist.X && Math.Abs(diff.Y) > avoidDist.Y)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, 0.0f, Vector2.Zero, Vector2.Zero));
continue;
}
if (diff.LengthSquared() < 1.0f) { diff = Vector2.UnitY; }
Vector2 normalizedDiff = Vector2.Normalize(diff);
float dot = controlledSub.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(controlledSub.Velocity, -normalizedDiff);
//not heading towards the wall -> ignore
if (dot < 1.0)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, Vector2.Zero, cell.Translation));
continue;
}
Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
if (change.LengthSquared() < 0.001f) { continue; }
newAvoidStrength += change * (dot - 1.0f);
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot - 1.0f, change * (dot - 1.0f), cell.Translation));
}
}
}
avoidStrength = Vector2.Lerp(avoidStrength, newAvoidStrength, deltaTime * 10.0f);
TargetVelocity = Vector2.Lerp(TargetVelocity, newVelocity + avoidStrength * 100.0f, AutoPilotSteeringLerp);
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == controlledSub || connectedSubs.Contains(sub)) { continue; }
Point sizeSum = controlledSub.Borders.Size + sub.Borders.Size;
Vector2 minDist = sizeSum.ToVector2() / 2;
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
float xDist = Math.Abs(diff.X);
float yDist = Math.Abs(diff.Y);
Vector2 maxAvoidDistance = minDist * 2;
if (xDist > maxAvoidDistance.X || yDist > maxAvoidDistance.Y)
{
//far enough -> ignore
continue;
}
float dot = controlledSub.Velocity == Vector2.Zero ? 0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.Velocity), -diff);
if (dot < 0.0f)
{
//heading away -> ignore
continue;
}
float distanceFactor = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(maxAvoidDistance.X + maxAvoidDistance.Y, minDist.X + minDist.Y, xDist + yDist));
float velocityFactor = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 3, controlledSub.Velocity.Length()));
TargetVelocity += 100 * Vector2.Normalize(diff) * distanceFactor * velocityFactor;
}
//clamp velocity magnitude to 100.0f (Is this required? The X and Y components are clamped in the property setter)
float velMagnitude = TargetVelocity.Length();
if (velMagnitude > 100.0f)
{
TargetVelocity *= 100.0f / velMagnitude;
}
#if CLIENT
HintManager.OnAutoPilotPathUpdated(this);
#endif
}
private float? GetNodePenalty(PathNode node, PathNode nextNode)
{
if (node.Waypoint?.Tunnel == null || controlledSub == null || node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath) { return 0.0f; }
//never navigate from the main path to another type of path
if (node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath && nextNode.Waypoint?.Tunnel?.Type != Level.TunnelType.MainPath) { return null; }
//higher cost for side paths (= autopilot prefers the main path, but can still navigate side paths if it ends up on one)
return 1000.0f;
}
private void UpdatePath()
{
if (Level.Loaded == null) { return; }
if (pathFinder == null)
{
pathFinder = new PathFinder(WayPoint.WayPointList, false)
{
GetNodePenalty = GetNodePenalty
};
}
Vector2 target;
if (navigateTactically)
{
target = ConvertUnits.ToSimUnits(AITacticalTarget);
}
else if (LevelEndSelected)
{
target = ConvertUnits.ToSimUnits(Level.Loaded.EndExitPosition);
}
else
{
target = ConvertUnits.ToSimUnits(Level.Loaded.StartExitPosition);
}
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition), target, errorMsgStr: "(Autopilot, target: " + target + ")");
}
public void SetDestinationLevelStart()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelEndSelected = false;
navigateTactically = false;
if (!LevelStartSelected)
{
LevelStartSelected = true;
UpdatePath();
}
}
public void SetDestinationLevelEnd()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelStartSelected = false;
navigateTactically = false;
if (!LevelEndSelected)
{
LevelEndSelected = true;
UpdatePath();
}
}
private void SetDestinationTactical()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelStartSelected = false;
LevelEndSelected = false;
if (!navigateTactically)
{
navigateTactically = true;
UpdatePath();
}
}
private void SetMaintainPosition()
{
if (!MaintainPos)
{
unsentChanges = true;
MaintainPos = true;
}
if (!posToMaintain.HasValue)
{
unsentChanges = true;
posToMaintain = controlledSub != null ?
controlledSub.WorldPosition :
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
}
}
/// <summary>
/// Get optimal velocity for moving towards a position
/// </summary>
/// <param name="worldPosition">Position to steer towards to</param>
/// <param name="slowdownAmount">How heavily the sub slows down when approaching the target</param>
/// <returns></returns>
private Vector2 GetSteeringVelocity(Vector2 worldPosition, float slowdownAmount)
{
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(controlledSub.Velocity) * slowdownAmount;
Vector2 targetSpeed = ((worldPosition - controlledSub.WorldPosition) - futurePosition);
if (targetSpeed.LengthSquared() > 500.0f * 500.0f)
{
return Vector2.Normalize(targetSpeed) * 100.0f;
}
else
{
return targetSpeed / 5.0f;
}
}
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
character.AIController.SteeringManager.Reset();
if (objective.Override)
{
if (user != character && user != null && user.SelectedItem == item && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogSteeringTaken").Value, null, 0.0f, "steeringtaken".ToIdentifier(), 10.0f);
}
}
user = character;
if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character))
{
if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
{
objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true));
return false;
}
else
{
character.Speak(TextManager.Get("DialogNavTerminalIsBroken").Value, identifier: "navterminalisbroken".ToIdentifier(), minDurationBetweenSimilar: 30.0f);
}
}
if (!AutoPilot)
{
unsentChanges = true;
AutoPilot = true;
}
IncreaseSkillLevel(user, deltaTime);
if (objective.Option == "maintainposition")
{
if (objective.Override)
{
SetMaintainPosition();
}
}
else if (!Level.IsLoadedOutpost)
{
if (objective.Option == "navigateback")
{
if (DockingSources.Any(d => d.Docked))
{
item.SendSignal("1", "toggle_docking");
}
if (objective.Override)
{
if (MaintainPos || LevelEndSelected || !LevelStartSelected || navigateTactically)
{
unsentChanges = true;
}
SetDestinationLevelStart();
}
}
else if (objective.Option == "navigatetodestination")
{
if (DockingSources.Any(d => d.Docked))
{
item.SendSignal("1", "toggle_docking");
}
if (objective.Override)
{
if (MaintainPos || !LevelEndSelected || LevelStartSelected || navigateTactically)
{
unsentChanges = true;
}
SetDestinationLevelEnd();
}
}
else if (objective.Option == "navigatetactical")
{
if (DockingSources.Any(d => d.Docked))
{
item.SendSignal("1", "toggle_docking");
}
if (objective.Override)
{
if (MaintainPos || LevelEndSelected || LevelStartSelected || !navigateTactically)
{
unsentChanges = true;
}
SetDestinationTactical();
}
}
}
sonar?.CrewAIOperate(deltaTime, character, objective);
if (!MaintainPos && showIceSpireWarning && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("dialogicespirespottedsonar").Value, null, 0.0f, "icespirespottedsonar".ToIdentifier(), 60.0f);
}
return false;
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
if (connection.Name == "velocity_in")
{
steeringAdjustSpeed = DefaultSteeringAdjustSpeed;
steeringInput = XMLExtensions.ParseVector2(signal.value, errorMessages: false);
steeringInput.X = MathHelper.Clamp(steeringInput.X, -100.0f, 100.0f);
steeringInput.Y = MathHelper.Clamp(-steeringInput.Y, -100.0f, 100.0f);
TargetVelocity = steeringInput;
lastReceivedSteeringSignalTime = Timing.TotalTime;
}
else
{
base.ReceiveSignal(signal, connection);
}
}
}
}