/// <summary>
        /// Spawn a group of support units.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="allyCoalitionDB">Ally coalition database entry</param>
        /// <param name="unitFamily">Family of support unit to spawn</param>
        /// <param name="unitMods">Unit mods selected units can belong to</param>
        /// <param name="TACAN">TACAN info for the unit, if any</param>
        private UnitFlightGroupBriefingDescription AddSupportUnit(DCSMission mission, DBEntryCoalition allyCoalitionDB, UnitFamily unitFamily, string[] unitMods, Tacan TACAN = null)
        {
            DebugLog.Instance.WriteLine($"Adding {unitFamily} support unit...", 1);

            string[] validUnitTypes = allyCoalitionDB.GetRandomUnits(unitFamily, mission.DateTime.Decade, 1, unitMods, false);

            if (validUnitTypes.Length == 0)
            {
                DebugLog.Instance.WriteLine($"No support unit found for this role in coalition \"{allyCoalitionDB.ID}\"", 2);
                return(new UnitFlightGroupBriefingDescription()); // Empty FG info will automatically be discarded
            }

            string groupLua;

            switch (unitFamily)
            {
            case UnitFamily.PlaneAWACS:
                groupLua = "GroupAircraftAWACS";
                break;

            case UnitFamily.PlaneTankerBasket:
            case UnitFamily.PlaneTankerBoom:
                groupLua = "GroupAircraftTanker";
                break;

            default:                                              // Should never happen
                return(new UnitFlightGroupBriefingDescription()); // Empty FG info will automatically be discarded
            }

            Coordinates location  = GeneratorTools.GetCoordinatesOnFlightPath(mission, .5) + Coordinates.CreateRandom(8, 12) * Toolbox.NM_TO_METERS;
            Coordinates location2 = location + Coordinates.CreateRandom(12, 20) * Toolbox.NM_TO_METERS;

            string unitType = Toolbox.RandomFrom(validUnitTypes);

            DCSMissionUnitGroup group = UnitMaker.AddUnitGroup(
                mission, new string[] { unitType },
                Side.Ally, location,
                groupLua, "UnitAircraft",
                DCSSkillLevel.Excellent, 0,
                UnitTaskPayload.Default,
                location2);

            if (group == null)
            {
                return(new UnitFlightGroupBriefingDescription()); // Empty FG info will automatically be discarded
            }
            group.TACAN = TACAN;
            mission.AircraftSpawnQueue.Insert(0, new DCSMissionAircraftSpawnQueueItem(group.GroupID, true)); // Support aircraft must be activated first

            return(new UnitFlightGroupBriefingDescription(
                       group.Name, group.Units.Length, unitType,
                       (unitFamily == UnitFamily.PlaneAWACS) ? "Early warning" : "Refueling",
                       Database.Instance.GetEntry <DBEntryUnit>(unitType).AircraftData.GetRadioAsString(), TACAN != null? $"TACAN: {TACAN.ToString()}":""));
        }
Beispiel #2
0
        /// <summary>
        /// Main unit generation method.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="objectiveDB">Mission objective database entry</param>
        /// <param name="coalitionDB">Enemy coalition database entry</param>
        /// <param name="coalition">Coalition of the spawn points air defense must be spawned at, or null to spawn them anywhere</param>
        /// <param name="unitMods">Unit mods the units can belong to</param>
        public void CreateUnitGroups(DCSMission mission, DBEntryObjective objectiveDB, DBEntryCoalition coalitionDB, Coalition?coalition, string[] unitMods)
        {
            foreach (AirDefenseRange airDefenseRange in (AirDefenseRange[])Enum.GetValues(typeof(AirDefenseRange)))
            {
                DebugLog.Instance.WriteLine($"Adding {airDefenseRange} air defense", 1);
                if (
                    ((airDefenseRange == AirDefenseRange.ShortRange) && objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.NoEnemyAirDefenseShort)) ||
                    ((airDefenseRange == AirDefenseRange.MediumRange) && objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.NoEnemyAirDefenseMedium)) ||
                    ((airDefenseRange == AirDefenseRange.LongRange) && objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.NoEnemyAirDefenseLong)))
                {
                    DebugLog.Instance.WriteLine($"{airDefenseRange} air defense disabled for this mission objective type, not spawning any units", 1);
                    continue;
                }

                AddAirDefenseUnits(mission, airDefenseRange, coalitionDB, coalition, unitMods);
            }
        }
        /// <summary>
        /// Main unit generation method.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="allyCoalitionDB">Ally coalition database entry</param>
        /// <param name="unitMods">Unit mods selected units can belong to</param>
        public UnitFlightGroupBriefingDescription[] CreateUnitGroups(DCSMission mission, DBEntryCoalition allyCoalitionDB, string[] unitMods)
        {
            List <UnitFlightGroupBriefingDescription> briefingFGList = new List <UnitFlightGroupBriefingDescription>
            {
                AddSupportUnit(mission, allyCoalitionDB, UnitFamily.PlaneTankerBasket, unitMods, new Tacan(29, "TKR", AA: true)),
                AddSupportUnit(mission, allyCoalitionDB, UnitFamily.PlaneTankerBoom, unitMods, new Tacan(92, "TKR", AA: true)),
                AddSupportUnit(mission, allyCoalitionDB, UnitFamily.PlaneAWACS, unitMods) // AWACS must be added last, so it its inserted first into the spawning queue
            };

            return((from UnitFlightGroupBriefingDescription fg in briefingFGList where !string.IsNullOrEmpty(fg.Type) select fg).ToArray());
        }
        /// <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);
        }
