Beispiel #1
0
        private static Tuple <Coordinates, Coordinates> GetSpawnAndDestination(
            UnitMaker unitMaker, MissionTemplateRecord template, DBEntryTheater theaterDB,
            List <Coordinates> usedCoordinates, Coordinates landbaseCoordinates, Coordinates objectivesCenter,
            double carrierPathDeg)
        {
            var         travelMinMax            = new MinMaxD(Database.Instance.Common.CarrierGroup.CourseLength, Database.Instance.Common.CarrierGroup.CourseLength * 2);
            Coordinates?carrierGroupCoordinates = null;
            Coordinates?destinationPath         = null;
            var         iteration   = 0;
            var         maxDistance = 25;

            while (iteration < 100)
            {
                carrierGroupCoordinates = unitMaker.SpawnPointSelector.GetRandomSpawnPoint(
                    new SpawnPointType[] { SpawnPointType.Sea },
                    landbaseCoordinates,
                    new MinMaxD(10, maxDistance),
                    objectivesCenter,
                    new MinMaxD(10, 99999),
                    GeneratorTools.GetSpawnPointCoalition(template, Side.Ally));
                if (!carrierGroupCoordinates.HasValue)
                {
                    maxDistance += 25;
                    continue;
                }
                var minDist = usedCoordinates.Aggregate(99999999.0, (acc, x) => x.GetDistanceFrom(carrierGroupCoordinates.Value) < acc ? x.GetDistanceFrom(carrierGroupCoordinates.Value) : acc);
                if (minDist < Database.Instance.Common.CarrierGroup.ShipSpacing)
                {
                    continue;
                }

                destinationPath = Coordinates.FromAngleAndDistance(carrierGroupCoordinates.Value, travelMinMax, carrierPathDeg);
                if (ShapeManager.IsPosValid(destinationPath.Value, theaterDB.WaterCoordinates, theaterDB.WaterExclusionCoordinates))
                {
                    break;
                }
                iteration++;
                if (iteration > 10)
                {
                    maxDistance += 1;
                }
            }

            if (!carrierGroupCoordinates.HasValue)
            {
                throw new BriefingRoomException($"Carrier spawnpoint could not be found.");
            }
            if (!destinationPath.HasValue)
            {
                throw new BriefingRoomException($"Carrier destination could not be found.");
            }
            if (!ShapeManager.IsPosValid(destinationPath.Value, theaterDB.WaterCoordinates, theaterDB.WaterExclusionCoordinates))
            {
                throw new BriefingRoomException($"Carrier waypoint is on shore");
            }

            return(new(carrierGroupCoordinates.Value, destinationPath.Value));
        }
Beispiel #2
0
 /// <summary>
 /// Constructor.
 /// </summary>
 public DatabaseCommon()
 {
     EnemyAirDefense = new DatabaseCommonAirDefenseInfo[Toolbox.EnumCount <AmountN>()];
     EnemyAirDefenseDistanceFromObjectives      = new MinMaxD[Toolbox.EnumCount <AirDefenseRange>()];
     EnemyAirDefenseDistanceFromTakeOffLocation = new int[Toolbox.EnumCount <AirDefenseRange>()];
     AllyAirDefense = new DatabaseCommonAirDefenseInfo[Toolbox.EnumCount <AmountN>()];
     AllyAirDefenseDistanceFromObjectives      = new int[Toolbox.EnumCount <AirDefenseRange>()];
     AllyAirDefenseDistanceFromTakeOffLocation = new MinMaxD[Toolbox.EnumCount <AirDefenseRange>()];
     EnemyCAPRelativePower = new double[Toolbox.EnumCount <AmountN>()];
 }
Beispiel #3
0
        private Coordinates?GetLandCoordinates(
            SpawnPointType[] validTypes,
            Coordinates distanceOrigin1, MinMaxD distanceFrom1,
            Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null,
            Coalition?coalition         = null
            )
        {
            var validSP = (from DBEntryTheaterSpawnPoint pt in SpawnPoints where validTypes.Contains(pt.PointType) select pt);

            Coordinates?[] distanceOrigin = new Coordinates?[] { distanceOrigin1, distanceOrigin2 };
            MinMaxD?[]     distanceFrom   = new MinMaxD?[] { distanceFrom1, distanceFrom2 };

            for (int i = 0; i < 2; i++) // Remove spawn points too far or too close from distanceOrigin1 and distanceOrigin2
            {
                if (validSP.Count() == 0)
                {
                    return(null);
                }
                if (!distanceFrom[i].HasValue || !distanceOrigin[i].HasValue)
                {
                    continue;
                }

                var searchRange = distanceFrom[i].Value * Toolbox.NM_TO_METERS; // convert distance to meters

                IEnumerable <DBEntryTheaterSpawnPoint> validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP select s);

                int iterationsLeft = MAX_RADIUS_SEARCH_ITERATIONS;

                do
                {
                    Coordinates origin = distanceOrigin[i].Value;

                    validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP
                                      where
                                      searchRange.Contains(origin.GetDistanceFrom(s.Coordinates)) &&
                                      CheckNotInHostileCoords(s.Coordinates, coalition)
                                      select s);
                    searchRange = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1));
                    validSP     = (from DBEntryTheaterSpawnPoint s in validSPInRange select s);
                    iterationsLeft--;
                } while ((validSPInRange.Count() == 0) && (iterationsLeft > 0));
            }

            if (validSP.Count() == 0)
            {
                return(null);
            }
            DBEntryTheaterSpawnPoint selectedSpawnPoint = Toolbox.RandomFrom(validSP.ToArray());

            SpawnPoints.Remove(selectedSpawnPoint); // Remove spawn point so it won't be used again
            return(selectedSpawnPoint.Coordinates);
        }
