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); }
private void CheckDoorsInPath() { for (int i = 0; i < 2; i++) { WayPoint node = null; WayPoint nextNode = null; if (i == 0) { node = currentPath.CurrentNode; nextNode = currentPath.NextNode; } else { node = currentPath.PrevNode; nextNode = currentPath.CurrentNode; } if (node == null || node.ConnectedGap == null || node.ConnectedGap.ConnectedDoor == null) { continue; } if (nextNode == null) { continue; } var door = node.ConnectedGap.ConnectedDoor; bool shouldBeOpen = false; if (door.LinkedGap.IsHorizontal) { int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X); shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f; } else { int currentDir = Math.Sign(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f; } //toggle the door if it's the previous node and open, or if it's current node and closed if (door.IsOpen != shouldBeOpen) { var buttons = door.Item.GetConnectedComponents <Controller>(true); Controller closestButton = null; float closestDist = 0.0f; foreach (Controller controller in buttons) { float dist = Vector2.Distance(controller.Item.WorldPosition, character.WorldPosition); if (dist > controller.Item.InteractDistance * 2.0f) { continue; } if (dist < closestDist || closestButton == null) { closestButton = controller; closestDist = dist; } } if (closestButton != null) { if (!closestButton.HasRequiredItems(character, false) && shouldBeOpen) { currentPath.Unreachable = true; return; } closestButton.Item.TryInteract(character, false, true, true); break; } } } }
public static void CreateItems(List <ItemPrefab> itemsToSpawn) { WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); if (wp == null) { DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); return; } Hull cargoRoom = Hull.FindHull(wp.WorldPosition); if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } Dictionary <ItemContainer, int> availableContainers = new Dictionary <ItemContainer, int>(); foreach (ItemPrefab prefab in itemsToSpawn) { Vector2 position = new Vector2( Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), cargoRoom.Rect.Y - cargoRoom.Rect.Height + prefab.Size.Y / 2); ItemContainer itemContainer = null; if (!string.IsNullOrEmpty(prefab.CargoContainerName)) { itemContainer = availableContainers.Keys.ToList().Find(ac => ac.Item.Prefab.NameMatches(prefab.CargoContainerName) || ac.Item.Prefab.Tags.Contains(prefab.CargoContainerName.ToLowerInvariant())); if (itemContainer == null) { var containerPrefab = MapEntityPrefab.List.Find(ep => ep.NameMatches(prefab.CargoContainerName) || (ep.Tags != null && ep.Tags.Contains(prefab.CargoContainerName.ToLowerInvariant()))) as ItemPrefab; if (containerPrefab == null) { DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); continue; } Item containerItem = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItem.GetComponent <ItemContainer>(); if (itemContainer == null) { DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); continue; } availableContainers.Add(itemContainer, itemContainer.Capacity); if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } } } if (itemContainer == null) { //no container, place at the waypoint if (GameMain.Server != null) { Entity.Spawner.AddToSpawnQueue(prefab, position, wp.Submarine); } else { new Item(prefab, position, wp.Submarine); } } else { //place in the container if (GameMain.Server != null) { Entity.Spawner.AddToSpawnQueue(prefab, itemContainer.Inventory); } else { var item = new Item(prefab, position, wp.Submarine); itemContainer.Inventory.TryPutItem(item, null); } //reduce the number of available slots in the container availableContainers[itemContainer]--; if (availableContainers[itemContainer] <= 0) { availableContainers.Remove(itemContainer); } } } itemsToSpawn.Clear(); }
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 WayPoint[] SelectCrewSpawnPoints(List <CharacterInfo> crew, Submarine submarine) { List <WayPoint> subWayPoints = WayPointList.FindAll(wp => wp.Submarine == submarine); List <WayPoint> unassignedWayPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human); WayPoint[] assignedWayPoints = new WayPoint[crew.Count]; for (int i = 0; i < crew.Count; i++) { //try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job for (int n = 0; n < unassignedWayPoints.Count; n++) { if (crew[i].Job.Prefab != unassignedWayPoints[n].assignedJob) { continue; } assignedWayPoints[i] = unassignedWayPoints[n]; unassignedWayPoints.RemoveAt(n); break; } } //go through the crewmembers that don't have a spawnpoint yet (if any) for (int i = 0; i < crew.Count; i++) { if (assignedWayPoints[i] != null) { continue; } //try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else foreach (WayPoint wp in subWayPoints) { if (wp.spawnType != SpawnType.Human || wp.assignedJob != crew[i].Job.Prefab) { continue; } assignedWayPoints[i] = wp; break; } if (assignedWayPoints[i] != null) { continue; } //try to assign a spawnpoint that isn't meant for any specific job var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.assignedJob == null); if (nonJobSpecificPoints.Any()) { assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.Server)]; } if (assignedWayPoints[i] != null) { continue; } //everything else failed -> just give a random spawnpoint inside the sub assignedWayPoints[i] = GetRandom(SpawnType.Human, null, submarine, true); } for (int i = 0; i < assignedWayPoints.Length; i++) { if (assignedWayPoints[i] == null) { DebugConsole.ThrowError("Couldn't find a waypoint for " + crew[i].Name + "!"); assignedWayPoints[i] = WayPointList[0]; } } return(assignedWayPoints); }
public PathNode(WayPoint wayPoint) { Waypoint = wayPoint; Position = wayPoint.SimPosition; WayPointID = Waypoint.ID; }
private void InitializeJobItem(Character character, XElement itemElement, WayPoint spawnPoint = null, Item parentItem = null) { ItemPrefab itemPrefab; if (itemElement.Attribute("name") != null) { string itemName = itemElement.Attribute("name").Value; DebugConsole.ThrowError("Error in Job config (" + Name + ") - use item identifiers instead of names to configure the items."); itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null) { DebugConsole.ThrowError("Tried to spawn \"" + Name + "\" with the item \"" + itemName + "\". Matching item prefab not found."); return; } } else { string itemIdentifier = itemElement.GetAttributeString("identifier", ""); itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; if (itemPrefab == null) { DebugConsole.ThrowError("Tried to spawn \"" + Name + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found."); return; } } Item item = new Item(itemPrefab, character.Position, null); #if SERVER if (GameMain.Server != null && Entity.Spawner != null) { if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item)) { string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created."; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item); GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item); } Entity.Spawner.CreateNetworkEvent(item, false); } #endif if (itemElement.GetAttributeBool("equip", false)) { List <InvSlotType> allowedSlots = new List <InvSlotType>(item.AllowedSlots); allowedSlots.Remove(InvSlotType.Any); character.Inventory.TryPutItem(item, null, allowedSlots); } else { character.Inventory.TryPutItem(item, null, item.AllowedSlots); } Wearable wearable = ((List <ItemComponent>)item.Components)?.Find(c => c is Wearable) as Wearable; if (wearable != null) { if (Variant > 0 && Variant <= wearable.Variants) { wearable.Variant = Variant; } else { wearable.Variant = wearable.Variant; //force server event if (wearable.Variants > 0 && Variant == 0) { //set variant to the same as the wearable to get the rest of the character's gear //to use the same variant (if possible) Variant = wearable.Variant; } } } if (item.Prefab.Identifier == "idcard" && spawnPoint != null) { foreach (string s in spawnPoint.IdCardTags) { item.AddTag(s); } item.AddTag("name:" + character.Name); item.AddTag("job:" + Name); if (!string.IsNullOrWhiteSpace(spawnPoint.IdCardDesc)) { item.Description = spawnPoint.IdCardDesc; } } foreach (WifiComponent wifiComponent in item.GetComponents <WifiComponent>()) { wifiComponent.TeamID = character.TeamID; } if (parentItem != null) { parentItem.Combine(item, user: null); } foreach (XElement childItemElement in itemElement.Elements()) { InitializeJobItem(character, childItemElement, spawnPoint, item); } }
static DebugConsole() { commands.Add(new Command("help", "", (string[] args) => { if (args.Length == 0) { foreach (Command c in commands) { if (string.IsNullOrEmpty(c.help)) { continue; } NewMessage(c.help, Color.Cyan); } } else { var matchingCommand = commands.Find(c => c.names.Any(name => name == args[0])); if (matchingCommand == null) { NewMessage("Command " + args[0] + " not found.", Color.Red); } else { NewMessage(matchingCommand.help, Color.Cyan); } } })); commands.Add(new Command("clientlist", "clientlist: List all the clients connected to the server.", (string[] args) => { if (GameMain.Server == null) { return; } NewMessage("***************", Color.Cyan); foreach (Client c in GameMain.Server.ConnectedClients) { NewMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan); } NewMessage("***************", Color.Cyan); })); commands.Add(new Command("createfilelist", "", (string[] args) => { UpdaterUtil.SaveFileList("filelist.xml"); })); commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) => { if (args.Length == 0) { return; } Character spawnedCharacter = null; Vector2 spawnPosition = Vector2.Zero; WayPoint spawnPoint = null; if (args.Length > 1) { switch (args[1].ToLowerInvariant()) { case "inside": spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); break; case "outside": spawnPoint = WayPoint.GetRandom(SpawnType.Enemy); break; case "near": case "close": float closestDist = -1.0f; foreach (WayPoint wp in WayPoint.WayPointList) { if (wp.Submarine != null) { continue; } //don't spawn inside hulls if (Hull.FindHull(wp.WorldPosition, null) != null) { continue; } float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter); if (closestDist < 0.0f || dist < closestDist) { spawnPoint = wp; closestDist = dist; } } break; case "cursor": spawnPosition = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); break; default: spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); break; } } else { spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); } if (string.IsNullOrWhiteSpace(args[0])) { return; } if (spawnPoint != null) { spawnPosition = spawnPoint.WorldPosition; } if (args[0].ToLowerInvariant() == "human") { spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition); #if CLIENT if (GameMain.GameSession != null) { SinglePlayerCampaign mode = GameMain.GameSession.GameMode as SinglePlayerCampaign; if (mode != null) { Character.Controlled = spawnedCharacter; GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled); GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled); } } #endif } else { List <string> characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character); foreach (string characterFile in characterFiles) { if (Path.GetFileNameWithoutExtension(characterFile).ToLowerInvariant() == args[0].ToLowerInvariant()) { Character.Create(characterFile, spawnPosition); return; } } ThrowError("No character matching the name \"" + args[0] + "\" found in the selected content package."); //attempt to open the config from the default path (the file may still be present even if it isn't included in the content package) string configPath = "Content/Characters/" + args[0].First().ToString().ToUpper() + args[0].Substring(1) + "/" + args[0].ToLower() + ".xml"; Character.Create(configPath, spawnPosition); } })); commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory]: Spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted.", (string[] args) => { if (args.Length < 1) { return; } Vector2?spawnPos = null; Inventory spawnInventory = null; int extraParams = 0; switch (args.Last()) { case "cursor": extraParams = 1; spawnPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); break; case "inventory": extraParams = 1; spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory; break; default: extraParams = 0; break; } string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant(); var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null) { ThrowError("Item \"" + itemName + "\" not found!"); return; } if (spawnPos == null && spawnInventory == null) { var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; } if (spawnPos != null) { Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); } else if (spawnInventory != null) { Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory); } })); commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = true; NewMessage("Crew AI disabled", Color.White); })); commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = false; NewMessage("Crew AI enabled", Color.White); })); commands.Add(new Command("autorestartinterval", "autorestartinterval [seconds]: Set how long the server waits between rounds before automatically starting a new one.", (string[] args) => { if (GameMain.Server == null) { return; } if (args.Length > 0) { int parsedInt = 0; if (int.TryParse(args[0], out parsedInt) && parsedInt >= 0) { GameMain.Server.AutoRestartInterval = parsedInt; NewMessage("Autorestart interval set to " + GameMain.Server.AutoRestartInterval + " seconds.", Color.White); } } })); commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) => { if (GameMain.Server == null) { return; } if (args.Length > 0) { int parsedInt = 0; if (int.TryParse(args[0], out parsedInt) && parsedInt >= 0) { GameMain.Server.AutoRestartTimer = parsedInt; GameMain.NetLobbyScreen.LastUpdateID++; NewMessage("Autorestart timer set to " + GameMain.Server.AutoRestartTimer + " seconds.", Color.White); } } })); commands.Add(new Command("kick", "kick [name]: Kick a player out of the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) { return; } string playerName = string.Join(" ", args); ShowQuestionPrompt("Reason for kicking \"" + playerName + "\"?", (reason) => { GameMain.NetworkMember.KickPlayer(playerName, reason); }); })); commands.Add(new Command("kickid", "kickid [id]: Kick the player with the specified client ID out of the server.", (string[] args) => { if (GameMain.Server == null || args.Length == 0) { return; } int id = 0; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } ShowQuestionPrompt("Reason for kicking \"" + client.Name + "\"?", (reason) => { GameMain.Server.KickPlayer(client.Name, reason); }); })); commands.Add(new Command("ban", "ban [name]: Kick and ban the player from the server.", (string[] args) => { if (GameMain.NetworkMember == null || args.Length == 0) { return; } string clientName = string.Join(" ", args); ShowQuestionPrompt("Reason for banning \"" + clientName + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => { TimeSpan?banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { TimeSpan parsedBanDuration; if (!TryParseTimeSpan(duration, out parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; } banDuration = parsedBanDuration; } GameMain.NetworkMember.BanPlayer(clientName, reason, false, banDuration); }); }); })); commands.Add(new Command("banid", "banid [id]: Kick and ban the player with the specified client ID from the server.", (string[] args) => { if (GameMain.Server == null || args.Length == 0) { return; } int id = 0; int.TryParse(args[0], out id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); if (client == null) { ThrowError("Client id \"" + id + "\" not found."); return; } ShowQuestionPrompt("Reason for banning \"" + client.Name + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => { TimeSpan?banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { TimeSpan parsedBanDuration; if (!TryParseTimeSpan(duration, out parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; } banDuration = parsedBanDuration; } GameMain.Server.BanPlayer(client.Name, reason, false, banDuration); }); }); })); commands.Add(new Command("banip", "banip [ip]: Ban the IP address from the server.", (string[] args) => { if (GameMain.Server == null || args.Length == 0) { return; } ShowQuestionPrompt("Reason for banning the ip \"" + commands[1] + "\"?", (reason) => { ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => { TimeSpan?banDuration = null; if (!string.IsNullOrWhiteSpace(duration)) { TimeSpan parsedBanDuration; if (!TryParseTimeSpan(duration, out parsedBanDuration)) { ThrowError("\"" + duration + "\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\"."); return; } banDuration = parsedBanDuration; } var client = GameMain.Server.ConnectedClients.Find(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]); if (client == null) { GameMain.Server.BanList.BanPlayer("Unnamed", args[0], reason, banDuration); } else { GameMain.Server.KickClient(client, reason); } }); }); })); commands.Add(new Command("teleportcharacter|teleport", "teleport [character name]: Teleport the specified character to the position of the cursor. If the name parameter is omitted, the controlled character will be teleported.", (string[] args) => { Character tpCharacter = null; if (args.Length == 0) { tpCharacter = Character.Controlled; } else { tpCharacter = FindMatchingCharacter(args, false); } if (tpCharacter == null) { return; } var cam = GameMain.GameScreen.Cam; tpCharacter.AnimController.CurrentHull = null; tpCharacter.Submarine = null; tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition))); tpCharacter.AnimController.FindHull(cam.ScreenToWorld(PlayerInput.MousePosition), true); })); commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) => { if (Submarine.MainSub == null) { return; } Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; NewMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", Color.White); })); commands.Add(new Command("lockx", "lockx: Lock horizontal movement of the main submarine.", (string[] args) => { Submarine.LockX = !Submarine.LockX; })); commands.Add(new Command("locky", "loxky: Lock vertical movement of the main submarine.", (string[] args) => { Submarine.LockY = !Submarine.LockY; })); commands.Add(new Command("dumpids", "", (string[] args) => { try { int count = args.Length == 0 ? 10 : int.Parse(args[0]); Entity.DumpIds(count); } catch (Exception e) { ThrowError("Failed to dump ids", e); } })); commands.Add(new Command("heal", "heal [character name]: Restore the specified character to full health. If the name parameter is omitted, the controlled character will be healed.", (string[] args) => { Character healedCharacter = null; if (args.Length == 0) { healedCharacter = Character.Controlled; } else { healedCharacter = FindMatchingCharacter(args); } if (healedCharacter != null) { healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); healedCharacter.Oxygen = 100.0f; healedCharacter.Bleeding = 0.0f; healedCharacter.SetStun(0.0f, true); } })); commands.Add(new Command("revive", "revive [character name]: Bring the specified character back from the dead. If the name parameter is omitted, the controlled character will be revived.", (string[] args) => { Character revivedCharacter = null; if (args.Length == 0) { revivedCharacter = Character.Controlled; } else { revivedCharacter = FindMatchingCharacter(args); } if (revivedCharacter == null) { return; } revivedCharacter.Revive(false); if (GameMain.Server != null) { foreach (Client c in GameMain.Server.ConnectedClients) { if (c.Character != revivedCharacter) { continue; } //clients stop controlling the character when it dies, force control back GameMain.Server.SetClientCharacter(c, revivedCharacter); break; } } })); commands.Add(new Command("freeze", "", (string[] args) => { if (Character.Controlled != null) { Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen; } })); commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) => { Character.Controlled = null; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; })); commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => { if (GameMain.Client == null) { Hull.EditWater = !Hull.EditWater; NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); } })); commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) => { if (GameMain.Client == null) { Hull.EditFire = !Hull.EditFire; NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White); } })); commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage]: Creates an explosion at the position of the cursor.", (string[] args) => { Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); float range = 500, force = 10, damage = 50, structureDamage = 10; if (args.Length > 0) { float.TryParse(args[0], out range); } if (args.Length > 1) { float.TryParse(args[1], out force); } if (args.Length > 2) { float.TryParse(args[2], out damage); } if (args.Length > 3) { float.TryParse(args[3], out structureDamage); } new Explosion(range, force, damage, structureDamage).Explode(explosionPos); })); commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => { foreach (Item it in Item.ItemList) { it.Condition = it.Prefab.Health; } })); commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) => { foreach (Structure w in Structure.WallList) { for (int i = 0; i < w.SectionCount; i++) { w.AddDamage(i, -100000.0f); } } })); commands.Add(new Command("power", "power [temperature]: Immediately sets the temperature of the nuclear reactor to the specified value.", (string[] args) => { Item reactorItem = Item.ItemList.Find(i => i.GetComponent <Reactor>() != null); if (reactorItem == null) { return; } float power = 5000.0f; if (args.Length > 0) { float.TryParse(args[0], out power); } var reactor = reactorItem.GetComponent <Reactor>(); reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f; reactor.AutoTemp = true; reactor.Temperature = power; if (GameMain.Server != null) { reactorItem.CreateServerEvent(reactor); } })); commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) => { foreach (Hull hull in Hull.hullList) { hull.OxygenPercentage = 100.0f; } })); commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => { foreach (Character c in Character.CharacterList) { if (!(c.AIController is EnemyAIController)) { continue; } c.AddDamage(CauseOfDeath.Damage, 10000.0f, null); } })); commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) => { if (GameMain.Server == null) { return; } GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; })); commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] ; [character name]: Gives the client control of the specified character.", (string[] args) => { if (GameMain.Server == null) { return; } int separatorIndex = Array.IndexOf(args, ";"); if (separatorIndex == -1 || args.Length < 3) { ThrowError("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\""); return; } string[] argsLeft = args.Take(separatorIndex).ToArray(); string[] argsRight = args.Skip(separatorIndex + 1).ToArray(); string clientName = String.Join(" ", argsLeft); var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName); if (client == null) { ThrowError("Client \"" + clientName + "\" not found."); } var character = FindMatchingCharacter(argsRight, false); GameMain.Server.SetClientCharacter(client, character); })); commands.Add(new Command("campaigninfo|campaignstatus", "campaigninfo: Display information about the state of the currently active campaign.", (string[] args) => { var campaign = GameMain.GameSession?.GameMode as CampaignMode; if (campaign == null) { ThrowError("No campaign active!"); return; } campaign.LogState(); })); commands.Add(new Command("campaigndestination|setcampaigndestination", "campaigndestination [index]: Set the location to head towards in the currently active campaign.", (string[] args) => { var campaign = GameMain.GameSession?.GameMode as CampaignMode; if (campaign == null) { ThrowError("No campaign active!"); return; } if (args.Length == 0) { int i = 0; foreach (LocationConnection connection in campaign.Map.CurrentLocation.Connections) { NewMessage(" " + i + ". " + connection.OtherLocation(campaign.Map.CurrentLocation).Name, Color.White); i++; } ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) => { int destinationIndex = -1; if (!int.TryParse(selectedDestination, out destinationIndex)) { return; } if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { NewMessage("Index out of bounds!", Color.Red); return; } Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); campaign.Map.SelectLocation(location); NewMessage(location.Name + " selected.", Color.White); }); } else { int destinationIndex = -1; if (!int.TryParse(args[0], out destinationIndex)) { return; } if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) { NewMessage("Index out of bounds!", Color.Red); return; } Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); campaign.Map.SelectLocation(location); NewMessage(location.Name + " selected.", Color.White); } })); #if DEBUG commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) => { foreach (Item item in Item.ItemList) { for (int i = 0; i < item.components.Count; i++) { if (item.components[i] is IServerSerializable) { GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, i }); } var itemContainer = item.GetComponent <ItemContainer>(); if (itemContainer != null) { GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState }); } GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); item.NeedsPositionUpdate = true; } } foreach (Character c in Character.CharacterList) { GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); } foreach (Structure wall in Structure.WallList) { GameMain.Server.CreateEntityEvent(wall); } })); #endif InitProjectSpecific(); commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); }
private void CheckDoorsInPath() { for (int i = 0; i < 2; i++) { Door door = null; bool shouldBeOpen = false; if (currentPath.Nodes.Count == 1) { door = currentPath.Nodes.First().ConnectedDoor; shouldBeOpen = door != null; } else { WayPoint node = null; WayPoint nextNode = null; if (i == 0) { node = currentPath.CurrentNode; nextNode = currentPath.NextNode; } else { node = currentPath.PrevNode; nextNode = currentPath.CurrentNode; } if (node?.ConnectedDoor == null) continue; if (nextNode == null) { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node shouldBeOpen = true; } else { door = node.ConnectedGap.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X); shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f; } else { int currentDir = Math.Sign(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f; } } } if (door == null) return; //toggle the door if it's the previous node and open, or if it's current node and closed if (door.IsOpen != shouldBeOpen) { var buttons = door.Item.GetConnectedComponents<Controller>(true); Controller closestButton = null; float closestDist = 0.0f; foreach (Controller controller in buttons) { float dist = Vector2.Distance(controller.Item.WorldPosition, character.WorldPosition); if (dist > controller.Item.InteractDistance * 2.0f) continue; if (dist < closestDist || closestButton == null) { closestButton = controller; closestDist = dist; } } if (closestButton != null) { if (!closestButton.HasRequiredItems(character, false) && shouldBeOpen) { currentPath.Unreachable = true; return; } closestButton.Item.TryInteract(character, false, true, false); buttonPressCooldown = ButtonPressInterval; break; } else { if (!door.HasRequiredItems(character, false) && shouldBeOpen) { currentPath.Unreachable = true; return; } door.Item.TryInteract(character, false, true, true); buttonPressCooldown = ButtonPressInterval; break; } } } }
private void InitializeJobItem(Character character, WayPoint spawnPoint, XElement itemElement, Item parentItem = null) { string itemName = itemElement.GetAttributeString("name", ""); ItemPrefab itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null) { DebugConsole.ThrowError("Tried to spawn \"" + Name + "\" with the item \"" + itemName + "\". Matching item prefab not found."); return; } Item item = new Item(itemPrefab, character.Position, null); if (GameMain.Server != null && Entity.Spawner != null) { Entity.Spawner.CreateNetworkEvent(item, false); } if (itemElement.GetAttributeBool("equip", false)) { List <InvSlotType> allowedSlots = new List <InvSlotType>(item.AllowedSlots); allowedSlots.Remove(InvSlotType.Any); character.Inventory.TryPutItem(item, null, allowedSlots); } else { character.Inventory.TryPutItem(item, null, item.AllowedSlots); } if (item.Prefab.NameMatches("ID Card") && spawnPoint != null) { foreach (string s in spawnPoint.IdCardTags) { item.AddTag(s); } item.AddTag("name:" + character.Name); item.AddTag("job:" + Name); if (!string.IsNullOrWhiteSpace(spawnPoint.IdCardDesc)) { item.Description = spawnPoint.IdCardDesc; } } item.AddTag("Starter_Item"); foreach (WifiComponent wifiComponent in item.GetComponents <WifiComponent>()) { wifiComponent.TeamID = character.TeamID; } if (parentItem != null) { parentItem.Combine(item); } foreach (XElement childItemElement in itemElement.Elements()) { InitializeJobItem(character, spawnPoint, childItemElement, item); } }
private void FindSpawnPosition(bool affectSubImmediately) { if (disallowed) { return; } if (Rand.Value(Rand.RandSync.Server) > prefab.SpawnProbability) { spawnPos = null; Finished(); return; } spawnPos = Vector2.Zero; var availablePositions = GetAvailableSpawnPositions(); var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false); bool isSubOrWreck = spawnPosType == Level.PositionType.Ruin || spawnPosType == Level.PositionType.Wreck; if (affectSubImmediately && !isSubOrWreck) { if (availablePositions.None()) { //no suitable position found, disable the event spawnPos = null; Finished(); return; } Submarine refSub = GetReferenceSub(); if (Submarine.MainSubs.Length == 2 && Submarine.MainSubs[1] != null) { refSub = Submarine.MainSubs.GetRandom(Rand.RandSync.Unsynced); } float closestDist = float.PositiveInfinity; //find the closest spawnposition that isn't too close to any of the subs foreach (var position in availablePositions) { Vector2 pos = position.Position.ToVector2(); float dist = Vector2.DistanceSquared(pos, refSub.WorldPosition); foreach (Submarine sub in Submarine.Loaded) { if (sub.Info.Type != SubmarineType.Player) { continue; } float minDistToSub = GetMinDistanceToSub(sub); if (dist < minDistToSub * minDistToSub) { continue; } if (closestDist == float.PositiveInfinity) { closestDist = dist; chosenPosition = position; continue; } //chosen position behind the sub -> override with anything that's closer or to the right if (chosenPosition.Position.X < refSub.WorldPosition.X) { if (dist < closestDist || pos.X > refSub.WorldPosition.X) { closestDist = dist; chosenPosition = position; } } //chosen position ahead of the sub -> only override with a position that's also ahead else if (chosenPosition.Position.X > refSub.WorldPosition.X) { if (dist < closestDist && pos.X > refSub.WorldPosition.X) { closestDist = dist; chosenPosition = position; } } } } //only found a spawnpos that's very far from the sub, pick one that's closer //and wait for the sub to move further before spawning if (closestDist > 15000.0f * 15000.0f) { foreach (var position in availablePositions) { float dist = Vector2.DistanceSquared(position.Position.ToVector2(), refSub.WorldPosition); if (dist < closestDist) { closestDist = dist; chosenPosition = position; } } } } else { if (!isSubOrWreck) { float minDistance = 20000; var refSub = GetReferenceSub(); availablePositions.RemoveAll(p => Vector2.DistanceSquared(refSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); if (Submarine.MainSubs.Length > 1) { for (int i = 1; i < Submarine.MainSubs.Length; i++) { availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSubs[i].WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); } } } if (availablePositions.None()) { //no suitable position found, disable the event spawnPos = null; Finished(); return; } chosenPosition = availablePositions.GetRandom(); } if (chosenPosition.IsValid) { spawnPos = chosenPosition.Position.ToVector2(); if (chosenPosition.Submarine != null || chosenPosition.Ruin != null) { var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine, ruin: chosenPosition.Ruin, useSyncedRand: false); if (spawnPoint != null) { System.Diagnostics.Debug.Assert(spawnPoint.Submarine == chosenPosition.Submarine); System.Diagnostics.Debug.Assert(spawnPoint.ParentRuin == chosenPosition.Ruin); spawnPos = spawnPoint.WorldPosition; } } else if ((chosenPosition.PositionType == Level.PositionType.MainPath || chosenPosition.PositionType == Level.PositionType.SidePath) && offset > 0) { Vector2 dir; var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null); var nearestWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value)).FirstOrDefault(); if (nearestWaypoint != null) { int currentIndex = waypoints.IndexOf(nearestWaypoint); var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)]; dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition); // Ensure that the spawn position is not offset to the left. if (dir.X < 0) { dir.X = 0; } } else { dir = new Vector2(1, Rand.Range(-1, 1)); } Vector2 targetPos = spawnPos.Value + dir * offset; var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault(); if (targetWaypoint != null) { spawnPos = targetWaypoint.WorldPosition; } } spawnPending = true; } }
private void InitEscort() { characters.Clear(); characterItems.Clear(); WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); Rand.RandSync randSync = Rand.RandSync.Server; if (terroristChance > 0f) { // in terrorist missions, reroll characters each retry to avoid confusion as to who the terrorists are randSync = Rand.RandSync.Unsynced; } //if any of the escortees have a job defined, try to use a spawnpoint designated for that job foreach (XElement element in characterConfig.Elements()) { var humanPrefab = GetHumanPrefabFromElement(element); if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; } var jobPrefab = humanPrefab.GetJobPrefab(); if (jobPrefab != null) { var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub); if (jobSpecificSpawnPos != null) { explicitStayInHullPos = jobSpecificSpawnPos; break; } } } foreach (XElement element in characterConfig.Elements()) { int count = CalculateScalingEscortedCharacterCount(inMission: true); for (int i = 0; i < count; i++) { Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync); if (spawnedCharacter.AIController is HumanAIController humanAI) { humanAI.InitMentalStateManager(); } } } if (terroristChance > 0f) { int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count); terroristCount = Math.Clamp(terroristCount, 1, characters.Count); terroristCharacters.Clear(); characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c)); terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f); #if DEBUG DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared)); foreach (Character character in terroristCharacters) { DebugConsole.AddWarning(character.Name + " is a terrorist."); } #endif } }
public static void CreateItems(List <PurchasedItem> itemsToSpawn) { WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); if (wp == null) { DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); return; } Hull cargoRoom = Hull.FindHull(wp.WorldPosition); if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } Dictionary <ItemContainer, int> availableContainers = new Dictionary <ItemContainer, int>(); ItemPrefab containerPrefab = null; foreach (PurchasedItem pi in itemsToSpawn) { Vector2 position = new Vector2( Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.ItemPrefab.Size.Y / 2); ItemContainer itemContainer = null; if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier)) { itemContainer = availableContainers.Keys.ToList().Find(ac => ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier || ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())); if (itemContainer == null) { containerPrefab = MapEntityPrefab.List.Find(ep => ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier || (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))) as ItemPrefab; if (containerPrefab == null) { DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); continue; } Item containerItem = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItem.GetComponent <ItemContainer>(); if (itemContainer == null) { DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); continue; } availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } } for (int i = 0; i < pi.Quantity; i++) { if (itemContainer == null) { //no container, place at the waypoint #if SERVER if (GameMain.Server != null) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine); } else { #endif new Item(pi.ItemPrefab, position, wp.Submarine); #if SERVER } #endif continue; } //if the intial container has been removed due to it running out of space, add a new container //of the same type and begin filling it if (!availableContainers.ContainsKey(itemContainer)) { Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItemOverFlow.GetComponent <ItemContainer>(); availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } //place in the container #if SERVER if (GameMain.Server != null) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory); } else { #endif var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemContainer.Inventory.TryPutItem(item, null); #if SERVER } #endif //reduce the number of available slots in the container //if there is a container if (availableContainers.ContainsKey(itemContainer)) { availableContainers[itemContainer]--; } if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) { availableContainers.Remove(itemContainer); } } } itemsToSpawn.Clear(); }
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null, IEnumerable <WayPoint> ignored = null, Func <WayPoint, bool> filter = null) { if (dir != -1 && dir != 1) { return(null); } float closestDist = 0.0f; WayPoint closest = null; foreach (WayPoint wp in WayPointList) { if (wp.SpawnType != SpawnType.Path || wp == this) { continue; } float xDiff = wp.Position.X - Position.X; float yDiff = wp.Position.Y - Position.Y; float xDist = Math.Abs(xDiff); float yDist = Math.Abs(yDiff); if (tolerance.X < xDist) { continue; } if (tolerance.Y < yDist) { continue; } float dist = 0.0f; float diff = 0.0f; if (horizontalSearch) { diff = xDiff; dist = xDist + yDist / 5.0f; } else { diff = yDiff; dist = yDist + xDist / 5.0f; //prefer ladder waypoints when moving vertically if (wp.Ladders != null) { dist *= 0.5f; } } if (Math.Sign(diff) != dir) { continue; } // Ignore if already linked if (linkedTo.Contains(wp)) { continue; } if (ignored != null && ignored.Contains(wp)) { continue; } if (filter != null && !filter(wp)) { continue; } if (closest == null || dist < closestDist) { var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false); if (body != null && body != ignoredBody && !(body.UserData is Submarine)) { if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } closestDist = dist; closest = wp; } } return(closest); }
public MainMenuScreen(GameMain game) { buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 0.5f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft) { RelativeOffset = new Vector2(0, 0.1f), AbsoluteOffset = new Point(50, 0) }) { Stretch = true, RelativeSpacing = 0.02f }; //debug button for quickly starting a new round new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform, Anchor.TopCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, -40) }, "Quickstart (dev)", style: "GUIButtonLarge", color: Color.Red) { IgnoreLayoutGroups = true, OnClicked = (tb, userdata) => { Submarine selectedSub = null; string subName = GameMain.Config.QuickStartSubmarineName; if (!string.IsNullOrEmpty(subName)) { DebugConsole.NewMessage($"Loading the predefined quick start sub \"{subName}\"", Color.White); selectedSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name.ToLower() == subName.ToLower()); if (selectedSub == null) { DebugConsole.NewMessage($"Cannot find a sub that matches the name \"{subName}\".", Color.Red); } } if (selectedSub == null) { DebugConsole.NewMessage("Loading a random sub.", Color.White); var subs = Submarine.SavedSubmarines.Where(s => !s.HasTag(SubmarineTag.Shuttle) && !s.HasTag(SubmarineTag.HideInMenus)); selectedSub = subs.ElementAt(Rand.Int(subs.Count())); } var gamesession = new GameSession( selectedSub, "Data/Saves/test.xml", GameModePreset.list.Find(gm => gm.Name == "SPSandbox"), missionPrefab: null); //(gamesession.GameMode as SinglePlayerCampaign).GenerateMap(ToolBox.RandomSeed(8)); gamesession.StartRound(ToolBox.RandomSeed(8)); GameMain.GameScreen.Select(); string[] jobIdentifiers = new string[] { "captain", "engineer", "mechanic" }; for (int i = 0; i < 3; i++) { var spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); if (spawnPoint == null) { DebugConsole.ThrowError("No spawnpoints found in the selected submarine. Quickstart failed."); GameMain.MainMenuScreen.Select(); return(true); } var characterInfo = new CharacterInfo( Character.HumanConfigFile, jobPrefab: JobPrefab.List.Find(j => j.Identifier == jobIdentifiers[i])); if (characterInfo.Job == null) { DebugConsole.ThrowError("Failed to find the job \"" + jobIdentifiers[i] + "\"!"); } var newCharacter = Character.Create(Character.HumanConfigFile, spawnPoint.WorldPosition, ToolBox.RandomSeed(8), characterInfo); newCharacter.GiveJobItems(spawnPoint); gamesession.CrewManager.AddCharacter(newCharacter); Character.Controlled = newCharacter; } return(true); } }; var minButtonSize = new Point(120, 20); var maxButtonSize = new Point(240, 40); new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("TutorialButton"), style: "GUIButtonLarge") { UserData = Tab.Tutorials, OnClicked = SelectTab, Enabled = false }; new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("NewGameButton"), style: "GUIButtonLarge") { UserData = Tab.NewGame, OnClicked = SelectTab }; new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("LoadGameButton"), style: "GUIButtonLarge") { UserData = Tab.LoadGame, OnClicked = SelectTab }; new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("JoinServerButton"), style: "GUIButtonLarge") { OnClicked = JoinServerClicked }; new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("HostServerButton"), style: "GUIButtonLarge") { UserData = Tab.HostServer, OnClicked = SelectTab }; new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("SubEditorButton"), style: "GUIButtonLarge") { OnClicked = (btn, userdata) => { GameMain.SubEditorScreen.Select(); return(true); } }; new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("CharacterEditorButton"), style: "GUIButtonLarge") { OnClicked = (btn, userdata) => { Submarine.MainSub = null; GameMain.CharacterEditorScreen.Select(); return(true); } }; if (Steam.SteamManager.USE_STEAM) { steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("SteamWorkshopButton"), style: "GUIButtonLarge") { Enabled = false, OnClicked = SteamWorkshopClicked }; } new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("SettingsButton"), style: "GUIButtonLarge") { UserData = Tab.Settings, OnClicked = SelectTab }; new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("QuitButton"), style: "GUIButtonLarge") { OnClicked = QuitClicked }; /* var buttons = GUI.CreateButtons(9, new Vector2(1, 0.04f), buttonsParent.RectTransform, anchor: Anchor.BottomLeft, * minSize: minButtonSize, maxSize: maxButtonSize, relativeSpacing: 0.005f, extraSpacing: i => i % 2 == 0 ? 20 : 0); * buttons.ForEach(b => b.Color *= 0.8f); * SetupButtons(buttons); * buttons.ForEach(b => b.TextBlock.SetTextPos());*/ var relativeSize = new Vector2(0.5f, 0.5f); var minSize = new Point(600, 400); var maxSize = new Point(900, 600); var anchor = Anchor.Center; var pivot = Pivot.Center; menuTabs = new GUIFrame[Enum.GetValues(typeof(Tab)).Length + 1]; menuTabs[(int)Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize)); var paddedNewGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.NewGame].RectTransform, Anchor.Center), style: null); menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize)); var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center), style: null); campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame) { LoadGame = LoadGame, StartNewGame = StartGame }; var hostServerScale = new Vector2(0.7f, 1.0f); menuTabs[(int)Tab.HostServer] = new GUIFrame(new RectTransform( Vector2.Multiply(relativeSize, hostServerScale), Frame.RectTransform, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))); CreateHostServerFields(); //---------------------------------------------------------------------- menuTabs[(int)Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize)); //PLACEHOLDER var tutorialList = new GUIListBox( new RectTransform(new Vector2(0.95f, 0.85f), menuTabs[(int)Tab.Tutorials].RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) }, false, null, ""); foreach (Tutorial tutorial in Tutorial.Tutorials) { var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), tutorialList.Content.RectTransform), tutorial.Name, textAlignment: Alignment.Center, font: GUI.LargeFont) { UserData = tutorial }; } tutorialList.OnSelected += (component, obj) => { TutorialMode.StartTutorial(obj as Tutorial); return(true); }; UpdateTutorialList(); this.game = game; }
public static void CreateItems(List <PurchasedItem> itemsToSpawn) { if (itemsToSpawn.Count == 0) { return; } WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); if (wp == null) { DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); return; } Hull cargoRoom = Hull.FindHull(wp.WorldPosition); if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } #if CLIENT new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true)); #endif Dictionary <ItemContainer, int> availableContainers = new Dictionary <ItemContainer, int>(); ItemPrefab containerPrefab = null; foreach (PurchasedItem pi in itemsToSpawn) { float floorPos = cargoRoom.Rect.Y - cargoRoom.Rect.Height; Vector2 position = new Vector2( Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), floorPos); //check where the actual floor structure is in case the bottom of the hull extends below it if (Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(position.X, cargoRoom.Rect.Y - cargoRoom.Rect.Height / 2)), ConvertUnits.ToSimUnits(position), collisionCategory: Physics.CollisionWall) != null) { float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y); if (floorStructurePos > floorPos) { floorPos = floorStructurePos; } } position.Y = floorPos + pi.ItemPrefab.Size.Y / 2; ItemContainer itemContainer = null; if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier)) { itemContainer = availableContainers.Keys.ToList().Find(ac => ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier || ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())); if (itemContainer == null) { containerPrefab = ItemPrefab.Prefabs.Find(ep => ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier || (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))); if (containerPrefab == null) { DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); continue; } Item containerItem = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItem.GetComponent <ItemContainer>(); if (itemContainer == null) { DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); continue; } availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } } for (int i = 0; i < pi.Quantity; i++) { if (itemContainer == null) { //no container, place at the waypoint if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine); } else { new Item(pi.ItemPrefab, position, wp.Submarine); } continue; } //if the intial container has been removed due to it running out of space, add a new container //of the same type and begin filling it if (!availableContainers.ContainsKey(itemContainer)) { Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItemOverFlow.GetComponent <ItemContainer>(); availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } //place in the container if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory); } else { var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemContainer.Inventory.TryPutItem(item, null); } //reduce the number of available slots in the container //if there is a container if (availableContainers.ContainsKey(itemContainer)) { availableContainers[itemContainer]--; } if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) { availableContainers.Remove(itemContainer); } } } itemsToSpawn.Clear(); }
public void InitRound() { characters.Clear(); List <WayPoint> spawnWaypoints = null; List <WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub).ToList(); if (Level.IsLoadedOutpost) { spawnWaypoints = WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Human && wp.Submarine == Level.Loaded.StartOutpost && wp.CurrentHull?.OutpostModuleTags != null && wp.CurrentHull.OutpostModuleTags.Contains("airlock")); while (spawnWaypoints.Count > characterInfos.Count) { spawnWaypoints.RemoveAt(Rand.Int(spawnWaypoints.Count)); } while (spawnWaypoints.Any() && spawnWaypoints.Count < characterInfos.Count) { spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]); } } if (spawnWaypoints == null || !spawnWaypoints.Any()) { spawnWaypoints = mainSubWaypoints; } System.Diagnostics.Debug.Assert(spawnWaypoints.Count == mainSubWaypoints.Count); for (int i = 0; i < spawnWaypoints.Count; i++) { var info = characterInfos[i]; info.TeamID = CharacterTeamType.Team1; Character character = Character.Create(info, spawnWaypoints[i].WorldPosition, info.Name); if (character.Info != null) { if (!character.Info.StartItemsGiven && character.Info.InventoryData != null) { DebugConsole.AddWarning($"Error when initializing a round: character \"{character.Name}\" has not been given their initial items but has saved inventory data. Using the saved inventory data instead of giving the character new items."); } if (character.Info.InventoryData != null) { character.SpawnInventoryItems(character.Inventory, character.Info.InventoryData); } else if (!character.Info.StartItemsGiven) { character.GiveJobItems(mainSubWaypoints[i]); } if (character.Info.HealthData != null) { character.Info.ApplyHealthData(character, character.Info.HealthData); } character.GiveIdCardTags(spawnWaypoints[i]); character.Info.StartItemsGiven = true; } AddCharacter(character); #if CLIENT if (IsSinglePlayer && (Character.Controlled == null || character.Info.LastControlled)) { Character.Controlled = character; } #endif } //longer delay in multiplayer to prevent the server from triggering NPC conversations while the players are still loading the round conversationTimer = IsSinglePlayer ? Rand.Range(5.0f, 10.0f) : Rand.Range(45.0f, 60.0f); }
public static void CreateItems(List <PurchasedItem> itemsToSpawn) { if (itemsToSpawn.Count == 0) { return; } WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); if (wp == null) { DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); return; } Hull cargoRoom = Hull.FindHull(wp.WorldPosition); if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } #if CLIENT new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true), new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "StoreShoppingCrateIcon"); #else foreach (Client client in GameMain.Server.ConnectedClients) { ChatMessage msg = ChatMessage.Create("", TextManager.ContainsTag(cargoRoom.RoomName) ? $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}" : $"CargoSpawnNotification~[roomname]={cargoRoom.RoomName}", ChatMessageType.ServerMessageBoxInGame, null); msg.IconStyle = "StoreShoppingCrateIcon"; GameMain.Server.SendDirectChatMessage(msg, client); } #endif List <ItemContainer> availableContainers = new List <ItemContainer>(); ItemPrefab containerPrefab = null; foreach (PurchasedItem pi in itemsToSpawn) { Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab); for (int i = 0; i < pi.Quantity; i++) { ItemContainer itemContainer = null; if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier)) { itemContainer = availableContainers.Find(ac => ac.Inventory.CanBePut(pi.ItemPrefab) && (ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier || ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))); if (itemContainer == null) { containerPrefab = ItemPrefab.Prefabs.Find(ep => ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier || (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))); if (containerPrefab == null) { DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + pi.ItemPrefab.CargoContainerIdentifier + "\"!"); continue; } Item containerItem = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItem.GetComponent <ItemContainer>(); if (itemContainer == null) { DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); continue; } availableContainers.Add(itemContainer); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } } if (itemContainer == null) { //no container, place at the waypoint if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine, onSpawned: itemSpawned); } else { var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemSpawned(item); } continue; } //place in the container if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory, onSpawned: itemSpawned); } else { var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemContainer.Inventory.TryPutItem(item, null); itemSpawned(item); }
private void CheckDoorsInPath() { for (int i = 0; i < 2; i++) { WayPoint currentWaypoint = null; WayPoint nextWaypoint = null; Door door = null; bool shouldBeOpen = false; if (currentPath.Nodes.Count == 1) { door = currentPath.Nodes.First().ConnectedDoor; shouldBeOpen = door != null; } else { if (i == 0) { currentWaypoint = currentPath.CurrentNode; nextWaypoint = currentPath.NextNode; } else { currentWaypoint = currentPath.PrevNode; nextWaypoint = currentPath.CurrentNode; } if (currentWaypoint?.ConnectedDoor == null) { continue; } if (nextWaypoint == null) { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node if (currentWaypoint.ConnectedDoor.LinkedGap != null && currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom) { shouldBeOpen = true; door = currentWaypoint.ConnectedDoor; } } else { door = currentWaypoint.ConnectedGap.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -50.0f; } else { int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * dir > -80.0f; } } } if (door == null) { return; } //toggle the door if it's the previous node and open, or if it's current node and closed if (door.IsOpen != shouldBeOpen) { Controller closestButton = null; float closestDist = 0; bool canAccess = CanAccessDoor(door, button => { if (currentWaypoint == null) { return(true); } // Check that the button is on the right side of the door. if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); if (button.Item.WorldPosition.X * dir > door.Item.WorldPosition.X * dir) { return(false); } } else { int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); if (button.Item.WorldPosition.Y * dir > door.Item.WorldPosition.Y * dir) { return(false); } } float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition); if (closestButton == null || distance < closestDist) { closestButton = button; closestDist = distance; } return(true); }); if (canAccess) { if (door.HasIntegratedButtons) { door.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else if (closestButton != null) { if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2)) { closestButton.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else { // Can't reach the button closest to the character. // It's possible that we could reach another buttons. // If this becomes an issue, we could go through them here and check if any of them are reachable // (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above) var body = Submarine.PickBody(character.SimPosition, character.GetRelativeSimPosition(closestButton.Item), collisionCategory: Physics.CollisionWall | Physics.CollisionLevel); if (body != null) { if (body.UserData is Item item) { var d = item.GetComponent <Door>(); if (d == null || d.IsOpen) { return; } } // The button is on the wrong side of the door or a wall currentPath.Unreachable = true; } return; } } } else if (shouldBeOpen) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Pathfinding error: Cannot access the door", Color.Yellow); #endif currentPath.Unreachable = true; return; } } } }
private void CheckDoorsInPath() { for (int i = 0; i < 2; i++) { WayPoint currentWaypoint = null; WayPoint nextWaypoint = null; Door door = null; bool shouldBeOpen = false; if (currentPath.Nodes.Count == 1) { door = currentPath.Nodes.First().ConnectedDoor; shouldBeOpen = door != null; } else { if (i == 0) { currentWaypoint = currentPath.CurrentNode; nextWaypoint = currentPath.NextNode; } else { currentWaypoint = currentPath.PrevNode; nextWaypoint = currentPath.CurrentNode; } if (currentWaypoint?.ConnectedDoor == null) { continue; } if (nextWaypoint == null) { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node shouldBeOpen = true; } else { door = currentWaypoint.ConnectedGap.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int currentDir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f; } else { int currentDir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f; } } } if (door == null) { return; } //toggle the door if it's the previous node and open, or if it's current node and closed if (door.IsOpen != shouldBeOpen) { Controller closestButton = null; float closestDist = 0; bool canAccess = CanAccessDoor(door, button => { if (currentWaypoint == null) { return(true); } float distance = Vector2.DistanceSquared(button.Item.WorldPosition, door.Item.WorldPosition); if (closestButton == null || distance < closestDist) { closestButton = button; closestDist = distance; } return(true); }); if (canAccess) { if (door.HasIntegratedButtons) { door.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else if (closestButton != null) { if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2)) { closestButton.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else { // Can't reach the button closest to the door. // It's possible that we could reach another buttons. // If this becomes an issue, we could go through them here and check if any of them are reachable // (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above) //currentPath.Unreachable = true; return; } } } else if (shouldBeOpen) { currentPath.Unreachable = true; return; } } } }
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (Submarine.Unloading) { return; } var waypoints = sender as IEnumerable <MapEntity>; foreach (MapEntity me in waypoints) { WayPoint wp = me as WayPoint; if (me == null) { continue; } var node = nodes.Find(n => n.Waypoint == wp); if (node == null) { return; } if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) { for (int i = node.connections.Count - 1; i >= 0; i--) { //remove connection if the waypoint isn't connected anymore if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null) { node.connections.RemoveAt(i); node.distances.RemoveAt(i); } } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { for (int i = 0; i < wp.linkedTo.Count; i++) { if (!(wp.linkedTo[i] is WayPoint connected)) { continue; } //already connected, continue if (node.connections.Any(n => n.Waypoint == connected)) { continue; } var matchingNode = nodes.Find(n => n.Waypoint == connected); if (matchingNode == null) { #if DEBUG DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder"); #endif return; } node.connections.Add(matchingNode); node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position)); } } } }
private void FindSpawnPosition(bool affectSubImmediately) { if (disallowed) { return; } spawnPos = Vector2.Zero; var availablePositions = GetAvailableSpawnPositions(); var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false); var removedPositions = new List <Level.InterestingPosition>(); foreach (var position in availablePositions) { if (Rand.Value(Rand.RandSync.Server) > prefab.SpawnProbability) { removedPositions.Add(position); if (prefab.AllowOnlyOnce) { Level.Loaded.UsedPositions.Add(position); } } } removedPositions.ForEach(p => availablePositions.Remove(p)); bool isSubOrWreck = spawnPosType == Level.PositionType.Ruin || spawnPosType == Level.PositionType.Wreck; if (affectSubImmediately && !isSubOrWreck) { if (availablePositions.None()) { //no suitable position found, disable the event Finished(); return; } float closestDist = float.PositiveInfinity; //find the closest spawnposition that isn't too close to any of the subs foreach (var position in availablePositions) { Vector2 pos = position.Position.ToVector2(); float dist = Vector2.DistanceSquared(pos, Submarine.MainSub.WorldPosition); foreach (Submarine sub in Submarine.Loaded) { if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } float minDistToSub = GetMinDistanceToSub(sub); if (dist > minDistToSub * minDistToSub && dist < closestDist) { closestDist = dist; chosenPosition = position; } } } //only found a spawnpos that's very far from the sub, pick one that's closer //and wait for the sub to move further before spawning if (closestDist > 15000.0f * 15000.0f) { foreach (var position in availablePositions) { float dist = Vector2.DistanceSquared(position.Position.ToVector2(), Submarine.MainSub.WorldPosition); if (dist < closestDist) { closestDist = dist; chosenPosition = position; } } } } else { if (!isSubOrWreck) { float minDistance = 20000; availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); } if (availablePositions.None()) { //no suitable position found, disable the event Finished(); return; } chosenPosition = availablePositions.GetRandom(); } if (chosenPosition.IsValid) { spawnPos = chosenPosition.Position.ToVector2(); if (chosenPosition.Submarine != null || chosenPosition.Ruin != null) { var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine, ruin: chosenPosition.Ruin, useSyncedRand: false); if (spawnPoint != null) { System.Diagnostics.Debug.Assert(spawnPoint.Submarine == chosenPosition.Submarine); System.Diagnostics.Debug.Assert(spawnPoint.ParentRuin == chosenPosition.Ruin); spawnPos = spawnPoint.WorldPosition; } } spawnPending = true; if (prefab.AllowOnlyOnce) { Level.Loaded.UsedPositions.Add(chosenPosition); } } }
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null) { if (dir != -1 && dir != 1) { return(null); } float closestDist = 0.0f; WayPoint closest = null; foreach (WayPoint wp in WayPointList) { if (wp.SpawnType != SpawnType.Path || wp == this) { continue; } float diff = 0.0f; if (horizontalSearch) { if ((wp.Position.Y - Position.Y) < tolerance.X || (wp.Position.Y - Position.Y) > tolerance.Y) { continue; } diff = wp.Position.X - Position.X; } else { if ((wp.Position.X - Position.X) < tolerance.X || (wp.Position.X - Position.X) > tolerance.Y) { continue; } diff = wp.Position.Y - Position.Y; } if (Math.Sign(diff) != dir) { continue; } float dist = Vector2.Distance(wp.Position, Position); if (closest == null || dist < closestDist) { var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, true, true, false); if (body != null && body != ignoredBody && !(body.UserData is Submarine)) { if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } closestDist = dist; closest = wp; } } return(closest); }
public void Generate(bool mirror = false) { Stopwatch sw = new Stopwatch(); sw.Start(); if (loaded != null) { loaded.Unload(); } loaded = this; positionsOfInterest = new List <InterestingPosition>(); Voronoi voronoi = new Voronoi(1.0); List <Vector2> sites = new List <Vector2>(); bodies = new List <Body>(); Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); #if CLIENT renderer = new LevelRenderer(this); backgroundColor = generationParams.BackgroundColor; float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3; GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f); #endif float minWidth = 6500.0f; if (Submarine.MainSub != null) { Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders(); minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height)); } startPosition = new Vector2( Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); endPosition = new Vector2( borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); List <Vector2> pathNodes = new List <Vector2>(); Rectangle pathBorders = borders;// new Rectangle((int)minWidth, (int)minWidth, borders.Width - (int)minWidth * 2, borders.Height - (int)minWidth); pathBorders.Inflate(-minWidth * 2, -minWidth * 2); pathNodes.Add(new Vector2(startPosition.X, borders.Height)); Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange; for (float x = startPosition.X + Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server); x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server); x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server)) { pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server))); } pathNodes.Add(new Vector2(endPosition.X, borders.Height)); if (pathNodes.Count <= 2) { pathNodes.Add((startPosition + endPosition) / 2); } List <List <Vector2> > smallTunnels = new List <List <Vector2> >(); for (int i = 0; i < generationParams.SmallTunnelCount; i++) { var tunnelStartPos = pathNodes[Rand.Range(2, pathNodes.Count - 2, Rand.RandSync.Server)]; tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right); float tunnelLength = Rand.Range( generationParams.SmallTunnelLengthRange.X, generationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server); var tunnelNodes = MathUtils.GenerateJaggedLine( tunnelStartPos, new Vector2(tunnelStartPos.X, pathBorders.Bottom) + Rand.Vector(tunnelLength, Rand.RandSync.Server), 4, 1000.0f); List <Vector2> tunnel = new List <Vector2>(); foreach (Vector2[] tunnelNode in tunnelNodes) { if (!pathBorders.Contains(tunnelNode[0])) { continue; } tunnel.Add(tunnelNode[0]); } if (tunnel.Any()) { smallTunnels.Add(tunnel); } } Vector2 siteInterval = generationParams.VoronoiSiteInterval; Vector2 siteVariance = generationParams.VoronoiSiteVariance; for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X) { for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y) { Vector2 site = new Vector2( x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server), y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server)); if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length()))) { //add some more sites around the small tunnels to generate more small voronoi cells if (x < borders.Width - siteInterval.X) { sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f); } if (y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f); } if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f); } } if (mirror) { site.X = borders.Width - site.X; } sites.Add(site); } } Stopwatch sw2 = new Stopwatch(); sw2.Start(); List <GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height); Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //construct voronoi cells based on the graph edges cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid); Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); List <VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize, new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror); for (int i = 2; i < mainPath.Count; i += 3) { positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath)); } List <VoronoiCell> pathCells = new List <VoronoiCell>(mainPath); EnlargeMainPath(pathCells, minWidth); foreach (InterestingPosition positionOfInterest in positionsOfInterest) { WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null); wayPoint.MoveWithLevel = true; } startPosition.X = pathCells[0].Center.X; foreach (List <Vector2> tunnel in smallTunnels) { if (tunnel.Count < 2) { continue; } //find the cell which the path starts from int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1); if (startCellIndex < 0) { continue; } //if it wasn't one of the cells in the main path, don't create a tunnel if (cells[startCellIndex].CellType != CellType.Path) { continue; } var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders); positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave)); if (tunnel.Count > 4) { positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave)); } pathCells.AddRange(newPathCells); } Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); cells = CleanCells(pathCells); pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); foreach (VoronoiCell cell in cells) { if (cell.Center.Y < borders.Height / 2) { continue; } cell.edges.ForEach(e => e.OutsideLevel = true); } foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); cell.CellType = CellType.Path; cells.Remove(cell); } //generate some narrow caves int caveAmount = 0;// Rand.Int(3, false); List <VoronoiCell> usedCaveCells = new List <VoronoiCell>(); for (int i = 0; i < caveAmount; i++) { Vector2 startPoint = Vector2.Zero; VoronoiCell startCell = null; var caveCells = new List <VoronoiCell>(); int maxTries = 5, tries = 0; while (tries < maxTries) { startCell = cells[Rand.Int(cells.Count, Rand.RandSync.Server)]; //find an edge between the cell and the already carved path GraphEdge startEdge = startCell.edges.Find(e => pathCells.Contains(e.AdjacentCell(startCell))); if (startEdge != null) { startPoint = (startEdge.point1 + startEdge.point2) / 2.0f; startPoint += startPoint - startCell.Center; //get the cells in which the cave will be carved caveCells = GetCells(startCell.Center, 2); //remove cells that have already been "carved" out caveCells.RemoveAll(c => c.CellType == CellType.Path); //if any of the cells have already been used as a cave, continue and find some other cells if (usedCaveCells.Any(c => caveCells.Contains(c))) { continue; } break; } tries++; } //couldn't find a place for a cave -> abort if (tries >= maxTries) { break; } if (!caveCells.Any()) { continue; } usedCaveCells.AddRange(caveCells); List <VoronoiCell> caveSolidCells; var cavePathCells = CaveGenerator.CarveCave(caveCells, startPoint, out caveSolidCells); //remove the large cells used as a "base" for the cave (they've now been replaced with smaller ones) caveCells.ForEach(c => cells.Remove(c)); cells.AddRange(caveSolidCells); foreach (VoronoiCell cell in cavePathCells) { cells.Remove(cell); } pathCells.AddRange(cavePathCells); for (int j = cavePathCells.Count / 2; j < cavePathCells.Count; j += 10) { positionsOfInterest.Add(new InterestingPosition(cavePathCells[j].Center, PositionType.Cave)); } } for (int x = 0; x < cellGrid.GetLength(0); x++) { for (int y = 0; y < cellGrid.GetLength(1); y++) { cellGrid[x, y].Clear(); } } foreach (VoronoiCell cell in cells) { int x = (int)Math.Floor(cell.Center.X / GridCellSize); int y = (int)Math.Floor(cell.Center.Y / GridCellSize); if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) { continue; } cellGrid[x, y].Add(cell); } ruins = new List <Ruin>(); for (int i = 0; i < generationParams.RuinCount; i++) { GenerateRuin(mainPath); } startPosition.Y = borders.Height; endPosition.Y = borders.Height; List <VoronoiCell> cellsWithBody = new List <VoronoiCell>(cells); List <Vector2[]> triangles; bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out triangles); #if CLIENT List <VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles); renderer.SetBodyVertices(bodyVertices.ToArray()); renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells)); renderer.PlaceSprites(generationParams.BackgroundSpriteAmount); #endif ShaftBody = BodyFactory.CreateEdge(GameMain.World, ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)), ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0))); ShaftBody.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f); ShaftBody.BodyType = BodyType.Static; ShaftBody.CollisionCategories = Physics.CollisionLevel; bodies.Add(ShaftBody); foreach (VoronoiCell cell in cells) { foreach (GraphEdge edge in cell.edges) { edge.cell1 = null; edge.cell2 = null; edge.site1 = null; edge.site2 = null; } } //initialize MapEntities that aren't in any sub (e.g. items inside ruins) MapEntity.MapLoaded(null); Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); if (mirror) { Vector2 temp = startPosition; startPosition = endPosition; endPosition = temp; } Debug.WriteLine("**********************************************************************************"); Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms"); Debug.WriteLine("Seed: " + seed); Debug.WriteLine("**********************************************************************************"); }
private void LoadItemAsChild(XElement element, Item parent) { ItemPrefab itemPrefab; if (element.Attribute("name") != null) { DebugConsole.ThrowError("Error in cargo mission \"" + Name + "\" - use item identifiers instead of names to configure the items."); string itemName = element.GetAttributeString("name", ""); itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null) { DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemName + "\" not found"); return; } } else { string itemIdentifier = element.GetAttributeString("identifier", ""); itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; if (itemPrefab == null) { DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemIdentifier + "\" not found"); return; } } if (itemPrefab == null) { DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + element.Name.ToString() + "\" not found"); return; } WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true); if (cargoSpawnPos == null) { DebugConsole.ThrowError("Couldn't spawn items for cargo mission, cargo spawnpoint not found"); return; } var cargoRoom = cargoSpawnPos.CurrentHull; if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } Vector2 position = new Vector2( cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.Server), cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2); var item = new Item(itemPrefab, position, cargoRoom.Submarine); item.FindHull(); items.Add(item); if (parent != null && parent.GetComponent <ItemContainer>() != null) { parentInventoryIDs.Add(item, parent.ID); parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(parent.GetComponent <ItemContainer>())); parent.Combine(item, user: null); } foreach (XElement subElement in element.Elements()) { int amount = subElement.GetAttributeInt("amount", 1); for (int i = 0; i < amount; i++) { LoadItemAsChild(subElement, item); } } }
private void EnlargeMainPath(List <VoronoiCell> pathCells, float minWidth) { List <WayPoint> wayPoints = new List <WayPoint>(); var newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Center.X, borders.Height, 10, 10), null); newWaypoint.MoveWithLevel = true; wayPoints.Add(newWaypoint); //WayPoint prevWaypoint = newWaypoint; for (int i = 0; i < pathCells.Count; i++) { ////clean "loops" from the path //for (int n = 0; n < i; n++) //{ // if (pathCells[n] != pathCells[i]) continue; // pathCells.RemoveRange(n + 1, i - n); // break; //} //if (i >= pathCells.Count) break; pathCells[i].CellType = CellType.Path; newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Center.X, (int)pathCells[i].Center.Y, 10, 10), null); newWaypoint.MoveWithLevel = true; wayPoints.Add(newWaypoint); wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint); newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]); for (int n = 0; n < wayPoints.Count; n++) { if (wayPoints[n].Position != newWaypoint.Position) { continue; } wayPoints[n].linkedTo.Add(newWaypoint); newWaypoint.linkedTo.Add(wayPoints[n]); break; } //prevWaypoint = newWaypoint; } newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Center.X, borders.Height, 10, 10), null); newWaypoint.MoveWithLevel = true; wayPoints.Add(newWaypoint); wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint); newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]); if (minWidth > 0.0f) { List <VoronoiCell> removedCells = GetTooCloseCells(pathCells, minWidth); foreach (VoronoiCell removedCell in removedCells) { if (removedCell.CellType == CellType.Path) { continue; } pathCells.Add(removedCell); removedCell.CellType = CellType.Path; } } }
public static void ExecuteCommand(string command, GameMain game) { if (string.IsNullOrWhiteSpace(command)) { return; } string[] commands = command.Split(' '); if (!commands[0].ToLowerInvariant().Equals("admin")) { NewMessage(textBox.Text, Color.White); } #if !DEBUG if (GameMain.Client != null && !IsCommandPermitted(commands[0].ToLowerInvariant(), GameMain.Client)) { ThrowError("You're not permitted to use the command \"" + commands[0].ToLowerInvariant() + "\"!"); return; } #endif switch (commands[0].ToLowerInvariant()) { case "help": NewMessage("menu: go to main menu", Color.Cyan); NewMessage("game: enter the \"game screen\"", Color.Cyan); NewMessage("edit: switch to submarine editor", Color.Cyan); NewMessage("edit [submarine name]: load a submarine and switch to submarine editor", Color.Cyan); NewMessage("load [submarine name]: load a submarine", Color.Cyan); NewMessage("save [submarine name]: save the current submarine using the specified name", Color.Cyan); NewMessage(" ", Color.Cyan); NewMessage("spawn [creaturename] [near/inside/outside]: spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine)", Color.Cyan); NewMessage("spawnitem [itemname] [cursor/inventory]: spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted", Color.Cyan); NewMessage(" ", Color.Cyan); NewMessage("lights: disable lighting", Color.Cyan); NewMessage("los: disable the line of sight effect", Color.Cyan); NewMessage("freecam: detach the camera from the controlled character", Color.Cyan); NewMessage("control [character name]: start controlling the specified character", Color.Cyan); NewMessage(" ", Color.Cyan); NewMessage("water: allows adding water into rooms or removing it by holding the left/right mouse buttons", Color.Cyan); NewMessage("fire: allows putting up fires by left clicking", Color.Cyan); NewMessage(" ", Color.Cyan); NewMessage("teleport: teleport the controlled character to the position of the cursor", Color.Cyan); NewMessage("teleport [character name]: teleport the specified character to the position of the cursor", Color.Cyan); NewMessage("heal: restore the controlled character to full health", Color.Cyan); NewMessage("heal [character name]: restore the specified character to full health", Color.Cyan); NewMessage("revive: bring the controlled character back from the dead", Color.Cyan); NewMessage("revive [character name]: bring the specified character back from the dead", Color.Cyan); NewMessage("killmonsters: immediately kills all AI-controlled enemies in the level", Color.Cyan); NewMessage(" ", Color.Cyan); NewMessage("fixwalls: fixes all the walls", Color.Cyan); NewMessage("fixitems: fixes every item/device in the sub", Color.Cyan); NewMessage("oxygen: replenishes the oxygen in every room to 100%", Color.Cyan); NewMessage("power [amount]: immediately sets the temperature of the reactor to the specified value", Color.Cyan); NewMessage(" ", Color.Cyan); NewMessage("kick [name]: kick a player out from the server", Color.Cyan); NewMessage("ban [name]: kick and ban the player from the server", Color.Cyan); NewMessage("banip [IP address]: ban the IP address from the server", Color.Cyan); NewMessage("debugdraw: toggles the \"debug draw mode\"", Color.Cyan); NewMessage("netstats: toggles the visibility of the network statistics panel", Color.Cyan); break; case "createfilelist": UpdaterUtil.SaveFileList("filelist.xml"); break; case "spawn": case "spawncharacter": if (commands.Length == 1) { return; } Character spawnedCharacter = null; Vector2 spawnPosition = Vector2.Zero; WayPoint spawnPoint = null; if (commands.Length > 2) { switch (commands[2].ToLowerInvariant()) { case "inside": spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); break; case "outside": spawnPoint = WayPoint.GetRandom(SpawnType.Enemy); break; case "near": case "close": float closestDist = -1.0f; foreach (WayPoint wp in WayPoint.WayPointList) { if (wp.Submarine != null) { continue; } //don't spawn inside hulls if (Hull.FindHull(wp.WorldPosition, null) != null) { continue; } float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter); if (closestDist < 0.0f || dist < closestDist) { spawnPoint = wp; closestDist = dist; } } break; case "cursor": spawnPosition = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); break; default: spawnPoint = WayPoint.GetRandom(commands[1].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); break; } } else { spawnPoint = WayPoint.GetRandom(commands[1].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy); } if (string.IsNullOrWhiteSpace(commands[1])) { return; } if (spawnPoint != null) { spawnPosition = spawnPoint.WorldPosition; } if (commands[1].ToLowerInvariant() == "human") { spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition); if (GameMain.GameSession != null) { SinglePlayerMode mode = GameMain.GameSession.gameMode as SinglePlayerMode; if (mode != null) { Character.Controlled = spawnedCharacter; GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled); GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled); } } } else { spawnedCharacter = Character.Create( "Content/Characters/" + commands[1].First().ToString().ToUpper() + commands[1].Substring(1) + "/" + commands[1].ToLower() + ".xml", spawnPosition); } break; case "spawnitem": if (commands.Length < 2) { return; } Vector2? spawnPos = null; Inventory spawnInventory = null; int extraParams = 0; switch (commands.Last()) { case "cursor": extraParams = 1; spawnPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); break; case "inventory": extraParams = 1; spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory; break; default: extraParams = 0; break; } string itemName = string.Join(" ", commands.Skip(1).Take(commands.Length - extraParams - 1)).ToLowerInvariant(); var itemPrefab = MapEntityPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == itemName) as ItemPrefab; if (itemPrefab == null) { ThrowError("Item \"" + itemName + "\" not found!"); return; } if (spawnPos == null && spawnInventory == null) { var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; } if (spawnPos != null) { Item.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); } else if (spawnInventory != null) { Item.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory); } break; case "disablecrewai": HumanAIController.DisableCrewAI = !HumanAIController.DisableCrewAI; break; case "enablecrewai": HumanAIController.DisableCrewAI = false; break; /*case "admin": * if (commands.Length < 2) break; * * if (GameMain.Server != null) * { * GameMain.Server.AdminAuthPass = commands[1]; * * } * else if (GameMain.Client != null) * { * GameMain.Client.RequestAdminAuth(commands[1]); * } * break;*/ case "kick": if (GameMain.NetworkMember == null || commands.Length < 2) { break; } GameMain.NetworkMember.KickPlayer(string.Join(" ", commands.Skip(1)), false); break; case "ban": if (GameMain.NetworkMember == null || commands.Length < 2) { break; } GameMain.NetworkMember.KickPlayer(string.Join(" ", commands.Skip(1)), true); break; case "banip": { if (GameMain.Server == null || commands.Length < 2) { break; } var client = GameMain.Server.ConnectedClients.Find(c => c.Connection.RemoteEndPoint.Address.ToString() == commands[1]); if (client == null) { GameMain.Server.BanList.BanPlayer("Unnamed", commands[1]); } else { GameMain.Server.KickClient(client, true); } } break; case "startclient": if (commands.Length == 1) { return; } if (GameMain.Client == null) { GameMain.NetworkMember = new GameClient("Name"); GameMain.Client.ConnectToServer(commands[1]); } break; case "mainmenuscreen": case "mainmenu": case "menu": GameMain.GameSession = null; List <Character> characters = new List <Character>(Character.CharacterList); foreach (Character c in characters) { c.Remove(); } GameMain.MainMenuScreen.Select(); break; case "gamescreen": case "game": GameMain.GameScreen.Select(); break; case "editmapscreen": case "editmap": case "edit": if (commands.Length > 1) { Submarine.Load(string.Join(" ", commands.Skip(1)), true); } GameMain.EditMapScreen.Select(); break; case "test": Submarine.Load("aegir mark ii", true); GameMain.DebugDraw = true; GameMain.LightManager.LosEnabled = false; GameMain.EditMapScreen.Select(); break; case "editcharacter": case "editchar": GameMain.EditCharacterScreen.Select(); break; case "controlcharacter": case "control": { if (commands.Length < 2) { break; } var character = FindMatchingCharacter(commands, true); if (character != null) { Character.Controlled = character; } } break; case "setclientcharacter": { if (GameMain.Server == null) { break; } int separatorIndex = Array.IndexOf(commands, ";"); if (separatorIndex == -1 || commands.Length < 4) { ThrowError("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\""); break; } string[] commandsLeft = commands.Take(separatorIndex).ToArray(); string[] commandsRight = commands.Skip(separatorIndex).ToArray(); string clientName = String.Join(" ", commandsLeft.Skip(1)); var client = GameMain.Server.ConnectedClients.Find(c => c.name == clientName); if (client == null) { ThrowError("Client \"" + clientName + "\" not found."); } var character = FindMatchingCharacter(commandsRight, false); GameMain.Server.SetClientCharacter(client, character); } break; case "teleportcharacter": case "teleport": var tpCharacter = FindMatchingCharacter(commands, false); if (commands.Length < 2) { tpCharacter = Character.Controlled; } if (tpCharacter != null) { var cam = GameMain.GameScreen.Cam; tpCharacter.AnimController.CurrentHull = null; tpCharacter.Submarine = null; tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition))); tpCharacter.AnimController.FindHull(cam.ScreenToWorld(PlayerInput.MousePosition), true); } break; case "godmode": if (Submarine.MainSub == null) { return; } Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode; break; case "lockx": Submarine.LockX = !Submarine.LockX; break; case "locky": Submarine.LockY = !Submarine.LockY; break; case "dumpids": try { int count = commands.Length < 2 ? 10 : int.Parse(commands[1]); Entity.DumpIds(count); } catch { return; } break; case "heal": Character healedCharacter = null; if (commands.Length == 1) { healedCharacter = Character.Controlled; } else { healedCharacter = FindMatchingCharacter(commands); } if (healedCharacter != null) { healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null); healedCharacter.Oxygen = 100.0f; healedCharacter.Bleeding = 0.0f; healedCharacter.Stun = 0.0f; } break; case "revive": Character revivedCharacter = null; if (commands.Length == 1) { revivedCharacter = Character.Controlled; } else { revivedCharacter = FindMatchingCharacter(commands); } if (revivedCharacter != null) { revivedCharacter.Revive(false); if (GameMain.Server != null) { foreach (Client c in GameMain.Server.ConnectedClients) { if (c.Character != revivedCharacter) { continue; } //clients stop controlling the character when it dies, force control back GameMain.Server.SetClientCharacter(c, revivedCharacter); break; } } } break; case "freeze": if (Character.Controlled != null) { Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen; } break; case "freecamera": case "freecam": Character.Controlled = null; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; break; case "editwater": case "water": if (GameMain.Client == null) { Hull.EditWater = !Hull.EditWater; } break; case "fire": if (GameMain.Client == null) { Hull.EditFire = !Hull.EditFire; } break; case "fixitems": foreach (Item it in Item.ItemList) { it.Condition = 100.0f; } break; case "fixhull": case "fixwalls": foreach (Structure w in Structure.WallList) { for (int i = 0; i < w.SectionCount; i++) { w.AddDamage(i, -100000.0f); } } break; case "power": Item reactorItem = Item.ItemList.Find(i => i.GetComponent <Reactor>() != null); if (reactorItem == null) { return; } float power = 5000.0f; if (commands.Length > 1) { float.TryParse(commands[1], out power); } var reactor = reactorItem.GetComponent <Reactor>(); reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f; reactor.AutoTemp = true; reactor.Temperature = power; if (GameMain.Server != null) { reactorItem.CreateServerEvent(reactor); } break; case "shake": GameMain.GameScreen.Cam.Shake = 10.0f; break; case "losenabled": case "los": case "drawlos": GameMain.LightManager.LosEnabled = !GameMain.LightManager.LosEnabled; break; case "lighting": case "lightingenabled": case "light": case "lights": GameMain.LightManager.LightingEnabled = !GameMain.LightManager.LightingEnabled; break; case "oxygen": case "air": foreach (Hull hull in Hull.hullList) { hull.OxygenPercentage = 100.0f; } break; case "tutorial": TutorialMode.StartTutorial(Tutorials.TutorialType.TutorialTypes[0]); break; case "editortutorial": GameMain.EditMapScreen.Select(); GameMain.EditMapScreen.StartTutorial(); break; case "lobbyscreen": case "lobby": GameMain.LobbyScreen.Select(); break; case "savemap": case "savesub": case "save": if (commands.Length < 2) { break; } if (GameMain.EditMapScreen.CharacterMode) { GameMain.EditMapScreen.ToggleCharacterMode(); } string fileName = string.Join(" ", commands.Skip(1)); if (fileName.Contains("../")) { DebugConsole.ThrowError("Illegal symbols in filename (../)"); return; } if (Submarine.SaveCurrent(System.IO.Path.Combine(Submarine.SavePath, fileName + ".sub"))) { NewMessage("Sub saved", Color.Green); //Submarine.Loaded.First().CheckForErrors(); } break; case "loadmap": case "loadsub": case "load": if (commands.Length < 2) { break; } Submarine.Load(string.Join(" ", commands.Skip(1)), true); break; case "cleansub": for (int i = MapEntity.mapEntityList.Count - 1; i >= 0; i--) { MapEntity me = MapEntity.mapEntityList[i]; if (me.SimPosition.Length() > 2000.0f) { DebugConsole.NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange); MapEntity.mapEntityList.RemoveAt(i); } else if (me.MoveWithLevel) { DebugConsole.NewMessage("Removed " + me.Name + " (MoveWithLevel==true)", Color.Orange); MapEntity.mapEntityList.RemoveAt(i); } else if (me is Item) { Item item = me as Item; var wire = item.GetComponent <Wire>(); if (wire == null) { continue; } if (wire.GetNodes().Count > 0 && !wire.Connections.Any(c => c != null)) { wire.Item.Drop(null); DebugConsole.NewMessage("Dropped wire (ID: " + wire.Item.ID + ") - attached on wall but no connections found", Color.Orange); } } } break; case "messagebox": if (commands.Length < 3) { break; } new GUIMessageBox(commands[1], commands[2]); break; case "debugdraw": GameMain.DebugDraw = !GameMain.DebugDraw; break; case "disablehud": case "hud": GUI.DisableHUD = !GUI.DisableHUD; GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible; break; case "followsub": Camera.FollowSub = !Camera.FollowSub; break; case "drawaitargets": case "showaitargets": AITarget.ShowAITargets = !AITarget.ShowAITargets; break; case "killmonsters": foreach (Character c in Character.CharacterList) { if (!(c.AIController is EnemyAIController)) { continue; } c.AddDamage(CauseOfDeath.Damage, 10000.0f, null); } break; case "netstats": if (GameMain.Server == null) { return; } GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats; break; #if DEBUG case "spamevents": foreach (Item item in Item.ItemList) { for (int i = 0; i < item.components.Count; i++) { if (item.components[i] is IServerSerializable) { GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, i }); } var itemContainer = item.GetComponent <ItemContainer>(); if (itemContainer != null) { GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState }); } GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); item.NeedsPositionUpdate = true; } } foreach (Character c in Character.CharacterList) { GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); } foreach (Structure wall in Structure.WallList) { GameMain.Server.CreateEntityEvent(wall); } break; case "spamchatmessages": int msgCount = 1000; if (commands.Length > 1) { int.TryParse(commands[1], out msgCount); } int msgLength = 50; if (commands.Length > 2) { int.TryParse(commands[2], out msgLength); } for (int i = 0; i < msgCount; i++) { if (GameMain.Server != null) { GameMain.Server.SendChatMessage(ToolBox.RandomSeed(msgLength), ChatMessageType.Default); } else { GameMain.Client.SendChatMessage(ToolBox.RandomSeed(msgLength)); } } break; #endif case "cleanbuild": GameMain.Config.MusicVolume = 0.5f; GameMain.Config.SoundVolume = 0.5f; DebugConsole.NewMessage("Music and sound volume set to 0.5", Color.Green); GameMain.Config.GraphicsWidth = 0; GameMain.Config.GraphicsHeight = 0; GameMain.Config.WindowMode = WindowMode.Fullscreen; DebugConsole.NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green); DebugConsole.NewMessage("Fullscreen enabled", Color.Green); GameSettings.VerboseLogging = false; if (GameMain.Config.MasterServerUrl != "http://www.undertowgames.com/baromaster") { DebugConsole.ThrowError("MasterServerUrl \"" + GameMain.Config.MasterServerUrl + "\"!"); } GameMain.Config.Save("config.xml"); var saveFiles = System.IO.Directory.GetFiles(SaveUtil.SaveFolder); foreach (string saveFile in saveFiles) { System.IO.File.Delete(saveFile); DebugConsole.NewMessage("Deleted " + saveFile, Color.Green); } if (System.IO.Directory.Exists(System.IO.Path.Combine(SaveUtil.SaveFolder, "temp"))) { System.IO.Directory.Delete(System.IO.Path.Combine(SaveUtil.SaveFolder, "temp"), true); DebugConsole.NewMessage("Deleted temp save folder", Color.Green); } if (System.IO.Directory.Exists(ServerLog.SavePath)) { var logFiles = System.IO.Directory.GetFiles(ServerLog.SavePath); foreach (string logFile in logFiles) { System.IO.File.Delete(logFile); DebugConsole.NewMessage("Deleted " + logFile, Color.Green); } } if (System.IO.File.Exists("filelist.xml")) { System.IO.File.Delete("filelist.xml"); DebugConsole.NewMessage("Deleted filelist", Color.Green); } if (System.IO.File.Exists("Submarines/TutorialSub.sub")) { System.IO.File.Delete("Submarines/TutorialSub.sub"); DebugConsole.NewMessage("Deleted TutorialSub from the submarine folder", Color.Green); } if (System.IO.File.Exists(GameServer.SettingsFile)) { System.IO.File.Delete(GameServer.SettingsFile); DebugConsole.NewMessage("Deleted server settings", Color.Green); } if (System.IO.File.Exists(GameServer.ClientPermissionsFile)) { System.IO.File.Delete(GameServer.ClientPermissionsFile); DebugConsole.NewMessage("Deleted client permission file", Color.Green); } if (System.IO.File.Exists("crashreport.txt")) { System.IO.File.Delete("crashreport.txt"); DebugConsole.NewMessage("Deleted crashreport.txt", Color.Green); } if (!System.IO.File.Exists("Content/Map/TutorialSub.sub")) { DebugConsole.ThrowError("TutorialSub.sub not found!"); } break; default: NewMessage("Command not found", Color.Red); break; } }
public void Generate(bool mirror = false) { if (backgroundSpriteManager == null) { var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundSpritePrefabs); if (files.Count > 0) { backgroundSpriteManager = new BackgroundSpriteManager(files); } else { backgroundSpriteManager = new BackgroundSpriteManager("Content/BackgroundSprites/BackgroundSpritePrefabs.xml"); } } #if CLIENT if (backgroundCreatureManager == null) { var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundCreaturePrefabs); if (files.Count > 0) { backgroundCreatureManager = new BackgroundCreatureManager(files); } else { backgroundCreatureManager = new BackgroundCreatureManager("Content/BackgroundSprites/BackgroundCreaturePrefabs.xml"); } } #endif Stopwatch sw = new Stopwatch(); sw.Start(); if (loaded != null) { loaded.Unload(); } loaded = this; positionsOfInterest = new List <InterestingPosition>(); Voronoi voronoi = new Voronoi(1.0); List <Vector2> sites = new List <Vector2>(); bodies = new List <Body>(); Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); #if CLIENT renderer = new LevelRenderer(this); backgroundColor = generationParams.BackgroundColor; float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3; GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f); #endif SeaFloorTopPos = generationParams.SeaFloorDepth + generationParams.MountainHeightMax + generationParams.SeaFloorVariance; float minWidth = 6500.0f; if (Submarine.MainSub != null) { Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders(); minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height)); } Rectangle pathBorders = borders; pathBorders.Inflate(-minWidth * 2, -minWidth * 2); startPosition = new Vector2( Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); endPosition = new Vector2( borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); //---------------------------------------------------------------------------------- //generate the initial nodes for the main path and smaller tunnels //---------------------------------------------------------------------------------- List <Vector2> pathNodes = new List <Vector2>(); pathNodes.Add(new Vector2(startPosition.X, borders.Height)); Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange; for (float x = startPosition.X + Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server); x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server); x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server)) { pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server))); } pathNodes.Add(new Vector2(endPosition.X, borders.Height)); if (pathNodes.Count <= 2) { pathNodes.Add((startPosition + endPosition) / 2); } GenerateTunnels(pathNodes, minWidth); //---------------------------------------------------------------------------------- //generate voronoi sites //---------------------------------------------------------------------------------- Vector2 siteInterval = generationParams.VoronoiSiteInterval; Vector2 siteVariance = generationParams.VoronoiSiteVariance; for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X) { for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y) { Vector2 site = new Vector2( x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server), y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server)); if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length()))) { //add some more sites around the small tunnels to generate more small voronoi cells if (x < borders.Width - siteInterval.X) { sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f); } if (y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f); } if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f); } } if (mirror) { site.X = borders.Width - site.X; } sites.Add(site); } } //---------------------------------------------------------------------------------- // construct the voronoi graph and cells //---------------------------------------------------------------------------------- Stopwatch sw2 = new Stopwatch(); sw2.Start(); List <GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height); Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //construct voronoi cells based on the graph edges cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid); Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //---------------------------------------------------------------------------------- // generate a path through the initial path nodes //---------------------------------------------------------------------------------- List <VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize, new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror); for (int i = 2; i < mainPath.Count; i += 3) { positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath)); } List <VoronoiCell> pathCells = new List <VoronoiCell>(mainPath); //make sure the path is wide enough to pass through EnlargeMainPath(pathCells, minWidth); foreach (InterestingPosition positionOfInterest in positionsOfInterest) { WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null); wayPoint.MoveWithLevel = true; } startPosition.X = pathCells[0].Center.X; //---------------------------------------------------------------------------------- // tunnels through the tunnel nodes //---------------------------------------------------------------------------------- foreach (List <Vector2> tunnel in smallTunnels) { if (tunnel.Count < 2) { continue; } //find the cell which the path starts from int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1); if (startCellIndex < 0) { continue; } //if it wasn't one of the cells in the main path, don't create a tunnel if (cells[startCellIndex].CellType != CellType.Path) { continue; } var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders); positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave)); if (tunnel.Count > 4) { positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave)); } pathCells.AddRange(newPathCells); } Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //---------------------------------------------------------------------------------- // remove unnecessary cells and create some holes at the bottom of the level //---------------------------------------------------------------------------------- cells = CleanCells(pathCells); pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); foreach (VoronoiCell cell in cells) { if (cell.Center.Y < borders.Height / 2) { continue; } cell.edges.ForEach(e => e.OutsideLevel = true); } //---------------------------------------------------------------------------------- // initialize the cells that are still left and insert them into the cell grid //---------------------------------------------------------------------------------- foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); cell.CellType = CellType.Path; cells.Remove(cell); } for (int x = 0; x < cellGrid.GetLength(0); x++) { for (int y = 0; y < cellGrid.GetLength(1); y++) { cellGrid[x, y].Clear(); } } foreach (VoronoiCell cell in cells) { int x = (int)Math.Floor(cell.Center.X / GridCellSize); int y = (int)Math.Floor(cell.Center.Y / GridCellSize); if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) { continue; } cellGrid[x, y].Add(cell); } //---------------------------------------------------------------------------------- // create some ruins //---------------------------------------------------------------------------------- ruins = new List <Ruin>(); for (int i = 0; i < generationParams.RuinCount; i++) { GenerateRuin(mainPath); } //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells //---------------------------------------------------------------------------------- startPosition.Y = borders.Height; endPosition.Y = borders.Height; List <VoronoiCell> cellsWithBody = new List <VoronoiCell>(cells); List <Vector2[]> triangles; bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out triangles); #if CLIENT renderer.SetBodyVertices(CaveGenerator.GenerateRenderVerticeList(triangles).ToArray(), generationParams.WallColor); renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells), generationParams.WallColor); #endif TopBarrier = BodyFactory.CreateEdge(GameMain.World, ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)), ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0))); TopBarrier.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f); TopBarrier.BodyType = BodyType.Static; TopBarrier.CollisionCategories = Physics.CollisionLevel; bodies.Add(TopBarrier); GenerateSeaFloor(); backgroundSpriteManager.PlaceSprites(this, generationParams.BackgroundSpriteAmount); #if CLIENT backgroundCreatureManager.SpawnSprites(80); #endif foreach (VoronoiCell cell in cells) { foreach (GraphEdge edge in cell.edges) { edge.cell1 = null; edge.cell2 = null; edge.site1 = null; edge.site2 = null; } } //initialize MapEntities that aren't in any sub (e.g. items inside ruins) MapEntity.MapLoaded(null); Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); if (mirror) { Vector2 temp = startPosition; startPosition = endPosition; endPosition = temp; } Debug.WriteLine("**********************************************************************************"); Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms"); Debug.WriteLine("Seed: " + seed); Debug.WriteLine("**********************************************************************************"); if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("Generated level with the seed " + seed + " (type: " + generationParams.Name + ")", Color.White); } }