Beispiel #5
0
        /// <summary>
        /// Add surface-to-air defense groups.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="template">Mission template to use</param>
        /// <param name="airDefenseRange">Air-defense range category</param>
        /// <param name="enemyCoalitionDB">Enemy coalition database entry</param>
        /// <param name="coalition">Coalition of the spawn points air defense must be spawned at, or null to spawn them anywhere</param>
        /// <param name="unitMods">Unit mods the units can belong to</param>
        private void AddAirDefenseUnits(DCSMission mission, AirDefenseRange airDefenseRange, DBEntryCoalition enemyCoalitionDB, Coalition?coalition, string[] unitMods)
        {
            // Get the proper number of groups
            int groupCount = Database.Instance.Common.
                             EnemyAirDefense[(int)airDefense].GroupsInArea[(int)airDefenseRange].GetValue();

            if (groupCount < 1)
            {
                return;                  // No groups to add, no need to go any further
            }
            DCSMissionUnitGroupFlags flags = optionsShowEnemyUnits;

            UnitFamily[] unitFamilies;
            TheaterLocationSpawnPointType[] validSpawnPoints;
            switch (airDefenseRange)
            {
            default:     // case AirDefenseRange.ShortRange:
                unitFamilies     = new UnitFamily[] { UnitFamily.VehicleAAA, UnitFamily.VehicleAAAStatic, UnitFamily.VehicleInfantryMANPADS, UnitFamily.VehicleSAMShort, UnitFamily.VehicleSAMShort, UnitFamily.VehicleSAMShortIR, UnitFamily.VehicleSAMShortIR };
                validSpawnPoints = new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.LandSmall, TheaterLocationSpawnPointType.LandMedium, TheaterLocationSpawnPointType.LandLarge };
                break;

            case AirDefenseRange.MediumRange:
                unitFamilies     = new UnitFamily[] { UnitFamily.VehicleSAMMedium };
                validSpawnPoints = new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.LandMedium, TheaterLocationSpawnPointType.LandLarge };
                break;

            case AirDefenseRange.LongRange:
                unitFamilies     = new UnitFamily[] { UnitFamily.VehicleSAMLong };
                validSpawnPoints = new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.LandLarge };
                break;
            }

            for (int i = 0; i < groupCount; i++)
            {
                // Find spawn point at the proper distance from the objective(s), but not to close from starting airbase
                DBEntryTheaterSpawnPoint?spawnPoint =
                    UnitMaker.SpawnPointSelector.GetRandomSpawnPoint(
                        validSpawnPoints,
                        centerPoint,
                        distsFromCenter[(int)airDefenseRange],
                        opposingPoint,
                        new MinMaxD(minDistFromOpposingPoint[(int)airDefenseRange], 99999),
                        coalition);

                // No spawn point found, stop here.
                if (!spawnPoint.HasValue)
                {
                    DebugLog.Instance.WriteLine($"No spawn point found for {airDefenseRange} air defense unit groups", 1, DebugLogMessageErrorLevel.Warning);
                    return;
                }

                string[] units = enemyCoalitionDB.GetRandomUnits(Toolbox.RandomFrom(unitFamilies), mission.DateTime.Decade, 1, unitMods);

                DCSMissionUnitGroup group = UnitMaker.AddUnitGroup(
                    mission, units, ally? Side.Ally : Side.Enemy,
                    spawnPoint.Value.Coordinates,
                    "GroupVehicle", "UnitVehicle",
                    Toolbox.BRSkillLevelToDCSSkillLevel(skillLevel),
                    flags);

                if (group == null)
                {
                    DebugLog.Instance.WriteLine($"Failed to add {airDefenseRange} air defense unit group of type {units[0]}", 1, DebugLogMessageErrorLevel.Warning);
                }
            }
        }