Beispiel #4
0
 internal Coordinates?GetRandomSpawnPoint(
     SpawnPointType[] validTypes,
     Coordinates distanceOrigin1, MinMaxD distanceFrom1,
     Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null,
     Coalition?coalition         = null)
 {
     if (validTypes.Contains(SpawnPointType.Air) || validTypes.Contains(SpawnPointType.Sea))
     {
         return(GetAirOrSeaCoordinates(validTypes, distanceOrigin1, distanceFrom1, distanceOrigin2, distanceFrom2, coalition));
     }
     return(GetLandCoordinates(validTypes, distanceOrigin1, distanceFrom1, distanceOrigin2, distanceFrom2, coalition));
 }
Beispiel #5
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="ini">Objective database entry ini file</param>
        /// <param name="section">Ini section to load from</param>
        public DBUnitGroup(INIFile ini, string section)
        {
            Count             = ini.GetValue <MinMaxI>(section, "Count");
            DistanceFromPoint = ini.GetValue <MinMaxD>(section, "DistanceFromPoint");

            Families = ini.GetValueArray <UnitFamily>(section, "Families");
            if (Families.Length > 0) // If the group has at least one unit family (no families mean the group is disabled)...
            {
                // ...make sure all families belong to the same category
                UnitFamily family0 = Families[0];
                // Families don't have to be Distinct() because one may want to mention the same familiy several times
                // so it appears more often
                Families = (from UnitFamily family in Families where Toolbox.GetUnitCategoryFromUnitFamily(family) == Toolbox.GetUnitCategoryFromUnitFamily(family0) select family).ToArray();
            }

            Flags    = ini.GetValueArrayAsEnumFlags <DBUnitGroupFlags>(section, "Flags");
            LuaGroup = ini.GetValueArray <string>(section, "Lua.Group");
            LuaUnit  = ini.GetValue <string>(section, "Lua.Unit");
            // TODO: check LuaGroup and LuaUnit Lua files exist in Include/Lua

            List <TheaterLocationSpawnPointType> spList = new List <TheaterLocationSpawnPointType>(ini.GetValueArray <TheaterLocationSpawnPointType>(section, "SpawnPoints"));

            if (spList.Count == 0)
            {
                spList.AddRange(Toolbox.GetEnumValues <TheaterLocationSpawnPointType>());                   // No spawn point type means all spawn point types
            }
            // LandSmall implies LandLarge and LandMedium, LandMedium implies LandLarge (larger spots can handle smaller units)
            if (spList.Contains(TheaterLocationSpawnPointType.LandSmall))
            {
                spList.AddRange(new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.LandMedium, TheaterLocationSpawnPointType.LandLarge });
            }
            if (spList.Contains(TheaterLocationSpawnPointType.LandMedium))
            {
                spList.Add(TheaterLocationSpawnPointType.LandLarge);
            }
            SpawnPoints = spList.Distinct().ToArray();

            if (Families.Length > 0)
            {
                foreach (string lGroup in LuaGroup)
                {
                    if (!File.Exists($"{BRPaths.INCLUDE_LUA_UNITS}{lGroup}.lua"))
                    {
                        DebugLog.Instance.WriteLine($"File \"Include\\Lua\\Units\\{lGroup}.lua\" doesn't exist.", 1, DebugLogMessageErrorLevel.Warning);
                    }
                }

                if (!File.Exists($"{BRPaths.INCLUDE_LUA_UNITS}{LuaUnit}.lua"))
                {
                    DebugLog.Instance.WriteLine($"File \"Include\\Lua\\Units\\{LuaUnit}.lua\" doesn't exist.", 1, DebugLogMessageErrorLevel.Warning);
                }
            }
        }
        /// <summary>
        /// Constructor. Loads data from a .ini file.
        /// </summary>
        /// <param name="ini">The .ini file to load from.</param>
        /// <param name="section">The .ini section to load from.</param>
        /// <param name="key">The top level .ini key to load from.</param>
        public LibraryCommonSettingsDistanceToObjective(INIFile ini, string section, string key)
        {
            DistanceFromStartLocation = ini.GetValue <MinMaxD>(section, $"{key}.DistanceFromStartLocation");
            DistanceFromStartLocation = new MinMaxD(
                Math.Max(0, DistanceFromStartLocation.Min),
                Math.Max(0, DistanceFromStartLocation.Max)) * HQTools.NM_TO_METERS;

            DistanceBetweenTargets = ini.GetValue <MinMaxD>(section, $"{key}.DistanceBetweenTargets");
            DistanceBetweenTargets = new MinMaxD(
                Math.Max(0, DistanceBetweenTargets.Min),
                Math.Max(0, DistanceBetweenTargets.Max)) * HQTools.NM_TO_METERS;
        }
