Beispiel #1
0
        public static LocationType Random(string seed = "", int?zone = null)
        {
            Debug.Assert(List.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");

            if (!string.IsNullOrWhiteSpace(seed))
            {
                Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
            }

            List <LocationType> allowedLocationTypes = zone.HasValue ? List.FindAll(lt => lt.CommonnessPerZone.ContainsKey(zone.Value)) : List;

            if (allowedLocationTypes.Count == 0)
            {
                DebugConsole.ThrowError("Could not generate a random location type - no location types for the zone " + zone + " found!");
            }

            if (zone.HasValue)
            {
                return(ToolBox.SelectWeightedRandom(
                           allowedLocationTypes,
                           allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToList(),
                           Rand.RandSync.Server));
            }
            else
            {
                return(allowedLocationTypes[Rand.Int(allowedLocationTypes.Count, Rand.RandSync.Server)]);
            }
        }
Beispiel #2
0
        public static LevelGenerationParams GetRandom(string seed, Biome biome = null)
        {
            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            if (levelParams == null || !levelParams.Any())
            {
                DebugConsole.ThrowError("Level generation presets not found - using default presets");
                return(new LevelGenerationParams(null));
            }

            if (biome == null)
            {
                return(levelParams.GetRandom(lp => lp.allowedBiomes.Count > 0, Rand.RandSync.Server));
            }

            var matchingLevelParams = levelParams.FindAll(lp => lp.allowedBiomes.Contains(biome));

            if (matchingLevelParams.Count == 0)
            {
                DebugConsole.ThrowError("Level generation presets not found for the biome \"" + biome.Identifier + "\"!");
                return(new LevelGenerationParams(null));
            }

            return(matchingLevelParams[Rand.Range(0, matchingLevelParams.Count, Rand.RandSync.Server)]);
        }
Beispiel #3
0
        private void CreateEditor()
        {
            editor = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), GUI.Canvas, Anchor.TopRight, minSize: new Point(400, 0)));
            var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), editor.RectTransform, Anchor.Center))
            {
                Stretch         = true,
                RelativeSpacing = 0.02f,
                CanBeFocused    = false
            };

            var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), paddedFrame.RectTransform, Anchor.Center));

            new SerializableEntityEditor(listBox.Content.RectTransform, generationParams, false, true);

            new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), "Generate")
            {
                OnClicked = (btn, userData) =>
                {
                    Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));
                    Generate();
                    InitProjectSpecific();
                    return(true);
                }
            };
        }
        public Map(string seed, int size)
        {
            this.seed = seed;

            this.size = size;

            levels = new List<Level>();

            locations = new List<Location>();

            connections = new List<LocationConnection>();

            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));

            GenerateMap(CONNECTION.VORONOI);
            //GenerateLocations();

            /*currentLocation = locations[locations.Count / 2];
            currentLocation.Discovered = true;
            GenerateDifficulties(currentLocation, new List<LocationConnection> (connections), 10.0f);*/

            foreach (LocationConnection connection in connections)
            {
                connection.Level = Level.CreateRandom(connection);
            }
        }
Beispiel #5
0
        /// <summary>
        /// Generate a new campaign map from the seed
        /// </summary>
        public Map(CampaignMode campaign, string seed) : this()
        {
            Seed = seed;
            Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));

            Generate();

            if (Locations.Count == 0)
            {
                throw new Exception($"Generating a campaign map failed (no locations created). Width: {Width}, height: {Height}");
            }

            for (int i = 0; i < Locations.Count; i++)
            {
                Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
            }

            foreach (Location location in Locations)
            {
                if (!location.Type.Identifier.Equals("city", StringComparison.OrdinalIgnoreCase) &&
                    !location.Type.Identifier.Equals("outpost", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }
                if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
                {
                    CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
                }
            }

            CurrentLocation.CreateStore();
            CurrentLocation.Discovered = true;

            InitProjectSpecific();
        }
Beispiel #6
0
        public static LevelData CreateRandom(string seed = "", float?difficulty = null, LevelGenerationParams generationParams = null)
        {
            if (string.IsNullOrEmpty(seed))
            {
                seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString();
            }

            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            LevelType type = generationParams == null ? LevelData.LevelType.LocationConnection : generationParams.Type;

            if (generationParams == null)
            {
                generationParams = LevelGenerationParams.GetRandom(seed, type);
            }
            var biome =
                LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
                LevelGenerationParams.GetBiomes().GetRandom(Rand.RandSync.Server);

            return(new LevelData(
                       seed,
                       difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server),
                       Rand.Range(0.0f, 1.0f, Rand.RandSync.Server),
                       generationParams,
                       biome));
        }