Beispiel #6
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()));
        }
Beispiel #7
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());
        }
Beispiel #8
0
        /// <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 DBEntryUnit GenerateCarrier(DCSMission mission, MissionTemplate template, DBEntryCoalition playerCoalitionDB, int windDirection0)
        {
            if (string.IsNullOrEmpty(template.PlayerCarrier))
            {
                return(null);
            }

            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[] { template.PlayerCarrier };
            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 \"{template.PlayerCarrier}\".", 1, DebugLogMessageErrorLevel.Warning);
            }
            else
            {
                //set all units against the wind
                double heading = Toolbox.ClampAngle((windDirection0 + 180) * Toolbox.DEGREES_TO_RADIANS);
                foreach (DCSMissionUnitGroupUnit unit in group.Units)
                {
                    unit.Heading = heading;
                }
            }

            mission.Carrier = group.Units[0];
            return((from DBEntryUnit unit in Database.Instance.GetAllEntries <DBEntryUnit>()
                    where unit.ID == template.PlayerCarrier
                    select unit).ToArray()[0]);
        }
Beispiel #9
0
        /// <summary>
        /// Adds "embedded" short-range air-defense units to an unit group.
        /// </summary>
        /// <param name="units">Array of units in the group</param>
        /// <param name="airDefenseLevel">Air defense level setting to use, from the mission template</param>
        /// <param name="coalitionDB">Database entry for the coalition to use for air-defense units</param>
        /// <param name="decade">Decade during which the units must be operated</param>
        /// <param name="unitMods">Unit mods the units can belong to</param>
        /// <returns>Updated array of units with added embedded air defense units</returns>
        public static string[] AddEmbeddedAirDefense(string[] units, AmountN airDefenseLevel, DBEntryCoalition coalitionDB, Decade decade, string[] unitMods)
        {
            int airDefenseLevelInt = (int)airDefenseLevel.Get();

            // No luck this time, don't add anything
            if (Toolbox.RandomDouble() >= Database.Instance.Common.EnemyAirDefense[airDefenseLevelInt].EmbeddedChance)
            {
                return(units);
            }

            // Convert the unit array to an open-ended list so that units can be added
            List <string> unitsList = new List <string>(units);

            // Add some air defense units
            int embeddedCount = Database.Instance.Common.EnemyAirDefense[airDefenseLevelInt].EmbeddedUnitCount.GetValue();

            for (int i = 0; i < embeddedCount; i++)
            {
                unitsList.AddRange(
                    coalitionDB.GetRandomUnits(Toolbox.RandomFrom(EMBEDDED_AIR_DEFENSE_FAMILIES), decade, 1, unitMods));
            }

            if (unitsList.Count == 0)
            {
                return(new string[0]);
            }
            // Randomize unit order so embbedded air defense units are not always at the end of the group
            // but keep unit #0 at its place, because the first unit of the group is used to determine the group type, and we don't want
            // a artillery platoon to be named "air defense bataillon" because the first unit is a AAA.
            string unit0 = unitsList[0];

            unitsList.RemoveAt(0);
            unitsList = unitsList.OrderBy(x => Toolbox.RandomInt()).ToList();
            unitsList.Insert(0, unit0);

            return(unitsList.ToArray());
        }
        /// <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="enemyCoalitionDB">Enemy coalition 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>
        public void CreateUnitGroups(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB, DBEntryCoalition enemyCoalitionDB, string aiEscortTypeCAP, string aiEscortTypeSEAD)
        {
            if (objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.NoEnemyCAP))
            {
                DebugLog.Instance.WriteLine("Enemy CAP disabled for this mission objective type, not spawning any units", 1);
                return;
            }
            int totalAirForcePower =
                (int)(GetMissionPackageAirPower(template, objectiveDB, aiEscortTypeCAP, aiEscortTypeSEAD) *
                      Database.Instance.Common.EnemyCAPRelativePower[(int)template.SituationEnemyAirForce.Get()]);

            DebugLog.Instance.WriteLine($"Enemy air power set to {totalAirForcePower}...", 1);

            DCSMissionUnitGroupFlags flags = template.Realism.Contains(RealismOption.HideEnemyUnits) ? DCSMissionUnitGroupFlags.Hidden : 0;

            int aircraftCount = 0;
            int groupCount    = 0;

            while (totalAirForcePower > 0)
            {
                string[] unitTypes = enemyCoalitionDB.GetRandomUnits(UnitFamily.PlaneFighter, mission.DateTime.Decade, 1, template.UnitMods);
                if (unitTypes.Length == 0)
                {
                    DebugLog.Instance.WriteLine("No valid units found for enemy fighter patrols.", 1, DebugLogMessageErrorLevel.Warning);
                    break;
                }

                // Find spawn point at the proper distance from the objective(s), but not to close from starting airbase
                DBEntryTheaterSpawnPoint?spawnPoint =
                    UnitMaker.SpawnPointSelector.GetRandomSpawnPoint(
                        null,
                        mission.ObjectivesCenter, Database.Instance.Common.EnemyCAPDistanceFromObjectives,
                        mission.InitialPosition, new MinMaxD(Database.Instance.Common.EnemyCAPMinDistanceFromTakeOffLocation, 99999),
                        GeneratorTools.GetEnemySpawnPointCoalition(template));

                if (!spawnPoint.HasValue) // No spawn point found, stop here.
                {
                    DebugLog.Instance.WriteLine("No spawn point found for enemy fighter patrol group.", 1, DebugLogMessageErrorLevel.Warning);
                    break;
                }

                int unitPower = Database.Instance.GetEntry <DBEntryUnit>(unitTypes[0]).AircraftData.AirToAirRating[1];
                int groupSize = 1;
                if (totalAirForcePower >= unitPower * 2)
                {
                    groupSize = 2;
                }
                if (Toolbox.RandomDouble() < .3)
                {
                    if (totalAirForcePower >= unitPower * 4)
                    {
                        groupSize = 4;
                    }
                    else if (totalAirForcePower >= unitPower * 3)
                    {
                        groupSize = 3;
                    }
                }
                totalAirForcePower -= unitPower * groupSize;

                DCSMissionUnitGroup group = UnitMaker.AddUnitGroup(
                    mission, Enumerable.Repeat(unitTypes[0], groupSize).ToArray(),
                    Side.Enemy, spawnPoint.Value.Coordinates,
                    "GroupAircraftCAP", "UnitAircraft",
                    Toolbox.BRSkillLevelToDCSSkillLevel(template.SituationEnemySkillLevelAir),
                    flags, UnitTaskPayload.AirToAir,
                    mission.ObjectivesCenter + Coordinates.CreateRandom(20, 40) * Toolbox.NM_TO_METERS);

                if (group == null)
                {
                    DebugLog.Instance.WriteLine($"Failed to add a group of {groupSize}× {unitTypes[0]} at {spawnPoint.Value.Coordinates}", 1, DebugLogMessageErrorLevel.Warning);
                }
                else
                {
                    DebugLog.Instance.WriteLine($"Added a group of {groupSize}× {unitTypes[0]} at {spawnPoint.Value.Coordinates}");
                    mission.AircraftSpawnQueue.Add(new DCSMissionAircraftSpawnQueueItem(group.GroupID, template.SituationEnemyCAPOnStationChance.RollChance()));
                }

                aircraftCount += groupSize;
                groupCount++;
            }
        }
