Example #1
0
        internal UnitMakerSpawnPointSelector(DBEntryTheater theaterDB, DBEntrySituation situationDB, bool invertCoalition)
        {
            TheaterDB           = theaterDB;
            SituationDB         = situationDB;
            AirbaseParkingSpots = new Dictionary <int, List <DBEntryAirbaseParkingSpot> >();
            SpawnPoints         = new List <DBEntryTheaterSpawnPoint>();
            InvertCoalition     = invertCoalition;

            if (TheaterDB.SpawnPoints is not null)
            {
                SpawnPoints.AddRange(TheaterDB.SpawnPoints.Where(x => CheckNotInNoSpawnCoords(x.Coordinates)).ToList());
            }

            foreach (DBEntryAirbase airbase in SituationDB.GetAirbases(InvertCoalition))
            {
                if (airbase.ParkingSpots.Length < 1)
                {
                    continue;
                }
                if (AirbaseParkingSpots.ContainsKey(airbase.DCSID))
                {
                    continue;
                }
                AirbaseParkingSpots.Add(airbase.DCSID, airbase.ParkingSpots.ToList());
            }
        }
Example #2
0
        public UnitMaker(DBEntryCoalition[] coalitionsDB, DBEntryTheater theaterDB)
        {
            CallsignGenerator  = new UnitMakerCallsignGenerator(coalitionsDB);
            SpawnPointSelector = new UnitMakerSpawnPointSelector(theaterDB);

            NextGroupID = 1;
            NextUnitID  = 1;
        }
Example #3
0
        private static void GenerateFOB(
            UnitMaker unitMaker, ZoneMaker zoneMaker, MissionTemplateFlightGroupRecord flightGroup, Dictionary <string, UnitMakerGroupInfo> carrierDictionary,
            DCSMission mission, MissionTemplateRecord template, Coordinates landbaseCoordinates, Coordinates objectivesCenter)
        {
            DBEntryTheater theaterDB = Database.Instance.GetEntry <DBEntryTheater>(template.ContextTheater);

            if (theaterDB == null)
            {
                return;                    // Theater doesn't exist. Should never happen.
            }
            Coordinates?spawnPoint =
                unitMaker.SpawnPointSelector.GetRandomSpawnPoint(
                    new SpawnPointType[] { SpawnPointType.LandLarge },
                    landbaseCoordinates,
                    new MinMaxD(5, template.FlightPlanObjectiveDistance),
                    objectivesCenter,
                    new MinMaxD(10, template.FlightPlanObjectiveDistance / 2), template.ContextPlayerCoalition);

            if (!spawnPoint.HasValue)
            {
                BriefingRoom.PrintToLog($"No spawn point found for FOB air defense unit groups", LogMessageErrorLevel.Warning);
                return;
            }

            DBEntryUnit unitDB = Database.Instance.GetEntry <DBEntryUnit>(flightGroup.Carrier);

            if (unitDB == null)
            {
                return;                 // Unit doesn't exist or is not a carrier
            }
            double radioFrequency = 127.5 + carrierDictionary.Count;
            var    FOBNames       = new List <string> {
                "FOB_London",
                "FOB_Dallas",
                "FOB_Paris",
                "FOB_Moscow",
                "FOB_Berlin"
            };

            UnitMakerGroupInfo?groupInfo =
                unitMaker.AddUnitGroup(
                    unitDB.Families[0], 1, Side.Ally,
                    "GroupStatic", "UnitStaticFOB",
                    spawnPoint.Value, 0,
                    "FOBCallSignIndex".ToKeyValuePair(FOBNames.IndexOf(flightGroup.Carrier) + 1),
                    "RadioBand".ToKeyValuePair((int)RadioModulation.AM),
                    "RadioFrequency".ToKeyValuePair(GeneratorTools.GetRadioFrenquency(radioFrequency)));

            if (!groupInfo.HasValue || (groupInfo.Value.UnitsID.Length == 0))
            {
                return;                                                               // Couldn't generate group
            }
            zoneMaker.AddCTLDPickupZone(spawnPoint.Value, true);
            mission.Briefing.AddItem(
                DCSMissionBriefingItemType.Airbase,
                $"{unitDB.UIDisplayName}\t-\t{GeneratorTools.FormatRadioFrequency(radioFrequency)}\t\t");
            carrierDictionary.Add(flightGroup.Carrier, groupInfo.Value); // This bit limits FOBS to one per game think about how we can fix this
        }
Example #4
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));
        }