Beispiel #7
0
        public static LevelData CreateRandom(string seed = "", float?difficulty = null, LevelGenerationParams generationParams = null)
        {
            if (string.IsNullOrEmpty(seed))
            {
                seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString();
            }

            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            LevelType type = generationParams == null ? LevelData.LevelType.LocationConnection : generationParams.Type;

            if (generationParams == null)
            {
                generationParams = LevelGenerationParams.GetRandom(seed, type);
            }
            var biome =
                LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
                LevelGenerationParams.GetBiomes().GetRandom(Rand.RandSync.Server);

            var levelData = new LevelData(
                seed,
                difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server),
                Rand.Range(0.0f, 1.0f, Rand.RandSync.Server),
                generationParams,
                biome);

            if (type == LevelType.LocationConnection)
            {
                float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
                levelData.HasBeaconStation = beaconRng < 0.5f;
                levelData.IsBeaconActive   = beaconRng > 0.25f;
            }
            GameMain.GameSession?.GameMode?.Mission?.AdjustLevelData(levelData);
            return(levelData);
        }
Beispiel #8
0
        private Character SpawnWatchman(Submarine outpost)
        {
            WayPoint watchmanSpawnpoint = WayPoint.WayPointList.Find(wp => wp.Submarine == outpost);

            if (watchmanSpawnpoint == null)
            {
                DebugConsole.ThrowError("Failed to spawn a watchman at the outpost. No spawnpoints found inside the outpost.");
                return(null);
            }

            string seed = outpost == Level.Loaded.StartOutpost ? map.SelectedLocation.Name : map.CurrentLocation.Name;

            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            JobPrefab     watchmanJob      = JobPrefab.List.Find(jp => jp.Identifier == "watchman");
            CharacterInfo characterInfo    = new CharacterInfo(Character.HumanConfigFile, jobPrefab: watchmanJob);
            var           spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
                                                              Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));

            InitializeWatchman(spawnedCharacter);
            (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager.SetOrder(
                new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, repeat: true, getDivingGearIfNeeded: false));
            if (watchmanJob != null)
            {
                spawnedCharacter.GiveJobItems();
            }
            return(spawnedCharacter);
        }
Beispiel #9
0
        public static Level CreateRandom(string seed = "")
        {
            if (seed == "")
            {
                seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString();
            }

            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            return(new Level(seed, Rand.Range(30.0f, 80.0f, Rand.RandSync.Server), LevelGenerationParams.GetRandom(seed)));
        }
Beispiel #10
0
        public static LevelGenerationParams GetRandom(string seed)
        {
            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            if (presets == null || !presets.Any())
            {
                DebugConsole.ThrowError("Level generation presets not found - using default presets");
                return(new LevelGenerationParams(null));
            }

            return(presets[Rand.Range(0, presets.Count, Rand.RandSync.Server)]);
        }
Beispiel #11
0
        public Map(string seed)
        {
            generationParams = MapGenerationParams.Instance;
            this.Seed        = seed;
            this.size        = generationParams.Size;

            levels = new List <Level>();

            Locations = new List <Location>();

            connections = new List <LocationConnection>();

            Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));

            Generate();

            //start from the colony furthest away from the center
            float   largestDist = 0.0f;
            Vector2 center      = new Vector2(size, size) / 2;

            foreach (Location location in Locations)
            {
                if (location.Type.Identifier != "City")
                {
                    continue;
                }
                float dist = Vector2.DistanceSquared(center, location.MapPosition);
                if (dist > largestDist)
                {
                    largestDist     = dist;
                    CurrentLocation = location;
                }
            }

            CurrentLocation.Discovered = true;

            foreach (LocationConnection connection in connections)
            {
                connection.Level = Level.CreateRandom(connection);
            }

            InitProjectSpecific();
        }