Beispiel #11
0
        /// <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.MissionType == MissionType.SinglePlayer)
            {
                if (string.IsNullOrEmpty(template.PlayerFlightGroups[0].Carrier))
                {
                    return;
                }
                carriers = carriers.Append(template.PlayerFlightGroups[0].Carrier).ToArray();
            }
            else
            {
                carriers = template.PlayerFlightGroups.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.UnitMods)[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.SituationFriendlyAISkillLevel));

                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;
        }
Beispiel #12
0
        /// <summary>
        /// Main unit generation method.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="allyCoalitionDB">Ally coalition database entry</param>
        /// <param name="unitMods">Unit mods selected units can belong to</param>
        public UnitFlightGroupBriefingDescription[] CreateUnitGroups(DCSMission mission, DBEntryCoalition allyCoalitionDB, string[] unitMods)
        {
            List <UnitFlightGroupBriefingDescription> briefingFGList = new List <UnitFlightGroupBriefingDescription>
            {
                AddSupportUnit(mission, allyCoalitionDB, UnitFamily.PlaneTankerBasket, unitMods, new Tacan(47, "TKR", 1134000000)), // TACAN choice due to https://forums.eagle.ru/topic/165047-hornet-mini-updates/page/6/?tab=comments#comment-3803291
                AddSupportUnit(mission, allyCoalitionDB, UnitFamily.PlaneTankerBoom, unitMods, new Tacan(48, "TKR", 1135000000)),
                AddSupportUnit(mission, allyCoalitionDB, UnitFamily.PlaneAWACS, unitMods)                                           // AWACS must be added last, so it its inserted first into the spawning queue
            };

            return((from UnitFlightGroupBriefingDescription fg in briefingFGList where !string.IsNullOrEmpty(fg.Type) select fg).ToArray());
        }
