Exemple #1
0
        internal static Tuple <double, double> GenerateWind(DCSMission mission, MissionTemplateRecord template, int turbulenceFromWeather)
        {
            var windSpeedAtSeaLevel     = 0.0;
            var windDirectionAtSeaLevel = 0.0;

            Wind windLevel = template.EnvironmentWind == Wind.Random ? PickRandomWindLevel() : template.EnvironmentWind;

            BriefingRoom.PrintToLog($"Wind speed level set to \"{windLevel}\".");

            int windAverage = 0;

            for (int i = 0; i < 3; i++)
            {
                int windSpeed     = Database.Instance.Common.Wind[(int)windLevel].Wind.GetValue();
                int windDirection = windSpeed > 0 ? Toolbox.RandomInt(0, 360) : 0;
                if (i == 0)
                {
                    windSpeedAtSeaLevel     = windSpeed;
                    windDirectionAtSeaLevel = windDirection * Toolbox.DEGREES_TO_RADIANS;
                }
                windAverage += windSpeed;

                mission.SetValue($"WeatherWindSpeed{i + 1}", windSpeed);
                mission.SetValue($"WeatherWindDirection{i + 1}", windDirection);
            }
            windAverage /= 3;

            mission.SetValue($"WeatherWindName", windLevel.ToString()); // TODO: get name from attribute
            mission.SetValue($"WeatherWindSpeedAverage", windAverage);

            mission.SetValue("WeatherGroundTurbulence", Database.Instance.Common.Wind[(int)windLevel].Turbulence.GetValue() + turbulenceFromWeather);
            return(new(windSpeedAtSeaLevel, windDirectionAtSeaLevel));
        }
Exemple #2
0
 internal static void GenerateBullseyes(DCSMission mission, Coordinates objectivesCenter)
 {
     mission.SetValue("BullseyeBlueX", objectivesCenter.X + GetBullseyeRandomDistance());
     mission.SetValue("BullseyeBlueY", objectivesCenter.Y + GetBullseyeRandomDistance());
     mission.SetValue("BullseyeRedX", objectivesCenter.X + GetBullseyeRandomDistance());
     mission.SetValue("BullseyeRedY", objectivesCenter.Y + GetBullseyeRandomDistance());
 }
Exemple #3
0
        internal static Country[][] GenerateCountries(DCSMission mission, MissionTemplateRecord template)
        {
            int i;

            List <Country>[] countries = new List <Country>[] { new List <Country>(), new List <Country>() };

            // Add default country for each coalition
            for (i = 0; i < 2; i++)
            {
                countries[i].Add(DEFAULT_COUNTRIES[i]);
            }

            // Add countries for player FGs to player coalition
            foreach (MissionTemplateFlightGroupRecord flightGroup in template.PlayerFlightGroups)
            {
                var group = flightGroup.Hostile ? template.ContextPlayerCoalition.GetEnemy() : template.ContextPlayerCoalition;
                countries[(int)group].Add(flightGroup.Country);
            }


            countries[(int)Coalition.Blue].AddRange(Database.Instance.GetEntry <DBEntryCoalition>(template.ContextCoalitionBlue).Countries);
            countries[(int)Coalition.Red].AddRange(Database.Instance.GetEntry <DBEntryCoalition>(template.ContextCoalitionRed).Countries);


            // Make sure each country doesn't contain the other's coalition default country
            for (i = 0; i < 2; i++)
            {
                countries[i].Remove(Country.ALL);
                countries[i] = countries[i].Distinct().ToList();
            }

            var intersect = countries[(int)Coalition.Blue].Intersect(countries[(int)Coalition.Red]).ToList();

            if (intersect.Count > 0)
            {
                throw new BriefingRoomException($"Countries can't be on both sides {string.Join(",", intersect)}. Check Red and Blue Coalitions as well as flight groups countries.");
            }

            // Add all non-aligned countries to the list of neutral countries
            List <Country> neutralCountries = new List <Country>(Toolbox.GetEnumValues <Country>());

            for (i = 0; i < 2; i++)
            {
                neutralCountries = neutralCountries.Except(countries[i]).ToList();
            }

            mission.SetValue("CoalitionNeutral", GetCountriesLuaTable(neutralCountries));
            mission.SetValue("CoalitionBlue", GetCountriesLuaTable(countries[(int)Coalition.Blue]));
            mission.SetValue("CoalitionRed", GetCountriesLuaTable(countries[(int)Coalition.Red]));

            return(new Country[][] { countries[0].ToArray(), countries[1].ToArray(), });
        }
