/// <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()}":"")); }
/// <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); }
/// <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); } } }
/// <summary> /// Creates an AI escort flight group in a single-player mission. /// </summary> /// <param name="mission">Mission to which generated units should be added</param> /// <param name="template">Mission template to use</param> /// <param name="count">Number of aircraft in the flight group</param> /// <param name="task">Escort task the flight group will be assigned with</param> /// <param name="playerCoalitionDB">Player coalition database entry</param> /// <returns>A <see cref="UnitFlightGroupBriefingDescription"/> describing the flight group, to be used in the briefing</returns> private UnitFlightGroupBriefingDescription?GenerateAIEscort(DCSMission mission, MissionTemplate template, int count, MissionTemplateMPFlightGroupTask task, DBEntryCoalition playerCoalitionDB) { if (count < 1) { return(null); // No aircraft, nothing to generate. } // Select proper payload for the flight group according to its tasking UnitTaskPayload payload = GetPayloadByTask(task, null); string groupLua; string[] aircraft; switch (task) { default: return(null); // Should never happen case MissionTemplateMPFlightGroupTask.SupportCAP: //groupLua = (template.GetMissionType() == MissionType.SinglePlayer) ? "GroupAircraftPlayerEscortCAP" : "GroupAircraftCAP"; groupLua = "GroupAircraftCAP"; aircraft = playerCoalitionDB.GetRandomUnits(UnitFamily.PlaneFighter, mission.DateTime.Decade, count, template.OptionsUnitMods); break; case MissionTemplateMPFlightGroupTask.SupportSEAD: //groupLua = (template.GetMissionType() == MissionType.SinglePlayer) ? "GroupAircraftPlayerEscortSEAD" : "GroupAircraftSEAD"; groupLua = "GroupAircraftSEAD"; aircraft = playerCoalitionDB.GetRandomUnits(UnitFamily.PlaneSEAD, mission.DateTime.Decade, count, template.OptionsUnitMods); break; } Coordinates position = mission.InitialPosition; // Player starts on runway, so escort starts in the air above the airfield (so player doesn't have to wait for them to take off) // OR mission is MP, so escorts start in air (but won't be spawned until at least one player takes off) // Add a random distance so they don't crash into each other. if ((template.PlayerStartLocation == PlayerStartLocation.Runway) || (template.GetMissionType() != MissionType.SinglePlayer)) { position += Coordinates.CreateRandom(2, 4) * Toolbox.NM_TO_METERS; } DCSMissionUnitGroup group; //if (template.GetMissionType() == MissionType.SinglePlayer) // group = UnitMaker.AddUnitGroup( // mission, aircraft, // Side.Ally, position, // groupLua, "UnitAircraft", // Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel), 0, // payload, null, mission.InitialAirbaseID, true); //else group = UnitMaker.AddUnitGroup( mission, aircraft, Side.Ally, position, groupLua, "UnitAircraft", Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel), 0, payload, mission.ObjectivesCenter); if (group == null) { DebugLog.Instance.WriteLine($"Failed to create AI escort flight group tasked with {task} with aircraft of type \"{aircraft[0]}\".", 1, DebugLogMessageErrorLevel.Warning); return(null); } switch (task) { default: return(null); // Should never happen case MissionTemplateMPFlightGroupTask.SupportCAP: mission.EscortCAPGroupId = group.GroupID; break; case MissionTemplateMPFlightGroupTask.SupportSEAD: mission.EscortSEADGroupId = group.GroupID; break; } return (new UnitFlightGroupBriefingDescription( group.Name, group.Units.Length, aircraft[0], GetTaskingDescription(task, null), Database.Instance.GetEntry <DBEntryUnit>(aircraft[0]).AircraftData.GetRadioAsString())); }
/// <summary> /// 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()); }
/// <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]); }
/// <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++; } }
/// <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; }
/// <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()); }
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 }