Beispiel #12
0
        public static void PlaceIfNeeded()
        {
            if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer)
            {
                return;
            }

            for (int i = 0; i < Submarine.MainSubs.Length; i++)
            {
                if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned)
                {
                    continue;
                }
                List <Submarine> subs = new List <Submarine>()
                {
                    Submarine.MainSubs[i]
                };
                subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost));
                Place(subs);
                subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
            }

            float difficultyModifier = GetLevelDifficultyModifier();

            foreach (var sub in Submarine.Loaded)
            {
                if (sub.Info.Type == SubmarineType.Player ||
                    sub.Info.Type == SubmarineType.Outpost ||
                    sub.Info.Type == SubmarineType.OutpostModule ||
                    sub.Info.Type == SubmarineType.EnemySubmarine)
                {
                    continue;
                }
                Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier);
            }

            if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost)
            {
                Rand.SetSyncedSeed(ToolBox.StringToInt(Level.Loaded.StartOutpost.Info.Name));
                Place(Level.Loaded.StartOutpost.ToEnumerable());
            }
        }
        public static LocationType Random(string seed = "")
        {
            Debug.Assert(list.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");

            if (!string.IsNullOrWhiteSpace(seed))
            {
                Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
            }

            int randInt = Rand.Int(totalWeight, Rand.RandSync.Server);

            foreach (LocationType type in list)
            {
                if (randInt < type.commonness)
                {
                    return(type);
                }
                randInt -= type.commonness;
            }

            return(null);
        }
Beispiel #14
0
        private Character SpawnWatchman(Submarine outpost)
        {
            WayPoint watchmanSpawnpoint = WayPoint.WayPointList.Find(wp => wp.Submarine == outpost);

            if (watchmanSpawnpoint == null)
            {
                DebugConsole.ThrowError("Failed to spawn a watchman at the outpost. No spawnpoints found inside the outpost.");
                return(null);
            }

            string seed = outpost == Level.Loaded.StartOutpost ? map.SelectedLocation.Name : map.CurrentLocation.Name;

            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            JobPrefab     watchmanJob      = JobPrefab.Get("watchman");
            var           variant          = Rand.Range(0, watchmanJob.Variants, Rand.RandSync.Server);
            CharacterInfo characterInfo    = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: watchmanJob, variant: variant);
            var           spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
                                                              Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));

            InitializeWatchman(spawnedCharacter);
            var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager;

            if (objectiveManager != null)
            {
                var moveOrder = new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false);
                moveOrder.Completed += () =>
                {
                    // Turn towards the center of the sub. Doesn't work in all possible cases, but this is the simplest solution for now.
                    spawnedCharacter.AnimController.TargetDir = spawnedCharacter.Submarine.WorldPosition.X > spawnedCharacter.WorldPosition.X ? Direction.Right : Direction.Left;
                };
                objectiveManager.SetOrder(moveOrder);
            }
            if (watchmanJob != null)
            {
                spawnedCharacter.GiveJobItems();
            }
            return(spawnedCharacter);
        }
        public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, Biome biome = null)
        {
            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            if (LevelParams == null || !LevelParams.Any())
            {
                DebugConsole.ThrowError("Level generation presets not found - using default presets");
                return(new LevelGenerationParams(null));
            }

            var matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type && lp.allowedBiomes.Any());

            if (biome == null)
            {
                matchingLevelParams = matchingLevelParams.FindAll(lp => !lp.allowedBiomes.Any(b => b.IsEndBiome));
            }
            else
            {
                matchingLevelParams = matchingLevelParams.FindAll(lp => lp.allowedBiomes.Contains(biome));
            }
            if (matchingLevelParams.Count == 0)
            {
                DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{(biome?.Identifier ?? "null")}\", type: \"{type}\"!");
                if (biome != null)
                {
                    //try to find params that at least have a suitable type
                    matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type);
                    if (matchingLevelParams.Count == 0)
                    {
                        //still not found, give up and choose some params randomly
                        matchingLevelParams = LevelParams;
                    }
                }
            }

            return(matchingLevelParams[Rand.Range(0, matchingLevelParams.Count, Rand.RandSync.Server)]);
        }
Beispiel #16
0
        public static void PlaceIfNeeded()
        {
            if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer)
            {
                return;
            }

            for (int i = 0; i < Submarine.MainSubs.Length; i++)
            {
                if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned)
                {
                    continue;
                }
                List <Submarine> subs = new List <Submarine>()
                {
                    Submarine.MainSubs[i]
                };
                subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost));
                Place(subs);
                subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
            }

            foreach (var wreck in Submarine.Loaded)
            {
                if (wreck.Info.IsWreck)
                {
                    Place(wreck.ToEnumerable());
                }
            }

            if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost)
            {
                Rand.SetSyncedSeed(ToolBox.StringToInt(Level.Loaded.StartOutpost.Info.Name));
                Place(Level.Loaded.StartOutpost.ToEnumerable());
            }
        }