Exemple #4
0
        internal static Month GenerateMissionDate(DCSMission mission, MissionTemplateRecord template)
        {
            int   day;
            Month month;

            // Select a random year from the most recent coalition's decade.
            var year = Toolbox.GetRandomYearFromDecade(template.ContextDecade);

            BriefingRoom.PrintToLog($"No fixed date provided in the mission template, generating date in decade {template.ContextDecade}");

            if (template.EnvironmentSeason == Season.Random) // Random season, pick any day of the year.
            {
                month = (Month)Toolbox.RandomInt(12);
                day   = Toolbox.RandomMinMax(1, GeneratorTools.GetDaysPerMonth(month, year));
            }
            else // Pick a date according to the desired season
            {
                Month[] seasonMonths = GetMonthsForSeason(template.EnvironmentSeason);

                int monthIndex = Toolbox.RandomInt(4);
                month = seasonMonths[monthIndex];
                switch (monthIndex)
                {
                case 0:     // First month of the season, season begins on the 21st
                    day = Toolbox.RandomMinMax(21, GeneratorTools.GetDaysPerMonth(month, year)); break;

                case 3:     // Last month of the season, season ends on the 20th
                    day = Toolbox.RandomMinMax(1, 20); break;

                default:
                    day = Toolbox.RandomMinMax(1, GeneratorTools.GetDaysPerMonth(month, year)); break;
                }
            }

            mission.SetValue("DateDay", day);
            mission.SetValue("DateMonth", (int)month + 1);
            mission.SetValue("DateYear", year);
            mission.SetValue("BriefingDate", $"{(int)month + 1:00}/{day:00}/{year:0000}");

            BriefingRoom.PrintToLog($"Misson date set to {day} {month} {year}.");
            return(month);
        }
Exemple #5
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
        }
Exemple #6
0
        internal static void GenerateWarehouses(DCSMission mission)
        {
            string warehousesAirportLua = "";

            if (!File.Exists(AIRPORT_TEMPLATE_FILEPATH))
            {
                throw new Exception("Airport warehouse template file (Include\\Lua\\Warehouses\\Airport.lua) not found.");
            }

            string airportLuaTemplate = File.ReadAllText(AIRPORT_TEMPLATE_FILEPATH);

            foreach (int airbaseID in mission.Airbases.Keys)
            {
                string airportLua = airportLuaTemplate;
                GeneratorTools.ReplaceKey(ref airportLua, "index", airbaseID);
                GeneratorTools.ReplaceKey(ref airportLua, "coalition", mission.Airbases[airbaseID].ToString().ToUpperInvariant());

                warehousesAirportLua += airportLua + "\r\n";
            }

            mission.SetValue("WarehousesAirports", warehousesAirportLua);
        }
Exemple #7
0
        internal static void GenerateMissionBriefingDescription(DCSMission mission, MissionTemplateRecord template, List <UnitFamily> objectiveTargetUnitFamilies, DBEntrySituation situationDB)
        {
            // Try to get the provided custom mission description.
            string briefingDescription = (template.BriefingMissionDescription ?? "").Replace("\r\n", "\n").Replace("\n", " ").Trim();

            // No custom description found, generate one from the most frequent objective task/target combination.
            if (string.IsNullOrEmpty(briefingDescription))
            {
                if (template.Objectives.Count == 0)
                {
                    briefingDescription = "";
                }
                else
                {
                    var familyCount = 0;
                    Dictionary <string, List <string> > descriptionsMap = new Dictionary <string, List <string> >();
                    foreach (var obj in template.Objectives)
                    {
                        DBEntryBriefingDescription descriptionDB =
                            Database.Instance.GetEntry <DBEntryBriefingDescription>(
                                Database.Instance.GetEntry <DBEntryObjectiveTask>(obj.Task).BriefingDescription);
                        AppendDescription(obj.Task, descriptionDB.DescriptionText[(int)objectiveTargetUnitFamilies[familyCount]], ref descriptionsMap);
                        familyCount++;
                        AddSubTasks(obj, objectiveTargetUnitFamilies, ref descriptionsMap, ref familyCount);
                    }

                    briefingDescription = ConstructTaskDescriptions(descriptionsMap, mission);
                }
            }

            if (situationDB.BriefingDescriptions != null && situationDB.BriefingDescriptions.Count > 0)
            {
                briefingDescription = GeneratorTools.ParseRandomString(string.Join(" ", Toolbox.RandomFrom(situationDB.BriefingDescriptions), briefingDescription), mission);
            }

            mission.Briefing.Description = briefingDescription;
            mission.SetValue("BRIEFINGDESCRIPTION", briefingDescription);
        }