Example #5
0
 internal DrawingMaker(
     DCSMission mission, MissionTemplateRecord template, DBEntryTheater theaterDB, DBEntrySituation situationDB)
 {
     Mission     = mission;
     Template    = template;
     TheaterDB   = theaterDB;
     SituationDB = situationDB;
     Clear();
     AddTheaterZones();
 }
        /// <summary>
        /// Generates weather settings (precipitation, cloud coverage, temperature...) for the mission.
        /// Must be called after mission date has been set because min/max temperature changes every month.
        /// </summary>
        /// <param name="mission">The mission.</param>
        /// <param name="weather">The preferred type of weather (clear, cloudy, storm...).</param>
        /// <param name="theaterDB">Theater database entry</param>
        public void GenerateWeather(DCSMission mission, Weather weather, DBEntryTheater theaterDB)
        {
            mission.Weather.Temperature  = theaterDB.Temperature[(int)mission.DateTime.Month].GetValue();
            mission.Weather.WeatherLevel = (weather == Weather.Random) ? Toolbox.RandomFrom(RANDOM_WEATHER) : weather;

            DebugLog.Instance.WriteLine($"Weather set to {mission.Weather.WeatherLevel}", 1);

            int weatherIndex = (int)mission.Weather.WeatherLevel;

            // Clouds and precipitations
            mission.Weather.CloudBase           = theaterDB.Weather[weatherIndex].CloudsBase.GetValue();
            mission.Weather.CloudsDensity       = theaterDB.Weather[weatherIndex].CloudsDensity.GetValue();
            mission.Weather.CloudsPrecipitation = Toolbox.RandomFrom(theaterDB.Weather[weatherIndex].CloudsPrecipitation);
            mission.Weather.CloudsThickness     = theaterDB.Weather[weatherIndex].CloudsThickness.GetValue();

            // Dust
            mission.Weather.DustEnabled = Toolbox.RandomFrom(theaterDB.Weather[weatherIndex].DustEnabled);
            mission.Weather.DustDensity = mission.Weather.DustEnabled ? theaterDB.Weather[weatherIndex].DustDensity.GetValue() : 0;

            // Fog
            mission.Weather.FogEnabled    = Toolbox.RandomFrom(theaterDB.Weather[weatherIndex].FogEnabled);
            mission.Weather.FogThickness  = mission.Weather.FogEnabled ? theaterDB.Weather[weatherIndex].FogThickness.GetValue() : 0;
            mission.Weather.FogVisibility = mission.Weather.FogEnabled ? theaterDB.Weather[weatherIndex].FogVisibility.GetValue() : 0;

            // Pressure, turbulence and visiblity
            mission.Weather.QNH        = theaterDB.Weather[weatherIndex].QNH.GetValue();
            mission.Weather.Turbulence = theaterDB.Weather[weatherIndex].Turbulence.GetValue();
            mission.Weather.Visibility = theaterDB.Weather[(int)mission.Weather.WeatherLevel].Visibility.GetValue();

            DebugLog.Instance.WriteLine($"Cloud base altitude set to {mission.Weather.CloudBase} m", 1);
            DebugLog.Instance.WriteLine($"Cloud density set to {mission.Weather.CloudBase}", 1);
            DebugLog.Instance.WriteLine($"Precipitation set to {mission.Weather.CloudsPrecipitation}", 1);
            DebugLog.Instance.WriteLine($"Cloud thickness set to {mission.Weather.CloudsThickness} m", 1);

            DebugLog.Instance.WriteLine($"Dust set to {mission.Weather.DustEnabled}", 1);
            DebugLog.Instance.WriteLine($"Dust density set to {mission.Weather.DustDensity}", 1);

            DebugLog.Instance.WriteLine($"Fog set to {mission.Weather.FogEnabled}", 1);
            DebugLog.Instance.WriteLine($"Fog thickness set to {mission.Weather.FogThickness}", 1);
            DebugLog.Instance.WriteLine($"Fog visibility set to {mission.Weather.FogVisibility} m", 1);

            DebugLog.Instance.WriteLine($"QNH set to {mission.Weather.QNH}", 1);
            DebugLog.Instance.WriteLine($"Turbulence set to {mission.Weather.Turbulence} m/s", 1);
            DebugLog.Instance.WriteLine($"Visibility set to {mission.Weather.Visibility} m", 1);
        }
Example #7
0
        internal UnitMaker(
            DCSMission mission, MissionTemplateRecord template,
            DBEntryCoalition[] coalitionsDB, DBEntryTheater theaterDB, DBEntrySituation situationDB,
            Coalition playerCoalition, Country[][] coalitionsCountries,
            bool singlePlayerMission)
        {
            CallsignGenerator  = new UnitMakerCallsignGenerator(coalitionsDB);
            SpawnPointSelector = new UnitMakerSpawnPointSelector(theaterDB, situationDB, template.OptionsMission.Contains("InvertCountriesCoalitions"));

            Mission  = mission;
            Template = template;

            CoalitionsDB        = coalitionsDB;
            PlayerCoalition     = playerCoalition;
            CoalitionsCountries = coalitionsCountries;
            SinglePlayerMission = singlePlayerMission;

            GroupID = 1;
            UnitID  = 1;
        }
