3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Globalization;
17 private const float AutopilotRayCastInterval = 0.5f;
18 private const float RecalculatePathInterval = 5.0f;
20 private const float AutoPilotSteeringLerp = 0.1f;
22 private const float AutoPilotMaxSpeed = 0.5f;
23 private const float AIPilotMaxSpeed = 1.0f;
33 const float DefaultSteeringAdjustSpeed = 0.2f;
35 private Vector2 targetVelocity;
37 private Vector2 steeringInput;
39 private bool autoPilot;
41 private Vector2? posToMaintain;
47 private float networkUpdateTimer;
48 private bool unsentChanges;
50 private float autopilotRayCastTimer;
51 private float autopilotRecalculatePathTimer;
53 private Vector2 avoidStrength;
55 private float neutralBallastLevel;
57 private float steeringAdjustSpeed = 1.0f;
68 bool navigateTactically;
70 private bool showIceSpireWarning;
72 private List<Submarine> connectedSubs =
new List<Submarine>();
73 private const float ConnectedSubUpdateInterval = 1.0f;
74 float connectedSubUpdateTimer;
76 private double lastReceivedSteeringSignalTime;
80 get {
return autoPilot; }
83 if (value == autoPilot) {
return; }
90 if (pathFinder ==
null)
94 GetNodePenalty = GetNodePenalty
98 if (posToMaintain ==
null)
100 posToMaintain = controlledSub !=
null ?
116 Serialize(0.5f,
IsPropertySaveable.Yes, description:
"How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
117 +
" Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine.")]
120 get {
return neutralBallastLevel; }
123 neutralBallastLevel = MathHelper.Clamp(value, 0.0f, 1.0f);
127 [
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.")]
136 get {
return targetVelocity; }
139 if (!MathUtils.IsValid(value))
141 if (!MathUtils.IsValid(targetVelocity))
143 targetVelocity = Vector2.Zero;
147 targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
148 targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
159 get {
return steeringInput; }
162 if (!MathUtils.IsValid(value))
return;
163 steeringInput.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
164 steeringInput.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
170 get {
return steeringPath; }
175 get {
return posToMaintain; }
176 set { posToMaintain = value; }
179 struct ObstacleDebugInfo
181 public Vector2 Point1;
182 public Vector2 Point2;
184 public Vector2? Intersection;
188 public Vector2 AvoidStrength;
190 public ObstacleDebugInfo(
GraphEdge edge, Vector2? intersection,
float dot, Vector2 avoidStrength, Vector2 translation)
192 Point1 = edge.
Point1 + translation;
193 Point2 = edge.Point2 + translation;
194 Intersection = intersection;
196 AvoidStrength = avoidStrength;
201 private List<ObstacleDebugInfo> debugDrawObstacles =
new List<ObstacleDebugInfo>();
205 private bool searchedConnectedDockingPort;
207 private bool dockingModeEnabled;
211 set { dockingModeEnabled = value; }
220 private void FindConnectedDockingPort()
222 searchedConnectedDockingPort =
true;
235 var dockingConnection =
item.
Connections.FirstOrDefault(c => c.Name ==
"toggle_docking");
236 if (dockingConnection !=
null)
238 var connectedPorts =
item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection, allowTraversingBackwards:
false);
239 DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine !=
null && !p.Item.Submarine.Info.IsOutpost));
245 : base(
item, element)
248 InitProjSpecific(element);
269 if (!searchedConnectedDockingPort)
271 FindConnectedDockingPort();
273 networkUpdateTimer -= deltaTime;
276 if (networkUpdateTimer <= 0.0f)
281 item.CreateClientEvent(
this);
286 item.CreateServerEvent(
this);
289 networkUpdateTimer = 0.1f;
290 unsentChanges =
false;
296 if (sonar !=
null && sonar.UseTransducers)
298 controlledSub = sonar.ConnectedTransducers.Any() ? sonar.ConnectedTransducers.First().Item.
Submarine :
null;
303 if (user !=
null && user.
Removed)
310 float userSkill = 0.0f;
311 if (user !=
null && controlledSub !=
null &&
326 if (lastReceivedSteeringSignalTime < Timing.TotalTime - 1)
328 UpdateAutoPilot(deltaTime);
329 float throttle = 1.0f;
330 if (controlledSub !=
null)
336 float maxSpeed = MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f;
342 showIceSpireWarning =
false;
343 if (user !=
null && user.
Info !=
null &&
346 IncreaseSkillLevel(user, deltaTime);
349 Vector2 velocityDiff = steeringInput - targetVelocity;
350 if (velocityDiff != Vector2.Zero)
352 if (steeringAdjustSpeed >= 0.99f)
358 float steeringChange = 1.0f / (1.0f - steeringAdjustSpeed);
359 steeringChange *= steeringChange * 10.0f;
362 Math.Min(steeringChange * deltaTime, velocityDiff.Length());
367 float velX = targetVelocity.X;
368 if (controlledSub !=
null && controlledSub.
FlippedX) { velX *= -1; }
369 item.
SendSignal(
new Signal(velX.ToString(CultureInfo.InvariantCulture), sender: user),
"velocity_x_out");
371 float velY = MathHelper.Lerp((neutralBallastLevel * 100 - 50) * 2, -100 * Math.Sign(targetVelocity.Y), Math.Abs(targetVelocity.Y) / 100.0f);
372 item.
SendSignal(
new Signal(velY.ToString(CultureInfo.InvariantCulture), sender: user),
"velocity_y_out");
375 if (controlledSub is { } sub)
377 item.
SendSignal(
new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString(
"0.0000", CultureInfo.InvariantCulture), sender: user),
"current_velocity_x");
378 item.
SendSignal(
new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString(
"0.0000", CultureInfo.InvariantCulture), sender: user),
"current_velocity_y");
380 Vector2 pos =
new Vector2(sub.WorldPosition.X * Physics.DisplayToRealWorldRatio, sub.RealWorldDepth);
381 if (sonar !=
null && sonar.UseTransducers && sonar.CenterOnTransducers && sonar.ConnectedTransducers.Any())
384 foreach (var connectedTransducer
in sonar.ConnectedTransducers)
386 pos += connectedTransducer.Item.WorldPosition;
388 pos /= sonar.ConnectedTransducers.Count();
390 pos.X * Physics.DisplayToRealWorldRatio,
394 item.
SendSignal(
new Signal(pos.X.ToString(
"0.0000", CultureInfo.InvariantCulture), sender: user),
"current_position_x");
395 item.
SendSignal(
new Signal(pos.Y.ToString(
"0.0000", CultureInfo.InvariantCulture), sender: user),
"current_position_y");
399 if (navigateTactically && (user ==
null || user.SelectedItem !=
item))
401 navigateTactically =
false;
403 SetMaintainPosition();
407 private void IncreaseSkillLevel(
Character user,
float deltaTime)
409 if (controlledSub ==
null) {
return; }
410 if (controlledSub.
Velocity.LengthSquared() < 0.01f) {
return; }
411 if (user?.Info ==
null) {
return; }
413 if (GameMain.GameSession?.Campaign !=
null&& controlledSub.
DockedTo.Any(d => d.PhysicsBody.BodyType == BodyType.Static)) {
return; }
415 float speedMultiplier = MathHelper.Clamp(
TargetVelocity.Length() / 100.0f, 0.0f, 1.0f);
417 SkillSettings.Current.SkillIncreasePerSecondWhenSteering * speedMultiplier * deltaTime);
420 private void UpdateAutoPilot(
float deltaTime)
422 if (controlledSub ==
null) {
return; }
423 if (posToMaintain !=
null)
425 Vector2 steeringVel = GetSteeringVelocity((Vector2)posToMaintain, 10.0f);
427 showIceSpireWarning =
false;
431 autopilotRayCastTimer -= deltaTime;
432 autopilotRecalculatePathTimer -= deltaTime;
433 if (autopilotRecalculatePathTimer <= 0.0f)
438 autopilotRecalculatePathTimer = RecalculatePathInterval;
441 if (steeringPath ==
null)
443 showIceSpireWarning =
false;
448 connectedSubUpdateTimer -= deltaTime;
449 if (connectedSubUpdateTimer <= 0.0f)
451 connectedSubs.Clear();
453 connectedSubUpdateTimer = ConnectedSubUpdateInterval;
456 if (autopilotRayCastTimer <= 0.0f && steeringPath.
NextNode !=
null)
461 float lengthSqr = diff.LengthSquared();
464 diff = Vector2.Normalize(diff);
468 bool nextVisible =
true;
469 for (
int x = -1; x < 2; x += 2)
471 for (
int y = -1; y < 2; y += 2)
474 new Vector2(controlledSub.
Borders.Width * x, controlledSub.
Borders.Height * y) / 2.0f;
476 cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.1f + controlledSub.
WorldPosition);
480 if (
Submarine.PickBody(cornerPos, cornerPos + diff * dist,
null, Physics.CollisionLevel) ==
null) {
continue; }
491 autopilotRayCastTimer = AutopilotRayCastInterval;
494 Vector2 newVelocity = Vector2.Zero;
500 Vector2 avoidDist =
new Vector2(
501 Math.Max(1000.0f * Math.Abs(controlledSub.
Velocity.X), controlledSub.
Borders.Width * 0.75f),
502 Math.Max(1000.0f * Math.Abs(controlledSub.
Velocity.Y), controlledSub.
Borders.Height * 0.75f));
504 float avoidRadius = avoidDist.Length();
505 float damagingWallAvoidRadius = MathHelper.Clamp(avoidRadius * 1.5f, 5000.0f, 10000.0f);
507 Vector2 newAvoidStrength = Vector2.Zero;
509 debugDrawObstacles.Clear();
512 showIceSpireWarning =
false;
513 var closeCells = Level.Loaded.GetCells(controlledSub.
WorldPosition, 4);
516 if (cell.
DoesDamage || cell.
Body is { BodyType: BodyType.Dynamic })
522 float dist = diff.Length() - Math.Max(controlledSub.
Borders.Width, controlledSub.
Borders.Height) / 2;
523 if (dist > damagingWallAvoidRadius) {
continue; }
525 Vector2 normalizedDiff = Vector2.Normalize(diff);
526 float dot = Vector2.Dot(normalizedDiff, controlledSub.
Velocity);
528 float avoidStrength = MathHelper.Clamp(MathHelper.Lerp(1.0f, 0.0f, dist / damagingWallAvoidRadius - dot), 0.0f, 1.0f);
529 Vector2 avoid = -normalizedDiff * avoidStrength;
530 newAvoidStrength += avoid;
531 debugDrawObstacles.Add(
new ObstacleDebugInfo(edge, edge.
Center, 1.0f, avoid, cell.
Translation));
535 showIceSpireWarning =
true;
547 if (Math.Abs(diff.X) > avoidDist.X && Math.Abs(diff.Y) > avoidDist.Y)
549 debugDrawObstacles.Add(
new ObstacleDebugInfo(edge, intersection, 0.0f, Vector2.Zero, Vector2.Zero));
552 if (diff.LengthSquared() < 1.0f) { diff = Vector2.UnitY; }
554 Vector2 normalizedDiff = Vector2.Normalize(diff);
555 float dot = controlledSub.
Velocity == Vector2.Zero ?
556 0.0f : Vector2.Dot(controlledSub.
Velocity, -normalizedDiff);
561 debugDrawObstacles.Add(
new ObstacleDebugInfo(edge, intersection, dot, Vector2.Zero, cell.
Translation));
565 Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
566 if (change.LengthSquared() < 0.001f) {
continue; }
567 newAvoidStrength += change * (dot - 1.0f);
568 debugDrawObstacles.Add(
new ObstacleDebugInfo(edge, intersection, dot - 1.0f, change * (dot - 1.0f), cell.
Translation));
573 avoidStrength = Vector2.Lerp(avoidStrength, newAvoidStrength, deltaTime * 10.0f);
577 foreach (Submarine sub
in Submarine.Loaded)
579 if (sub == controlledSub || connectedSubs.Contains(sub)) {
continue; }
580 Point sizeSum = controlledSub.
Borders.Size + sub.Borders.Size;
581 Vector2 minDist = sizeSum.ToVector2() / 2;
582 Vector2 diff = controlledSub.
WorldPosition - sub.WorldPosition;
583 float xDist = Math.Abs(diff.X);
584 float yDist = Math.Abs(diff.Y);
585 Vector2 maxAvoidDistance = minDist * 2;
586 if (xDist > maxAvoidDistance.X || yDist > maxAvoidDistance.Y)
591 float dot = controlledSub.
Velocity == Vector2.Zero ? 0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.
Velocity), -diff);
597 float distanceFactor = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(maxAvoidDistance.X + maxAvoidDistance.Y, minDist.X + minDist.Y, xDist + yDist));
598 float velocityFactor = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 3, controlledSub.
Velocity.Length()));
599 TargetVelocity += 100 * Vector2.Normalize(diff) * distanceFactor * velocityFactor;
604 if (velMagnitude > 100.0f)
610 HintManager.OnAutoPilotPathUpdated(
this);
614 private float? GetNodePenalty(PathNode node, PathNode nextNode)
616 if (node.Waypoint?.Tunnel ==
null || controlledSub ==
null || node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath) {
return 0.0f; }
618 if (node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath && nextNode.Waypoint?.Tunnel?.Type != Level.TunnelType.MainPath) {
return null; }
623 private void UpdatePath()
625 if (Level.Loaded ==
null) {
return; }
627 if (pathFinder ==
null)
629 pathFinder =
new PathFinder(WayPoint.WayPointList,
false);
634 if (navigateTactically)
640 target = ConvertUnits.ToSimUnits(Level.Loaded.EndExitPosition);
644 target = ConvertUnits.ToSimUnits(Level.Loaded.StartExitPosition);
646 steeringPath = pathFinder.
FindPath(ConvertUnits.ToSimUnits(controlledSub ==
null ?
item.
WorldPosition : controlledSub.
WorldPosition), target, errorMsgStr:
"(Autopilot, target: " + target +
")");
653 posToMaintain =
null;
655 navigateTactically =
false;
667 posToMaintain =
null;
669 navigateTactically =
false;
677 private void SetDestinationTactical()
681 posToMaintain =
null;
684 if (!navigateTactically)
686 navigateTactically =
true;
691 private void SetMaintainPosition()
695 unsentChanges =
true;
698 if (!posToMaintain.HasValue)
700 unsentChanges =
true;
701 posToMaintain = controlledSub !=
null ?
713 private Vector2 GetSteeringVelocity(Vector2 worldPosition,
float slowdownAmount)
715 Vector2 futurePosition = ConvertUnits.ToDisplayUnits(controlledSub.
Velocity) * slowdownAmount;
716 Vector2 targetSpeed = ((worldPosition - controlledSub.
WorldPosition) - futurePosition);
718 if (targetSpeed.LengthSquared() > 500.0f * 500.0f)
720 return Vector2.Normalize(targetSpeed) * 100.0f;
724 return targetSpeed / 5.0f;
735 character.
Speak(TextManager.Get(
"DialogSteeringTaken").Value,
null, 0.0f,
"steeringtaken".ToIdentifier(), 10.0f);
742 if (
Item.
Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
749 character.
Speak(TextManager.Get(
"DialogNavTerminalIsBroken").Value, identifier:
"navterminalisbroken".ToIdentifier(), minDurationBetweenSimilar: 30.0f);
755 unsentChanges =
true;
758 IncreaseSkillLevel(user, deltaTime);
759 if (objective.
Option ==
"maintainposition")
763 SetMaintainPosition();
768 if (objective.
Option ==
"navigateback")
779 unsentChanges =
true;
785 else if (objective.
Option ==
"navigatetodestination")
796 unsentChanges =
true;
802 else if (objective.
Option ==
"navigatetactical")
813 unsentChanges =
true;
816 SetDestinationTactical();
824 character.
Speak(TextManager.Get(
"dialogicespirespottedsonar").Value,
null, 0.0f,
"icespirespottedsonar".ToIdentifier(), 60.0f);
831 if (connection.
Name ==
"velocity_in")
833 steeringAdjustSpeed = DefaultSteeringAdjustSpeed;
834 steeringInput = XMLExtensions.ParseVector2(signal.
value, errorMessages:
false);
835 steeringInput.X = MathHelper.Clamp(steeringInput.X, -100.0f, 100.0f);
836 steeringInput.Y = MathHelper.Clamp(-steeringInput.Y, -100.0f, 100.0f);
838 lastReceivedSteeringSignalTime = Timing.TotalTime;
842 base.ReceiveSignal(signal, connection);
SteeringManager SteeringManager
void AddSubObjective(AIObjective objective, bool addFirst=false)
readonly Identifier Option
readonly AIObjectiveManager objectiveManager
override bool IsValidTarget(Item item)
float GetSkillLevel(string skillIdentifier)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
virtual AIController AIController
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
void ApplySkillGain(Identifier skillIdentifier, float baseGain, bool gainedFromAbility=false, float maxGain=2f)
Increases the characters skill at a rate proportional to their current skill. If you want to increase...
virtual Vector2 WorldPosition
List< Connection > Connections
List< Repairable > Repairables
float ConditionPercentage
void SendSignal(string signal, string connectionName)
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
const float CorrectionDelay
override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
true if the operation was completed
override void Update(float deltaTime, Camera cam)
List< DockingPort > DockingSources
float NeutralBallastLevel
override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
true if the operation was completed
float TargetVelocityLengthSquared
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
override bool Select(Character character)
float DockingAssistThreshold
Steering(Item item, ContentXElement element)
void SetDestinationLevelStart()
void SetDestinationLevelEnd()
override void ReceiveSignal(Signal signal, Connection connection)
const float PressureWarningThreshold
How many units before crush depth the pressure warning is shown
const float AutopilotMinDistToPathNode
float GetRealWorldDepth(float worldPositionY)
Calculate the "real" depth in meters from the surface of Europa (the value you see on the nav termina...
static bool IsLoadedOutpost
Is there a loaded level set and is it an outpost?
override Vector2 Position
readonly List< MapEntity > linkedTo
override Vector2 SimPosition
SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub=null, string errorMsgStr=null, float minGapSize=0, Func< PathNode, bool > startNodeFilter=null, Func< PathNode, bool > endNodeFilter=null, Func< PathNode, bool > nodeFilter=null, bool checkVisibility=true)
WayPoint CheckProgress(Vector2 simPosition, float minSimDistance=0.1f)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
override Vector2? WorldPosition
IEnumerable< Submarine > DockedTo
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
Rectangle? Borders
Extents of the solid items/structures (ones with a physics body) and hulls
static List< WayPoint > WayPointList
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.