Exemple #8
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());
        }
Exemple #9
0
        internal static void GenerateForcedOptions(DCSMission mission, MissionTemplateRecord template)
        {
            string forcedOptionsLua = "";

            foreach (RealismOption realismOption in template.OptionsRealism)
            {
                switch (realismOption)
                {
                case RealismOption.BirdStrikes: forcedOptionsLua += "[\"birds\"] = 300,"; break;

                case RealismOption.HideLabels: forcedOptionsLua += "[\"labels\"] = 0,"; break;

                case RealismOption.NoBDA: forcedOptionsLua += "[\"RBDAI\"] = false,\r\n"; break;

                case RealismOption.NoCheats: forcedOptionsLua += "[\"immortal\"] = false, [\"fuel\"] = false, [\"weapons\"] = false,"; break;

                case RealismOption.NoCrashRecovery: forcedOptionsLua += "[\"permitCrash\"] = false,"; break;

                case RealismOption.NoEasyComms: forcedOptionsLua += "[\"easyCommunication\"] = false,"; break;

                case RealismOption.NoExternalViews: forcedOptionsLua += "[\"externalViews\"] = false,"; break;

                case RealismOption.NoGameMode: forcedOptionsLua += "[\"easyFlight\"] = false, [\"easyRadar\"] = false,"; break;

                case RealismOption.NoOverlays: forcedOptionsLua += "[\"miniHUD\"] = false, [\"cockpitStatusBarAllowed\"] = false,"; break;

                case RealismOption.NoPadlock: forcedOptionsLua += "[\"padlock\"] = false,"; break;

                case RealismOption.RandomFailures: forcedOptionsLua += "[\"accidental_failures\"] = true,"; break;

                case RealismOption.RealisticGEffects: forcedOptionsLua += "[\"geffect\"] = \"realistic\","; break;

                case RealismOption.WakeTurbulence: forcedOptionsLua += "[\"wakeTurbulence\"] = true,"; break;
                }
            }

            // Some realism options are forced OFF when not explicitely enabled
            if (!template.OptionsRealism.Contains(RealismOption.BirdStrikes))
            {
                forcedOptionsLua += "[\"birds\"] = 0,";
            }
            else if (!template.OptionsRealism.Contains(RealismOption.RandomFailures))
            {
                forcedOptionsLua += "[\"accidental_failures\"] = false,";
            }
            else if (!template.OptionsRealism.Contains(RealismOption.NoBDA))
            {
                forcedOptionsLua += "[\"RBDAI\"] = true,";
            }

            forcedOptionsLua += $"[\"civTraffic\"] = \"{(template.OptionsMission.Contains("EnableCivilianTraffic") ? "medium" : "false")}\",";
            forcedOptionsLua += $"[\"radio\"] = {(template.OptionsRealism.Contains(RealismOption.DisableDCSRadioAssists) ? "false" : "true")},";

            switch (template.OptionsFogOfWar)
            {
            default: forcedOptionsLua += "[\"optionsView\"] = \"optview_all\","; break;     // case FogOfWar.All

            case FogOfWar.AlliesOnly: forcedOptionsLua += "[\"optionsView\"] = \"optview_onlyallies\","; break;

            case FogOfWar.KnownUnitsOnly: forcedOptionsLua += "[\"optionsView\"] = \"optview_allies\","; break;

            case FogOfWar.SelfOnly: forcedOptionsLua += "[\"optionsView\"] = \"optview_myaircraft\","; break;

            case FogOfWar.None: forcedOptionsLua += "[\"optionsView\"] = \"optview_onlymap\","; break;
            }

            mission.SetValue("ForcedOptions", forcedOptionsLua);
        }