Example #8
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="theaterDB">Theater database entry to use</param>
        public UnitMakerSpawnPointSelector(DBEntryTheater theaterDB)
        {
            TheaterDB = theaterDB;

            AirbaseParkingSpots = new Dictionary <int, List <DBEntryTheaterAirbaseParkingSpot> >();
            SpawnPoints         = new List <DBEntryTheaterSpawnPoint>();

            SpawnPoints.AddRange(theaterDB.SpawnPoints);
            foreach (DBEntryTheaterAirbase airbase in theaterDB.Airbases)
            {
                if (airbase.ParkingSpots.Length < 1)
                {
                    continue;
                }
                if (AirbaseParkingSpots.ContainsKey(airbase.DCSID))
                {
                    continue;
                }
                AirbaseParkingSpots.Add(airbase.DCSID, airbase.ParkingSpots.ToList());
            }
        }
        /// <summary>
        /// Generates wind settings for the mission.
        /// Must be called once mission weather level has been set, as weather is used for auto wind.
        /// </summary>
        /// <param name="mission">The mission.</param>
        /// <param name="wind">The preferred wind speed.</param>
        /// <param name="theaterDB">Theater database entry</param>
        public void GenerateWind(DCSMission mission, Wind wind, DBEntryTheater theaterDB)
        {
            // If auto, speed depends on weather, so we never end up with no wind in a storm
            mission.Weather.WindLevel = (wind == Wind.Auto) ?
                                        (Wind)Toolbox.Clamp((int)mission.Weather.WeatherLevel + Toolbox.RandomMinMax(-1, 1), 0, (int)Wind.StrongGale)
                : wind;

            DebugLog.Instance.WriteLine($"Wind speed level set to {mission.Weather.WindLevel}", 1);

            int windIndex = (int)mission.Weather.WindLevel;

            for (int i = 0; i < 3; i++)
            {
                mission.Weather.WindSpeed[i]     = Math.Max(0, theaterDB.Wind[windIndex].Wind.GetValue());
                mission.Weather.WindDirection[i] = (mission.Weather.WindSpeed[i] > 0) ? Toolbox.RandomInt(0, 360) : 0;
                DebugLog.Instance.WriteLine($"Wind speed at {WIND_ALTITUDE[i]} meters set to {mission.Weather.WindSpeed[i]} m/s, direction of {mission.Weather.WindDirection[i]}", 1);
            }


            // Turbulence is equals to max(weatherTurbulence, windTurbulence)
            mission.Weather.Turbulence = Math.Max(mission.Weather.Turbulence, theaterDB.Wind[windIndex].Turbulence.GetValue());
            DebugLog.Instance.WriteLine($"Turbulence updated to {mission.Weather.Turbulence} m/s", 1);
        }