Beispiel #17
0
        public void Generate(bool mirror = false)
        {
            Stopwatch sw = new Stopwatch();

            sw.Start();

            if (loaded != null)
            {
                loaded.Unload();
            }
            loaded = this;

            positionsOfInterest = new List <InterestingPosition>();

            renderer = new LevelRenderer(this);

            Voronoi voronoi = new Voronoi(1.0);

            List <Vector2> sites = new List <Vector2>();

            bodies = new List <Body>();

            Rand.SetSyncedSeed(ToolBox.StringToInt(seed));

            backgroundColor = generationParams.BackgroundColor;
            float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3;

            GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f);

            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, false),
                Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false));

            endPosition = new Vector2(
                borders.Width - Rand.Range(minWidth, minWidth * 2, false),
                Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false));

            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, false);
                 x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, false);
                 x += Rand.Range(nodeInterval.X, nodeInterval.Y, false))
            {
                pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, false)));
            }

            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, false)];
                tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right);

                float tunnelLength = Rand.Range(
                    generationParams.SmallTunnelLengthRange.X,
                    generationParams.SmallTunnelLengthRange.Y,
                    false);

                var tunnelNodes = MathUtils.GenerateJaggedLine(
                    tunnelStartPos,
                    new Vector2(tunnelStartPos.X, pathBorders.Bottom) + Rand.Vector(tunnelLength, false),
                    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, false),
                        y + Rand.Range(-siteVariance.Y, siteVariance.Y, false));

                    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, false)];

                    //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 <VertexPositionTexture> bodyVertices;

            bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out bodyVertices);

            renderer.SetBodyVertices(bodyVertices.ToArray());
            renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells));

            renderer.PlaceSprites(generationParams.BackgroundSpriteAmount);

            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("**********************************************************************************");
        }
        public void PlaceNestObjects(Level level, Level.Cave cave, Vector2 nestPosition, float nestRadius, int objectAmount)
        {
            Rand.SetSyncedSeed(ToolBox.StringToInt(level.Seed));

            var availablePrefabs = new List <LevelObjectPrefab>(LevelObjectPrefab.List.FindAll(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.NestWall)));
            Dictionary <LevelObjectPrefab, List <SpawnPosition> > suitableSpawnPositions = new Dictionary <LevelObjectPrefab, List <SpawnPosition> >();
            Dictionary <LevelObjectPrefab, List <float> >         spawnPositionWeights   = new Dictionary <LevelObjectPrefab, List <float> >();

            List <SpawnPosition> availableSpawnPositions = new List <SpawnPosition>();
            var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
            List <VoronoiCell> caveWallCells = new List <VoronoiCell>();

            foreach (var edge in caveCells.SelectMany(c => c.Edges))
            {
                if (!edge.NextToCave)
                {
                    continue;
                }
                if (MathUtils.LineSegmentToPointDistanceSquared(edge.Point1.ToPoint(), edge.Point2.ToPoint(), nestPosition.ToPoint()) > nestRadius * nestRadius)
                {
                    continue;
                }
                if (edge.Cell1?.CellType == CellType.Solid)
                {
                    caveWallCells.Add(edge.Cell1);
                }
                if (edge.Cell2?.CellType == CellType.Solid)
                {
                    caveWallCells.Add(edge.Cell2);
                }
            }
            availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));

            for (int i = 0; i < objectAmount; i++)
            {
                //get a random prefab and find a place to spawn it
                LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride: false);
                if (prefab == null)
                {
                    continue;
                }
                if (!suitableSpawnPositions.ContainsKey(prefab))
                {
                    suitableSpawnPositions.Add(prefab,
                                               availableSpawnPositions.Where(sp =>
                                                                             sp.Length >= prefab.MinSurfaceWidth &&
                                                                             (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
                    spawnPositionWeights.Add(prefab,
                                             suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
                }
                SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
                if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None)
                {
                    continue;
                }
                PlaceObject(prefab, spawnPosition, level);
                if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
                {
                    availablePrefabs.Remove(prefab);
                }
            }
        }
Beispiel #19
0
        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 + nodeInterval.X;
                 x < endPosition.X - nodeInterval.X;
                 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.Insert(1, borders.Center.ToVector2());
            }

            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);
            }
        }
Beispiel #20
0
        /// <summary>
        /// Load a previously saved campaign map from XML
        /// </summary>
        private Map(CampaignMode campaign, XElement element) : this()
        {
            Seed = element.GetAttributeString("seed", "a");
            Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
            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);
                    break;
                }
            }
            System.Diagnostics.Debug.Assert(!Locations.Contains(null));
            for (int i = 0; i < Locations.Count; i++)
            {
                Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
            }

            foreach (XElement subElement in element.Elements())
            {
                switch (subElement.Name.ToString().ToLowerInvariant())
                {
                case "connection":
                    Point locationIndices = subElement.GetAttributePoint("locations", new Point(0, 1));
                    if (locationIndices.X == locationIndices.Y)
                    {
                        continue;
                    }
                    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().FirstOrDefault(b => b.OldIdentifier == 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();
        }