public void CheckTemplate(MissionTemplate template)
        {
#if !DEBUG
            if (template.GetPlayerCount() < 1)
            {
                throw new HQ4DCSException($"No player flight groups. A mission must have at least one player flight group.");
            }
#endif

            CheckMissingDefinitions(template);
            CheckCoalitions(template);
            CheckTheaterNodes(template);

            //mission.Countries[(int)Coalition.Red].Except(mission.Countries[(int)Coalition.Blue])

            //// Copy blue and red coalitions' countries list
            //mission.Countries[(int)Coalition.Blue] = coalitions[(int)Coalition.Blue].Countries.ToArray();
            //if (mission.Countries[(int)Coalition.Blue].Length == 0)
            //    throw new Exception("Blue coalition has no countries.");

            //mission.Countries[(int)Coalition.Red] = coalitions[(int)Coalition.Red].Countries.ToArray();
            //if (mission.Countries[(int)Coalition.Red].Length == 0)
            //    throw new Exception("Red coalition has no countries.");

            //// Remove blue countries from red coalition to make sure no country belongs to both.
            //mission.Countries[(int)Coalition.Red] = mission.Countries[(int)Coalition.Red].Except(mission.Countries[(int)Coalition.Blue]).ToArray();
            //if (mission.Countries[(int)Coalition.Red].Length == 0)
            //    throw new Exception("Blue and red coalitions share the same countries.");

            //// TODO: check template nodes
            //foreach (HQTemplateNode n in template.Nodes.Values)
            //{
            //}
        }
コード例 #2
0
        /// <summary>
        /// Generates the list of tasks for the mission.
        /// </summary>
        /// <param name="mission"></param>
        /// <param name="template"></param>
        /// <param name="missionTask"></param>
        //public void GenerateMissionTasks(HQMission mission, MissionTemplate template, DefinitionMissionObjective missionTask)
        //{
        //    HQDebugLog.Instance.Log("Generating mission briefing objectives...");

        //    mission.BriefingTasks.Clear();

        //    mission.BriefingTasks.Add(Language.GetStringRandom("BriefingCommon", $"Task.TakeOff").Replace("$AIRBASE$", mission.Airbases[0].Name));

        //    foreach (HQMissionObjectiveLocation o in mission.Objectives)
        //        mission.BriefingTasks.Add(Language.GetStringRandom("BriefingMission", $"Task.{missionTask.BriefingTask}").Replace("$NAME$", o.Name));

        //    mission.BriefingTasks.Add(Language.GetStringRandom("BriefingCommon", $"Task.Land").Replace("$AIRBASE$", mission.Airbases[1].Name));

        //    HQDebugLog.Instance.Log("");
        //}

        //public void GenerateMissionRemarks(HQMission mission, MissionTemplate template, DefinitionMissionObjective missionTask)
        //{
        //    HQDebugLog.Instance.Log("Generating mission briefing remarks...");

        //    mission.BriefingRemarks.Clear();

        //    foreach (string s in missionTask.BriefingRemarks)
        //        mission.BriefingRemarks.Add(Language.GetStringRandom("BriefingMission", $"Remark.{s}"));

        //    HQDebugLog.Instance.Log("");
        //}

        public void GenerateRawTextBriefing(DCSMission mission, MissionTemplate template)
        {
            DebugLog.Instance.Log("Generating raw text MIZ briefing...");

            string text = "";

            if (template.GetPlayerCount() == 1)
            {
                text += $"{Language.GetString("Briefing", "Subtitle.SinglePlayer")}\n\n";
            }
            else
            {
                text += $"{Language.GetString("Briefing", "Subtitle.PvE").Replace("$PLAYERS$", HQTools.ValToString(template.GetPlayerCount()))}\n\n";
            }

            text += mission.BriefingDescription + "\n\n";

            // Tasks
            text += $"{Language.GetString("Briefing", "Section.Tasks").ToUpperInvariant()}{Language.Semicolon}\n";
            foreach (string t in mission.BriefingTasks)
            {
                text += $"- {t}\n";
            }
            if (mission.BriefingTasks.Count == 0)
            {
                text += $"- {Language.GetString("Briefing", "Misc.None")}\n";
            }
            text += "\n";

            // Remarks
            text += $"{Language.GetString("Briefing", "Section.Remarks").ToUpperInvariant()}{Language.Semicolon}\n";
            if (mission.BriefingImperialUnits)
            {
                text += $"- {Language.GetString("Briefing", "Remark.TotalFlightPlanNM", "Distance", (mission.TotalFlightPlanDistance * HQTools.METERS_TO_NM).ToString("F0"))}\n";
            }
            else
            {
                text += $"- {Language.GetString("Briefing", "Remark.TotalFlightPlanKM", "Distance", (mission.TotalFlightPlanDistance / 1000.0).ToString("F0"))}\n";
            }
            foreach (string t in mission.BriefingRemarks)
            {
                text += $"- {t}\n";
            }
            text += "\n";

            // Flight package
            //text += $"{GetString("Section.Package").ToUpperInvariant()}{Language.Semicolon}\n";
            //foreach (HQMissionBriefingFlightGroup fg in (from HQMissionBriefingFlightGroup f in mission.BriefingFlightPackage where !f.IsSupport select f).OrderBy(x => x.Task))
            //    text += $"- {fg.Callsign} ({fg.UnitCount}x {GetUnitName(fg.UnitType)}), {HQTools.ValToString(fg.Frequency, "F1")} Mhz\n";

            // Make sure endlines are in the proper format (escaped LF) or it can cause bugs.
            text = text.Replace("\r\n", "\n").Trim(' ', '\n', '\t').Replace("\n", "\\\n");
            mission.BriefingRawText = text;

            DebugLog.Instance.Log("");
        }
