Esempio n. 1
0
        /// <summary>
        /// Main unit generation method.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="template">Mission template to use</param>
        /// <param name="objectiveDB">Mission objective database entry</param>
        /// <param name="playerCoalitionDB">Player coalition database entry</param>
        /// <param name="aiEscortTypeCAP">Type of aircraft selected for AI CAP escort</param>
        /// <param name="aiEscortTypeSEAD">Type of aircraft selected for AI SEAD escort</param>
        /// <returns>An array of <see cref="UnitFlightGroupBriefingDescription"/> describing the flight groups, to be used in the briefing</returns>
        public UnitFlightGroupBriefingDescription[] CreateUnitGroups(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB, DBEntryCoalition playerCoalitionDB, out string aiEscortTypeCAP, out string aiEscortTypeSEAD)
        {
            List <UnitFlightGroupBriefingDescription> briefingFGList = new List <UnitFlightGroupBriefingDescription>();

            if (template.GetMissionType() == MissionType.SinglePlayer)
            {
                briefingFGList.Add(GenerateSinglePlayerFlightGroup(mission, template, objectiveDB));
            }
            else
            {
                briefingFGList.AddRange(GenerateMultiplayerFlightGroups(mission, template, objectiveDB));
            }

            aiEscortTypeCAP  = "";
            aiEscortTypeSEAD = "";
            UnitFlightGroupBriefingDescription?escortDescription;

            escortDescription = GenerateAIEscort(mission, template, template.PlayerEscortCAP, MissionTemplateMPFlightGroupTask.SupportCAP, playerCoalitionDB);
            if (escortDescription.HasValue)
            {
                briefingFGList.Add(escortDescription.Value);
                aiEscortTypeCAP = escortDescription.Value.Type;
            }

            escortDescription = GenerateAIEscort(mission, template, template.PlayerEscortSEAD, MissionTemplateMPFlightGroupTask.SupportSEAD, playerCoalitionDB);
            if (escortDescription.HasValue)
            {
                briefingFGList.Add(escortDescription.Value);
                aiEscortTypeSEAD = escortDescription.Value.Type;
            }

            return(briefingFGList.ToArray());
        }