Beispiel #7
0
        private Coordinates?GetAirOrSeaCoordinates(
            SpawnPointType[] validTypes,
            Coordinates distanceOrigin1, MinMaxD distanceFrom1,
            Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null,
            Coalition?coalition         = null)
        {
            var searchRange = distanceFrom1 * Toolbox.NM_TO_METERS;

            MinMaxD?secondSearchRange = null;

            if (distanceOrigin2.HasValue && distanceFrom2.HasValue)
            {
                secondSearchRange = distanceFrom2.Value * Toolbox.NM_TO_METERS;
            }

            var iterations = 0;

            do
            {
                var coordOptionsLinq = Enumerable.Range(0, 50)
                                       .Select(x => Coordinates.CreateRandom(distanceOrigin1, searchRange))
                                       .Where(x => CheckNotInHostileCoords(x, coalition) && CheckNotInNoSpawnCoords(x));

                if (secondSearchRange.HasValue)
                {
                    coordOptionsLinq = coordOptionsLinq.Where(x => secondSearchRange.Value.Contains(distanceOrigin2.Value.GetDistanceFrom(x)));
                }

                if (validTypes.First() == SpawnPointType.Sea) //sea position
                {
                    coordOptionsLinq = coordOptionsLinq.Where(x => ShapeManager.IsPosValid(x, TheaterDB.WaterCoordinates, TheaterDB.WaterExclusionCoordinates));
                }

                var coordOptions = coordOptionsLinq.ToList();
                if (coordOptions.Count > 0)
                {
                    return(Toolbox.RandomFrom(coordOptions));
                }

                searchRange = new MinMaxD(searchRange.Min * 0.9, searchRange.Max * 1.1);

                if (secondSearchRange.HasValue)
                {
                    secondSearchRange = new MinMaxD(secondSearchRange.Value.Min * 0.9, secondSearchRange.Value.Max * 1.1);
                }

                iterations++;
            } while (iterations < MAX_RADIUS_SEARCH_ITERATIONS);

            return(null);
        }
        /// <summary>
        /// Constructor. Load data from a .ini file.
        /// </summary>
        /// <param name="ini">The .ini file to load from.</param>
        /// <param name="section">The .ini file section.</param>
        /// <param name="key">The top level .ini key to load from.</param>
        public LibraryCommonSettingsEnemyAirDefenseDistance(INIFile ini, string section, string key)
        {
            key = (key == null) ? "" : $"{key}.";

            DistanceFromObjective = ini.GetValue <MinMaxD>(section, $"{key}DistanceFromTarget");
            DistanceFromObjective = new MinMaxD(Math.Max(0.0, DistanceFromObjective.Min), Math.Max(0.0, DistanceFromObjective.Max)) * HQTools.NM_TO_METERS;

            MinDistanceFromTakeOffLocation =
                Math.Max(0, ini.GetValue <double>(section, $"{key}MinDistanceFromTakeOffLocation")) * HQTools.NM_TO_METERS;

            NodeTypes = ini.GetValueArray <TheaterLocationSpawnPointType>(section, $"{key}SpawnPointTypes").Distinct().ToArray();
            if (NodeTypes.Length == 0)
            {
                NodeTypes = new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.LandMedium, TheaterLocationSpawnPointType.LandLarge }
            }
            ;
        }
    }
Beispiel #9
0
        internal DBCommonAirDefense()
        {
            INIFile ini = new($"{BRPaths.DATABASE}AirDefense.ini");

            AirDefenseLevels = new DBCommonAirDefenseLevel[Toolbox.EnumCount <AmountNR>()];
            for (var i = 0; i < Toolbox.EnumCount <AmountNR>(); i++)
            {
                AirDefenseLevels[i] = new DBCommonAirDefenseLevel(ini, (AmountNR)i);
            }

            DistanceFromCenter           = new MinMaxD[2, Toolbox.EnumCount <AirDefenseRange>()];
            MinDistanceFromOpposingPoint = new double[2, Toolbox.EnumCount <AirDefenseRange>()];
            foreach (Side side in Toolbox.GetEnumValues <Side>())
            {
                foreach (AirDefenseRange airDefenseRange in Toolbox.GetEnumValues <AirDefenseRange>())
                {
                    DistanceFromCenter[(int)side, (int)airDefenseRange]           = ini.GetValue <MinMaxD>($"AirDefenseRange.{side}", $"{airDefenseRange}.DistanceFromCenter");
                    MinDistanceFromOpposingPoint[(int)side, (int)airDefenseRange] = ini.GetValue <double>($"AirDefenseRange.{side}", $"{airDefenseRange}.MinDistanceFromOpposingPoint");
                }
            }
        }
Beispiel #10
0
        /// <summary>
        /// Picks a starting airbase for the player(s)
        /// </summary>
        /// <param name="mission">Mission for which the starting airbase must be set</param>
        /// <param name="template">Mission template to use</param>
        /// <param name="theaterDB">Theater database entry</param>
        /// <param name="lastCoordinates">Last location for referance</param>
        /// <param name="distance">Base Distance Range</param>
        /// <param name="first">is first objective</param>
        /// <returns>Information about the starting airbase</returns>
        public DBEntryTheaterAirbase SelectObjectiveAirbase(DCSMission mission, MissionTemplate template, DBEntryTheater theaterDB, Coordinates lastCoordinates, MinMaxD distance, bool first = false)
        {
            List <DBEntryTheaterAirbase> airbasesList = new List <DBEntryTheaterAirbase>();

            // Select all airbases with enough parking spots, trying to match the preferred coalition for enemy unit location, if any
            if ((template.OptionsTheaterCountriesCoalitions == CountryCoalition.AllBlue) || (template.OptionsTheaterCountriesCoalitions == CountryCoalition.AllRed) ||
                (template.OptionsEnemyUnitsLocation == SpawnPointPreferredCoalition.Any))
            {
                airbasesList.AddRange((from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE select ab).ToArray());
            }
            else
            {
                Coalition preferredCoalition;

                if (template.OptionsEnemyUnitsLocation == SpawnPointPreferredCoalition.Blue)
                {
                    preferredCoalition = (template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted) ? Coalition.Red : Coalition.Blue;
                }
                else
                {
                    preferredCoalition = (template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted) ? Coalition.Blue : Coalition.Red;
                }

                airbasesList.AddRange(
                    (from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE && ab.Coalition == preferredCoalition select ab).ToArray());

                if (airbasesList.Count == 0)
                {
                    airbasesList.AddRange((from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE select ab).ToArray());
                }
            }

            // Remove players' home airbase and airbases already used by other objectives from the list of available airbases
            List <int> airbasesInUse = (from DCSMissionObjective objective in mission.Objectives select objective.AirbaseID).ToList();

            airbasesInUse.Add(mission.InitialAirbaseID);
            airbasesList = (from DBEntryTheaterAirbase ab in airbasesList where !airbasesInUse.Contains(ab.DCSID) select ab).ToList();

            if (airbasesList.Count == 0)
            {
                throw new Exception($"No airbase found with at least {Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE} parking spots to use as an objective.");
            }

            int distanceMultiplier = 1;

            do
            {
                MinMaxD searchDistance = new MinMaxD(first ? distance.Min : 0, distance.Max * distanceMultiplier);
                List <DBEntryTheaterAirbase> airbasesInRange = airbasesList.FindAll(x => searchDistance.Contains(x.Coordinates.GetDistanceFrom(lastCoordinates) * Toolbox.METERS_TO_NM));
                if (airbasesInRange.Count > 0)
                {
                    DBEntryTheaterAirbase selectedAirbase = Toolbox.RandomFrom(airbasesInRange);
                    mission.AirbasesCoalition[selectedAirbase.DCSID] = mission.CoalitionEnemy;
                    return(selectedAirbase);
                }

                distanceMultiplier++;

                if (distanceMultiplier > 128)
                {
                    throw new Exception($"No target airbase found within range, try a larger objective range.");
                }
            } while (true);
        }