Example #10
0
        public static string MakeBriefingStringReplacements(string briefingString, DCSMission mission, DBEntryCoalition[] coalitionsDB, int objectiveIndex = 0)
        {
            DBEntryTheater theaterDB = Database.Instance.GetEntry <DBEntryTheater>(mission.Theater);

            briefingString = briefingString.Replace("$ALLYADJ$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.Adjective]));
            briefingString = briefingString.Replace("$ENEMYADJ$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionEnemy].BriefingElements[(int)CoalitionBriefingElement.Adjective]));
            briefingString = briefingString.Replace("$RECON$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.Recon]));
            briefingString = briefingString.Replace("$STRCOMMAND$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.StrategicCommand]));
            briefingString = briefingString.Replace("$TACCOMMAND$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.TacticalCommand]));
            briefingString = briefingString.Replace("$THEATER$", Toolbox.RandomFrom(theaterDB.BriefingNames));
            briefingString = briefingString.Replace("$THEALLIES$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.TheCoalition]));
            briefingString = briefingString.Replace("$THEENEMIES$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionEnemy].BriefingElements[(int)CoalitionBriefingElement.TheCoalition]));

            briefingString = briefingString.Replace("$OBJECTIVE$", mission.Objectives[objectiveIndex].Name);

            briefingString = briefingString.Replace("$UNITFAMILIES$",
                                                    mission.Objectives[objectiveIndex].TargetFamily.HasValue ?
                                                    GetBriefingStringForUnitFamily(mission.Objectives[objectiveIndex].TargetFamily.Value, true) : "units");
            briefingString = briefingString.Replace("$UNITFAMILY$",
                                                    mission.Objectives[objectiveIndex].TargetFamily.HasValue ?
                                                    GetBriefingStringForUnitFamily(mission.Objectives[objectiveIndex].TargetFamily.Value, false) : "unit");

            return(Toolbox.ICase(SanitizeString(briefingString)));
        }
Example #11
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();
        }
Example #12
0
        /// <summary>
        /// Generates the <see cref="DCSMissionObjective"/>.
        /// </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>
        public void CreateObjectives(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB, DBEntryTheater theaterDB)
        {
            // Set the array for the proper number of objective
            mission.Objectives = new DCSMissionObjective[template.ObjectiveCount];

            GenerateObjectivesData(mission, template, objectiveDB, theaterDB);
            GenerateObjectivesScript(mission);
        }
Example #13
0
        internal static void GenerateMissionTime(DCSMission mission, MissionTemplateRecord template, DBEntryTheater theaterDB, Month month)
        {
            double totalMinutes;
            int    hour, minute;

            switch (template.EnvironmentTimeOfDay)
            {
            default:     // case TimeOfDay.Random
                totalMinutes = Toolbox.RandomInt(Toolbox.MINUTES_PER_DAY);
                break;

            case TimeOfDay.RandomDaytime:
                totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Max - 60);
                break;

            case TimeOfDay.Dawn:
                totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Min + 120);
                break;

            case TimeOfDay.Noon:
                totalMinutes = Toolbox.RandomInt(
                    (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 - 90,
                    (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 + 90);
                break;

            case TimeOfDay.Twilight:
                totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Max - 120, theaterDB.DayTime[(int)month].Max + 30);
                break;

            case TimeOfDay.Night:
                totalMinutes = Toolbox.RandomInt(0, theaterDB.DayTime[(int)month].Min - 120);
                break;
            }

            hour   = Toolbox.Clamp((int)Math.Floor(totalMinutes / 60), 0, 23);
            minute = Toolbox.Clamp((int)Math.Floor((totalMinutes - hour * 60) / 15) * 15, 0, 45);

            mission.SetValue("BriefingTime", $"{hour:00}:{minute:00}");
            mission.SetValue("StartTime", hour * 3600 + minute * 60); // DCS World time is stored in seconds since midnight
        }
        /// <summary>
        /// Generates a <see cref="DCSMission"/> from a <see cref="MissionTemplate"/>
        /// </summary>
        /// <param name="template">The <see cref="MissionTemplate"/> to use</param>
        /// <returns>A <see cref="DCSMission"/>, or nuLL if something when wrong</returns>
        private DCSMission DoMissionGeneration(MissionTemplate template)
        {
            DateTime generationStartTime = DateTime.Now;

            DebugLog.Instance.Clear();
            DebugLog.Instance.WriteLine($"Starting mission generation...");

            // Check for missing entries in the database
            GeneratorTools.CheckDBForMissingEntry <DBEntryCoalition>(template.ContextCoalitionBlue);
            GeneratorTools.CheckDBForMissingEntry <DBEntryCoalition>(template.ContextCoalitionRed);
            GeneratorTools.CheckDBForMissingEntry <DBEntryObjective>(template.ObjectiveType);
            GeneratorTools.CheckDBForMissingEntry <DBEntryTheater>(template.TheaterID);

            // Create the mission and copy some values (theater database entry ID, etc.) from the template
            DCSMission mission = new DCSMission();

            CopyTemplateValues(mission, template);

            // Get some DB entries from the database for easier reference
            DBEntryCoalition[] coalitionsDB = new DBEntryCoalition[2];
            coalitionsDB[(int)Coalition.Blue] = Database.Instance.GetEntry <DBEntryCoalition>(template.ContextCoalitionBlue);
            coalitionsDB[(int)Coalition.Red]  = Database.Instance.GetEntry <DBEntryCoalition>(template.ContextCoalitionRed);
            DBEntryObjective objectiveDB;

            if (template.ObjectiveType == "Random")
            {
                objectiveDB = Toolbox.RandomFrom <DBEntryObjective>(Database.Instance.GetAllEntries <DBEntryObjective>().Where(x => x.ID != "Random").ToArray());
            }
            else
            {
                objectiveDB = Database.Instance.GetEntry <DBEntryObjective>(template.ObjectiveType);
            }
            DBEntryTheater theaterDB = Database.Instance.GetEntry <DBEntryTheater>(template.TheaterID);

            // Create the unit maker, which will be used to generate unit groups and their properties
            UnitMaker unitMaker = new UnitMaker(coalitionsDB, theaterDB);

            // Create a list of flight group descriptions which will be used in the briefing
            List <UnitFlightGroupBriefingDescription> briefingFGList = new List <UnitFlightGroupBriefingDescription>();

            // Setup airbases
            DBEntryTheaterAirbase airbaseDB;

            using (MissionGeneratorAirbases airbaseGen = new MissionGeneratorAirbases())
            {
                airbaseDB = airbaseGen.SelectStartingAirbase(mission, template, theaterDB, objectiveDB);

                mission.InitialAirbaseID = airbaseDB.DCSID;
                mission.InitialPosition  = airbaseDB.Coordinates;

                airbaseGen.SetupAirbasesCoalitions(mission, template.TheaterRegionsCoalitions, theaterDB);
            }

            // Generate mission objectives
            DebugLog.Instance.WriteLine("Generating mission objectives...");
            using (MissionGeneratorObjectives objectives = new MissionGeneratorObjectives(unitMaker.SpawnPointSelector))
                objectives.CreateObjectives(mission, template, objectiveDB, theaterDB);

            // Generate mission date and time
            DebugLog.Instance.WriteLine("Generating mission date and time...");
            using (MissionGeneratorDateTime dateTime = new MissionGeneratorDateTime())
            {
                dateTime.GenerateMissionDate(mission, template, coalitionsDB);
                dateTime.GenerateMissionTime(mission, template, theaterDB);
            }

            // Generate mission weather
            DebugLog.Instance.WriteLine("Generating mission weather...");
            using (MissionGeneratorWeather weather = new MissionGeneratorWeather())
            {
                weather.GenerateWeather(mission, template.EnvironmentWeather, theaterDB);
                weather.GenerateWind(mission, template.EnvironmentWind, theaterDB);
            }

            // Generate Carrier
            using (MissionGeneratorCarrier unitGroupGen = new MissionGeneratorCarrier(unitMaker))
                unitGroupGen.GenerateCarriers(mission, template, coalitionsDB[(int)mission.CoalitionPlayer]);


            // Generate player unit groups
            DebugLog.Instance.WriteLine("Generating player unit groups and mission package...");
            string aiEscortTypeCAP, aiEscortTypeSEAD;

            using (MissionGeneratorPlayerFlightGroups unitGroupGen = new MissionGeneratorPlayerFlightGroups(unitMaker))
                briefingFGList.AddRange(
                    unitGroupGen.CreateUnitGroups(
                        mission, template, objectiveDB, coalitionsDB[(int)mission.CoalitionPlayer],
                        out aiEscortTypeCAP, out aiEscortTypeSEAD));

            // Generate objective unit groups
            DebugLog.Instance.WriteLine("Generating objectives unit groups...");
            using (MissionGeneratorObjectivesUnitGroups unitGroupGen = new MissionGeneratorObjectivesUnitGroups(unitMaker))
                unitGroupGen.CreateUnitGroups(mission, template, objectiveDB, coalitionsDB);

            // Generate friendly support units
            DebugLog.Instance.WriteLine("Generating friendly support units...");
            using (MissionGeneratorSupportUnits unitGroupGen = new MissionGeneratorSupportUnits(unitMaker))
                briefingFGList.AddRange(unitGroupGen.CreateUnitGroups(mission, coalitionsDB[(int)mission.CoalitionPlayer], template.OptionsUnitMods));

            // Generate enemy air defense unit groups
            DebugLog.Instance.WriteLine("Generating enemy air defense unit groups...");
            using (MissionGeneratorAirDefense unitGroupGen = new MissionGeneratorAirDefense(unitMaker, false, template, mission))
                unitGroupGen.CreateUnitGroups(mission, objectiveDB, coalitionsDB[(int)mission.CoalitionEnemy], GeneratorTools.GetEnemySpawnPointCoalition(template), template.OptionsUnitMods);

            // Generate ally air defense unit groups
            DebugLog.Instance.WriteLine("Generating friendly air defense unit groups...");
            using (MissionGeneratorAirDefense unitGroupGen = new MissionGeneratorAirDefense(unitMaker, true, template, mission))
                unitGroupGen.CreateUnitGroups(mission, objectiveDB, coalitionsDB[(int)mission.CoalitionPlayer], GeneratorTools.GetAllySpawnPointCoalition(template), template.OptionsUnitMods);

            //// Generate enemy fighter patrols
            DebugLog.Instance.WriteLine("Generating enemy fighter patrol unit groups...");
            using (MissionGeneratorEnemyFighterPatrols unitGroupGen = new MissionGeneratorEnemyFighterPatrols(unitMaker))
                unitGroupGen.CreateUnitGroups(mission, template, objectiveDB, coalitionsDB[(int)mission.CoalitionEnemy], aiEscortTypeCAP, aiEscortTypeSEAD);

            //// Generate mission features
            DebugLog.Instance.WriteLine("Generating mission features unit groups...");
            using (MissionGeneratorExtensionsAndFeatures featuresGen = new MissionGeneratorExtensionsAndFeatures(unitMaker))
                featuresGen.GenerateExtensionsAndFeatures(mission, template, objectiveDB, coalitionsDB);

            // Generates the mission flight plan
            DebugLog.Instance.WriteLine("Generating mission flight plan...");
            using (MissionGeneratorFlightPlan flightPlan = new MissionGeneratorFlightPlan())
            {
                flightPlan.SetBullseye(mission);
                flightPlan.AddObjectiveWaypoints(mission, objectiveDB);
                flightPlan.AddExtraWaypoints(mission, template);
            }

            // Generate briefing. Must be last because it uses information from other generators
            DebugLog.Instance.WriteLine("Generating mission briefing...");
            using (MissionGeneratorBriefing briefing = new MissionGeneratorBriefing())
            {
                briefing.GenerateMissionName(mission, template);
                briefing.GenerateMissionBriefing(mission, template, objectiveDB, airbaseDB, briefingFGList, coalitionsDB);
            }

            // Set if radio sounds are enabled
            mission.RadioSounds = !template.OptionsPreferences.Contains(MissionTemplatePreferences.DisableRadioSounds);

            // Add common .ogg vorbis files and make sure each only appears only once.
            mission.OggFiles.AddRange(Database.Instance.Common.CommonOGG);
            mission.OggFiles.AddRange(Database.Instance.Common.CommonOGGForGameMode[(int)template.GetMissionType()]);
            mission.OggFiles =
                (from string f in mission.OggFiles
                 where !string.IsNullOrEmpty(f.Trim()) select f.Trim())
                .Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();

            // If radio sounds are disabled, do not include radio .ogg files to save on file size
            if (!mission.RadioSounds)
            {
                mission.OggFiles =
                    (from string f in mission.OggFiles
                     where (f.ToLowerInvariant() == "radio0") || (!f.ToLowerInvariant().StartsWith("radio")) select f).ToList();
            }

            // Make sure included Lua scripts appear only once
            mission.IncludedLuaScripts = mission.IncludedLuaScripts.Distinct().OrderBy(x => x).ToList();

            // Create aircraft queues and finalize the core script
            CreateAircraftActivationQueues(mission);
            switch (template.GetMissionType())
            {
            case MissionType.SinglePlayer:
                mission.CoreLuaScript += "briefingRoom.mission.missionType = brMissionType.SINGLE_PLAYER\r\n"; break;

            case MissionType.Cooperative:
                mission.CoreLuaScript += "briefingRoom.mission.missionType = brMissionType.COOPERATIVE\r\n"; break;

            case MissionType.Versus:
                mission.CoreLuaScript += "briefingRoom.mission.missionType = brMissionType.VERSUS\r\n"; break;
            }

            DebugLog.Instance.WriteLine($"Mission generation completed successfully in {(DateTime.Now - generationStartTime).TotalSeconds.ToString("F3", NumberFormatInfo.InvariantInfo)} second(s).");

            unitMaker.Dispose();

            return(mission);
        }
Example #15
0
        /// <summary>
        /// Sets the coalition to which the various airbases on the theater belong.
        /// </summary>
        /// <param name="mission">Mission for which airbase coalitions must be set</param>
        /// <param name="theaterAirbasesCoalitions">Airbase coalition setting</param>
        /// <param name="theaterDB">Theater database entry</param>
        public void SetupAirbasesCoalitions(DCSMission mission, CountryCoalition theaterAirbasesCoalitions, DBEntryTheater theaterDB)
        {
            mission.AirbasesCoalition.Clear();
            foreach (DBEntryTheaterAirbase ab in theaterDB.Airbases)
            {
                // Airbase ID already exists in the mission
                if (mission.AirbasesCoalition.ContainsKey(ab.DCSID))
                {
                    continue;
                }

                // Airbase is the player starting airbase, always set it to the player coalition
                if (ab.DCSID == mission.InitialAirbaseID)
                {
                    mission.AirbasesCoalition.Add(ab.DCSID, mission.CoalitionPlayer);
                    continue;
                }

                // Other airbases are assigned to a coalition according to the theater and the template settings
                Coalition airbaseCoalition = ab.Coalition;
                switch (theaterAirbasesCoalitions)
                {
                case CountryCoalition.AllBlue: airbaseCoalition = Coalition.Blue; break;

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

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

                mission.AirbasesCoalition.Add(ab.DCSID, airbaseCoalition);
            }
        }
Example #16
0
        internal static Dictionary <string, UnitMakerGroupInfo> GenerateCarrierGroup(
            UnitMaker unitMaker, ZoneMaker zoneMaker, DCSMission mission, MissionTemplateRecord template,
            Coordinates landbaseCoordinates, Coordinates objectivesCenter, double windSpeedAtSeaLevel,
            double windDirectionAtSeaLevel)
        {
            Dictionary <string, UnitMakerGroupInfo> carrierDictionary = new Dictionary <string, UnitMakerGroupInfo>(StringComparer.InvariantCultureIgnoreCase);

            DBEntryTheater theaterDB    = Database.Instance.GetEntry <DBEntryTheater>(template.ContextTheater);
            double         carrierSpeed = Math.Max(
                Database.Instance.Common.CarrierGroup.MinimumCarrierSpeed,
                Database.Instance.Common.CarrierGroup.IdealWindOfDeck - windSpeedAtSeaLevel);

            if (windSpeedAtSeaLevel == 0) // No wind? Pick a random direction so carriers don't always go to a 0 course when wind is calm.
            {
                windDirectionAtSeaLevel = Toolbox.RandomDouble(Toolbox.TWO_PI);
            }
            var carrierPathDeg  = ((windDirectionAtSeaLevel + Math.PI) % Toolbox.TWO_PI) * Toolbox.RADIANS_TO_DEGREES;
            var usedCoordinates = new List <Coordinates>();

            foreach (MissionTemplateFlightGroupRecord flightGroup in template.PlayerFlightGroups)
            {
                if (string.IsNullOrEmpty(flightGroup.Carrier))
                {
                    continue;                                            // No carrier for
                }
                if (carrierDictionary.ContainsKey(flightGroup.Carrier))
                {
                    continue;                                                     // Carrier type already added
                }
                if (flightGroup.Carrier.StartsWith("FOB"))
                {
                    //It Carries therefore carrier not because I can't think of a name to rename this lot
                    GenerateFOB(unitMaker, zoneMaker, flightGroup, carrierDictionary, mission, template, landbaseCoordinates, objectivesCenter);
                    continue;
                }
                DBEntryUnit unitDB = Database.Instance.GetEntry <DBEntryUnit>(flightGroup.Carrier);
                if ((unitDB == null) || !unitDB.Families.Any(x => x.IsCarrier()))
                {
                    continue;                                                               // Unit doesn't exist or is not a carrier
                }
                var(shipCoordinates, shipDestination) = GetSpawnAndDestination(unitMaker, template, theaterDB, usedCoordinates, landbaseCoordinates, objectivesCenter, carrierPathDeg);
                usedCoordinates.Add(shipCoordinates);
                string cvnID          = carrierDictionary.Count > 0 ? (carrierDictionary.Count + 1).ToString() : "";
                int    ilsChannel     = 11 + carrierDictionary.Count;
                double radioFrequency = 127.5 + carrierDictionary.Count;
                string tacanCallsign  = $"CVN{cvnID}";
                int    tacanChannel   = 74 + carrierDictionary.Count;

                UnitMakerGroupInfo?groupInfo =
                    unitMaker.AddUnitGroup(
                        new string[] { unitDB.ID }, Side.Ally, unitDB.Families[0],
                        "GroupShipCarrier", "UnitShip",
                        shipCoordinates, 0,
                        "GroupX2".ToKeyValuePair(shipDestination.X),
                        "GroupY2".ToKeyValuePair(shipDestination.Y),
                        "ILS".ToKeyValuePair(ilsChannel),
                        "RadioBand".ToKeyValuePair((int)RadioModulation.AM),
                        "RadioFrequency".ToKeyValuePair(GeneratorTools.GetRadioFrenquency(radioFrequency)),
                        "Speed".ToKeyValuePair(carrierSpeed),
                        "TACANCallsign".ToKeyValuePair(tacanCallsign),
                        "TACANChannel".ToKeyValuePair(tacanChannel),
                        "TACANFrequency".ToKeyValuePair(GeneratorTools.GetTACANFrequency(tacanChannel, 'X', false)),
                        "TACANMode".ToKeyValuePair("X"));

                if (!groupInfo.HasValue || (groupInfo.Value.UnitsID.Length == 0))
                {
                    continue;                                                               // Couldn't generate group
                }
                mission.Briefing.AddItem(
                    DCSMissionBriefingItemType.Airbase,
                    $"{unitDB.UIDisplayName}\t-\t{GeneratorTools.FormatRadioFrequency(radioFrequency)}\t{ilsChannel}\t{tacanCallsign}, {tacanChannel}X");

                carrierDictionary.Add(flightGroup.Carrier, groupInfo.Value);
            }

            return(carrierDictionary);
        }
Example #17
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);
        }
Example #18
0
        internal static int GenerateWeather(DCSMission mission, MissionTemplateRecord template, DBEntryTheater theaterDB, Month month, DBEntryAirbase playerAirbase)
        {
            var baseAlt = template.OptionsMission.Contains("SeaLevelRefCloud") ? 0.0 : playerAirbase.Elevation;

            if (template.OptionsMission.Contains("HighCloud"))
            {
                baseAlt += 2000;
            }
            DBEntryWeatherPreset weatherDB;

            if (string.IsNullOrEmpty(template.EnvironmentWeatherPreset)) // Random weather
            {
                weatherDB = Toolbox.RandomFrom(Database.Instance.GetAllEntries <DBEntryWeatherPreset>());
            }
            else
            {
                weatherDB = Database.Instance.GetEntry <DBEntryWeatherPreset>(template.EnvironmentWeatherPreset);
            }

            mission.SetValue("WeatherName", weatherDB.BriefingDescription);
            mission.SetValue("WeatherCloudsBase", weatherDB.CloudsBase.GetValue() + baseAlt);
            mission.SetValue("WeatherCloudsPreset", Toolbox.RandomFrom(weatherDB.CloudsPresets));
            mission.SetValue("WeatherCloudsThickness", weatherDB.CloudsThickness.GetValue());
            mission.SetValue("WeatherDust", weatherDB.Dust);
            mission.SetValue("WeatherDustDensity", weatherDB.DustDensity.GetValue());
            mission.SetValue("WeatherFog", weatherDB.Fog);
            mission.SetValue("WeatherFogThickness", weatherDB.FogThickness.GetValue());
            mission.SetValue("WeatherFogVisibility", weatherDB.FogVisibility.GetValue());
            mission.SetValue("WeatherQNH", weatherDB.QNH.GetValue());
            mission.SetValue("WeatherTemperature", theaterDB.Temperature[(int)month].GetValue());
            mission.SetValue("WeatherVisibility", weatherDB.Visibility.GetValue());

            return(weatherDB.Turbulence.GetValue());
        }
Example #19
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="objectiveDB">Objective database entry</param>
        /// <returns>Information about the starting airbase</returns>
        public DBEntryTheaterAirbase SelectStartingAirbase(DCSMission mission, MissionTemplate template, DBEntryTheater theaterDB, DBEntryObjective objectiveDB)
        {
            List <DBEntryTheaterAirbase[]> airbasesList = new List <DBEntryTheaterAirbase[]>();

            // Select all airbases with enough parking spots
            int requiredParkingSpots = template.GetMissionPackageRequiredParkingSpots();

            airbasesList.Add((from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= requiredParkingSpots select ab).ToArray());

            // Select all airbases belonging to the proper coalition (unless all airbase belong to the same coalition)
            if ((template.OptionsTheaterCountriesCoalitions == CountryCoalition.Default) || (template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted))
            {
                Coalition requiredCoalition = template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted ? mission.CoalitionEnemy : mission.CoalitionPlayer;
                airbasesList.Add((from DBEntryTheaterAirbase ab in airbasesList.Last() where ab.Coalition == requiredCoalition select ab).ToArray());
            }

            // If mission must start near water, or some player start on a carrier, select all airbases near water
            if (objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.MustStartNearWater) || !string.IsNullOrEmpty(template.PlayerFlightGroups[0].Carrier))
            {
                airbasesList.Add((from DBEntryTheaterAirbase ab in airbasesList.Last() where ab.Flags.HasFlag(DBEntryTheaterAirbaseFlag.NearWater) select ab).ToArray());
            }

            // If a particular airbase name has been specified and an airbase with this name exists, pick it
            if (!string.IsNullOrEmpty(template.FlightPlanTheaterStartingAirbase))
            {
                airbasesList.Add((from DBEntryTheaterAirbase airbase in theaterDB.Airbases
                                  where airbase.Name.ToLowerInvariant() == template.FlightPlanTheaterStartingAirbase.ToLowerInvariant()
                                  select airbase).ToArray());

                if (airbasesList.Last().Length == 0)
                {
                    DebugLog.Instance.WriteLine($"Airbase \"{template.FlightPlanTheaterStartingAirbase}\" not found or airbase doesn't have enough parking spots. Selecting a random airbase instead.", 1, DebugLogMessageErrorLevel.Warning);
                }
            }

            // Check for valid airbases in all list, starting from the last one (with the most criteria filtered, and go back to the previous ones
            // as long as no airbase is found.
            for (int i = airbasesList.Count - 1; i >= 0; i--)
            {
                if (airbasesList[i].Length > 0)
                {
                    return(Toolbox.RandomFrom(airbasesList[i]));
                }
            }

            throw new Exception($"No airbase found with {requiredParkingSpots} parking spots, cannot spawn all player aircraft.");
        }
        /// <summary>
        /// Picks a starting time for the mission.
        /// Must be called after <see cref="GenerateMissionDate(DCSMission, MissionTemplate, DBEntryCoalition[])"/> because sunrise/sunset
        /// time depends on the selected date.
        /// </summary>
        /// <param name="mission">The mission.</param>
        /// <param name="template">Mission template to use</param>
        /// <param name="theaterDB">Theater database entry</param>
        public void GenerateMissionTime(DCSMission mission, MissionTemplate template, DBEntryTheater theaterDB)
        {
            Month month = mission.DateTime.Month;

            double totalMinutes;
            int    hour, minute;

            switch (template.EnvironmentTimeOfDay)
            {
            default:     // case TimeOfDay.Random
                totalMinutes = Toolbox.RandomInt(Toolbox.MINUTES_PER_DAY);
                break;

            case TimeOfDay.RandomDaytime:
                totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Max - 60);
                break;

            case TimeOfDay.Dawn:
                totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Min + 120);
                break;

            case TimeOfDay.Noon:
                totalMinutes = Toolbox.RandomInt(
                    (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 - 90,
                    (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 + 90);
                break;

            case TimeOfDay.Twilight:
                totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Max - 120, theaterDB.DayTime[(int)month].Max + 30);
                break;

            case TimeOfDay.Night:
                totalMinutes = Toolbox.RandomInt(0, theaterDB.DayTime[(int)month].Min - 120);
                break;
            }

            hour   = Toolbox.Clamp((int)Math.Floor(totalMinutes / 60), 0, 23);
            minute = Toolbox.Clamp((int)Math.Floor((totalMinutes - hour * 60) / 15) * 15, 0, 45);

            mission.DateTime.Hour   = hour;
            mission.DateTime.Minute = minute;

            DebugLog.Instance.WriteLine($"Starting time set to {mission.DateTime.ToTimeString()}", 1);
        }