Esempio n. 2
0
        /// <summary>
        /// Directly copies some simple values (theater database entry ID, etc.) from the template.
        /// </summary>
        /// <param name="mission">The mission</param>
        /// <param name="template">Mission template to use</param>
        private void CopyTemplateValues(DCSMission mission, MissionTemplate template)
        {
            mission.Coalitions[(int)Coalition.Blue] = template.GetCoalition(Coalition.Blue);
            mission.Coalitions[(int)Coalition.Red]  = template.GetCoalition(Coalition.Red);
            mission.CoalitionPlayer     = template.ContextCoalitionPlayer;
            mission.Theater             = template.TheaterID;
            mission.PlayerStartLocation = template.PlayerStartLocation;
            mission.EndMode             = template.OptionsEndMode;

            // "Runway" start locations is not available in MP missions, change to "Parking hot".
            if ((template.GetMissionType() != MissionType.SinglePlayer) &&
                (template.PlayerStartLocation == PlayerStartLocation.Runway))
            {
                mission.PlayerStartLocation = PlayerStartLocation.ParkingHot;
            }
        }
        /// <summary>
        /// Directly copies some simple values (theater database entry ID, etc.) from the template.
        /// </summary>
        /// <param name="mission">The mission</param>
        /// <param name="template">Mission template to use</param>
        private void CopyTemplateValues(DCSMission mission, MissionTemplate template)
        {
            mission.Coalitions[(int)Coalition.Blue] = template.GetCoalition(Coalition.Blue);
            mission.Coalitions[(int)Coalition.Red]  = template.GetCoalition(Coalition.Red);
            mission.CivilianTraffic     = template.OptionsCivilianTraffic;
            mission.CoalitionPlayer     = template.ContextCoalitionPlayer;
            mission.RadioAssists        = template.OptionsPreferences.Contains(MissionTemplatePreferences.DCSRadioAssists);
            mission.Theater             = template.TheaterID;
            mission.PlayerStartLocation = template.PlayerStartLocation;
            mission.EndMode             = template.OptionsEndMode;
            mission.RealismOptions      = template.OptionsRealism;

            // "Runway" start locations is not available in MP missions, change to "Parking hot".
            if ((template.GetMissionType() != MissionType.SinglePlayer) &&
                (template.PlayerStartLocation == PlayerStartLocation.Runway))
            {
                mission.PlayerStartLocation = PlayerStartLocation.ParkingHot;
            }
        }
        private string CreateHTMLBriefing(
            DCSMission mission, MissionTemplate template, string description,
            List <string> tasks, List <string> remarks,
            List <UnitFlightGroupBriefingDescription> flightGroups, DBEntryTheaterAirbase airbaseDB,
            DBEntryCoalition[] coalitionsDB, DBEntryObjective objectiveDB)
        {
            DebugLog.Instance.WriteLine("Generating HTML mission briefing...", 2);

            if (!File.Exists(HTML_TEMPLATE_FILE)) // Briefing template not found
            {
                DebugLog.Instance.WriteLine("HTML template file not found.", 1, DebugLogMessageErrorLevel.Warning);
                return("HTML template file not found.");
            }

            string briefing = File.ReadAllText(HTML_TEMPLATE_FILE);

            // Title
            briefing = briefing.Replace("$MISSIONNAME$", mission.MissionName);
            briefing = briefing.Replace("$MISSIONTYPE$",
                                        GeneratorTools.RemoveAfterComma(objectiveDB.ID) + " mission " +
                                        ((template.GetMissionType() == MissionType.SinglePlayer) ?
                                         "(single-player)" : $"({template.GetPlayerCount()}-players multiplayer)"));

            // Situation summary
            briefing = briefing.Replace("$LONGDATE$", mission.DateTime.ToDateString(true));
            briefing = briefing.Replace("$LONGTIME$", mission.DateTime.ToTimeString());
            briefing = briefing.Replace("$SHORTDATE$", mission.DateTime.ToDateString(false));
            briefing = briefing.Replace("$SHORTTIME$", mission.DateTime.ToTimeString());
            briefing = briefing.Replace("$WEATHER$", GeneratorTools.GetEnumString(mission.Weather.WeatherLevel));
            briefing = briefing.Replace("$WIND$", GeneratorTools.GetEnumString(mission.Weather.WindLevel));
            briefing = briefing.Replace("$WINDSPEED$", mission.Weather.WindSpeedAverage.ToString("F0"));

            // Friends and enemies
            briefing = briefing.Replace("$PLAYERCOALITION$", GeneratorTools.RemoveAfterComma(template.GetCoalition(mission.CoalitionPlayer)));
            briefing = briefing.Replace("$ENEMYCOALITION$", GeneratorTools.RemoveAfterComma(template.GetCoalition(mission.CoalitionEnemy)));

            // Description
            briefing = briefing.Replace("$DESCRIPTION$", description.Replace("\n", "<br />"));

            // Tasks
            string tasksHTML = "";

            foreach (string task in tasks)
            {
                tasksHTML += $"<li>{task}</li>";
            }
            briefing = briefing.Replace("$TASKS$", tasksHTML);

            // Remarks
            string remarksHTML = "";

            foreach (string remark in remarks)
            {
                remarksHTML += $"<li>{remark}</li>";
            }
            briefing = briefing.Replace("$REMARKS$", remarksHTML);

            // Flight groups
            string flightGroupsHTML = "";

            foreach (UnitFlightGroupBriefingDescription fg in flightGroups)
            {
                flightGroupsHTML +=
                    "<tr>" +
                    $"<td>{fg.Callsign}</td>" +
                    $"<td>{fg.Count}×{fg.Type}</td>" +
                    $"<td>{fg.Task}</td><td>{fg.Radio}</td>" +
                    $"<td>{fg.Remarks}</td>" +
                    "</tr>";
            }
            briefing = briefing.Replace("$FLIGHTGROUPS$", flightGroupsHTML);

            // Airbases
            string airbasesHTML =
                "<tr>" +
                $"<td>{airbaseDB.Name}</td>" +
                $"<td>{airbaseDB.Runways}</td>" +
                $"<td>{airbaseDB.ATC}</td>" +
                $"<td>{airbaseDB.ILS}</td>" +
                $"<td>{airbaseDB.TACAN}</td>" +
                "</tr>";

            briefing = briefing.Replace("$AIRBASES$", airbasesHTML);

            string carrierHTML = "";

            foreach (var carrier in mission.Carriers)
            {
                carrierHTML +=
                    "<tr>" +
                    $"<td>{carrier.Units[0].Name}</td>" +
                    $"<td>{carrier.RadioFrequency.ToString("n3")}{carrier.RadioModulation}</td>" +
                    $"<td>{carrier.ILS}</td>" +
                    $"<td>{carrier.TACAN.ToString()}</td>" +
                    "</tr>";
            }
            briefing = briefing.Replace("$CARRIERS$", carrierHTML);


            // Waypoints
            string      waypointsHTML = "";
            double      distance;
            double      totalDistance   = 0.0;
            Coordinates currentPosition = mission.InitialPosition;

            waypointsHTML += $"<tr><td><strong>TAKEOFF</strong></td><td>-</td><td>-</td></tr>";
            foreach (DCSMissionWaypoint wp in mission.Waypoints)
            {
                distance        = currentPosition.GetDistanceFrom(wp.Coordinates);
                totalDistance  += distance;
                currentPosition = wp.Coordinates;

                waypointsHTML +=
                    $"<tr><td>{wp.Name}</td>" +
                    $"<td>{GeneratorTools.ConvertDistance(distance, template.BriefingUnitSystem)}</td>" +
                    $"<td>{GeneratorTools.ConvertDistance(totalDistance, template.BriefingUnitSystem)}</td></tr>";
            }
            distance       = currentPosition.GetDistanceFrom(mission.InitialPosition);
            totalDistance += distance;
            waypointsHTML += $"<tr><td><strong>LANDING</strong></td>" +
                             $"<td>{GeneratorTools.ConvertDistance(distance, template.BriefingUnitSystem)}</td>" +
                             $"<td>{GeneratorTools.ConvertDistance(totalDistance, template.BriefingUnitSystem)}</td></tr>";
            briefing = briefing.Replace("$WAYPOINTS$", waypointsHTML);

            return(briefing);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="mission"></param>
        /// <param name="template"></param>
        /// <param name="playerCoalitionDB"></param>
        /// <param name="windDirection0">Wind direction at altitude 0, in degrees. Used by carrier groups to make sure carriers sail into the wind.</param>
        /// <returns></returns>
        public void GenerateCarriers(DCSMission mission, MissionTemplate template, DBEntryCoalition playerCoalitionDB)
        {
            var carriers = new string[] {};

            if (template.GetMissionType() == MissionType.SinglePlayer)
            {
                if (string.IsNullOrEmpty(template.PlayerSPCarrier))
                {
                    return;
                }
                carriers = carriers.Append(template.PlayerSPCarrier).ToArray();
            }
            else
            {
                carriers = template.PlayerMPFlightGroups.Aggregate(new string[] {}, (acc, x) => !string.IsNullOrEmpty(x.Carrier)? acc.Append(x.Carrier).ToArray() : acc);
            }

            if (carriers.Length == 0)
            {
                return;
            }

            foreach (string carrier in carriers)
            {
                DBEntryTheaterSpawnPoint?spawnPoint =
                    UnitMaker.SpawnPointSelector.GetRandomSpawnPoint(
                        // If spawn point types are specified, use them. Else look for spawn points of any type
                        new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.Sea },
                        // Select spawn points at a proper distance from last location (previous objective or home airbase)
                        mission.InitialPosition, new MinMaxD(10, 50),
                        // Make sure no objective is too close to the initial location
                        null, null,
                        GeneratorTools.GetAllySpawnPointCoalition(template));

                if (!spawnPoint.HasValue)
                {
                    throw new Exception($"Failed to find a spawn point for Carrier");
                }

                Coordinates         position = mission.InitialPosition;
                DCSMissionUnitGroup group;
                string[]            ships = new string[] { carrier };
                foreach (var ship in new UnitFamily[] {
                    UnitFamily.ShipFrigate,
                    UnitFamily.ShipFrigate,
                    UnitFamily.ShipCruiser,
                    UnitFamily.ShipCruiser,
                    UnitFamily.ShipTransport
                })
                {
                    ships = ships.Append(playerCoalitionDB.GetRandomUnits(ship, mission.DateTime.Decade, 1, template.OptionsUnitMods)[0]).ToArray();
                }
                DebugLog.Instance.WriteLine($"Ships to be spawned {ships.Aggregate((acc, x) => $"{acc}, {x}")}", 1, DebugLogMessageErrorLevel.Warning);
                group = UnitMaker.AddUnitGroup(
                    mission, ships,
                    Side.Ally,
                    spawnPoint.Value.Coordinates,
                    "GroupCarrier", "UnitShip",
                    Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel));

                if (group == null)
                {
                    DebugLog.Instance.WriteLine($"Failed to create AI Carrier with ship of type \"{carrier}\".", 1, DebugLogMessageErrorLevel.Warning);
                }
                else
                {
                    //set all units against the wind
                    int    windDirection = mission.Weather.WindDirection[0];
                    double WindSpeed     = mission.Weather.WindSpeed[0];
                    double windOverDeck  = 12.8611; // 25kts
                    group.Speed = windOverDeck - WindSpeed;
                    if (group.Speed <= 2.6)
                    {
                        group.Speed = 2.57222; //5kts
                    }
                    double heading = Toolbox.ClampAngle((windDirection + 180) * Toolbox.DEGREES_TO_RADIANS);
                    foreach (DCSMissionUnitGroupUnit unit in group.Units)
                    {
                        unit.Heading = heading;
                    }
                    group.Units[0].Coordinates = group.Coordinates;
                    group.Coordinates2         = Coordinates.FromAngleAndDistance(group.Coordinates, (group.Speed * Toolbox.METERS_PER_SECOND_TO_KNOTS) * Toolbox.NM_TO_METERS, heading);
                }
                string cvnId = mission.Carriers.Length > 0? (mission.Carriers.Length + 1).ToString() : "";
                group.TACAN          = new Tacan(74 + mission.Carriers.Length, $"CVN{cvnId}");
                group.ILS            = 11 + mission.Carriers.Length;
                group.RadioFrequency = 127.5f + mission.Carriers.Length;
                mission.Carriers     = mission.Carriers.Append(group).ToArray();
            }
            return;
        }
        /// <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);
        }
