partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change);
/// <summary> /// Load a previously saved campaign map from XML /// </summary> private Map(CampaignMode campaign, XElement element) : this() { Seed = element.GetAttributeString("seed", "a"); foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "location": int i = subElement.GetAttributeInt("i", 0); while (Locations.Count <= i) { Locations.Add(null); } Locations[i] = new Location(subElement); Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server)); break; } } System.Diagnostics.Debug.Assert(!Locations.Contains(null)); foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "connection": Point locationIndices = subElement.GetAttributePoint("locations", new Point(0, 1)); var connection = new LocationConnection(Locations[locationIndices.X], Locations[locationIndices.Y]) { Passed = subElement.GetAttributeBool("passed", false), Difficulty = subElement.GetAttributeFloat("difficulty", 0.0f) }; Locations[locationIndices.X].Connections.Add(connection); Locations[locationIndices.Y].Connections.Add(connection); connection.LevelData = new LevelData(subElement.Element("Level")); string biomeId = subElement.GetAttributeString("biome", ""); connection.Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeId) ?? LevelGenerationParams.GetBiomes().First(); Connections.Add(connection); break; } } int startLocationindex = element.GetAttributeInt("startlocation", -1); if (startLocationindex > 0 && startLocationindex < Locations.Count) { StartLocation = Locations[startLocationindex]; } else { DebugConsole.AddWarning($"Error while loading the map. Start location index out of bounds (index: {startLocationindex}, location count: {Locations.Count})."); foreach (Location location in Locations) { if (!location.Type.HasOutpost) { continue; } if (StartLocation == null || location.MapPosition.X < StartLocation.MapPosition.X) { StartLocation = location; } } } int endLocationindex = element.GetAttributeInt("endlocation", -1); if (endLocationindex > 0 && endLocationindex < Locations.Count) { EndLocation = Locations[endLocationindex]; } else { DebugConsole.AddWarning($"Error while loading the map. End location index out of bounds (index: {endLocationindex}, location count: {Locations.Count})."); foreach (Location location in Locations) { if (EndLocation == null || location.MapPosition.X > EndLocation.MapPosition.X) { EndLocation = location; } } } InitProjectSpecific(); }
private void CreateEndLocation() { float zoneWidth = Width / generationParams.DifficultyZones; Vector2 endPos = new Vector2(Width - zoneWidth / 2, Height / 2); float closestDist = float.MaxValue; EndLocation = Locations.First(); foreach (Location location in Locations) { float dist = Vector2.DistanceSquared(endPos, location.MapPosition); if (location.Biome.IsEndBiome && dist < closestDist) { EndLocation = location; closestDist = dist; } } Location previousToEndLocation = null; foreach (Location location in Locations) { if (!location.Biome.IsEndBiome && (previousToEndLocation == null || location.MapPosition.X > previousToEndLocation.MapPosition.X)) { previousToEndLocation = location; } } if (EndLocation == null || previousToEndLocation == null) { return; } //remove all locations from the end biome except the end location for (int i = Locations.Count - 1; i >= 0; i--) { if (Locations[i].Biome.IsEndBiome && Locations[i] != EndLocation) { for (int j = Locations[i].Connections.Count - 1; j >= 0; j--) { if (j >= Locations[i].Connections.Count) { continue; } var connection = Locations[i].Connections[j]; var otherLocation = connection.OtherLocation(Locations[i]); Locations[i].Connections.RemoveAt(j); otherLocation?.Connections.Remove(connection); Connections.Remove(connection); } Locations.RemoveAt(i); } } //removed all connections from the second-to-last location, need to reconnect it if (!previousToEndLocation.Connections.Any()) { Location connectTo = Locations.First(); foreach (Location location in Locations) { if (!location.Biome.IsEndBiome && location != previousToEndLocation && location.MapPosition.X > connectTo.MapPosition.X) { connectTo = location; } } var newConnection = new LocationConnection(previousToEndLocation, connectTo) { Biome = EndLocation.Biome, Difficulty = 100.0f }; Connections.Add(newConnection); previousToEndLocation.Connections.Add(newConnection); connectTo.Connections.Add(newConnection); } var endConnection = new LocationConnection(previousToEndLocation, EndLocation) { Biome = EndLocation.Biome, Difficulty = 100.0f }; Connections.Add(endConnection); previousToEndLocation.Connections.Add(endConnection); EndLocation.Connections.Add(endConnection); }
private void ProgressWorld() { foreach (Location location in Locations) { if (!location.Discovered) { continue; } if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) { furthestDiscoveredLocation = location; } if (location == CurrentLocation || location == SelectedLocation) { continue; } //find which types of locations this one can change to List <LocationTypeChange> allowedTypeChanges = new List <LocationTypeChange>(); List <LocationTypeChange> readyTypeChanges = new List <LocationTypeChange>(); foreach (LocationTypeChange typeChange in location.Type.CanChangeTo) { //check if there are any adjacent locations that would prevent the change bool disallowedFound = false; foreach (string disallowedLocationName in typeChange.DisallowedAdjacentLocations) { if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(disallowedLocationName, StringComparison.OrdinalIgnoreCase))) { disallowedFound = true; break; } } if (disallowedFound) { continue; } //check that there's a required adjacent location present bool requiredFound = false; foreach (string requiredLocationName in typeChange.RequiredAdjacentLocations) { if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(requiredLocationName, StringComparison.OrdinalIgnoreCase))) { requiredFound = true; break; } } if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) { continue; } allowedTypeChanges.Add(typeChange); if (location.TypeChangeTimer >= typeChange.RequiredDuration) { readyTypeChanges.Add(typeChange); } } //select a random type change if (Rand.Range(0.0f, 1.0f) < readyTypeChanges.Sum(t => t.Probability)) { var selectedTypeChange = ToolBox.SelectWeightedRandom(readyTypeChanges, readyTypeChanges.Select(t => t.Probability).ToList(), Rand.RandSync.Unsynced); if (selectedTypeChange != null) { string prevName = location.Name; location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(selectedTypeChange.ChangeToType, StringComparison.OrdinalIgnoreCase))); ChangeLocationType(location, prevName, selectedTypeChange); location.TypeChangeTimer = -1; break; } } if (allowedTypeChanges.Count > 0) { location.TypeChangeTimer++; } else { location.TypeChangeTimer = 0; } location.UpdateStore(); } }
private void Generate() { Connections.Clear(); Locations.Clear(); List <Vector2> voronoiSites = new List <Vector2>(); for (float x = 10.0f; x < Width - 10.0f; x += generationParams.VoronoiSiteInterval.X) { for (float y = 10.0f; y < Height - 10.0f; y += generationParams.VoronoiSiteInterval.Y) { voronoiSites.Add(new Vector2( x + generationParams.VoronoiSiteVariance.X * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server), y + generationParams.VoronoiSiteVariance.Y * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server))); } } Voronoi voronoi = new Voronoi(0.5f); List <GraphEdge> edges = voronoi.MakeVoronoiGraph(voronoiSites, Width, Height); float zoneWidth = Width / generationParams.DifficultyZones; Vector2 margin = new Vector2( Math.Min(10, Width * 0.1f), Math.Min(10, Height * 0.2f)); float startX = margin.X, endX = Width - margin.X; float startY = margin.Y, endY = Height - margin.Y; if (!edges.Any()) { throw new Exception($"Generating a campaign map failed (no edges in the voronoi graph). Width: {Width}, height: {Height}, margin: {margin}"); } voronoiSites.Clear(); foreach (GraphEdge edge in edges) { if (edge.Point1 == edge.Point2) { continue; } if (edge.Point1.X < margin.X || edge.Point1.X > Width - margin.X || edge.Point1.Y < startY || edge.Point1.Y > endY) { continue; } if (edge.Point2.X < margin.X || edge.Point2.X > Width - margin.X || edge.Point2.Y < startY || edge.Point2.Y > endY) { 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, Rand.RandSync.Server); Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; } int zone = MathHelper.Clamp((int)Math.Floor(position.X / zoneWidth) + 1, 1, generationParams.DifficultyZones); newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, Locations); Locations.Add(newLocations[i]); } var newConnection = new LocationConnection(newLocations[0], newLocations[1]); Connections.Add(newConnection); } //remove connections that are too short float minConnectionDistanceSqr = generationParams.MinConnectionDistance * generationParams.MinConnectionDistance; for (int i = Connections.Count - 1; i >= 0; i--) { LocationConnection connection = Connections[i]; if (Vector2.DistanceSquared(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minConnectionDistanceSqr) { 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]; } } } HashSet <Location> connectedLocations = new HashSet <Location>(); foreach (LocationConnection connection in Connections) { connection.Locations[0].Connections.Add(connection); connection.Locations[1].Connections.Add(connection); connectedLocations.Add(connection.Locations[0]); connectedLocations.Add(connection.Locations[1]); } //remove orphans Locations.RemoveAll(c => !connectedLocations.Contains(c)); //remove locations that are too close to each other float minLocationDistanceSqr = generationParams.MinLocationDistance * generationParams.MinLocationDistance; for (int i = Locations.Count - 1; i >= 0; i--) { for (int j = Locations.Count - 1; j > i; j--) { float dist = Vector2.DistanceSquared(Locations[i].MapPosition, Locations[j].MapPosition); if (dist > minLocationDistanceSqr) { continue; } //move connections from Locations[j] to Locations[i] foreach (LocationConnection connection in Locations[j].Connections) { if (connection.Locations[0] == Locations[j]) { connection.Locations[0] = Locations[i]; } else { connection.Locations[1] = Locations[i]; } Locations[i].Connections.Add(connection); } Locations[i].Connections.RemoveAll(c => c.OtherLocation(Locations[i]) == Locations[j]); Locations.RemoveAt(j); } } 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 (Location location in Locations) { for (int i = location.Connections.Count - 1; i >= 0; i--) { if (!Connections.Contains(location.Connections[i])) { location.Connections.RemoveAt(i); } } } foreach (LocationConnection connection in Connections) { connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f); } AssignBiomes(); CreateEndLocation(); foreach (Location location in Locations) { location.LevelData = new LevelData(location); location.NormalizedDepth = location.MapPosition.X / Width; } foreach (LocationConnection connection in Connections) { connection.LevelData = new LevelData(connection); } }