/// <summary> /// Returns a rect that contains the borders of this sub and all subs docked to it /// </summary> public Rectangle GetDockedBorders() { Rectangle dockedBorders = Borders; dockedBorders.Y -= dockedBorders.Height; var connectedSubs = GetConnectedSubs(); foreach (Submarine dockedSub in connectedSubs) { if (dockedSub == this) { continue; } Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition; Rectangle dockedSubBorders = dockedSub.Borders; dockedSubBorders.Y -= dockedSubBorders.Height; dockedSubBorders.Location += MathUtils.ToPoint(diff); dockedBorders = Rectangle.Union(dockedBorders, dockedSubBorders); } dockedBorders.Y += dockedBorders.Height; return(dockedBorders); }
public override void UpdatePlacing(Camera cam) { if (PlayerInput.SecondaryMouseButtonClicked()) { selected = null; return; } Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); Vector2 size = ScaledSize; Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y); if (placePosition == Vector2.Zero) { if (PlayerInput.PrimaryMouseButtonHeld()) { placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); } newRect.X = (int)position.X; newRect.Y = (int)position.Y; } else { Vector2 placeSize = size; if (ResizeHorizontal) { placeSize.X = position.X - placePosition.X; } if (ResizeVertical) { placeSize.Y = placePosition.Y - position.Y; } //don't allow resizing width/height to less than the grid size if (ResizeHorizontal && Math.Abs(placeSize.X) < Submarine.GridSize.X) { placeSize.X = Submarine.GridSize.X; } if (ResizeVertical && Math.Abs(placeSize.Y) < Submarine.GridSize.Y) { placeSize.Y = Submarine.GridSize.Y; } newRect = Submarine.AbsRect(placePosition, placeSize); if (PlayerInput.PrimaryMouseButtonReleased()) { newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position); new Structure(newRect, this, Submarine.MainSub) { Submarine = Submarine.MainSub }; selected = null; return; } } }
public virtual void UpdatePlacing(Camera cam) { if (PlayerInput.SecondaryMouseButtonClicked()) { selected = null; return; } Vector2 placeSize = Submarine.GridSize; if (placePosition == Vector2.Zero) { Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); if (PlayerInput.PrimaryMouseButtonHeld()) { placePosition = position; } } else { Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); if (ResizeHorizontal) { placeSize.X = position.X - placePosition.X; } if (ResizeVertical) { placeSize.Y = placePosition.Y - position.Y; } Rectangle newRect = Submarine.AbsRect(placePosition, placeSize); newRect.Width = (int)Math.Max(newRect.Width, Submarine.GridSize.X); newRect.Height = (int)Math.Max(newRect.Height, Submarine.GridSize.Y); if (Submarine.MainSub != null) { newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position); } if (PlayerInput.PrimaryMouseButtonReleased()) { CreateInstance(newRect); placePosition = Vector2.Zero; if (!PlayerInput.IsShiftDown()) { selected = null; } } newRect.Y = -newRect.Y; } }
public static LinkedSubmarine Load(XElement element, Submarine submarine) { Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero); LinkedSubmarine linkedSub = null; if (Screen.Selected == GameMain.SubEditorScreen) { linkedSub = CreateDummy(submarine, element, pos); linkedSub.saveElement = element; linkedSub.purchasedLostShuttles = false; } else { linkedSub = new LinkedSubmarine(submarine) { saveElement = element }; linkedSub.purchasedLostShuttles = GameMain.GameSession.GameMode is CampaignMode campaign && campaign.PurchasedLostShuttles; string levelSeed = element.GetAttributeString("location", ""); if (!string.IsNullOrWhiteSpace(levelSeed) && GameMain.GameSession.Level != null && GameMain.GameSession.Level.Seed != levelSeed && !linkedSub.purchasedLostShuttles) { linkedSub.loadSub = false; } else { linkedSub.loadSub = true; linkedSub.rect.Location = MathUtils.ToPoint(pos); } } linkedSub.filePath = element.GetAttributeString("filepath", ""); int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]); for (int i = 0; i < linkedToIds.Length; i++) { linkedSub.linkedToID.Add((ushort)linkedToIds[i]); if (Screen.Selected == GameMain.SubEditorScreen) { if (FindEntityByID((ushort)linkedToIds[i]) is MapEntity linked) { linkedSub.linkedTo.Add(linked); } } } linkedSub.originalLinkedToID = (ushort)element.GetAttributeInt("originallinkedto", 0); linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0); return(linkedSub.loadSub ? linkedSub : null); }
public override void UpdatePlacing(Camera cam) { Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); Vector2 size = ScaledSize; Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y); if (placePosition == Vector2.Zero) { if (PlayerInput.LeftButtonHeld()) { placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); } newRect.X = (int)position.X; newRect.Y = (int)position.Y; } else { Vector2 placeSize = size; if (ResizeHorizontal) { placeSize.X = position.X - placePosition.X; } if (ResizeVertical) { placeSize.Y = placePosition.Y - position.Y; } newRect = Submarine.AbsRect(placePosition, placeSize); if (PlayerInput.LeftButtonReleased()) { //don't allow resizing width/height to zero if ((!ResizeHorizontal || placeSize.X != 0.0f) && (!ResizeVertical || placeSize.Y != 0.0f)) { newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position); var structure = new Structure(newRect, this, Submarine.MainSub); structure.Submarine = Submarine.MainSub; } selected = null; return; } } if (PlayerInput.RightButtonHeld()) { selected = null; } }
/// <summary> /// Moves away any character that is inside the bounding box of the sub (but not inside the sub) /// </summary> /// <param name="subTranslation">The translation that was applied to the sub before doing the displacement /// (used for determining where to push the characters)</param> private void DisplaceCharacters(Vector2 subTranslation) { Rectangle worldBorders = Borders; worldBorders.Location += MathUtils.ToPoint(ConvertUnits.ToDisplayUnits(Body.SimPosition)); Vector2 translateDir = Vector2.Normalize(subTranslation); if (!MathUtils.IsValid(translateDir)) { translateDir = Vector2.UnitY; } foreach (Character c in Character.CharacterList) { if (c.AnimController.CurrentHull != null && c.AnimController.CanEnterSubmarine) { continue; } foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered) { continue; } //if the character isn't inside the bounding box, continue if (!Submarine.RectContains(worldBorders, limb.WorldPosition)) { continue; } //cast a line from the position of the character to the same direction as the translation of the sub //and see where it intersects with the bounding box if (!MathUtils.GetLineRectangleIntersection(limb.WorldPosition, limb.WorldPosition + translateDir * 100000.0f, worldBorders, out Vector2 intersection)) { //should never happen when casting a line out from inside the bounding box Debug.Assert(false); continue; } //"+ translatedir" in order to move the character slightly away from the wall c.AnimController.SetPosition(ConvertUnits.ToSimUnits(c.WorldPosition + (intersection - limb.WorldPosition)) + translateDir); return; } } }
/// <summary> /// Finds the sub whose borders contain the position /// </summary> public static Submarine FindContaining(Vector2 position) { foreach (Submarine sub in Submarine.Loaded) { Rectangle subBorders = sub.Borders; subBorders.Location += MathUtils.ToPoint(sub.HiddenSubPosition) - new Microsoft.Xna.Framework.Point(0, sub.Borders.Height); subBorders.Inflate(500.0f, 500.0f); if (subBorders.Contains(position)) { return(sub); } } return(null); }
public void AddToGrid(Submarine submarine) { foreach (EntityGrid grid in entityGrids) { if (grid.Submarine != submarine) { continue; } rect.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition); grid.InsertEntity(this); rect.Location += MathUtils.ToPoint(submarine.HiddenSubPosition); return; } }
// LinkedSubmarine.Load() is called from MapEntity.LoadAll() public static LinkedSubmarine Load(XElement element, Submarine submarine, IdRemap idRemap) { Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero); LinkedSubmarine linkedSub; if (Screen.Selected == GameMain.SubEditorScreen) { linkedSub = CreateDummy(submarine, element, pos, idRemap.AssignMaxId()); linkedSub.saveElement = element; linkedSub.purchasedLostShuttles = false; } else { string levelSeed = element.GetAttributeString("location", ""); LevelData levelData = GameMain.GameSession?.Campaign?.NextLevel ?? GameMain.GameSession?.LevelData; linkedSub = new LinkedSubmarine(submarine, idRemap.AssignMaxId()) { purchasedLostShuttles = GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.PurchasedLostShuttles, saveElement = element }; if (!string.IsNullOrWhiteSpace(levelSeed) && levelData != null && levelData.Seed != levelSeed && !linkedSub.purchasedLostShuttles) { linkedSub.loadSub = false; } else { linkedSub.loadSub = true; linkedSub.rect.Location = MathUtils.ToPoint(pos); } } linkedSub.filePath = element.GetAttributeString("filepath", ""); int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]); for (int i = 0; i < linkedToIds.Length; i++) { linkedSub.linkedToID.Add(idRemap.GetOffsetId(linkedToIds[i])); } linkedSub.originalLinkedToID = idRemap.GetOffsetId(element.GetAttributeInt("originallinkedto", 0)); linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0); return(linkedSub.loadSub ? linkedSub : null); }
public static LinkedSubmarine Load(XElement element, Submarine submarine) { Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero); LinkedSubmarine linkedSub = null; if (Screen.Selected == GameMain.SubEditorScreen) { linkedSub = CreateDummy(submarine, element, pos); linkedSub.saveElement = element; } else { linkedSub = new LinkedSubmarine(submarine) { saveElement = element }; string levelSeed = element.GetAttributeString("location", ""); if (!string.IsNullOrWhiteSpace(levelSeed) && GameMain.GameSession.Level != null && GameMain.GameSession.Level.Seed != levelSeed) { linkedSub.loadSub = false; return(null); } linkedSub.loadSub = true; linkedSub.rect.Location = MathUtils.ToPoint(pos); } linkedSub.filePath = element.GetAttributeString("filepath", ""); string linkedToString = element.GetAttributeString("linkedto", ""); if (linkedToString != "") { string[] linkedToIds = linkedToString.Split(','); for (int i = 0; i < linkedToIds.Length; i++) { linkedSub.linkedToID.Add((ushort)int.Parse(linkedToIds[i])); } } return(linkedSub); }
/// <summary> /// Returns a rect that contains the borders of this sub and all subs docked to it /// </summary> public Rectangle GetDockedBorders() { Rectangle dockedBorders = Borders; dockedBorders.Y -= dockedBorders.Height; foreach (Submarine dockedSub in DockedTo) { Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition; Rectangle dockedSubBorders = dockedSub.Borders; dockedSubBorders.Y -= dockedSubBorders.Height; dockedSubBorders.Location += MathUtils.ToPoint(diff); dockedBorders = Rectangle.Union(dockedBorders, dockedSubBorders); } dockedBorders.Y += dockedBorders.Height; return(dockedBorders); }
private void GenerateRuin(List <VoronoiCell> mainPath) { Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server), Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server)); float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f; Vector2 ruinPos = cells[Rand.Int(cells.Count, Rand.RandSync.Server)].Center; //50% chance of placing the ruins at a cave if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.5f) { TryGetInterestingPosition(true, PositionType.Cave, 0.0f, out ruinPos); } ruinPos.Y = Math.Min(ruinPos.Y, borders.Y + borders.Height - ruinSize.Y / 2); ruinPos.Y = Math.Max(ruinPos.Y, SeaFloorTopPos + ruinSize.Y / 2.0f); int iter = 0; while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius * 2.0f)) { Vector2 weighedPathPos = ruinPos; iter++; foreach (VoronoiCell pathCell in mainPath) { float dist = Vector2.Distance(pathCell.Center, ruinPos); if (dist > 10000.0f) { continue; } Vector2 moveAmount = Vector2.Normalize(ruinPos - pathCell.Center) * 100000.0f / dist; weighedPathPos += moveAmount; weighedPathPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPos.Y); } ruinPos = weighedPathPos; if (iter > 10000) { break; } } VoronoiCell closestPathCell = null; float closestDist = 0.0f; foreach (VoronoiCell pathCell in mainPath) { float dist = Vector2.Distance(pathCell.Center, ruinPos); if (closestPathCell == null || dist < closestDist) { closestPathCell = pathCell; closestDist = dist; } } var ruin = new Ruin(closestPathCell, cells, new Rectangle(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize))); ruins.Add(ruin); ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); for (int i = 0; i < 4; i++) { positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center.ToVector2(), PositionType.Ruin)); } foreach (RuinShape ruinShape in ruin.RuinShapes) { var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height)); foreach (VoronoiCell cell in tooClose) { if (cell.CellType == CellType.Empty) { continue; } foreach (GraphEdge e in cell.edges) { Rectangle rect = ruinShape.Rect; rect.Y += rect.Height; if (ruinShape.Rect.Contains(e.point1) || ruinShape.Rect.Contains(e.point2) || MathUtils.GetLineRectangleIntersection(e.point1, e.point2, rect) != null) { cell.CellType = CellType.Removed; int x = (int)Math.Floor(cell.Center.X / GridCellSize); int y = (int)Math.Floor(cell.Center.Y / GridCellSize); cellGrid[x, y].Remove(cell); cells.Remove(cell); break; } } } } }
public void Update(float deltaTime) { while (impactQueue.Count > 0) { var impact = impactQueue.Dequeue(); if (impact.Target.UserData is VoronoiCell cell) { HandleLevelCollision(impact); } else if (impact.Target.Body.UserData is Structure) { HandleLevelCollision(impact); } else if (impact.Target.Body.UserData is Submarine otherSub) { HandleSubCollision(impact, otherSub); } else if (impact.Target.Body.UserData is Limb limb) { HandleLimbCollision(impact, limb); } } //------------------------- if (Body.FarseerBody.BodyType == BodyType.Static) { return; } ClientUpdatePosition(deltaTime); if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } Vector2 totalForce = CalculateBuoyancy(); //------------------------- //if outside left or right edge of the level if (Level.Loaded != null && (Position.X < 0 || Position.X > Level.Loaded.Size.X)) { Rectangle worldBorders = Borders; worldBorders.Location += MathUtils.ToPoint(Position); //push the sub back below the upper "barrier" of the level if (worldBorders.Y > Level.Loaded.Size.Y) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y))); } else if (worldBorders.Y - worldBorders.Height < Level.Loaded.BottomPos) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height)))); } if (Position.X < 0) { float force = Math.Abs(Position.X * 0.5f); totalForce += Vector2.UnitX * force; if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f)); } } else { float force = (Position.X - Level.Loaded.Size.X) * 0.5f; totalForce -= Vector2.UnitX * force; if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f)); } } } //------------------------- if (Body.LinearVelocity.LengthSquared() > 0.0001f) { //TODO: sync current drag with clients? float attachedMass = 0.0f; JointEdge jointEdge = Body.FarseerBody.JointList; while (jointEdge != null) { Body otherBody = jointEdge.Joint.BodyA == Body.FarseerBody ? jointEdge.Joint.BodyB : jointEdge.Joint.BodyA; Character character = (otherBody.UserData as Limb)?.character; if (character != null) { attachedMass += character.Mass; } jointEdge = jointEdge.Next; } float horizontalDragCoefficient = MathHelper.Clamp(HorizontalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag); totalForce.X -= Math.Sign(Body.LinearVelocity.X) * Body.LinearVelocity.X * Body.LinearVelocity.X * horizontalDragCoefficient * Body.Mass; float verticalDragCoefficient = MathHelper.Clamp(VerticalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag); totalForce.Y -= Math.Sign(Body.LinearVelocity.Y) * Body.LinearVelocity.Y * Body.LinearVelocity.Y * verticalDragCoefficient * Body.Mass; } ApplyForce(totalForce); UpdateDepthDamage(deltaTime); }
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 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); }
public void Update(float deltaTime) { if (Body.FarseerBody.IsStatic) { return; } ClientUpdatePosition(deltaTime); if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } //if outside left or right edge of the level if (Position.X < 0 || Position.X > Level.Loaded.Size.X) { Rectangle worldBorders = Borders; worldBorders.Location += MathUtils.ToPoint(Position); //push the sub back below the upper "barrier" of the level if (worldBorders.Y > Level.Loaded.Size.Y) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y))); } else if (worldBorders.Y - worldBorders.Height < Level.Loaded.BottomPos) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height)))); } } //------------------------- Vector2 totalForce = CalculateBuoyancy(); if (Body.LinearVelocity.LengthSquared() > 0.0001f) { //TODO: sync current drag with clients? float attachedMass = 0.0f; JointEdge jointEdge = Body.FarseerBody.JointList; while (jointEdge != null) { Body otherBody = jointEdge.Joint.BodyA == Body.FarseerBody ? jointEdge.Joint.BodyB : jointEdge.Joint.BodyA; Character character = (otherBody.UserData as Limb)?.character; if (character != null) { attachedMass += character.Mass; } jointEdge = jointEdge.Next; } float horizontalDragCoefficient = MathHelper.Clamp(HorizontalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag); totalForce.X -= Math.Sign(Body.LinearVelocity.X) * Body.LinearVelocity.X * Body.LinearVelocity.X * horizontalDragCoefficient * Body.Mass; float verticalDragCoefficient = MathHelper.Clamp(VerticalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag); totalForce.Y -= Math.Sign(Body.LinearVelocity.Y) * Body.LinearVelocity.Y * Body.LinearVelocity.Y * verticalDragCoefficient * Body.Mass; } ApplyForce(totalForce); UpdateDepthDamage(deltaTime); }
public void Update(float deltaTime) { if (GameMain.Client != null) { if (memPos.Count == 0) { return; } Vector2 newVelocity = Body.LinearVelocity; Vector2 newPosition = Body.SimPosition; Body.CorrectPosition(memPos, deltaTime, out newVelocity, out newPosition); Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition); newVelocity = newVelocity.ClampLength(100.0f); if (!MathUtils.IsValid(newVelocity)) { return; } List <Submarine> subsToMove = submarine.GetConnectedSubs(); foreach (Submarine dockedSub in subsToMove) { if (dockedSub == submarine) { continue; } //clear the position buffer of the docked subs to prevent unnecessary position corrections dockedSub.SubBody.memPos.Clear(); } Submarine closestSub = null; if (Character.Controlled == null) { closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter); } else { closestSub = Character.Controlled.Submarine; } bool displace = moveAmount.Length() > 100.0f; foreach (Submarine sub in subsToMove) { sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); sub.PhysicsBody.LinearVelocity = newVelocity; if (displace) { sub.SubBody.DisplaceCharacters(moveAmount); } } if (closestSub != null && subsToMove.Contains(closestSub)) { GameMain.GameScreen.Cam.Position += moveAmount; if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) { GameMain.GameScreen.Cam.TargetPos += moveAmount; } if (Character.Controlled != null) { Character.Controlled.CursorPosition += moveAmount; } } return; } //if outside left or right edge of the level if (Position.X < 0 || Position.X > Level.Loaded.Size.X) { Rectangle worldBorders = Borders; worldBorders.Location += MathUtils.ToPoint(Position); //push the sub back below the upper "barrier" of the level if (worldBorders.Y > Level.Loaded.Size.Y) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y))); } else if (worldBorders.Y - worldBorders.Height < Level.Loaded.BottomPos) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height)))); } } //------------------------- Vector2 totalForce = CalculateBuoyancy(); if (Body.LinearVelocity.LengthSquared() > 0.000001f) { float dragCoefficient = 0.01f; float speedLength = (Body.LinearVelocity == Vector2.Zero) ? 0.0f : Body.LinearVelocity.Length(); float drag = speedLength * speedLength * dragCoefficient * Body.Mass; totalForce += -Vector2.Normalize(Body.LinearVelocity) * drag; } ApplyForce(totalForce); UpdateDepthDamage(deltaTime); }
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; } }