示例#1
0
        internal static void GenerateTitle(DCSMission mission, MissionTemplateRecord template)
        {
            ImageMaker imageMaker = new();

            imageMaker.TextOverlay.Alignment = ContentAlignment.MiddleCenter;
            imageMaker.TextOverlay.Text      = mission.Briefing.Name;

            List <ImageMakerLayer> imageLayers = new List <ImageMakerLayer>();

            string[] theaterImages = Directory.GetFiles($"{BRPaths.INCLUDE_JPG}Theaters\\", $"{Database.Instance.GetEntry<DBEntryTheater>(template.ContextTheater).DCSID}*.jpg");
            if (theaterImages.Length == 0)
            {
                imageLayers.Add(new ImageMakerLayer("_default.jpg"));
            }
            else
            {
                imageLayers.Add(new ImageMakerLayer("Theaters\\" + Path.GetFileName(Toolbox.RandomFrom(theaterImages))));
            }

            imageLayers.Add(new ImageMakerLayer($"Flags\\{template.GetCoalitionID(template.ContextPlayerCoalition)}.png", ContentAlignment.TopLeft, 8, 8, 0, .5));

            byte[] imageBytes = imageMaker.GetImageBytes(imageLayers.ToArray());

            mission.AddMediaFile($"l10n/DEFAULT/title_{mission.UniqueID}.jpg", imageBytes);
        }
示例#2
0
        private static async Task <int> GenerateKneeboardImageAsync(string html, DCSMission mission, int inc = 1, string aircraftID = "")
        {
            var tempRenderPath = $"{BRPaths.INCLUDE_JPG}temp.png";

            await new HtmlToImageConverter().ConvertAsync(html, tempRenderPath, new GeneralImageOptions {
                Width       = 1200,
                Transparent = true
            });
            var img       = Image.FromFile(tempRenderPath);
            var pageCount = Math.Ceiling((decimal)(img.Size.Height / 1725.0));

            for (int i = 0; i < pageCount; i++)
            {
                var page     = new Bitmap(1200, 1800);
                var graphics = Graphics.FromImage(page);
                graphics.DrawImage(img, new Rectangle(0, 25, 1200, 1750), new Rectangle(0, i * 1725, 1200, 1750), GraphicsUnit.Pixel);
                graphics.Dispose();
                var tempPagePath = $"{BRPaths.INCLUDE_JPG}temp{i}.png";
                page.Save(tempPagePath);
                page.Dispose();
                byte[] imageBytes;

                ImageMaker imgMaker = new();
                imgMaker.ImageSizeX = 1200;
                imgMaker.ImageSizeY = 1800;

                List <ImageMakerLayer> layers = new List <ImageMakerLayer> {
                    new ImageMakerLayer("notebook.png"),
                    new ImageMakerLayer($"temp{i}.png")
                };

                var random = new Random();
                if (random.Next(100) < 3)
                {
                    layers.Add(easterEggLogos[random.Next(easterEggLogos.Count)]);
                }

                imageBytes = imgMaker.GetImageBytes(layers.ToArray());
                var midPath = !string.IsNullOrEmpty(aircraftID)? $"{aircraftID}/" : "";
                mission.AddMediaFile($"KNEEBOARD/{midPath}IMAGES/comms_{mission.UniqueID}_{inc}.jpg", imageBytes);
                inc++;
                File.Delete(tempPagePath);
            }
            img.Dispose();
            File.Delete(tempRenderPath);
            return(inc);
        }