Exemple #10
0
        internal static async Task <DCSMission> GenerateAsync(MissionTemplateRecord template, bool useObjectivePresets)
        {
            // Check for missing entries in the database
            GeneratorTools.CheckDBForMissingEntry <DBEntryCoalition>(template.ContextCoalitionBlue);
            GeneratorTools.CheckDBForMissingEntry <DBEntryCoalition>(template.ContextCoalitionRed);
            GeneratorTools.CheckDBForMissingEntry <DBEntryWeatherPreset>(template.EnvironmentWeatherPreset, true);
            GeneratorTools.CheckDBForMissingEntry <DBEntryTheater>(template.ContextTheater);
            if (!template.PlayerFlightGroups.Any(x => !x.Hostile))
            {
                throw new BriefingRoomException("Cannot have all players on hostile side.");
            }

            var mission = new DCSMission();

            var waypoints = new List <Waypoint>();
            var immediateActivationAircraftGroupsIDs = new List <int>();
            var lateActivationAircraftGroupsIDs      = new List <int>();

            var theaterDB   = Database.Instance.GetEntry <DBEntryTheater>(template.ContextTheater);
            var situationDB = Toolbox.RandomFrom(
                Database.Instance.GetAllEntries <DBEntrySituation>()
                .Where(x => x.Theater == template.ContextTheater.ToLower())
                .ToArray()
                );

            if (template.ContextSituation.StartsWith(template.ContextTheater))
            {
                situationDB = Database.Instance.GetEntry <DBEntrySituation>(template.ContextSituation);
            }


            var coalitionsDB = new DBEntryCoalition[]
            {
                Database.Instance.GetEntry <DBEntryCoalition>(template.ContextCoalitionBlue),
                Database.Instance.GetEntry <DBEntryCoalition>(template.ContextCoalitionRed)
            };

            // Copy values from the template
            mission.SetValue("BriefingTheater", theaterDB.UIDisplayName);
            mission.SetValue("BriefingSituation", situationDB.UIDisplayName);
            mission.SetValue("BriefingAllyCoalition", coalitionsDB[(int)template.ContextPlayerCoalition].UIDisplayName);
            mission.SetValue("BriefingEnemyCoalition", coalitionsDB[(int)template.ContextPlayerCoalition.GetEnemy()].UIDisplayName);
            mission.SetValue("EnableAudioRadioMessages", !template.OptionsMission.Contains("RadioMessagesTextOnly"));
            mission.SetValue("LuaPlayerCoalition", $"coalition.side.{template.ContextPlayerCoalition.ToString().ToUpperInvariant()}");
            mission.SetValue("LuaEnemyCoalition", $"coalition.side.{template.ContextPlayerCoalition.GetEnemy().ToString().ToUpperInvariant()}");
            mission.SetValue("TheaterID", theaterDB.DCSID);
            mission.SetValue("AircraftActivatorCurrentQueue", ""); // Just to make sure aircraft groups spawning queues are empty
            mission.SetValue("AircraftActivatorReserveQueue", "");
            mission.SetValue("MissionPlayerSlots", template.GetPlayerSlotsCount() == 1 ? "Single-player mission" : $"{template.GetPlayerSlotsCount()}-players mission");


            foreach (string oggFile in Database.Instance.Common.CommonOGG)
            {
                mission.AddMediaFile($"l10n/DEFAULT/{Toolbox.AddMissingFileExtension(oggFile, ".ogg")}", $"{BRPaths.INCLUDE_OGG}{Toolbox.AddMissingFileExtension(oggFile, ".ogg")}");
            }


            var coalitionsCountries = MissionGeneratorCountries.GenerateCountries(mission, template);


            var unitMaker = new UnitMaker(mission, template, coalitionsDB, theaterDB, situationDB, template.ContextPlayerCoalition, coalitionsCountries, template.GetPlayerSlotsCount() == 1);

            var drawingMaker = new DrawingMaker(mission, template, theaterDB, situationDB);
            var zoneMaker    = new ZoneMaker(unitMaker);


            BriefingRoom.PrintToLog("Generating mission date and time...");
            var month = MissionGeneratorDateTime.GenerateMissionDate(mission, template);

            MissionGeneratorDateTime.GenerateMissionTime(mission, template, theaterDB, month);


            BriefingRoom.PrintToLog("Setting up airbases...");
            var airbasesGenerator = new MissionGeneratorAirbases(template, situationDB);
            var requiredRunway    = template.PlayerFlightGroups.Select(x => Database.Instance.GetEntry <DBEntryUnit>(x.Aircraft).AircraftData.MinimumRunwayLengthFt).Max();
            var playerAirbase     = airbasesGenerator.SelectStartingAirbase(mission, template.FlightPlanTheaterStartingAirbase, requiredRunway: requiredRunway);

            mission.Briefing.AddItem(DCSMissionBriefingItemType.Airbase, $"{playerAirbase.Name}\t{playerAirbase.Runways}\t{playerAirbase.ATC}\t{playerAirbase.ILS}\t{playerAirbase.TACAN}");
            airbasesGenerator.SelectStartingAirbaseForPackages(mission, playerAirbase);
            airbasesGenerator.SetupAirbasesCoalitions(mission, playerAirbase);
            zoneMaker.AddAirbaseZones(playerAirbase, mission.MissionPackages);
            mission.SetValue("PlayerAirbaseName", playerAirbase.Name);
            mission.SetValue("MissionAirbaseX", playerAirbase.Coordinates.X);
            mission.SetValue("MissionAirbaseY", playerAirbase.Coordinates.Y);


            BriefingRoom.PrintToLog("Generating mission weather...");
            var turbulenceFromWeather = MissionGeneratorWeather.GenerateWeather(mission, template, theaterDB, month, playerAirbase);

            var(windSpeedAtSeaLevel, windDirectionAtSeaLevel) = MissionGeneratorWeather.GenerateWind(mission, template, turbulenceFromWeather);

            // Generate objectives
            BriefingRoom.PrintToLog("Generating objectives...");
            var objectiveCoordinates        = new List <Coordinates>();
            var objectiveTargetUnitFamilies = new List <UnitFamily>();
            var lastObjectiveCoordinates    = playerAirbase.Coordinates;
            var objectivesGenerator         = new MissionGeneratorObjectives(unitMaker, drawingMaker, template);
            var objectiveGroupedWaypoints   = new List <List <Waypoint> >();
            var i = 0;

            foreach (var objectiveTemplate in template.Objectives)
            {
                var(objectiveCoords, waypointGroup) = objectivesGenerator.GenerateObjective(
                    mission, template, situationDB,
                    objectiveTemplate, lastObjectiveCoordinates, playerAirbase, useObjectivePresets,
                    ref i, ref objectiveCoordinates, ref waypoints, ref objectiveTargetUnitFamilies);
                lastObjectiveCoordinates = objectiveCoords;
                objectiveGroupedWaypoints.Add(waypointGroup);
                i++;
            }
            var objectivesCenter = (objectiveCoordinates.Count == 0) ? playerAirbase.Coordinates : Coordinates.Sum(objectiveCoordinates) / objectiveCoordinates.Count;

            mission.SetValue("MissionCenterX", objectivesCenter.X);
            mission.SetValue("MissionCenterY", objectivesCenter.Y);

            // Generate carrier groups
            BriefingRoom.PrintToLog("Generating carrier groups...");
            var carrierDictionary = MissionGeneratorCarrierGroup.GenerateCarrierGroup(
                unitMaker, zoneMaker, mission, template,
                playerAirbase.Coordinates, objectivesCenter,
                windSpeedAtSeaLevel, windDirectionAtSeaLevel);
            var averageInitialPosition = playerAirbase.Coordinates;

            if (carrierDictionary.Count > 0)
            {
                averageInitialPosition = (averageInitialPosition + carrierDictionary.First().Value.Coordinates) / 2.0;
            }

            // Generate extra flight plan info
            MissionGeneratorFlightPlan.GenerateBullseyes(mission, objectivesCenter);
            MissionGeneratorFlightPlan.GenerateObjectiveWPCoordinatesLua(template, mission, waypoints, drawingMaker);
            MissionGeneratorFlightPlan.GenerateAircraftPackageWaypoints(template, mission, objectiveGroupedWaypoints, averageInitialPosition, objectivesCenter);
            MissionGeneratorFlightPlan.GenerateIngressAndEgressWaypoints(template, waypoints, averageInitialPosition, objectivesCenter);

            // Generate surface-to-air defenses
            MissionGeneratorAirDefense.GenerateAirDefense(template, unitMaker, averageInitialPosition, objectivesCenter);

            // Generate combat air patrols
            var capGroupsID = MissionGeneratorCombatAirPatrols.GenerateCAP(unitMaker, template, averageInitialPosition, objectivesCenter);

            foreach (int capGroupID in capGroupsID) // Add 50% of CAP groups to the list of A/C activated on takeoff, the other 50% to the list of A/C activated later.
            {
                if (Toolbox.RandomChance(2))
                {
                    immediateActivationAircraftGroupsIDs.Add(capGroupID);
                }
                else
                {
                    lateActivationAircraftGroupsIDs.Add(capGroupID);
                }
            }

            // Generate player flight groups
            BriefingRoom.PrintToLog("Generating player flight groups...");
            foreach (var templateFlightGroup in template.PlayerFlightGroups)
            {
                MissionGeneratorPlayerFlightGroups.GeneratePlayerFlightGroup(unitMaker, mission, template, templateFlightGroup, playerAirbase, waypoints, carrierDictionary, averageInitialPosition, objectivesCenter);
            }

            // Generate mission features
            BriefingRoom.PrintToLog("Generating mission features...");
            mission.AppendValue("ScriptMissionFeatures", ""); // Just in case there's no features
            var missionFeaturesGenerator = new MissionGeneratorFeaturesMission(unitMaker, template);

            foreach (var templateFeature in template.MissionFeatures)
            {
                missionFeaturesGenerator.GenerateMissionFeature(mission, templateFeature, playerAirbase.Coordinates, objectivesCenter);
            }


            // Add ogg files to the media files dictionary
            foreach (string mediaFile in mission.GetMediaFileNames())
            {
                if (!mediaFile.ToLowerInvariant().EndsWith(".ogg"))
                {
                    continue;                                                 // Not an .ogg file
                }
                mission.AppendValue("MapResourcesFiles", $"[\"ResKey_Snd_{Path.GetFileNameWithoutExtension(mediaFile)}\"] = \"{Path.GetFileName(mediaFile)}\",\n");
            }

            // Get unit tables from the unit maker (MUST BE DONE AFTER ALL UNITS ARE GENERATED)
            mission.SetValue("CountriesBlue", unitMaker.GetUnitsLuaTable(Coalition.Blue));
            mission.SetValue("CountriesRed", unitMaker.GetUnitsLuaTable(Coalition.Red));
            mission.SetValue("Drawings", drawingMaker.GetLuaDrawings());
            mission.SetValue("Zones", zoneMaker.GetLuaZones());

            // Generate briefing and additional mission info
            BriefingRoom.PrintToLog("Generating briefing...");
            var missionName = GeneratorTools.GenerateMissionName(template.BriefingMissionName);

            mission.Briefing.Name = missionName;
            mission.SetValue("MISSIONNAME", missionName);

            MissionGeneratorBriefing.GenerateMissionBriefingDescription(mission, template, objectiveTargetUnitFamilies, situationDB);
            mission.SetValue("DescriptionText", mission.Briefing.GetBriefingAsRawText("\\\n"));

            // Generate mission options
            BriefingRoom.PrintToLog("Generating options...");
            MissionGeneratorOptions.GenerateForcedOptions(mission, template);

            // Generate warehouses
            BriefingRoom.PrintToLog("Generating warehouses...");
            MissionGeneratorWarehouses.GenerateWarehouses(mission);

            // Generate image files
            BriefingRoom.PrintToLog("Generating images...");
            MissionGeneratorImages.GenerateTitle(mission, template);
            await MissionGeneratorImages.GenerateKneeboardImagesAsync(mission);

            return(mission);
        }