Beispiel #11
0
        /// <summary>
        /// Gets a random spawn point around a given point.
        /// </summary>
        /// <param name="validTypes">Valid spawn point types</param>
        /// <param name="distanceOrigin1">Origin point distance must be computed from</param>
        /// <param name="distanceFrom1">Min/max distance from origin point, in nautical miles</param>
        /// <param name="distanceOrigin2">Second origin point distance must be computed from</param>
        /// <param name="distanceFrom2">Min/max distance from second origin point, in nautical miles</param>
        /// <param name="coalition">Which coalition should the spawn point belong to?</param>
        /// <returns>A spawn point, or null if none found matching the provided criteria</returns>
        public DBEntryTheaterSpawnPoint?GetRandomSpawnPoint(
            TheaterLocationSpawnPointType[] validTypes = null,
            Coordinates?distanceOrigin1 = null, MinMaxD?distanceFrom1 = null,
            Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null,
            Coalition?coalition         = null)
        {
            // Select all spoint points
            IEnumerable <DBEntryTheaterSpawnPoint> validSP = from DBEntryTheaterSpawnPoint pt in SpawnPoints select pt;

            if (validTypes != null) // Remove spawn points of invalid types
            {
                validSP = (from DBEntryTheaterSpawnPoint pt in validSP where validTypes.Contains(pt.PointType) select pt);
            }

            if (coalition.HasValue) // Select spawn points belonging to the proper coalition
            {
                IEnumerable <DBEntryTheaterSpawnPoint> coalitionValidSP =
                    coalitionValidSP = (from DBEntryTheaterSpawnPoint sp in validSP where sp.Coalition == coalition.Value select sp);

                // At least one spawn point found, only use SP for the preferred coalition
                if (coalitionValidSP.Count() > 0)
                {
                    validSP = coalitionValidSP;
                }
            }

            Coordinates?[] distanceOrigin = new Coordinates?[] { distanceOrigin1, distanceOrigin2 };
            MinMaxD?[]     distanceFrom   = new MinMaxD?[] { distanceFrom1, distanceFrom2 };

            for (int i = 0; i < 2; i++) // Remove spawn points too far or too close from distanceOrigin1 and distanceOrigin2
            {
                if (validSP.Count() == 0)
                {
                    return(null);
                }
                if (!distanceFrom[i].HasValue || !distanceOrigin[i].HasValue)
                {
                    continue;
                }

                MinMaxD searchRange = distanceFrom[i].Value * Toolbox.NM_TO_METERS; // convert distance to meters

                IEnumerable <DBEntryTheaterSpawnPoint> validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP select s);

                int iterationsLeft = MAX_RADIUS_SEARCH_ITERATIONS;

                do
                {
                    Coordinates origin = distanceOrigin[i].Value;

                    validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP
                                      where searchRange.Contains(origin.GetDistanceFrom(s.Coordinates))
                                      select s);
                    searchRange = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1));
                    iterationsLeft--;
                } while ((validSPInRange.Count() == 0) && (iterationsLeft > 0));

                validSP = (from DBEntryTheaterSpawnPoint s in validSPInRange select s);
            }

            if (validSP.Count() == 0)
            {
                return(null);
            }

            DBEntryTheaterSpawnPoint selectedSpawnPoint = Toolbox.RandomFrom(validSP.ToArray());

            SpawnPoints.Remove(selectedSpawnPoint); // Remove spawn point so it won't be used again
            return(selectedSpawnPoint);
        }
