using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Voronoi2; namespace Barotrauma { class Map { Vector2 difficultyIncrease = new Vector2(5.0f,10.0f); Vector2 difficultyCutoff = new Vector2(80.0f, 100.0f); private List levels; private List locations; private List connections; private string seed; private int size; private static Sprite iceTexture; private static Texture2D iceCraters; private static Texture2D iceCrack; private Location currentLocation; private Location selectedLocation; private Location highlightedLocation; private LocationConnection selectedConnection; public Location CurrentLocation { get { return currentLocation; } } public int CurrentLocationIndex { get { return locations.IndexOf(currentLocation); } } public Location SelectedLocation { get { return selectedLocation; } } public LocationConnection SelectedConnection { get { return selectedConnection; } } public string Seed { get { return seed; } } public static Map Load(XElement element) { string mapSeed = ToolBox.GetAttributeString(element, "seed", "a"); int size = ToolBox.GetAttributeInt(element, "size", 500); Map map = new Map(mapSeed, size); map.SetLocation(ToolBox.GetAttributeInt(element, "currentlocation", 0)); string discoveredStr = ToolBox.GetAttributeString(element, "discovered", ""); string[] discoveredStrs = discoveredStr.Split(','); for (int i = 0; i < discoveredStrs.Length; i++ ) { int index = -1; if (int.TryParse(discoveredStrs[i], out index)) map.locations[index].Discovered = true; } string passedStr = ToolBox.GetAttributeString(element, "passed", ""); string[] passedStrs = passedStr.Split(','); for (int i = 0; i < passedStrs.Length; i++) { int index = -1; if (int.TryParse(passedStrs[i], out index)) map.connections[index].Passed = true; } return map; } public Map(string seed, int size) { this.seed = seed; this.size = size; levels = new List(); locations = new List(); connections = new List(); if (iceTexture == null) iceTexture = new Sprite("Content/Map/iceSurface.png", Vector2.Zero); if (iceCraters == null) iceCraters = TextureLoader.FromFile("Content/Map/iceCraters.png"); if (iceCrack == null) iceCrack = TextureLoader.FromFile("Content/Map/iceCrack.png"); Rand.SetSyncedSeed(ToolBox.StringToInt(this.seed)); GenerateLocations(); currentLocation = locations[locations.Count / 2]; currentLocation.Discovered = true; GenerateDifficulties(currentLocation, new List (connections), 10.0f); foreach (LocationConnection connection in connections) { connection.Level = Level.CreateRandom(connection); } } private void GenerateLocations() { Voronoi voronoi = new Voronoi(0.5f); List sites = new List(); for (int i = 0; i < 50; i++) { sites.Add(new Vector2(Rand.Range(0.0f, size, false), Rand.Range(0.0f, size, false))); } List edges = voronoi.MakeVoronoiGraph(sites, size, size); sites.Clear(); foreach (GraphEdge edge in edges) { if (edge.point1 == edge.point2) continue; //remove points from the edge of the map if (edge.point1.X == 0 || edge.point1.X == size) continue; if (edge.point1.Y == 0 || edge.point1.Y == size) continue; if (edge.point2.X == 0 || edge.point2.X == size) continue; if (edge.point2.Y == 0 || edge.point2.Y == size) continue; Location[] newLocations = new Location[2]; newLocations[0] = locations.Find(l => l.MapPosition == edge.point1 || l.MapPosition == edge.point2); newLocations[1] = locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.point1 || l.MapPosition == edge.point2)); for (int i = 0; i < 2; i++) { if (newLocations[i] != null) continue; Vector2[] points = new Vector2[] { edge.point1, edge.point2 }; int positionIndex = Rand.Int(1, false); Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex]; newLocations[i] = Location.CreateRandom(position); locations.Add(newLocations[i]); } //int seed = (newLocations[0].GetHashCode() | newLocations[1].GetHashCode()); connections.Add(new LocationConnection(newLocations[0], newLocations[1])); } float minDistance = 50.0f; for (int i = connections.Count - 1; i >= 0; i--) { LocationConnection connection = connections[i]; if (Vector2.Distance(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minDistance) { continue; } locations.Remove(connection.Locations[0]); connections.Remove(connection); foreach (LocationConnection connection2 in connections) { if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1]; if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1]; } } foreach (LocationConnection connection in connections) { connection.Locations[0].Connections.Add(connection); connection.Locations[1].Connections.Add(connection); } for (int i = connections.Count - 1; i >= 0; i--) { i = Math.Min(i, connections.Count - 1); LocationConnection connection = connections[i]; for (int n = Math.Min(i - 1,connections.Count - 1); n >= 0; n--) { if (connection.Locations.Contains(connections[n].Locations[0]) && connection.Locations.Contains(connections[n].Locations[1])) { connections.RemoveAt(n); } } } foreach (LocationConnection connection in connections) { Vector2 start = connection.Locations[0].MapPosition; Vector2 end = connection.Locations[1].MapPosition; int generations = (int)(Math.Sqrt(Vector2.Distance(start, end) / 10.0f)); connection.CrackSegments = MathUtils.GenerateJaggedLine(start, end, generations, 5.0f); } } private void GenerateDifficulties(Location start, List locations, float currDifficulty) { //start.Difficulty = currDifficulty; currDifficulty += Rand.Range(difficultyIncrease.X, difficultyIncrease.Y, false); if (currDifficulty > Rand.Range(difficultyCutoff.X, difficultyCutoff.Y, false)) currDifficulty = 10.0f; foreach (LocationConnection connection in start.Connections) { if (!locations.Contains(connection)) continue; Location nextLocation = connection.OtherLocation(start); locations.Remove(connection); connection.Difficulty = currDifficulty; GenerateDifficulties(nextLocation, locations, currDifficulty); } } public void MoveToNextLocation() { selectedConnection.Passed = true; currentLocation = selectedLocation; currentLocation.Discovered = true; selectedLocation = null; } public void SetLocation(int index) { if (index < 0 || index >= locations.Count) { DebugConsole.ThrowError("Location index out of bounds"); return; } currentLocation = locations[index]; currentLocation.Discovered = true; } public void Update(float deltaTime, Rectangle rect, float scale = 1.0f) { Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y); Vector2 offset = -currentLocation.MapPosition; float maxDist = 20.0f; float closestDist = 0.0f; highlightedLocation = null; for (int i = 0; i < locations.Count; i++) { Location location = locations[i]; Vector2 pos = rectCenter + (location.MapPosition + offset) * scale; if (!rect.Contains(pos)) continue; float dist = Vector2.Distance(PlayerInput.MousePosition, pos); if (dist < maxDist && (highlightedLocation == null || dist < closestDist)) { closestDist = dist; highlightedLocation = location; } } foreach (LocationConnection connection in connections) { if (highlightedLocation != currentLocation && connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation)) { if (PlayerInput.LeftButtonClicked() && selectedLocation != highlightedLocation && highlightedLocation != null) { selectedConnection = connection; selectedLocation = highlightedLocation; GameMain.LobbyScreen.SelectLocation(highlightedLocation, connection); } } } } public void Draw(SpriteBatch spriteBatch, Rectangle rect, float scale = 1.0f) { Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y); Vector2 offset = -currentLocation.MapPosition; iceTexture.DrawTiled(spriteBatch, new Vector2(rect.X, rect.Y), new Vector2(rect.Width, rect.Height), Vector2.Zero, Color.White*0.8f); foreach (LocationConnection connection in connections) { Color crackColor = Color.White * Math.Max(connection.Difficulty/100.0f, 1.5f); if (selectedLocation != currentLocation && (connection.Locations.Contains(selectedLocation) && connection.Locations.Contains(currentLocation))) { crackColor = Color.Red; } else if (highlightedLocation != currentLocation && (connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation))) { crackColor = Color.Red * 0.5f; } else if (!connection.Passed) { crackColor *= 0.2f; } for (int i = 0; i < connection.CrackSegments.Count; i++ ) { var segment = connection.CrackSegments[i]; Vector2 start = rectCenter + (segment[0] + offset) * scale; Vector2 end = rectCenter + (segment[1] + offset) * scale; if (!rect.Contains(start) && !rect.Contains(end)) { continue; } else { Vector2? intersection = MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(rect.X, rect.Y + rect.Height, rect.Width, rect.Height)); if (intersection != null) { if (!rect.Contains(start)) { start = (Vector2)intersection; } else { end = (Vector2)intersection; } } } float dist = Vector2.Distance(start, end); int width = (int)(MathHelper.Clamp(connection.Difficulty, 2.0f, 20.0f) * scale); spriteBatch.Draw(iceCrack, new Rectangle((int)start.X, (int)start.Y, (int)dist + 2, width), new Rectangle(0, 0, iceCrack.Width, 60), crackColor, MathUtils.VectorToAngle(end - start), new Vector2(0, 30), SpriteEffects.None, 0.01f); } } rect.Inflate(8, 8); GUI.DrawRectangle(spriteBatch, rect, Color.Black, 8); GUI.DrawRectangle(spriteBatch, rect, Color.LightGray); for (int i = 0; i < locations.Count; i++) { Location location = locations[i]; Vector2 pos = rectCenter + (location.MapPosition + offset) * scale; Rectangle drawRect = location.Type.Sprite.SourceRect; Rectangle sourceRect = drawRect; drawRect.X = (int)pos.X - drawRect.Width/2; drawRect.Y = (int)pos.Y - drawRect.Width/2; if (!rect.Intersects(drawRect)) continue; Color color = location.Connections.Find(c => c.Locations.Contains(currentLocation))==null ? Color.White : Color.Green; color *= (location.Discovered) ? 0.8f : 0.2f; if (location == currentLocation) color = Color.Orange; if (drawRect.X < rect.X) { sourceRect.X += rect.X - drawRect.X; sourceRect.Width -= sourceRect.X; drawRect.X = rect.X; } else if (drawRect.Right > rect.Right) { sourceRect.Width -= (drawRect.Right - rect.Right); } if (drawRect.Y < rect.Y) { sourceRect.Y += rect.Y - drawRect.Y; sourceRect.Height -= sourceRect.Y; drawRect.Y = rect.Y; } else if (drawRect.Bottom > rect.Bottom) { sourceRect.Height -= drawRect.Bottom - rect.Bottom; } drawRect.Width = sourceRect.Width; drawRect.Height = sourceRect.Height; spriteBatch.Draw(location.Type.Sprite.Texture, drawRect, sourceRect, color); } for (int i = 0; i < 3; i++ ) { Location location = (i == 0) ? highlightedLocation : selectedLocation; if (i == 2) location = currentLocation; if (location == null) continue; Vector2 pos = rectCenter + (location.MapPosition + offset) * scale; pos.X = (int)(pos.X + location.Type.Sprite.SourceRect.Width*0.6f); pos.Y = (int)(pos.Y - 10); GUI.DrawString(spriteBatch, pos, location.Name, Color.White, Color.Black * 0.8f, 3); } } public void Save(XElement element) { XElement mapElement = new XElement("map"); mapElement.Add(new XAttribute("currentlocation", CurrentLocationIndex)); mapElement.Add(new XAttribute("seed", Seed)); mapElement.Add(new XAttribute("size", size)); List discoveredLocations = new List(); for (int i = 0; i < locations.Count; i++ ) { if (locations[i].Discovered) discoveredLocations.Add(i); } mapElement.Add(new XAttribute("discovered", string.Join(",", discoveredLocations))); List passedConnections = new List(); for (int i = 0; i < connections.Count; i++) { if (connections[i].Passed) passedConnections.Add(i); } mapElement.Add(new XAttribute("passed", string.Join(",", passedConnections))); element.Add(mapElement); } } class LocationConnection { private Location[] locations; private Level level; public float Difficulty; public List CrackSegments; public bool Passed; private int missionsCompleted; private Mission mission; public Mission Mission { get { if (mission == null || mission.Completed) { if (mission != null && mission.Completed) missionsCompleted++; long seed = (long)locations[0].MapPosition.X + (long)locations[0].MapPosition.Y * 100; seed += (long)locations[1].MapPosition.X * 10000 + (long)locations[1].MapPosition.Y * 1000000; MTRandom rand = new MTRandom((int)((seed + missionsCompleted) % int.MaxValue)); if (rand.NextDouble() < 0.3f) return null; mission = Mission.LoadRandom(locations, rand, "", true); } return mission; } } public Location[] Locations { get { return locations; } } public Level Level { get { return level; } set { level = value; } } public LocationConnection(Location location1, Location location2) { locations = new Location[] { location1, location2 }; missionsCompleted = 0; } public Location OtherLocation(Location location) { if (locations[0] == location) { return locations[1]; } else if (locations[1] == location) { return locations[0]; } else { return null; } } } }