public static void GenerateSubWaypoints(Submarine submarine) { if (!Hull.hullList.Any()) { DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found."); return; } List <WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path); foreach (WayPoint wayPoint in existingWaypoints) { wayPoint.Remove(); } float minDist = 150.0f; float heightFromFloor = 110.0f; foreach (Hull hull in Hull.hullList) { if (hull.Rect.Height < 150) { continue; } WayPoint prevWaypoint = null; if (hull.Rect.Width < minDist * 3.0f) { new WayPoint( new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine); continue; } for (float x = hull.Rect.X + minDist; x <= hull.Rect.Right - minDist; x += minDist) { var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine); if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); } prevWaypoint = wayPoint; } } float outSideWaypointInterval = 200.0f; int outsideWaypointDist = 100; Rectangle borders = Hull.GetBorders(); borders.X -= outsideWaypointDist; borders.Y += outsideWaypointDist; borders.Width += outsideWaypointDist * 2; borders.Height += outsideWaypointDist * 2; borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition); if (borders.Width <= outSideWaypointInterval * 2) { borders.Inflate(outSideWaypointInterval * 2 - borders.Width, 0); } if (borders.Height <= outSideWaypointInterval * 2) { int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height; borders.Y += inflateAmount / 2; borders.Height += inflateAmount; } WayPoint[,] cornerWaypoint = new WayPoint[2, 2]; for (int i = 0; i < 2; i++) { for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval) { var wayPoint = new WayPoint( new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition, SpawnType.Path, submarine); if (x == borders.X + outSideWaypointInterval) { cornerWaypoint[i, 0] = wayPoint; } else { wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]); } } cornerWaypoint[i, 1] = WayPoint.WayPointList[WayPointList.Count - 1]; } for (int i = 0; i < 2; i++) { WayPoint wayPoint = null; for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval) { wayPoint = new WayPoint( new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition, SpawnType.Path, submarine); if (y == borders.Y - borders.Height) { wayPoint.ConnectTo(cornerWaypoint[1, i]); } else { wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]); } } wayPoint.ConnectTo(cornerWaypoint[0, i]); } List <Structure> stairList = new List <Structure>(); foreach (MapEntity me in mapEntityList) { Structure stairs = me as Structure; if (stairs == null) { continue; } if (stairs.StairDirection != Direction.None) { stairList.Add(stairs); } } foreach (Structure stairs in stairList) { WayPoint[] stairPoints = new WayPoint[3]; stairPoints[0] = new WayPoint( new Vector2(stairs.Rect.X - 32.0f, stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine); stairPoints[1] = new WayPoint( new Vector2(stairs.Rect.Right + 32.0f, stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine); for (int i = 0; i < 2; i++) { for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f, 30f)); if (closest == null) { continue; } stairPoints[i].ConnectTo(closest); } } stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine); stairPoints[0].ConnectTo(stairPoints[2]); stairPoints[2].ConnectTo(stairPoints[1]); } foreach (Item item in Item.ItemList) { var ladders = item.GetComponent <Items.Components.Ladder>(); if (ladders == null) { continue; } WayPoint[] ladderPoints = new WayPoint[2]; ladderPoints[0] = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height + heightFromFloor), SpawnType.Path, submarine); ladderPoints[1] = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine); WayPoint prevPoint = ladderPoints[0]; Vector2 prevPos = prevPoint.SimPosition; List <Body> ignoredBodies = new List <Body>(); while (prevPoint != ladderPoints[1]) { var pickedBody = Submarine.PickBody(prevPos, ladderPoints[1].SimPosition, ignoredBodies); if (pickedBody == null) { break; } ignoredBodies.Add(pickedBody); if (pickedBody.UserData is Item) { var door = ((Item)pickedBody.UserData).GetComponent <Door>(); if (door != null) { WayPoint newPoint = new WayPoint(door.Item.Position, SpawnType.Path, submarine); newPoint.Ladders = ladders; newPoint.ConnectedGap = door.LinkedGap; newPoint.ConnectTo(prevPoint); prevPoint = newPoint; prevPos = ConvertUnits.ToSimUnits(door.Item.Position - Vector2.UnitY * door.Item.Rect.Height); } else { prevPos = Submarine.LastPickedPosition; } } else { prevPos = Submarine.LastPickedPosition; } } prevPoint.ConnectTo(ladderPoints[1]); //for (float y = ladderPoints[0].Position.Y+100.0f; y < ladderPoints[1].Position.Y; y+=100.0f ) //{ // var midPoint = new WayPoint(new Vector2(item.Rect.Center.X, y), SpawnType.Path, Submarine.Loaded); // midPoint.Ladders = ladders; // midPoint.ConnectTo(prevPoint); // prevPoint = midPoint; //} //ladderPoints[1].ConnectTo(prevPoint); for (int i = 0; i < 2; i++) { ladderPoints[i].Ladders = ladders; for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = ladderPoints[i].FindClosest(dir, true, new Vector2(-150.0f, 10f)); if (closest == null) { continue; } ladderPoints[i].ConnectTo(closest); } } //ladderPoints[0].ConnectTo(ladderPoints[1]); } foreach (Gap gap in Gap.GapList) { if (!gap.isHorizontal) { continue; } //too small to walk through if (gap.Rect.Height < 150.0f) { continue; } var wayPoint = new WayPoint( new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor), SpawnType.Path, submarine, gap); for (int dir = -1; dir <= 1; dir += 2) { float tolerance = gap.IsRoomToRoom ? 50.0f : outSideWaypointInterval / 2.0f; WayPoint closest = wayPoint.FindClosest( dir, true, new Vector2(-tolerance, tolerance), gap.ConnectedDoor == null ? null : gap.ConnectedDoor.Body.FarseerBody); if (closest != null) { wayPoint.ConnectTo(closest); } } } foreach (Gap gap in Gap.GapList) { if (gap.isHorizontal || gap.IsRoomToRoom) { continue; } //too small to walk through if (gap.Rect.Width < 100.0f) { continue; } var wayPoint = new WayPoint( new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2), SpawnType.Path, submarine, gap); for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = wayPoint.FindClosest(dir, false, new Vector2(-outSideWaypointInterval, outSideWaypointInterval) / 2.0f); if (closest == null) { continue; } wayPoint.ConnectTo(closest); } } var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any()); foreach (WayPoint wp in orphans) { wp.Remove(); } }
public static void GenerateSubWaypoints(Submarine submarine) { if (!Hull.hullList.Any()) { DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found."); return; } List <WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path); foreach (WayPoint wayPoint in existingWaypoints) { wayPoint.Remove(); } //find all open doors and temporarily activate their bodies to prevent visibility checks //from ignoring the doors and generating waypoint connections that go straight through the door List <Door> openDoors = new List <Door>(); foreach (Item item in Item.ItemList) { var door = item.GetComponent <Door>(); if (door != null && !door.Body.Enabled) { openDoors.Add(door); door.Body.Enabled = true; } } float minDist = 150.0f; float heightFromFloor = 110.0f; foreach (Hull hull in Hull.hullList) { if (hull.Rect.Height < 150) { continue; } WayPoint prevWaypoint = null; if (hull.Rect.Width < minDist * 3.0f) { new WayPoint( new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine); continue; } for (float x = hull.Rect.X + minDist; x <= hull.Rect.Right - minDist; x += minDist) { var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine); if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); } prevWaypoint = wayPoint; } } float outSideWaypointInterval = 200.0f; int outsideWaypointDist = 100; Rectangle borders = Hull.GetBorders(); borders.X -= outsideWaypointDist; borders.Y += outsideWaypointDist; borders.Width += outsideWaypointDist * 2; borders.Height += outsideWaypointDist * 2; borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition); if (borders.Width <= outSideWaypointInterval * 2) { borders.Inflate(outSideWaypointInterval * 2 - borders.Width, 0); } if (borders.Height <= outSideWaypointInterval * 2) { int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height; borders.Y += inflateAmount / 2; borders.Height += inflateAmount; } WayPoint[,] cornerWaypoint = new WayPoint[2, 2]; for (int i = 0; i < 2; i++) { for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval) { var wayPoint = new WayPoint( new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition, SpawnType.Path, submarine); if (x == borders.X + outSideWaypointInterval) { cornerWaypoint[i, 0] = wayPoint; } else { wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]); } } cornerWaypoint[i, 1] = WayPointList[WayPointList.Count - 1]; } for (int i = 0; i < 2; i++) { WayPoint wayPoint = null; for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval) { wayPoint = new WayPoint( new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition, SpawnType.Path, submarine); if (y == borders.Y - borders.Height) { wayPoint.ConnectTo(cornerWaypoint[1, i]); } else { wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]); } } wayPoint.ConnectTo(cornerWaypoint[0, i]); } List <Structure> stairList = new List <Structure>(); foreach (MapEntity me in mapEntityList) { Structure stairs = me as Structure; if (stairs == null) { continue; } if (stairs.StairDirection != Direction.None) { stairList.Add(stairs); } } foreach (Structure stairs in stairList) { WayPoint[] stairPoints = new WayPoint[3]; stairPoints[0] = new WayPoint( new Vector2(stairs.Rect.X - 32.0f, stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine); stairPoints[1] = new WayPoint( new Vector2(stairs.Rect.Right + 32.0f, stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine); for (int i = 0; i < 2; i++) { for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f, 30f)); if (closest == null) { continue; } stairPoints[i].ConnectTo(closest); } } stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine); stairPoints[0].ConnectTo(stairPoints[2]); stairPoints[2].ConnectTo(stairPoints[1]); } foreach (Item item in Item.ItemList) { var ladders = item.GetComponent <Ladder>(); if (ladders == null) { continue; } List <WayPoint> ladderPoints = new List <WayPoint>(); ladderPoints.Add(new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height + heightFromFloor), SpawnType.Path, submarine)); WayPoint prevPoint = ladderPoints[0]; Vector2 prevPos = prevPoint.SimPosition; List <Body> ignoredBodies = new List <Body>(); for (float y = ladderPoints[0].Position.Y + 100.0f; y < item.Rect.Y - 1.0f; y += 100.0f) { //first check if there's a door in the way //(we need to create a waypoint linked to the door for NPCs to open it) Body pickedBody = Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)), prevPos, ignoredBodies, Physics.CollisionWall, false, (Fixture f) => f.Body.UserData is Item && ((Item)f.Body.UserData).GetComponent <Door>() != null); Door pickedDoor = null; if (pickedBody != null) { pickedDoor = (pickedBody?.UserData as Item).GetComponent <Door>(); } else { //no door, check for walls pickedBody = Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)), prevPos, ignoredBodies, null, false); } if (pickedBody == null) { prevPos = Submarine.LastPickedPosition; continue; } else { ignoredBodies.Add(pickedBody); } if (pickedDoor != null) { WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine); ladderPoints.Add(newPoint); newPoint.ConnectedGap = pickedDoor.LinkedGap; newPoint.ConnectTo(prevPoint); prevPoint = newPoint; prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(pickedDoor.Item.Position.Y - pickedDoor.Item.Rect.Height)); } else { WayPoint newPoint = new WayPoint(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor, SpawnType.Path, submarine); ladderPoints.Add(newPoint); newPoint.ConnectTo(prevPoint); prevPoint = newPoint; prevPos = ConvertUnits.ToSimUnits(newPoint.Position); } } if (prevPoint.rect.Y < item.Rect.Y - 10.0f) { WayPoint newPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine); ladderPoints.Add(newPoint); newPoint.ConnectTo(prevPoint); } //connect ladder waypoints to hull points at the right and left side foreach (WayPoint ladderPoint in ladderPoints) { ladderPoint.Ladders = ladders; //don't connect if the waypoint is at a gap (= at the boundary of hulls and/or at a hatch) if (ladderPoint.ConnectedGap != null) { continue; } for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = ladderPoint.FindClosest(dir, true, new Vector2(-150.0f, 10f)); if (closest == null) { continue; } ladderPoint.ConnectTo(closest); } } } foreach (Gap gap in Gap.GapList) { if (!gap.IsHorizontal) { continue; } //too small to walk through if (gap.Rect.Height < 150.0f) { continue; } var wayPoint = new WayPoint( new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor), SpawnType.Path, submarine, gap); for (int dir = -1; dir <= 1; dir += 2) { float tolerance = gap.IsRoomToRoom ? 50.0f : outSideWaypointInterval / 2.0f; WayPoint closest = wayPoint.FindClosest( dir, true, new Vector2(-tolerance, tolerance), gap.ConnectedDoor?.Body.FarseerBody); if (closest != null) { wayPoint.ConnectTo(closest); } } } foreach (Gap gap in Gap.GapList) { if (gap.IsHorizontal || gap.IsRoomToRoom || !gap.linkedTo.Any(l => l is Hull)) { continue; } //too small to walk through if (gap.Rect.Width < 100.0f) { continue; } var wayPoint = new WayPoint( new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2), SpawnType.Path, submarine, gap); float tolerance = outSideWaypointInterval / 2.0f; Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull); int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y); WayPoint closest = wayPoint.FindClosest( dir, false, new Vector2(-tolerance, tolerance), gap.ConnectedDoor?.Body.FarseerBody); if (closest != null) { wayPoint.ConnectTo(closest); } } var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any()); foreach (WayPoint wp in orphans) { wp.Remove(); } //re-disable the bodies of the doors that are supposed to be open foreach (Door door in openDoors) { door.Body.Enabled = false; } }
public static bool GenerateSubWaypoints(Submarine submarine) { if (!Hull.hullList.Any()) { DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found."); return(false); } List <WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path); foreach (WayPoint wayPoint in existingWaypoints) { wayPoint.Remove(); } //find all open doors and temporarily activate their bodies to prevent visibility checks //from ignoring the doors and generating waypoint connections that go straight through the door List <Door> openDoors = new List <Door>(); foreach (Item item in Item.ItemList) { var door = item.GetComponent <Door>(); if (door != null && !door.Body.Enabled) { openDoors.Add(door); door.Body.Enabled = true; } } float diffFromHullEdge = 50; float minDist = 100.0f; float heightFromFloor = 110.0f; float hullMinHeight = 100; foreach (Hull hull in Hull.hullList) { // Ignore hulls that a human couldn't fit in. // Doesn't take multi-hull rooms into account, but it's probably best to leave them to be setup manually. if (hull.Rect.Height < hullMinHeight) { continue; } // Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor. Body floor = null; for (int i = 0; i < 5; i++) { float horizontalOffset = 0; switch (i) { case 1: horizontalOffset = hull.RectWidth * 0.2f; break; case 2: horizontalOffset = hull.RectWidth * 0.4f; break; case 3: horizontalOffset = -hull.RectWidth * 0.2f; break; case 4: horizontalOffset = -hull.RectWidth * 0.4f; break; } horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset); Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50)); floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine)); if (floor != null) { break; } } if (floor == null) { continue; } float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2; if (hull.Rect.Width < diffFromHullEdge * 3.0f) { new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); } else { WayPoint prevWaypoint = null; for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist) { var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); } prevWaypoint = wayPoint; } if (prevWaypoint == null) { // Ensure that we always create at least one waypoint per hull. new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); } } } // Platforms foreach (Structure platform in Structure.WallList) { if (!platform.IsPlatform) { continue; } float waypointHeight = heightFromFloor; WayPoint prevWaypoint = null; for (float x = platform.Rect.X + diffFromHullEdge; x <= platform.Rect.Right - diffFromHullEdge; x += minDist) { WayPoint wayPoint = new WayPoint(new Vector2(x, platform.Rect.Y + waypointHeight), SpawnType.Path, submarine); if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); } // If the waypoint is close to hull waypoints, remove it. if (wayPoint != null) { for (int dir = -1; dir <= 1; dir += 2) { if (wayPoint.FindClosest(dir, horizontalSearch: true, tolerance: new Vector2(minDist, heightFromFloor), ignored: prevWaypoint.ToEnumerable()) != null) { wayPoint.Remove(); wayPoint = null; break; } } } prevWaypoint = wayPoint; } } float outSideWaypointInterval = 100.0f; if (submarine.Info.Type != SubmarineType.OutpostModule) { List <(WayPoint, int)> outsideWaypoints = new List <(WayPoint, int)>(); Rectangle borders = Hull.GetBorders(); int originalWidth = borders.Width; int originalHeight = borders.Height; borders.X -= Math.Min(500, originalWidth / 4); borders.Y += Math.Min(500, originalHeight / 4); borders.Width += Math.Min(1500, originalWidth / 2); borders.Height += Math.Min(1000, originalHeight / 2); borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition); if (borders.Width <= outSideWaypointInterval * 2) { borders.Inflate(outSideWaypointInterval * 2 - borders.Width, 0); } if (borders.Height <= outSideWaypointInterval * 2) { int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height; borders.Y += inflateAmount / 2; borders.Height += inflateAmount; } WayPoint[,] cornerWaypoint = new WayPoint[2, 2]; for (int i = 0; i < 2; i++) { for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval) { var wayPoint = new WayPoint( new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition, SpawnType.Path, submarine); outsideWaypoints.Add((wayPoint, i)); if (x == borders.X + outSideWaypointInterval) { cornerWaypoint[i, 0] = wayPoint; } else { wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]); } } cornerWaypoint[i, 1] = WayPointList[WayPointList.Count - 1]; } for (int i = 0; i < 2; i++) { WayPoint wayPoint = null; for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval) { wayPoint = new WayPoint( new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition, SpawnType.Path, submarine); outsideWaypoints.Add((wayPoint, i)); if (y == borders.Y - borders.Height) { wayPoint.ConnectTo(cornerWaypoint[1, i]); } else { wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]); } } wayPoint.ConnectTo(cornerWaypoint[0, i]); } Vector2 center = ConvertUnits.ToSimUnits(submarine.HiddenSubPosition); float halfHeight = ConvertUnits.ToSimUnits(borders.Height / 2); // Try to move the waypoints so that they are near the walls, roughly following the shape of the sub. foreach (var wayPoint in outsideWaypoints) { WayPoint wp = wayPoint.Item1; float xDiff = center.X - wp.SimPosition.X; Vector2 targetPos = new Vector2(center.X - xDiff * 0.5f, center.Y); Body wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine)); if (wall == null) { // Try again, and shoot to the center now. It happens with some subs that the first, offset raycast don't hit the walls. targetPos = new Vector2(center.X - xDiff, center.Y); wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine)); } if (wall != null) { float distanceFromWall = 1; if (xDiff > 0 && !submarine.Info.HasTag(SubmarineTag.Shuttle)) { // We don't want to move the waypoints near the tail too close to the engine. float yDist = Math.Abs(center.Y - wp.SimPosition.Y); distanceFromWall = MathHelper.Lerp(1, 3, MathUtils.InverseLerp(halfHeight, 0, yDist)); } Vector2 newPos = Submarine.LastPickedPosition + Submarine.LastPickedNormal * distanceFromWall; wp.rect = new Rectangle(ConvertUnits.ToDisplayUnits(newPos).ToPoint(), wp.rect.Size); wp.FindHull(); } } // Remove unwanted points var removals = new List <WayPoint>(); WayPoint previous = null; float tooClose = outSideWaypointInterval / 2; foreach (var wayPoint in outsideWaypoints) { WayPoint wp = wayPoint.Item1; if (wp.CurrentHull != null || Submarine.PickBody(wp.SimPosition, wp.SimPosition + Vector2.Normalize(center - wp.SimPosition) * 0.1f, collisionCategory: Physics.CollisionWall | Physics.CollisionItem, customPredicate: f => !(f.Body.UserData is Submarine), allowInsideFixture: true) != null) { // Remove waypoints that got inside/too near the sub. removals.Add(wp); previous = wp; continue; } foreach (var otherWayPoint in outsideWaypoints) { WayPoint otherWp = otherWayPoint.Item1; if (otherWp == wp) { continue; } if (removals.Contains(otherWp)) { continue; } float sqrDist = Vector2.DistanceSquared(wp.Position, otherWp.Position); // Remove waypoints that are too close to each other. if (!removals.Contains(previous) && sqrDist < tooClose * tooClose) { removals.Add(wp); } } previous = wp; } foreach (WayPoint wp in removals) { outsideWaypoints.RemoveAll(w => w.Item1 == wp); wp.Remove(); } for (int i = 0; i < outsideWaypoints.Count; i++) { WayPoint current = outsideWaypoints[i].Item1; if (current.linkedTo.Count > 1) { continue; } WayPoint next = null; int maxConnections = 2; float tooFar = outSideWaypointInterval * 5; for (int j = 0; j < maxConnections; j++) { if (current.linkedTo.Count >= maxConnections) { break; } tooFar /= current.linkedTo.Count; next = current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp.Item1 != next && wp.Item1.linkedTo.None(e => current.linkedTo.Contains(e)) && wp.Item1.linkedTo.Count < 2 && wp.Item2 < i); if (next != null) { current.ConnectTo(next); } } } } List <Structure> stairList = new List <Structure>(); foreach (MapEntity me in mapEntityList) { if (!(me is Structure stairs)) { continue; } if (stairs.StairDirection != Direction.None) { stairList.Add(stairs); } } foreach (Structure stairs in stairList) { WayPoint[] stairPoints = new WayPoint[3]; stairPoints[0] = new WayPoint( new Vector2(stairs.Rect.X - 32.0f, stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine); stairPoints[1] = new WayPoint( new Vector2(stairs.Rect.Right + 32.0f, stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine); for (int i = 0; i < 2; i++) { for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(100, 70)); if (closest == null) { continue; } stairPoints[i].ConnectTo(closest); } } stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine); stairPoints[0].ConnectTo(stairPoints[2]); stairPoints[2].ConnectTo(stairPoints[1]); } foreach (Item item in Item.ItemList) { var ladders = item.GetComponent <Ladder>(); if (ladders == null) { continue; } Vector2 bottomPoint = new Vector2(item.Rect.Center.X, item.Rect.Top - item.Rect.Height + 10); List <WayPoint> ladderPoints = new List <WayPoint> { new WayPoint(bottomPoint, SpawnType.Path, submarine), }; List <Body> ignoredBodies = new List <Body>(); // Lowest point is only meaningful for hanging ladders inside the sub, but it shouldn't matter in other cases either. // Start point is where the bots normally grasp the ladder when they stand on ground. WayPoint lowestPoint = ladderPoints[0]; WayPoint prevPoint = lowestPoint; Vector2 prevPos = prevPoint.SimPosition; Body ground = Submarine.PickBody(lowestPoint.SimPosition, lowestPoint.SimPosition - Vector2.UnitY, ignoredBodies, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform | Physics.CollisionStairs, customPredicate: f => !(f.Body.UserData is Submarine)); float startHeight = ground != null?ConvertUnits.ToDisplayUnits(ground.Position.Y) : bottomPoint.Y; startHeight += heightFromFloor; WayPoint startPoint = lowestPoint; Vector2 nextPos = new Vector2(item.Rect.Center.X, startHeight); // Don't create the start point if it's too close to the lowest point or if it's outside of the sub. // If we skip creating the start point, the lowest point is used instead. if (lowestPoint == null || Math.Abs(startPoint.Position.Y - startHeight) > 40 && Hull.FindHull(nextPos) != null) { startPoint = new WayPoint(nextPos, SpawnType.Path, submarine); ladderPoints.Add(startPoint); if (lowestPoint != null) { startPoint.ConnectTo(lowestPoint); } prevPoint = startPoint; prevPos = prevPoint.SimPosition; } for (float y = startPoint.Position.Y + LadderWaypointInterval; y < item.Rect.Y - 1.0f; y += LadderWaypointInterval) { //first check if there's a door in the way //(we need to create a waypoint linked to the door for NPCs to open it) Body pickedBody = Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)), prevPos, ignoredBodies, Physics.CollisionWall, false, (Fixture f) => f.Body.UserData is Item && ((Item)f.Body.UserData).GetComponent <Door>() != null); Door pickedDoor = null; if (pickedBody != null) { pickedDoor = (pickedBody?.UserData as Item).GetComponent <Door>(); } else { //no door, check for walls pickedBody = Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)), prevPos, ignoredBodies, null, false, (Fixture f) => f.Body.UserData is Structure); } if (pickedBody == null) { prevPos = Submarine.LastPickedPosition; continue; } else { ignoredBodies.Add(pickedBody); } if (pickedDoor != null) { WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine); ladderPoints.Add(newPoint); newPoint.ConnectedGap = pickedDoor.LinkedGap; newPoint.ConnectTo(prevPoint); prevPoint = newPoint; prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(pickedDoor.Item.Position.Y - pickedDoor.Item.Rect.Height)); } else { WayPoint newPoint = new WayPoint(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor, SpawnType.Path, submarine); ladderPoints.Add(newPoint); newPoint.ConnectTo(prevPoint); prevPoint = newPoint; prevPos = ConvertUnits.ToSimUnits(newPoint.Position); } } // Cap if (prevPoint.rect.Y < item.Rect.Y - 40) { WayPoint wayPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine); ladderPoints.Add(wayPoint); wayPoint.ConnectTo(prevPoint); } // Connect ladder waypoints to hull points at the right and left side foreach (WayPoint ladderPoint in ladderPoints) { ladderPoint.Ladders = ladders; bool isHatch = ladderPoint.ConnectedGap != null && !ladderPoint.ConnectedGap.IsRoomToRoom; for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = null; if (isHatch) { closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderPoints); } else { closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints); } if (closest == null) { continue; } ladderPoint.ConnectTo(closest); } } } // Another pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders) foreach (Item item in Item.ItemList) { var ladders = item.GetComponent <Ladder>(); if (ladders == null) { continue; } var wps = WayPointList.Where(wp => wp.Ladders == ladders).OrderByDescending(wp => wp.Rect.Y); WayPoint cap = wps.First(); WayPoint above = cap.FindClosest(1, horizontalSearch: false, tolerance: new Vector2(25, 50), filter: wp => wp.Ladders != null && wp.Ladders != ladders); above?.ConnectTo(cap); WayPoint bottom = wps.Last(); WayPoint below = bottom.FindClosest(-1, horizontalSearch: false, tolerance: new Vector2(25, 50), filter: wp => wp.Ladders != null && wp.Ladders != ladders); below?.ConnectTo(bottom); } foreach (Gap gap in Gap.GapList) { if (gap.IsHorizontal) { // Too small to walk through if (gap.Rect.Height < hullMinHeight) { continue; } Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor); var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap); // The closest waypoint can be quite far if the gap is at an exterior door. Vector2 tolerance = gap.IsRoomToRoom ? new Vector2(150, 70) : new Vector2(1000, 1000); for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, tolerance, gap.ConnectedDoor?.Body.FarseerBody); if (closest != null) { wayPoint.ConnectTo(closest); } } } else { // Create waypoints on vertical gaps on the outer walls, also hatches. if (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull)) { continue; } // Too small to swim through if (gap.Rect.Width < 50.0f) { continue; } Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2); // Some hatches are created in the block above where we handle the ladder waypoints. So we need to check for duplicates. if (WayPointList.Any(wp => wp.ConnectedGap == gap)) { continue; } var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap); Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull); int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y); WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(50, 100)); if (closest != null) { wayPoint.ConnectTo(closest); } for (dir = -1; dir <= 1; dir += 2) { closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), gap.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null); if (closest != null) { wayPoint.ConnectTo(closest); } } } } var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && w.linkedTo.None()); foreach (WayPoint wp in orphans) { wp.Remove(); } foreach (WayPoint wp in WayPointList) { if (wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2) { DebugConsole.ThrowError($"Couldn't automatically link the waypoint {wp.ID} outside of the submarine. You should do it manually. The waypoint ID is shown in red color."); } } //re-disable the bodies of the doors that are supposed to be open foreach (Door door in openDoors) { door.Body.Enabled = false; } return(true); }