示例#3
0
        internal Tuple <Coordinates, List <Waypoint> > GenerateObjective(
            DCSMission mission,
            MissionTemplateRecord template,
            DBEntrySituation situationDB,
            MissionTemplateObjectiveRecord objectiveTemplate,
            Coordinates lastCoordinates,
            DBEntryAirbase playerAirbase,
            bool useObjectivePreset,
            ref int objectiveIndex,
            ref List <Coordinates> objectiveCoordinatesList,
            ref List <Waypoint> waypoints,
            ref List <UnitFamily> objectiveTargetUnitFamilies)
        {
            var extraSettings = new List <KeyValuePair <string, object> >();
            var waypointList  = new List <Waypoint>();

            string[] featuresID;
            DBEntryObjectiveTarget         targetDB;
            DBEntryObjectiveTargetBehavior targetBehaviorDB;
            DBEntryObjectiveTask           taskDB;

            ObjectiveOption[] objectiveOptions;

            GetObjectiveData(objectiveTemplate, useObjectivePreset, out featuresID, out targetDB, out targetBehaviorDB, out taskDB, out objectiveOptions);
            var         luaUnit = targetBehaviorDB.UnitLua[(int)targetDB.UnitCategory];
            Coordinates objectiveCoordinates = GetSpawnCoordinates(template, lastCoordinates, playerAirbase, targetDB);

            // Spawn target on airbase
            var unitCount = targetDB.UnitCount[(int)objectiveTemplate.TargetCount].GetValue();
            var objectiveTargetUnitFamily = Toolbox.RandomFrom(targetDB.UnitFamilies);

            if (AIRBASE_LOCATIONS.Contains(targetBehaviorDB.Location) && targetDB.UnitCategory.IsAircraft())
            {
                objectiveCoordinates = PlaceInAirbase(template, situationDB, playerAirbase, extraSettings, targetDB, targetBehaviorDB, ref luaUnit, objectiveCoordinates, unitCount, objectiveTargetUnitFamily);
            }

            UnitMakerGroupFlags groupFlags = 0;

            if (objectiveOptions.Contains(ObjectiveOption.ShowTarget))
            {
                groupFlags = UnitMakerGroupFlags.NeverHidden;
            }
            else if (objectiveOptions.Contains(ObjectiveOption.HideTarget))
            {
                groupFlags = UnitMakerGroupFlags.AlwaysHidden;
            }
            if (objectiveOptions.Contains(ObjectiveOption.EmbeddedAirDefense))
            {
                groupFlags |= UnitMakerGroupFlags.EmbeddedAirDefense;
            }

            // Set destination point for moving unit groups
            Coordinates destinationPoint = objectiveCoordinates +
                                           (
                (targetDB.UnitCategory == UnitCategory.Plane ? Coordinates.CreateRandom(30, 60) : Coordinates.CreateRandom(10, 20)) *
                Toolbox.NM_TO_METERS
                                           );

            if (targetBehaviorDB.Location == DBEntryObjectiveTargetBehaviorLocation.GoToPlayerAirbase)
            {
                destinationPoint = playerAirbase.Coordinates;
            }


            var unitCoordinates = objectiveCoordinates;
            var objectiveName   = Toolbox.RandomFrom(ObjectiveNames);

            if (TRANSPORT_TASKS.Contains(taskDB.ID))
            {
                Coordinates?spawnPoint = UnitMaker.SpawnPointSelector.GetRandomSpawnPoint(
                    targetDB.ValidSpawnPoints,
                    playerAirbase.Coordinates,
                    new MinMaxD(1, 5),
                    coalition: GeneratorTools.GetSpawnPointCoalition(template, Side.Ally));
                if (!spawnPoint.HasValue) // Failed to generate target group
                {
                    throw new BriefingRoomException($"Failed to find Cargo SpawnPoint");
                }
                unitCoordinates = spawnPoint.Value;
                var cargoWaypoint = GenerateObjectiveWaypoint(objectiveTemplate, unitCoordinates, $"{objectiveName} Pickup", template, true);
                waypoints.Add(cargoWaypoint);
                waypointList.Add(cargoWaypoint);
                if (taskDB.isEscort())
                {
                    extraSettings.Add("GroupX2".ToKeyValuePair(objectiveCoordinates.X));
                    extraSettings.Add("GroupY2".ToKeyValuePair(objectiveCoordinates.Y));
                    groupFlags |= UnitMakerGroupFlags.RadioAircraftSpawn;
                }
            }

            extraSettings.Add("GroupX2".ToKeyValuePair(destinationPoint.X));
            extraSettings.Add("GroupY2".ToKeyValuePair(destinationPoint.Y));

            UnitMakerGroupInfo?targetGroupInfo = UnitMaker.AddUnitGroup(
                objectiveTargetUnitFamily, unitCount,
                taskDB.TargetSide,
                targetBehaviorDB.GroupLua[(int)targetDB.UnitCategory], luaUnit,
                unitCoordinates,
                groupFlags,
                extraSettings.ToArray());

            if (!targetGroupInfo.HasValue) // Failed to generate target group
            {
                throw new BriefingRoomException($"Failed to generate group for objective.");
            }

            if (objectiveOptions.Contains(ObjectiveOption.EmbeddedAirDefense) && (targetDB.UnitCategory == UnitCategory.Static))
            {
                AddEmbeddedAirDefenseUnits(template, targetDB, targetBehaviorDB, taskDB, objectiveOptions, objectiveCoordinates, groupFlags, extraSettings);
            }

            var pluralIndex = targetGroupInfo.Value.UnitsID.Length == 1 ? 0 : 1;
            var taskString  = GeneratorTools.ParseRandomString(taskDB.BriefingTask[pluralIndex], mission).Replace("\"", "''");

            // Pick a name, then remove it from the list
            ObjectiveNames.Remove(objectiveName);
            CreateTaskString(mission, pluralIndex, ref taskString, objectiveName, objectiveTargetUnitFamily);

            CreateLua(mission, template, targetDB, taskDB, objectiveIndex, objectiveName, targetGroupInfo, taskString);

            // Add briefing remarks for this objective task
            if (taskDB.BriefingRemarks.Length > 0)
            {
                string remark = Toolbox.RandomFrom(taskDB.BriefingRemarks);
                GeneratorTools.ReplaceKey(ref remark, "ObjectiveName", objectiveName);
                GeneratorTools.ReplaceKey(ref remark, "UnitFamily", Database.Instance.Common.Names.UnitFamilies[(int)objectiveTargetUnitFamily][pluralIndex]);
                mission.Briefing.AddItem(DCSMissionBriefingItemType.Remark, remark);
            }

            // Add feature ogg files
            foreach (string oggFile in taskDB.IncludeOgg)
            {
                mission.AddMediaFile($"l10n/DEFAULT/{oggFile}", $"{BRPaths.INCLUDE_OGG}{oggFile}");
            }

            // Add objective features Lua for this objective
            mission.AppendValue("ScriptObjectivesFeatures", ""); // Just in case there's no features
            foreach (string featureID in featuresID)
            {
                FeaturesGenerator.GenerateMissionFeature(mission, featureID, objectiveName, objectiveIndex, targetGroupInfo.Value.GroupID, objectiveCoordinates, taskDB.TargetSide, objectiveOptions.Contains(ObjectiveOption.HideTarget));
            }

            objectiveCoordinatesList.Add(objectiveCoordinates);
            var waypoint = GenerateObjectiveWaypoint(objectiveTemplate, objectiveCoordinates, objectiveName, template);

            waypoints.Add(waypoint);
            waypointList.Add(waypoint);
            objectiveTargetUnitFamilies.Add(objectiveTargetUnitFamily);

            var preValidSpawns = targetDB.ValidSpawnPoints.ToList();

            foreach (var subTasks in objectiveTemplate.SubTasks)
            {
                objectiveIndex++;
                GenerateSubTask(mission, template, situationDB,
                                subTasks, objectiveCoordinates,
                                playerAirbase,
                                preValidSpawns,
                                targetBehaviorDB.Location,
                                featuresID,
                                ref objectiveIndex,
                                ref objectiveCoordinatesList, ref waypoints, ref waypointList, ref objectiveTargetUnitFamilies);
            }
            return(new (objectiveCoordinates, waypointList));
        }