Esempio n. 7
0
        /// <summary>
        /// Creates an AI escort flight group in a single-player mission.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="template">Mission template to use</param>
        /// <param name="count">Number of aircraft in the flight group</param>
        /// <param name="task">Escort task the flight group will be assigned with</param>
        /// <param name="playerCoalitionDB">Player coalition database entry</param>
        /// <returns>A <see cref="UnitFlightGroupBriefingDescription"/> describing the flight group, to be used in the briefing</returns>
        private UnitFlightGroupBriefingDescription?GenerateAIEscort(DCSMission mission, MissionTemplate template, int count, MissionTemplateMPFlightGroupTask task, DBEntryCoalition playerCoalitionDB)
        {
            if (count < 1)
            {
                return(null);           // No aircraft, nothing to generate.
            }
            // Select proper payload for the flight group according to its tasking
            UnitTaskPayload payload = GetPayloadByTask(task, null);

            string groupLua;

            string[] aircraft;

            switch (task)
            {
            default: return(null);    // Should never happen

            case MissionTemplateMPFlightGroupTask.SupportCAP:
                //groupLua = (template.GetMissionType() == MissionType.SinglePlayer) ? "GroupAircraftPlayerEscortCAP" : "GroupAircraftCAP";
                groupLua = "GroupAircraftCAP";
                aircraft = playerCoalitionDB.GetRandomUnits(UnitFamily.PlaneFighter, mission.DateTime.Decade, count, template.OptionsUnitMods);
                break;

            case MissionTemplateMPFlightGroupTask.SupportSEAD:
                //groupLua = (template.GetMissionType() == MissionType.SinglePlayer) ? "GroupAircraftPlayerEscortSEAD" : "GroupAircraftSEAD";
                groupLua = "GroupAircraftSEAD";
                aircraft = playerCoalitionDB.GetRandomUnits(UnitFamily.PlaneSEAD, mission.DateTime.Decade, count, template.OptionsUnitMods);
                break;
            }

            Coordinates position = mission.InitialPosition;

            // Player starts on runway, so escort starts in the air above the airfield (so player doesn't have to wait for them to take off)
            // OR mission is MP, so escorts start in air (but won't be spawned until at least one player takes off)
            // Add a random distance so they don't crash into each other.
            if ((template.PlayerStartLocation == PlayerStartLocation.Runway) ||
                (template.GetMissionType() != MissionType.SinglePlayer))
            {
                position += Coordinates.CreateRandom(2, 4) * Toolbox.NM_TO_METERS;
            }

            DCSMissionUnitGroup group;

            //if (template.GetMissionType() == MissionType.SinglePlayer)
            //    group = UnitMaker.AddUnitGroup(
            //        mission, aircraft,
            //        Side.Ally, position,
            //        groupLua, "UnitAircraft",
            //        Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel), 0,
            //        payload, null, mission.InitialAirbaseID, true);
            //else
            group = UnitMaker.AddUnitGroup(
                mission, aircraft,
                Side.Ally, position,
                groupLua, "UnitAircraft",
                Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel), 0,
                payload, mission.ObjectivesCenter);

            if (group == null)
            {
                DebugLog.Instance.WriteLine($"Failed to create AI escort flight group tasked with {task} with aircraft of type \"{aircraft[0]}\".", 1, DebugLogMessageErrorLevel.Warning);
                return(null);
            }

            switch (task)
            {
            default: return(null);    // Should never happen

            case MissionTemplateMPFlightGroupTask.SupportCAP:
                mission.EscortCAPGroupId = group.GroupID;
                break;

            case MissionTemplateMPFlightGroupTask.SupportSEAD:
                mission.EscortSEADGroupId = group.GroupID;
                break;
            }

            return
                (new UnitFlightGroupBriefingDescription(
                     group.Name, group.Units.Length, aircraft[0],
                     GetTaskingDescription(task, null),
                     Database.Instance.GetEntry <DBEntryUnit>(aircraft[0]).AircraftData.GetRadioAsString()));
        }
        /// <summary>
        /// Returns the total air-to-air power rating of the player's (and AI escort) flight package
        /// </summary>
        /// <param name="template">Mission template to use</param>
        /// <param name="objectiveDB">Mission objective database entry</param>
        /// <param name="aiEscortTypeCAP">Type of aircraft selected for player AI CAP escort (single-player only)</param>
        /// <param name="aiEscortTypeSEAD">Type of aircraft selected for player AI SEAD escort (single-player only)</param>
        /// <returns>Total air-to-air power rating of the flight package</returns>
        private int GetMissionPackageAirPower(MissionTemplate template, DBEntryObjective objectiveDB, string aiEscortTypeCAP, string aiEscortTypeSEAD)
        {
            int         airPowerRating = 0;
            DBEntryUnit aircraft;

            if (template.GetMissionType() == MissionType.SinglePlayer)
            {
                // Player flight group
                aircraft        = Database.Instance.GetEntry <DBEntryUnit>(template.PlayerSPAircraft);
                airPowerRating += ((aircraft != null) ? aircraft.AircraftData.AirToAirRating[1] : 1) * (template.PlayerSPWingmen + 1);
            }
            else // Mission is multi-player
            {
                foreach (MissionTemplateMPFlightGroup fg in template.PlayerMPFlightGroups)
                {
                    aircraft = Database.Instance.GetEntry <DBEntryUnit>(fg.AircraftType);

                    if (aircraft == null) // Aircraft doesn't exist
                    {
                        airPowerRating += fg.Count;
                        continue;
                    }

                    bool hasAirToAirLoadout;
                    switch (fg.Task)
                    {
                    default:     // case MissionTemplateMPFlightGroupTask.Objectives
                        if (objectiveDB.Payload == UnitTaskPayload.Default)
                        {
                            hasAirToAirLoadout = aircraft.Families.Contains(UnitFamily.PlaneFighter) || aircraft.Families.Contains(UnitFamily.PlaneInterceptor);
                        }
                        else if (objectiveDB.Payload == UnitTaskPayload.AirToAir)
                        {
                            hasAirToAirLoadout = true;
                        }
                        else
                        {
                            hasAirToAirLoadout = false;
                        }
                        break;

                    case MissionTemplateMPFlightGroupTask.SupportCAP:
                        hasAirToAirLoadout = true;
                        break;

                    case MissionTemplateMPFlightGroupTask.SupportSEAD:
                        hasAirToAirLoadout = false;
                        break;
                    }

                    airPowerRating += aircraft.AircraftData.AirToAirRating[hasAirToAirLoadout ? 1 : 0] * fg.Count;
                }
            }

            // AI CAP escort
            aircraft        = Database.Instance.GetEntry <DBEntryUnit>(aiEscortTypeCAP);
            airPowerRating += ((aircraft != null) ? aircraft.AircraftData.AirToAirRating[1] : 1) * template.PlayerEscortCAP;

            // AI SEAD escort
            aircraft        = Database.Instance.GetEntry <DBEntryUnit>(aiEscortTypeSEAD);
            airPowerRating += ((aircraft != null) ? aircraft.AircraftData.AirToAirRating[0] : 1) * template.PlayerEscortSEAD;

            return(airPowerRating);
        }