Beispiel #13
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);
        }
        /// <summary>
        /// Spawn a group of support units.
        /// </summary>
        /// <param name="mission">Mission to which generated units should be added</param>
        /// <param name="enemyCoalitionDB">Ally coalition database entry</param>
        /// <param name="unitFamily">Family of support unit to spawn</param>
        /// <param name="unitMods">Unit mods selected units can belong to</param>
        /// <param name="TACAN">TACAN info for the unit, if any</param>
        private void AddSupportUnit(DCSMission mission, MissionTemplate template, DBEntryCoalition enemyCoalitionDB, UnitFamily unitFamily, string[] unitMods, Tacan TACAN = null)
        {
            DebugLog.Instance.WriteLine($"Adding {unitFamily} enemy support unit...", 1);

            string[] validUnitTypes = enemyCoalitionDB.GetRandomUnits(unitFamily, mission.DateTime.Decade, 1, unitMods, false);

            if (validUnitTypes.Length == 0)
            {
                DebugLog.Instance.WriteLine($"No support unit found for this role in coalition \"{enemyCoalitionDB.ID}\"", 2);
                return; // Empty FG info will automatically be discarded
            }

            string groupLua;

            switch (unitFamily)
            {
            case UnitFamily.PlaneAWACS:
                groupLua = "GroupAircraftAWACSMortal";
                break;

            case UnitFamily.PlaneTankerBasket:
            case UnitFamily.PlaneTankerBoom:
                groupLua = "GroupAircraftTankerMortal";
                break;

            default:     // Should never happen
                return;  // Empty FG info will automatically be discarded
            }

            DBEntryTheaterSpawnPoint?spawnPoint =
                UnitMaker.SpawnPointSelector.GetRandomSpawnPoint(
                    null,
                    mission.ObjectivesCenter, Database.Instance.Common.EnemyCAPDistanceFromObjectives,
                    mission.InitialPosition, new MinMaxD(Database.Instance.Common.EnemyCAPMinDistanceFromTakeOffLocation, 99999),
                    GeneratorTools.GetEnemySpawnPointCoalition(template));

            if (!spawnPoint.HasValue) // No spawn point found, stop here.
            {
                DebugLog.Instance.WriteLine("No spawn point found for enemy fighter patrol group.", 1, DebugLogMessageErrorLevel.Warning);
                return;
            }

            Coordinates location2 = spawnPoint.Value.Coordinates + Coordinates.CreateRandom(12, 20) * Toolbox.NM_TO_METERS;

            string unitType = Toolbox.RandomFrom(validUnitTypes);

            DCSMissionUnitGroup group = UnitMaker.AddUnitGroup(
                mission, new string[] { unitType },
                Side.Enemy, spawnPoint.Value.Coordinates,
                groupLua, "UnitAircraft",
                Toolbox.BRSkillLevelToDCSSkillLevel(template.SituationEnemySkillLevelAir), 0,
                UnitTaskPayload.Default,
                location2);

            if (group == null)
            {
                return; // Empty FG info will automatically be discarded
            }
            group.TACAN = TACAN;
            mission.AircraftSpawnQueue.Insert(0, new DCSMissionAircraftSpawnQueueItem(group.GroupID, true)); // Support aircraft must be activated first
        }