Beispiel #12
0
        /// <summary>
        /// Generates the data for the objectives.
        /// </summary>
        /// <param name="mission">The mission for which to generate objectives</param>
        /// <param name="template">Mission template to use</param>
        /// <param name="objectiveDB">Objective database entry</param>
        private void GenerateObjectivesData(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB, DBEntryTheater theaterDB)
        {
            // Keep in mind the position of the last objective/player location.
            // Start with initial player location.
            Coordinates lastCoordinates = mission.InitialPosition;

            // Common family to use for all objectives if DBEntryObjectiveFlags.SingleTargetUnitFamily is set
            UnitFamily singleObjectiveUnitFamily = objectiveDB.GetRandomUnitFamily();

            for (int i = 0; i < template.ObjectiveCount; i++)
            {
                // Pick a random unique name, or a waypoint number if objectives shouldn't be named
                string objectiveName = PickUniqueObjectiveName();

                DebugLog.Instance.WriteLine($"Adding objective #{i + 1}, designated {objectiveName}", 1);

                // Compute a random distance from last position, in nautical miles
                double objectiveDistanceNM =
                    (template.ObjectiveDistanceNM == 0) ?
                    Toolbox.RandomInt(TemplateTools.MIN_OBJECTIVE_DISTANCE, TemplateTools.MAX_OBJECTIVE_DISTANCE) :
                    template.ObjectiveDistanceNM;

                if (i > 0) // Objective is not the first one, spawn it close to the previous objective
                {
                    objectiveDistanceNM /= 5.0;
                }

                MinMaxD distanceFromLast =
                    new MinMaxD(OBJECTIVE_DISTANCE_VARIATION_MIN, OBJECTIVE_DISTANCE_VARIATION_MAX) * objectiveDistanceNM;
                Coordinates           objectiveCoordinates;
                DBEntryTheaterAirbase?airbase = null;

                if (objectiveDB.UnitGroup.SpawnPoints[0] != TheaterLocationSpawnPointType.Airbase)
                {
                    // Look for a valid spawn point
                    DBEntryTheaterSpawnPoint?spawnPoint =
                        SpawnPointSelector.GetRandomSpawnPoint(
                            // If spawn point types are specified, use them. Else look for spawn points of any type
                            (objectiveDB.UnitGroup.SpawnPoints.Length > 0) ? objectiveDB.UnitGroup.SpawnPoints : null,
                            // Select spawn points at a proper distance from last location (previous objective or home airbase)
                            lastCoordinates, distanceFromLast,
                            // Make sure no objective is too close to the initial location
                            mission.InitialPosition, new MinMaxD(objectiveDistanceNM * OBJECTIVE_DISTANCE_VARIATION_MIN, 999999999),
                            GeneratorTools.GetEnemySpawnPointCoalition(template));
                    // No spawn point found for the objective, abort mission creation.
                    if (!spawnPoint.HasValue)
                    {
                        throw new Exception($"Failed to find a spawn point for objective {i + 1}");
                    }
                    objectiveCoordinates = spawnPoint.Value.Coordinates;
                }
                else
                {
                    airbase = new MissionGeneratorAirbases().SelectObjectiveAirbase(mission, template, theaterDB, lastCoordinates, distanceFromLast, i == 0);
                    if (!airbase.HasValue)
                    {
                        throw new Exception($"Failed to find a airbase point for objective {i + 1}");
                    }
                    objectiveCoordinates = airbase.Value.Coordinates;
                }


                // Set the waypoint coordinates according the the inaccuracy defined in the objective database entry
                Coordinates waypointCoordinates =
                    objectiveCoordinates +
                    Coordinates.CreateRandom(objectiveDB.WaypointInaccuracy * Toolbox.NM_TO_METERS);

                // Select an objective family for the target if any or default to VehicleTransport.
                UnitFamily objectiveUnitFamily = singleObjectiveUnitFamily;

                if (!objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.SingleTargetUnitFamily))
                {
                    objectiveUnitFamily = objectiveDB.GetRandomUnitFamily();
                }

                // Set the mission objective
                mission.Objectives[i] = new DCSMissionObjective(
                    objectiveName, objectiveCoordinates, objectiveUnitFamily, waypointCoordinates, airbase.HasValue? airbase.Value.DCSID: 0);

                // Last position is now the position of this objective
                lastCoordinates = objectiveCoordinates;
            }

            // If the target is a static object, make sure the correct flag is enabled as it has an influence of some scripts
            mission.ObjectiveIsStatic = objectiveDB.UnitGroup.Category.HasValue && (objectiveDB.UnitGroup.Category.Value == UnitCategory.Static);

            // Make sure objectives are ordered by distance from the players' starting location
            mission.Objectives = mission.Objectives.OrderBy(x => mission.InitialPosition.GetDistanceFrom(x.WaypointCoordinates)).ToArray();
        }
        public DefinitionTheaterSpawnPoint?GetRandomSpawnPoint(
            TheaterLocationSpawnPointType[] validTypes = null, DCSCountry[] validCountries = null,
            MinMaxD?distanceFrom  = null, Coordinates?distanceOrigin  = null,
            MinMaxD?distanceFrom2 = null, Coordinates?distanceOrigin2 = null)
        {
            IEnumerable <DefinitionTheaterSpawnPoint> validSP = (from DefinitionTheaterSpawnPoint s in SpawnPoints where !UsedSpawnPointsID.Contains(s.UniqueID) select s);

            if (validTypes != null)
            {
                validSP = (from DefinitionTheaterSpawnPoint s in validSP where validTypes.Contains(s.PointType) select s);
            }

            if (validCountries != null)
            {
                validSP = (from DefinitionTheaterSpawnPoint s in validSP where validCountries.Contains(s.Country) select s);
            }

            if (distanceFrom.HasValue && distanceOrigin.HasValue)
            {
                if (validSP.Count() == 0)
                {
                    return(null);
                }

                MinMaxD searchRange = distanceFrom.Value;

                IEnumerable <DefinitionTheaterSpawnPoint> validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP select s);

                do
                {
                    validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP where searchRange.Contains(distanceOrigin.Value.GetDistanceFrom(s.Coordinates)) select s);
                    searchRange    = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1));
                } while (validSPInRange.Count() == 0);

                validSP = (from DefinitionTheaterSpawnPoint s in validSPInRange select s);
            }

            if (distanceFrom2.HasValue && distanceOrigin2.HasValue)
            {
                if (validSP.Count() == 0)
                {
                    return(null);
                }

                MinMaxD searchRange = distanceFrom2.Value;

                IEnumerable <DefinitionTheaterSpawnPoint> validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP select s);

                do
                {
                    validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP where searchRange.Contains(distanceOrigin2.Value.GetDistanceFrom(s.Coordinates)) select s);
                    searchRange    = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1));
                } while (validSPInRange.Count() == 0);

                validSP = (from DefinitionTheaterSpawnPoint s in validSPInRange select s);
            }

            if (validSP.Count() == 0)
            {
                return(null);
            }

            DefinitionTheaterSpawnPoint selectedSpawnPoint = HQTools.RandomFrom(validSP.ToArray());

            UsedSpawnPointsID.Add(selectedSpawnPoint.UniqueID);
            return(selectedSpawnPoint);
        }
        public DCSMission Generate(MissionTemplate template, out string errorMessage)
        {
            int i;

            errorMessage = "";

            // Clear log, begin timing then create an instance of the HQ mission class
            Stopwatch  stopwatch = new Stopwatch(); stopwatch.Start();
            DCSMission mission   = new DCSMission();

            DebugLog.Instance.Clear();
            DebugLog.Instance.Log($"STARTING MISSION GENERATION AT {DateTime.Now.ToLongTimeString()}...");
            DebugLog.Instance.Log(new string('=', DebugLog.Instance.GetLastMessage().Length));
            DebugLog.Instance.Log();

            try
            {
                using (MissionGeneratorTemplateChecker templateChecker = new MissionGeneratorTemplateChecker())
                { templateChecker.CheckTemplate(template); }

                if (template.GetPlayerCount() < 1)
                {
                    throw new HQ4DCSException("Mission must include at least one player-controlled aircraft.");
                }

                // Pick definitions
                DefinitionCoalition[] coalitions = new DefinitionCoalition[2];
                coalitions[(int)Coalition.Blue] = Library.Instance.GetDefinition <DefinitionCoalition>(template.ContextCoalitionBlue);
                coalitions[(int)Coalition.Red]  = Library.Instance.GetDefinition <DefinitionCoalition>(template.ContextCoalitionRed);

                DefinitionLanguage  languageDef  = Library.Instance.GetDefinition <DefinitionLanguage>(template.PreferencesLanguage.ToLowerInvariant());
                DefinitionObjective objectiveDef = Library.Instance.GetDefinition <DefinitionObjective>(template.ObjectiveType.ToLowerInvariant());
                DefinitionTheater   theaterDef   = Library.Instance.GetDefinition <DefinitionTheater>(template.ContextTheater);
                theaterDef.ResetUsedSpawnPoints();

                // Create a list of all available objective names
                List <string> objectiveNames = languageDef.GetStringArray("Mission", "Waypoint.ObjectiveNames").ToList();

                // Create unit generators
                MissionGeneratorCallsign   callsignGenerator   = new MissionGeneratorCallsign(coalitions[(int)Coalition.Blue].NATOCallsigns, coalitions[(int)Coalition.Red].NATOCallsigns);
                MissionGeneratorUnitGroups unitGroupsGenerator = new MissionGeneratorUnitGroups(languageDef, callsignGenerator);

                // Copy values from the template to the mission
                mission.TheaterDefinition   = template.ContextTheater;
                mission.ObjectiveDefinition = template.ObjectiveType;
                mission.Language            = template.PreferencesLanguage;
                mission.CoalitionPlayer     = template.ContextPlayerCoalition;
                mission.SinglePlayer        = (template.GetPlayerCount() < 2);
                mission.UseNATOCallsigns    = coalitions[(int)template.ContextPlayerCoalition].NATOCallsigns;

                // Make sure no countries are shared between both coalitions
                mission.Countries[(int)Coalition.Blue] = coalitions[(int)Coalition.Blue].Countries.ToArray();
                mission.Countries[(int)Coalition.Red]  = coalitions[(int)Coalition.Red].Countries.Except(coalitions[(int)Coalition.Blue].Countries).ToArray();

                if (mission.Countries[(int)Coalition.Red].Length == 0)
                {
                    throw new HQ4DCSException("Red and blue coalitions cannot share the same countries.");
                }

                switch (template.BriefingUnits)
                {
                case UnitSystem.ByCoalition:
                    mission.BriefingImperialUnits = (coalitions[(int)mission.CoalitionPlayer].UnitSystem == UnitSystem.Imperial); break;

                case UnitSystem.Imperial: mission.BriefingImperialUnits = true; break;

                case UnitSystem.Metric: mission.BriefingImperialUnits = false; break;
                }

                // Generate mission environment parameters (weather, time of day, date...)
                using (MissionGeneratorEnvironment environment = new MissionGeneratorEnvironment())
                {
                    environment.GenerateMissionDate(mission, template.ContextTimePeriod, template.EnvironmentSeason);
                    environment.GenerateMissionTime(mission, template.EnvironmentTimeOfDay, theaterDef);
                    environment.GenerateWeather(mission, template.EnvironmentWeather, theaterDef);
                    environment.GenerateWind(mission, template.EnvironmentWind, theaterDef);
                }

                // Randomly select players' airbase
                DefinitionTheaterAirbase missionAirbase = HQTools.RandomFrom((from DefinitionTheaterAirbase ab in theaterDef.Airbases where ab.Coalition == template.ContextPlayerCoalition select ab).ToArray());

                // Randomly select objective spawn points
                int objectiveCount = (int)template.ObjectiveCount;
                if (objectiveCount == 0)
                {
                    objectiveCount = HQTools.RandomFrom(1, 1, 1, 2, 2, 3, 3, 4, 5);                      // Random objective count
                }
                //AmountR objectiveDistance = template.ObjectiveDistance;
                //if (objectiveDistance == AmountR.Random) objectiveDistance =
                //        HQTools.RandomFrom(AmountR.VeryLow, AmountR.VeryLow, AmountR.Low, AmountR.Low, AmountR.Low, AmountR.Average, AmountR.Average, AmountR.Average, AmountR.High, AmountR.High, AmountR.VeryHigh);
                List <DCSMissionObjectiveLocation> objectivesList = new List <DCSMissionObjectiveLocation>();
                List <DCSMissionWaypoint>          waypointsList  = new List <DCSMissionWaypoint>();
                for (i = 0; i < objectiveCount; i++)
                {
                    // If this is the first objective, measure distance from the airbase. Else measure distance from the previous objective.
                    Coordinates previousPoint = (i == 0) ? missionAirbase.Coordinates : objectivesList[i - 1].Coordinates;

                    MinMaxD distanceFromLastPoint = new MinMaxD(template.ObjectiveDistance * 0.75, template.ObjectiveDistance * 1.25) * HQTools.NM_TO_METERS;
                    if (i > 0)
                    {
                        distanceFromLastPoint /= 4.0;
                    }

                    DefinitionTheaterSpawnPoint?spawnPoint =
                        theaterDef.GetRandomSpawnPoint(objectiveDef.SpawnPointType, null, distanceFromLastPoint, previousPoint);

                    if (!spawnPoint.HasValue) // No valid spawn point, throw an error
                    {
                        throw new HQ4DCSException($"Cannot find a valid spawn point for objective #{i + 1}");
                    }

                    // Select a random name for the objective
                    string objName;
                    if (objectiveNames.Count == 0)
                    {
                        objName = $"OBJECTIVE{(i + 1).ToString("00")}";
                    }
                    else
                    {
                        objName = HQTools.RandomFrom(objectiveNames);
                        objectiveNames.Remove(objName);
                    }

                    objectivesList.Add(new DCSMissionObjectiveLocation(spawnPoint.Value.Coordinates, objName, objectiveDef.WaypointOnGround ? 0.0 : 1.0, 0));

                    // Add a waypoint for each objective
                    waypointsList.Add(new DCSMissionWaypoint(spawnPoint.Value.Coordinates + Coordinates.CreateRandomInaccuracy(objectiveDef.WaypointInaccuracy), objName));
                }

                // If required, add additional waypoints on the way to & from the objectives
                if (template.PreferencesExtraWaypoints && (waypointsList.Count > 0))
                {
                    Coordinates firstWPCoos = waypointsList.First().Coordinates;
                    Coordinates lastWPCoos  = waypointsList.Last().Coordinates;

                    int wpBeforeCount = HQTools.RandomMinMax(1, 3);
                    for (i = 0; i < wpBeforeCount; i++)
                    {
                        waypointsList.Insert(i,
                                             new DCSMissionWaypoint(
                                                 Coordinates.Lerp(missionAirbase.Coordinates, firstWPCoos, (double)(i + 1) / (wpBeforeCount + 1)) +
                                                 Coordinates.CreateRandomInaccuracy(firstWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.05, firstWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.15),
                                                 $"WP{(i + 1).ToString()}"));
                    }

                    int wpAfterCount = HQTools.RandomMinMax(1, 2);
                    for (i = 0; i < wpAfterCount; i++)
                    {
                        waypointsList.Add(
                            new DCSMissionWaypoint(
                                Coordinates.Lerp(lastWPCoos, missionAirbase.Coordinates, (double)(i + 1) / (wpAfterCount + 1)) +
                                Coordinates.CreateRandomInaccuracy(lastWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.05, lastWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.15),
                                $"WP{(waypointsList.Count + 1).ToString()}"));
                    }
                }

                mission.Objectives = objectivesList.ToArray();
                mission.Waypoints  = waypointsList.ToArray();

                mission.TotalFlightPlanDistance = 0.0;
                for (i = 0; i <= mission.Waypoints.Length; i++)
                {
                    if (i == 0) // first point, add distance between the takeoff airbase and the first waypoint
                    {
                        mission.TotalFlightPlanDistance += missionAirbase.Coordinates.GetDistanceFrom(mission.Waypoints.First().Coordinates);
                    }
                    else if (i == mission.Waypoints.Length) // last point, add distance between last waypoint and landing airbase
                    {
                        mission.TotalFlightPlanDistance += missionAirbase.Coordinates.GetDistanceFrom(mission.Waypoints.Last().Coordinates);
                    }
                    else // any other point, add distance between this waypoint and the last one
                    {
                        mission.TotalFlightPlanDistance += mission.Waypoints[i].Coordinates.GetDistanceFrom(mission.Waypoints[i - 1].Coordinates);
                    }
                }

                // Create a list of used player aircraft types, so the proper kneeboard subdirectories can be created in the .miz file
                mission.UsedPlayerAircraftTypes =
                    (from MissionTemplatePlayerFlightGroup pfg in template.FlightPackagePlayers select pfg.AircraftType).Distinct().OrderBy(x => x).ToArray();

                // Generate bullseyes and map center
                mission.MapCenter = Coordinates.GetCenter(
                    (from DCSMissionObjectiveLocation o in mission.Objectives select o.Coordinates).Union(new Coordinates[] { missionAirbase.Coordinates }).ToArray());
                mission.Bullseye = new Coordinates[2];
                for (i = 0; i < 2; i++)
                {
                    mission.Bullseye[i] = mission.MapCenter + Coordinates.CreateRandomInaccuracy(10000, 20000);
                }

                // Copy scripts
                //mission.ScriptsMission = missionObjective.ScriptMission.ToList();
                //mission.ScriptsObjective = missionObjective.ScriptObjective.ToList();

                mission.RealismAllowExternalViews = template.RealismAllowExternalViews;
                mission.RealismBirdStrikes        = template.RealismBirdStrikes;
                mission.RealismRandomFailures     = template.RealismRandomFailures;

                // Create list of airbase alignment from the theater definition
                mission.AirbasesCoalition.Clear();
                foreach (DefinitionTheaterAirbase ab in theaterDef.Airbases)
                {
                    if (mission.AirbasesCoalition.ContainsKey(ab.DCSID))
                    {
                        continue;
                    }

                    Coalition airbaseCoalition = ab.Coalition;
                    switch (template.ContextCountriesCoalitions)
                    {
                    case CountriesCoalition.AllBlue: airbaseCoalition = Coalition.Blue; break;

                    case CountriesCoalition.AllRed: airbaseCoalition = Coalition.Red; break;

                    case CountriesCoalition.Inverted: airbaseCoalition = (Coalition)(1 - (int)ab.Coalition); break;
                    }

                    mission.AirbasesCoalition.Add(ab.DCSID, airbaseCoalition);
                }

                // Make sure the starting airbase belongs to the players' coalition no matter which coalition other airbases belong to
                if (mission.AirbasesCoalition.ContainsKey(missionAirbase.DCSID))
                {
                    mission.AirbasesCoalition[missionAirbase.DCSID] = template.ContextPlayerCoalition;
                }

                List <string> oggFilesList = new List <string>();
                oggFilesList.AddRange(Library.Instance.Common.SharedOggFiles); // Default wave files
                oggFilesList.AddRange(objectiveDef.IncludeOgg);                // Objective wave files
                mission.OggFiles = oggFilesList.Distinct().ToArray();

                //mission.Scripts

                /*
                 * // Generate mission flight plan
                 * using (GeneratorFlightPlan flightPlan = new GeneratorFlightPlan(Library, language, csGenerator))
                 * {
                 *  flightPlan.SelectTakeoffAndLandingAirbases(mission, theater);
                 *  mission.MapCenter = mission.Airbases[0].Coordinates; // Center the map on the starting airdrome
                 *  flightPlan.GenerateObjectiveLocations(mission, template, theater, missionObjective);
                 *  flightPlan.GenerateWaypoints(mission, template, theater, missionObjective);
                 *  flightPlan.GenerateBullseye(mission);
                 * }
                 */

                // Generate units
                AmountNR selectedEnemyAirDefense, selectedEnemyCAP; // We have to store these values here because they're used in the briefing remarks
                using (MissionGeneratorUnitGroups unitGenerator = new MissionGeneratorUnitGroups(languageDef, callsignGenerator))
                {
                    foreach (MissionTemplatePlayerFlightGroup pfg in template.FlightPackagePlayers)
                    {
                        unitGenerator.AddPlayerFlightGroup(mission, template, pfg, objectiveDef, missionAirbase);
                    }

                    //unitGroups.GeneratePlayerFlightGroups(mission, template, missionObjective);
                    //unitGroups.GenerateAIEscortFlightGroups(mission, template, coalitions, template.FlightGroupsAICAP, UnitFamily.PlaneFighter, "GroupPlaneEscortCAP", AircraftPayloadType.A2A, DCSAircraftTask.CAP);
                    //unitGroups.GenerateAIEscortFlightGroups(mission, template, coalitions, template.FlightGroupsAISEAD, UnitFamily.PlaneSEAD, "GroupPlaneEscortSEAD", AircraftPayloadType.SEAD, DCSAircraftTask.SEAD);

                    unitGenerator.AddObjectiveUnitGroupsAtEachObjective(mission, template, objectiveDef, coalitions);
                    //unitGroups.GenerateObjectiveUnitGroupsAtCenter(mission, template, missionObjective, coalitions);
                    unitGenerator.AddFriendlySupportAircraft(mission, template, coalitions[(int)mission.CoalitionPlayer], theaterDef, missionAirbase);
                    unitGenerator.AddEnemyAirDefenseUnits(mission, template, theaterDef, objectiveDef, coalitions, missionAirbase, out selectedEnemyAirDefense);
                    unitGenerator.AddFriendlyAirDefenseUnits(mission, template, theaterDef, objectiveDef, coalitions, missionAirbase, out AmountNR selectedFriendlyAirDefense);
                    unitGenerator.AddCombatAirPatrolUnits(mission, template, theaterDef, coalitions, missionAirbase, out AmountNR selectedFriendlyCAP, out selectedEnemyCAP);
                }

                using (MissionGeneratorBriefing briefingGenerator = new MissionGeneratorBriefing(languageDef))
                {
                    // Add briefing remarks
                    for (i = 0; i < objectiveDef.BriefingRemarks.Length; i++)
                    {
                        mission.BriefingRemarks.Add(languageDef.GetStringRandom("Briefing", $"Remark.{objectiveDef.BriefingRemarks}"));
                    }
                    mission.BriefingRemarks.Add(languageDef.GetStringRandom("Briefing", $"Remark.EnemyAirDefense.{selectedEnemyAirDefense}"));
                    mission.BriefingRemarks.Add(languageDef.GetStringRandom("Briefing", $"Remark.EnemyCAP.{selectedEnemyCAP}"));

                    mission.BriefingTasks.Add(languageDef.GetString("Briefing", "Task.TakeOffFrom", "Airbase", missionAirbase.Name));
                    for (i = 0; i < mission.Objectives.Length; i++)
                    {
                        mission.BriefingTasks.Add(languageDef.GetString("Briefing", $"Task.{objectiveDef.BriefingTask}", "Objective", mission.Objectives[i].Name.ToUpperInvariant()));
                    }
                    mission.BriefingTasks.Add(languageDef.GetString("Briefing", "Task.LandAt", "Airbase", missionAirbase.Name));

                    briefingGenerator.GenerateMissionName(mission, template.BriefingName);
                    briefingGenerator.GenerateMissionDescription(mission, template.BriefingDescription, objectiveDef);

                    /*
                     *  briefing.GenerateMissionTasks(mission, template, missionObjective);
                     *  briefing.GenerateMissionRemarks(mission, template, missionObjective);
                     */
                    briefingGenerator.GenerateRawTextBriefing(mission, template /*, missionObjective*/);
                    briefingGenerator.GenerateHTMLBriefing(mission, template /*, missionObjective*/);
                }

                stopwatch.Stop();
                DebugLog.Instance.Log();
                DebugLog.Instance.Log($"COMPLETED MISSION GENERATION AT {DateTime.Now.ToLongTimeString()} (TOOK {stopwatch.Elapsed.TotalMilliseconds.ToString("F0")} MILLISECONDS).");
                DebugLog.Instance.Log();
                mission.GenerationLog = DebugLog.Instance.GetFullLog();
            }
#if DEBUG
            catch (HQ4DCSException e)
#else
            catch (Exception e)
#endif
            {
                stopwatch.Stop();
                DebugLog.Instance.Log($"ERROR: {e.Message}");
                DebugLog.Instance.Log();
                DebugLog.Instance.Log($"MISSION GENERATION FAILED.");
                DebugLog.Instance.Log();
                errorMessage = e.Message;

                mission.Dispose();
                mission = null;
            }

            DebugLog.Instance.SaveToFileAndClear("MissionGeneration");
            return(mission);
        }