コード例 #3
0
        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);
        }
コード例 #4
0
        public void GenerateHTMLBriefing(DCSMission mission, MissionTemplate template /*, DefinitionMissionObjective missionTask*/)
        {
            DebugLog.Instance.Log("Generating HTML briefing...");

            string htmlTemplateFile = HQTools.PATH_INCLUDE + "Briefing.html";
            string htmlTemplate     = File.Exists(htmlTemplateFile) ? File.ReadAllText(htmlTemplateFile) : "$BRIEFING$";
            string html             = "";

            string semiColon = Language.GetString("Misc", "Semicolon");

            html += $"<h1>{mission.BriefingName}</h1>";

            if (template.GetPlayerCount() == 1)
            {
                html += $"<h3>{Language.GetString("Briefing", "Subtitle.SinglePlayer")}</h3>";
            }
            else
            {
                html += $"<h3>{Language.GetString("Briefing", "Subtitle.PvE").Replace("$PLAYERS$", HQTools.ValToString(template.GetPlayerCount()))}</h3>";
            }

            // Header (objective/task, date, time...)
            html += "<p>";
            html += $"<strong>{Language.GetString("Briefing", "Section.Date")}{semiColon}</strong> {FormatDate(mission, true)}<br />";
            html += $"<strong>{Language.GetString("Briefing", "Section.Time")}{semiColon}</strong> {FormatTime(mission, true)}<br />";
            html += $"<strong>{Language.GetString("Briefing", "Section.Weather")}{semiColon}</strong> {Language.GetEnum(mission.WeatherLevel)}<br />";
            html += $"<strong>{Language.GetString("Briefing", "Section.Wind")}{semiColon}</strong> {Language.GetEnum(mission.WindLevel)}";
            html += $" ({mission.WeatherWindSpeedAverage.ToString("F0")} m/s)";
            html += "</p>";

            // Description
            html += $"<h2>{Language.GetString("Briefing", "Section.Description")}</h2>";
            html += $"<p>{mission.BriefingDescription}</p>";

            // Tasks
            html += $"<h2>{Language.GetString("Briefing", "Section.Tasks")}</h2>";
            html += "<ul>";
            foreach (string task in mission.BriefingTasks)
            {
                html += $"<li>{task}</li>";
            }
            if (mission.BriefingTasks.Count == 0)
            {
                html += $"<li>{Language.GetString("Briefing", "Misc.None")}</li>";
            }
            html += "</ul>";

            // Remarks
            html += $"<h2>{Language.GetString("Briefing", "Section.Remarks")}</h2>";
            html += "<ul>";
            if (mission.BriefingImperialUnits)
            {
                html += $"<li>{Language.GetString("Briefing", "Remark.TotalFlightPlanNM", "Distance", (mission.TotalFlightPlanDistance * HQTools.METERS_TO_NM).ToString("F0"))}</li>";
            }
            else
            {
                html += $"<li>{Language.GetString("Briefing", "Remark.TotalFlightPlanKM", "Distance", (mission.TotalFlightPlanDistance / 1000.0).ToString("F0"))}</li>";
            }
            foreach (string remark in mission.BriefingRemarks)
            {
                html += $"<li>{remark}</li>";
            }
            html += "</ul>";

            // mission.FlightPlanLength

            // Airbases
            //html += $"<h2>{Language.GetString("BriefingCommon", "Airbases")}</h2>";
            //html += "<table>";
            //html += "<tr><th></th><th>Airbase</th><th>TCN</th><th>ATC</th><th>RWY</th><th>ILS</th></tr>"; // FIXME: Localize
            //{
            //    string header; // FIXME: Localize
            //    if (i == 0) header = "DEP";
            //    else if (i == 1) header = "ARR";
            //    else header = "NAV";

            //    DefinitionTheaterAirbase airbase = mission.Airbases[i];

            //    html += $"<tr><th>{header}</th><td>{airbase.Name}</td><td>{airbase.TACAN}</td><td>{HQTools.ValToString(airbase.ATC, "F1")}</td><td>{airbase.Runways[0]}</td><td>{airbase.ILS}</td></tr>";
            //}
            //html += "</table>";

            // Flight package
            html += $"<h2>{Language.GetString("Briefing", "Section.FlightPackage")}</h2>";
            html += "<table>";
            html += $"<tr><th>{Language.GetString("Briefing", "Table.Header.Callsign")}</th><th>{Language.GetString("Briefing", "Table.Header.Aircraft")}</th><th>{Language.GetString("Briefing", "Table.Header.Task")}</th><th>{Language.GetString("Briefing", "Table.Header.Airbase")}</th><th>{Language.GetString("Briefing", "Table.Header.Radio")}</th></tr>";
            foreach (DCSMissionBriefingFlightGroup fg in (from DCSMissionBriefingFlightGroup f in mission.BriefingFlightPackage where !f.IsSupport select f).OrderBy(x => x.Task))
            {
                html += // TODO: localize fg.Task
                        $"<tr><td>{fg.Callsign}</td><td>{fg.UnitCount}x {GetUnitName(fg.UnitType)}</td>" +
                        $"<td>{fg.Task}</td><td>{fg.AirbaseName}</td>" +
                        $"<td>{HQTools.ValToString(fg.Frequency, "F1")}</td></tr>";
            }
            html += "</table>";

            // Support flight groups
            //html += $"<h2>{Language.GetString("BriefingCommon", "Support")}</h2>";
            //html += "<table>";
            //html += "<tr><th></th><th>Aircraft</th><th>Callsign</th><th>UHF</th><th>TACAN</th></tr>"; // FIXME: Localize
            //foreach (HQMissionBriefingFlightGroup fg in (from HQMissionBriefingFlightGroup f in mission.BriefingFlightPackage where f.IsSupport select f).OrderBy(x => x.Task))
            //    html += $"<tr><th>{fg.Task}</th><td>{GetUnitName(fg.UnitType)}</td><td>{fg.Callsign}</td><td>{HQTools.ValToString(fg.Frequency, "F1")}</td><td>{fg.TACAN}</td></tr>";
            //html += "</table>";

            // Flight plan
            //html += $"<h2>{Language.GetString("BriefingCommon", "FlightPlan")}</h2>";
            //html += "<table>";
            //html += "<tr><th></th><th>ID</th><th>Action</th><th>Dist</th><th>Alt</th></tr>"; // FIXME: Localize
            //double totalWpDist = 0.0;
            //for (int i = 0; i < mission.Waypoints.Count; i++)
            //{
            //    HQMissionWaypoint wp = mission.Waypoints[i];
            //    if (i > 0) totalWpDist += wp.Coordinates.GetDistanceFrom(mission.Waypoints[i - 1].Coordinates);

            //    if (template.BriefingUnits == SpeedAndDistanceUnit.Metric)
            //        html += $"<tr><th>{i + 1}</th><td>{wp.Name}</td><td>NO ACTION FIXME</td><td>{((i == 0) ? "0" : Math.Round(totalWpDist / 1000.0).ToString("F0"))} Km</td><td>{wp.AltitudeMultiplier * 2000}</td></tr>";
            //    else
            //        html += $"<tr><th>{i + 1}</th><td>{wp.Name}</td><td>NO ACTION FIXME</td><td>{((i == 0) ? "0" : Math.Round(totalWpDist * HQTools.METERS_TO_NM).ToString("F0"))} nm</td><td>{wp.AltitudeMultiplier * 2000}</td></tr>";
            //}
            //html += "</table>";

            mission.BriefingHTML = htmlTemplate.Replace("$BRIEFING$", html);

            DebugLog.Instance.Log("");
        }
コード例 #5
0
        public DCSMission Generate(MissionTemplate template, out string errorMessage)
        {
            int i;

            errorMessage = "";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                //mission.Scripts

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

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

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

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

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

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

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

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

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

                mission.Dispose();
                mission = null;
            }

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