Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs
T
Joonas Rikkonen 5dc31c213f ad0bbaf...7245c72
commit 7245c721339885d062567befc052a592391b3b4a
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sun Mar 10 15:22:31 2019 +0200

    Fixed StatusEffects only applying afflictions to one limb even if the target is "Character" instead of "Limb", added a subtle screen distortion effect to heavy radiation sickness. Closes #1256

commit e0db27e62ec9546fd4b182a0cc97f7e5830645ae
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 21:53:51 2019 +0200

    Fixed WrapText adding unnecessary spaces after every line break. Closes #1215

commit 988bc58d51c195ad9265b84a1e97e0101cd3f808
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 21:12:50 2019 +0200

    Fixed crashing when attempting to create a body for a wall section that's less than 1 unit long (e.g. if a wall that's just slightly longer than the wall section size receives damage).

commit 8c31157425a9e2ec02312618d1bfa359ab3ee87d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 20:30:44 2019 +0200

    Fixed clients being unable to toggle the respawn shuttle on/off

commit a4ccb039219830efe9cd305c26942dda1bd04e9c
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 19:33:22 2019 +0200

    Fixed inability to select the respawn shuttle as a client host

commit b89b2d2c282d8c74d7ccd37b3f29dcab51eff680
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 19:32:41 2019 +0200

    Made it possible to edit the style of the ListBox under GUIDropDowns, increased the opacity of the listbox to make the contents more readable when there's text behind it

commit 8f6d9aef3d637fe37a18c78f4b15ef8fd266374e
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 18:11:23 2019 +0200

    Fixed NetLobbyScreen not showing the names of the submarines the client doesn't have
2019-03-18 22:39:57 +02:00