示例#4
0
        protected UnitMakerGroupInfo?AddMissionFeature(T featureDB, DCSMission mission, Coordinates coordinates, Coordinates?coordinates2, ref Dictionary <string, object> extraSettings, Side?objectiveTargetSide = null, bool hideEnemy = false)
        {
            // Add secondary coordinates (destination point) to the extra settings
            if (!coordinates2.HasValue)
            {
                coordinates2 = coordinates;                         // No destination point? Use initial point
            }
            extraSettings.AddIfKeyUnused("GroupX2", coordinates2.Value.X);
            extraSettings.AddIfKeyUnused("GroupY2", coordinates2.Value.Y);
            var TACANStr = GetExtraSettingsFromFeature(featureDB, ref extraSettings); // Add specific settings for this feature (TACAN frequencies, etc)

            // Feature unit group
            UnitMakerGroupInfo?groupInfo = null;

            if (FeatureHasUnitGroup(featureDB))
            {
                UnitMakerGroupFlags groupFlags = 0;

                if (featureDB.UnitGroupFlags.HasFlag(FeatureUnitGroupFlags.ImmediateAircraftActivation))
                {
                    groupFlags |= UnitMakerGroupFlags.ImmediateAircraftSpawn;
                }

                if (featureDB.UnitGroupFlags.HasFlag(FeatureUnitGroupFlags.RadioAircraftActivation))
                {
                    groupFlags |= UnitMakerGroupFlags.RadioAircraftSpawn;
                }

                Side groupSide = Side.Enemy;
                if (featureDB.UnitGroupFlags.HasFlag(FeatureUnitGroupFlags.Friendly))
                {
                    groupSide = Side.Ally;
                }
                else if (featureDB.UnitGroupFlags.HasFlag(FeatureUnitGroupFlags.SameSideAsTarget) && objectiveTargetSide.HasValue)
                {
                    groupSide = objectiveTargetSide.Value;
                }

                if (hideEnemy && groupSide == Side.Enemy)
                {
                    groupFlags |= UnitMakerGroupFlags.AlwaysHidden;
                }

                extraSettings.AddIfKeyUnused("Payload", featureDB.UnitGroupPayload);
                var groupLua   = featureDB.UnitGroupLuaGroup;
                var unitCount  = featureDB.UnitGroupSize.GetValue();
                var unitFamily = Toolbox.RandomFrom(featureDB.UnitGroupFamilies);
                var luaUnit    = featureDB.UnitGroupLuaUnit;
                SetAirbase(featureDB, unitFamily, ref groupLua, ref luaUnit, groupSide, ref coordinates, coordinates2.Value, unitCount, ref extraSettings);

                groupInfo = _unitMaker.AddUnitGroup(
                    unitFamily, unitCount,
                    groupSide,
                    groupLua, luaUnit,
                    coordinates, groupFlags,
                    extraSettings.ToArray());
                if (
                    groupSide == Side.Ally &&
                    groupInfo.HasValue &&
                    groupInfo.Value.UnitDB != null &&
                    groupInfo.Value.UnitDB.IsAircraft)
                {
                    mission.Briefing.AddItem(DCSMissionBriefingItemType.FlightGroup,
                                             $"{groupInfo.Value.Name}\t" +
                                             $"{unitCount}× {groupInfo.Value.UnitDB.UIDisplayName}\t" +
                                             $"{GeneratorTools.FormatRadioFrequency(groupInfo.Value.Frequency)}{TACANStr}\t" +
                                             $"{Toolbox.FormatPayload(featureDB.UnitGroupPayload)}"); // TODO: human-readable payload name
                }
                if (featureDB.ExtraGroups.Max > 1)
                {
                    SpawnExtraGroups(featureDB, mission, groupSide, groupFlags, coordinates, coordinates2.Value, extraSettings);
                }
            }

            // Feature Lua script
            string featureLua = "";

            // Adds the features' group ID to the briefingRoom.mission.missionFeatures.groupsID table
            if (this is MissionGeneratorFeaturesMission)
            {
                featureLua += $"briefingRoom.mission.missionFeatures.groupsID.{GeneratorTools.LowercaseFirstCharacter(featureDB.ID)} = {(groupInfo.HasValue ? groupInfo.Value.GroupID : 0)}\n";
                featureLua += $"briefingRoom.mission.missionFeatures.unitsID.{GeneratorTools.LowercaseFirstCharacter(featureDB.ID)} = {{{(groupInfo.HasValue ? string.Join(",", groupInfo.Value.UnitsID) : "")}}}\n";
            }

            if (!string.IsNullOrEmpty(featureDB.IncludeLuaSettings))
            {
                featureLua = featureDB.IncludeLuaSettings + "\n";
            }
            foreach (string luaFile in featureDB.IncludeLua)
            {
                featureLua += Toolbox.ReadAllTextIfFileExists($"{featureDB.SourceLuaDirectory}{luaFile}") + "\n";
            }
            foreach (KeyValuePair <string, object> extraSetting in extraSettings)
            {
                GeneratorTools.ReplaceKey(ref featureLua, extraSetting.Key, extraSetting.Value);
            }
            if (groupInfo.HasValue)
            {
                GeneratorTools.ReplaceKey(ref featureLua, "FeatureGroupID", groupInfo.Value.GroupID);
            }

            if (featureDB is DBEntryFeatureObjective)
            {
                mission.AppendValue("ScriptObjectivesFeatures", featureLua);
            }
            else
            {
                mission.AppendValue("ScriptMissionFeatures", featureLua);
            }

            // Add feature ogg files
            foreach (string oggFile in featureDB.IncludeOgg)
            {
                mission.AddMediaFile($"l10n/DEFAULT/{oggFile}", $"{BRPaths.INCLUDE_OGG}{oggFile}");
            }

            return(groupInfo);
        }
示例#5
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);
        }