607 lines
21 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
{
private const float AutopilotRayCastInterval = 0.5f;
private const float RecalculatePathInterval = 5.0f;
private Vector2 currVelocity;
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 bool AutoPilot
{
get { return autoPilot; }
set
{
if (value == autoPilot) return;
autoPilot = value;
#if CLIENT
autopilotTickBox.Selected = autoPilot;
manualTickBox.Selected = !autoPilot;
maintainPosTickBox.Enabled = autoPilot;
levelEndTickBox.Enabled = autoPilot;
levelStartTickBox.Enabled = autoPilot;
#endif
if (autoPilot)
{
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
MaintainPos = true;
}
else
{
PosToMaintain = null;
MaintainPos = false;
LevelEndSelected = false;
LevelStartSelected = false;
}
}
}
[Editable(0.0f, 1.0f, decimals: 3, ToolTip = "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."), Serialize(0.5f, true)]
public float NeutralBallastLevel
{
get { return neutralBallastLevel; }
set
{
neutralBallastLevel = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
[Serialize(1000.0f, true)]
public float DockingAssistThreshold
{
get;
set;
}
public Vector2 TargetVelocity
{
get { return targetVelocity;}
set
{
if (!MathUtils.IsValid(value)) return;
targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
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)
{
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>();
public Steering(Item item, XElement element)
: base(item, element)
{
IsActive = true;
InitProjSpecific();
}
partial void InitProjSpecific();
public override void OnItemLoaded()
{
sonar = item.GetComponent<Sonar>();
}
public override bool Select(Character character)
{
if (!CanBeSelected) return false;
user = character;
return true;
}
public override void Update(float deltaTime, Camera cam)
{
networkUpdateTimer -= deltaTime;
if (unsentChanges)
{
if (networkUpdateTimer <= 0.0f)
{
#if CLIENT
if (GameMain.Client != null)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
else
#endif
#if SERVER
if (GameMain.Server != null)
{
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;
}
currPowerConsumption = powerConsumption;
if (voltage < minVoltage && currPowerConsumption > 0.0f) { return; }
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (autoPilot)
{
UpdateAutoPilot(deltaTime);
}
else
{
if (user != null && user.Info != null && user.SelectedConstruction == item)
{
user.Info.IncreaseSkillLevel("helm", 0.005f * deltaTime, user.WorldPosition + Vector2.UnitY * 150.0f);
}
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());
}
}
}
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", null);
float targetLevel = -targetVelocity.Y;
targetLevel += (neutralBallastLevel - 0.5f) * 100.0f;
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", null);
voltage -= deltaTime;
}
private void UpdateAutoPilot(float deltaTime)
{
if (controlledSub == null) return;
if (posToMaintain != null)
{
SteerTowardsPosition((Vector2)posToMaintain);
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;
}
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
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 < 500.0f)
{
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.2f + 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;
}
if (steeringPath.CurrentNode != null)
{
SteerTowardsPosition(steeringPath.CurrentNode.WorldPosition);
}
Vector2 avoidDist = new Vector2(
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.X), controlledSub.Borders.Width * 1.5f),
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 1.5f));
float avoidRadius = avoidDist.Length();
Vector2 newAvoidStrength = Vector2.Zero;
debugDrawObstacles.Clear();
//steer away from nearby walls
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
foreach (GraphEdge edge in cell.Edges)
{
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, 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));
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 < 0.5)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, Vector2.Zero));
continue;
}
Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
newAvoidStrength += change * dot;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, change * dot));
}
}
}
avoidStrength = Vector2.Lerp(avoidStrength, newAvoidStrength, deltaTime * 10.0f);
targetVelocity += avoidStrength * 100.0f;
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == controlledSub) continue;
if (controlledSub.DockedTo.Contains(sub)) continue;
float thisSize = Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height);
float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height);
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
float dist = diff == Vector2.Zero ? 0.0f : diff.Length();
//far enough -> ignore
if (dist > thisSize + otherSize) continue;
Vector2 dir = dist <= 0.0001f ? Vector2.UnitY : diff / dist;
float dot = controlledSub.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.Velocity), -dir);
//heading away -> ignore
if (dot < 0.0f) continue;
targetVelocity += diff * 200.0f;
}
//clamp velocity magnitude to 100.0f
float velMagnitude = targetVelocity.Length();
if (velMagnitude > 100.0f)
{
targetVelocity *= 100.0f / velMagnitude;
}
}
private void UpdatePath()
{
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
Vector2 target;
if (LevelEndSelected)
{
target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
}
else
{
target = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition);
}
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition), target, "(Autopilot, target: " + target + ")");
}
public void SetDestinationLevelStart()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelEndSelected = false;
if (!LevelStartSelected)
{
LevelStartSelected = true;
UpdatePath();
}
}
public void SetDestinationLevelEnd()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelStartSelected = false;
if (!LevelEndSelected)
{
LevelEndSelected = true;
UpdatePath();
}
}
private void SteerTowardsPosition(Vector2 worldPosition)
{
float prediction = 10.0f;
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(controlledSub.Velocity) * prediction;
Vector2 targetSpeed = ((worldPosition - controlledSub.WorldPosition) - futurePosition);
if (targetSpeed.Length() > 500.0f)
{
targetSpeed = Vector2.Normalize(targetSpeed);
TargetVelocity = targetSpeed * 100.0f;
}
else
{
TargetVelocity = targetSpeed / 5.0f;
}
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (user != character && user != null && user.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
}
user = character;
switch (objective.Option.ToLowerInvariant())
{
case "maintainposition":
if (!posToMaintain.HasValue)
{
unsentChanges = true;
posToMaintain = controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition;
}
if (!AutoPilot || !MaintainPos) unsentChanges = true;
AutoPilot = true;
MaintainPos = true;
break;
case "navigateback":
if (!AutoPilot || MaintainPos || LevelEndSelected || !LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelStart();
break;
case "navigatetodestination":
if (!AutoPilot || MaintainPos || !LevelEndSelected || LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelEnd();
break;
}
sonar?.AIOperate(deltaTime, character, objective);
return false;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.Name == "velocity_in")
{
currVelocity = XMLExtensions.ParseVector2(signal, false);
}
else
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
}
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
{
bool autoPilot = msg.ReadBoolean();
Vector2 newSteeringInput = targetVelocity;
bool maintainPos = false;
Vector2? newPosToMaintain = null;
bool headingToStart = false;
if (autoPilot)
{
maintainPos = msg.ReadBoolean();
if (maintainPos)
{
newPosToMaintain = new Vector2(
msg.ReadFloat(),
msg.ReadFloat());
}
else
{
headingToStart = msg.ReadBoolean();
}
}
else
{
newSteeringInput = new Vector2(msg.ReadFloat(), msg.ReadFloat());
}
if (!item.CanClientAccess(c)) return;
user = c.Character;
AutoPilot = autoPilot;
if (!AutoPilot)
{
steeringInput = newSteeringInput;
steeringAdjustSpeed = MathHelper.Lerp(0.2f, 1.0f, c.Character.GetSkillLevel("helm") / 100.0f);
}
else
{
MaintainPos = newPosToMaintain != null;
posToMaintain = newPosToMaintain;
if (posToMaintain == null)
{
LevelStartSelected = headingToStart;
LevelEndSelected = !headingToStart;
UpdatePath();
}
else
{
LevelStartSelected = false;
LevelEndSelected = false;
}
}
//notify all clients of the changed state
unsentChanges = true;
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
{
msg.Write(autoPilot);
if (!autoPilot)
{
//no need to write steering info if autopilot is controlling
msg.Write(steeringInput.X);
msg.Write(steeringInput.Y);
msg.Write(targetVelocity.X);
msg.Write(targetVelocity.Y);
msg.Write(steeringAdjustSpeed);
}
else
{
msg.Write(posToMaintain != null);
if (posToMaintain != null)
{
msg.Write(((Vector2)posToMaintain).X);
msg.Write(((Vector2)posToMaintain).Y);
}
else
{
msg.Write(LevelStartSelected);
}
}
}
}
}