/// <summary> /// Constructor. /// </summary> /// <param name="unitMaker">Unit maker class to use to generate units</param> public MissionGeneratorAirDefense(UnitMaker unitMaker, bool _ally, MissionTemplate template, DCSMission mission) { UnitMaker = unitMaker; ally = _ally; if (ally) { airDefense = template.SituationFriendlyAirDefense.Get(); centerPoint = mission.InitialPosition; opposingPoint = mission.ObjectivesCenter; distsFromCenter = Database.Instance.Common.AllyAirDefenseDistanceFromTakeOffLocation; minDistFromOpposingPoint = Database.Instance.Common.AllyAirDefenseDistanceFromObjectives; skillLevel = template.SituationFriendlyAISkillLevel; optionsShowEnemyUnits = 0; return; } airDefense = template.SituationEnemyAirDefense.Get(); centerPoint = mission.ObjectivesCenter; opposingPoint = mission.InitialPosition; distsFromCenter = Database.Instance.Common.EnemyAirDefenseDistanceFromObjectives; minDistFromOpposingPoint = Database.Instance.Common.EnemyAirDefenseDistanceFromTakeOffLocation; skillLevel = template.SituationEnemySkillLevelGround; optionsShowEnemyUnits = template.Realism.Contains(RealismOption.HideEnemyUnits) ? DCSMissionUnitGroupFlags.Hidden : 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); } } }
/// <summary> /// Adds media files, scripts and units associated with mission features. /// </summary> /// <param name="mission">Mission to which features and extensions should be added</param> /// <param name="template">Mission template to use</param> /// <param name="objectiveDB">Mission objective database entry</param> /// <param name="coalitionsDB">Coalitions database entries</param> public void GenerateExtensionsAndFeatures(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB, DBEntryCoalition[] coalitionsDB) { int i, j; DBEntryExtension[] extensions = Database.Instance.GetEntries <DBEntryExtension>(template.ScriptExtensions); foreach (DBEntryExtension extension in extensions) { AddIncludedFiles(mission, extension); } DBEntryMissionFeature[] features = Database.Instance.GetEntries <DBEntryMissionFeature>(objectiveDB.MissionFeatures); foreach (DBEntryMissionFeature feature in features) { AddIncludedFiles(mission, feature); // No unit family in the unit group, so not unit group to add if (feature.UnitGroup.Families.Length == 0) { continue; } Side side = feature.UnitGroup.Flags.HasFlag(DBUnitGroupFlags.Friendly) ? Side.Ally : Side.Enemy; UnitFamily unitFamily = Toolbox.RandomFrom(feature.UnitGroup.Families); // Pick units string[] units = coalitionsDB[(int)((side == Side.Ally) ? mission.CoalitionPlayer : mission.CoalitionEnemy)].GetRandomUnits( unitFamily, mission.DateTime.Decade, feature.UnitGroup.Count.GetValue(), template.UnitMods); DCSSkillLevel skillLevel; if (side == Side.Ally) { skillLevel = Toolbox.BRSkillLevelToDCSSkillLevel(template.SituationFriendlyAISkillLevel); } else { skillLevel = Toolbox.IsUnitFamilyAircraft(unitFamily) ? Toolbox.BRSkillLevelToDCSSkillLevel(template.SituationEnemySkillLevelAir) : Toolbox.BRSkillLevelToDCSSkillLevel(template.SituationEnemySkillLevelGround); } DCSMissionUnitGroupFlags flags = 0; List <int> unitGroupsID = new List <int>(); for (i = 0; i < mission.Objectives.Length; i++) { Coordinates[] coordinates = new Coordinates[2]; for (j = 0; j < 2; j++) { switch (feature.UnitGroupCoordinates[j]) { case DBEntryMissionFeatureUnitGroupLocation.Homebase: coordinates[j] = mission.InitialPosition + Coordinates.CreateRandom(2, 6) * Toolbox.NM_TO_METERS; break; case DBEntryMissionFeatureUnitGroupLocation.Objective: coordinates[j] = mission.Objectives[i].Coordinates; break; case DBEntryMissionFeatureUnitGroupLocation.ObjectiveNear: coordinates[j] = mission.Objectives[i].Coordinates + Coordinates.CreateRandom(1.5, 4) * Toolbox.NM_TO_METERS; break; case DBEntryMissionFeatureUnitGroupLocation.Waypoint: coordinates[j] = mission.Objectives[i].WaypointCoordinates; break; case DBEntryMissionFeatureUnitGroupLocation.WaypointNear: coordinates[j] = mission.Objectives[i].WaypointCoordinates + Coordinates.CreateRandom(1.5, 4) * Toolbox.NM_TO_METERS; break; } } DCSMissionUnitGroup group = UnitMaker.AddUnitGroup( mission, units, side, coordinates[0], Toolbox.RandomFrom(feature.UnitGroup.LuaGroup), feature.UnitGroup.LuaUnit, skillLevel, flags, UnitTaskPayload.Default, coordinates[1]); if (group == null) { DebugLog.Instance.WriteLine($"Failed to create mission feature unit group for objective #{i + 1} made of the following units: {string.Join(", ", units)}", 1, DebugLogMessageErrorLevel.Warning); } unitGroupsID.Add(group.GroupID); // Add aircraft group to the queue of aircraft groups to be spawned if (!feature.UnitGroup.Flags.HasFlag(DBUnitGroupFlags.ManualActivation) && ((group.Category == UnitCategory.Helicopter) || (group.Category == UnitCategory.Plane) || feature.UnitGroup.Flags.HasFlag(DBUnitGroupFlags.DelaySpawn))) { mission.AircraftSpawnQueue.Add(new DCSMissionAircraftSpawnQueueItem(group.GroupID, true)); } } if (unitGroupsID.Count > 0) { mission.LuaSettings += $"briefingRoom.mission.featuresUnitGroups.{feature.ID} = {{ {string.Join(", ", unitGroupsID)} }}"; } } }
public DCSMissionUnitGroup AddUnitGroup( DCSMission mission, string[] units, Side side, Coordinates coordinates, string groupLua, string unitLua, DCSSkillLevel skill, DCSMissionUnitGroupFlags flags = 0, UnitTaskPayload payload = UnitTaskPayload.Default, Coordinates?coordinates2 = null, int airbaseID = 0, bool requiresParkingSpots = false, bool requiresOpenAirParking = false, Country?country = null, PlayerStartLocation startLocation = PlayerStartLocation.Runway) { if (units.Length == 0) { return(null); // No units database entries ID provided, cancel group creation } // TODO: check for missing units DBEntryUnit[] unitsBP = (from string u in units where Database.Instance.EntryExists <DBEntryUnit>(u) select Database.Instance.GetEntry <DBEntryUnit>(u)).ToArray(); unitsBP = (from DBEntryUnit u in unitsBP where u != null select u).ToArray(); if (unitsBP.Length == 0) { return(null); // All database entries were null, cancel group creation } Coalition coalition = (side == Side.Ally) ? mission.CoalitionPlayer : mission.CoalitionEnemy; // Pick group coalition if (!country.HasValue) { country = coalition == Coalition.Blue? Country.CJTFBlue : Country.CJTFRed; } double groupHeading = unitsBP[0].IsAircraft ? 0 : Toolbox.RandomDouble(Toolbox.TWO_PI); // Generate global group heading // Generate units in the group int unitIndex = 0; Coordinates?lastSpot = null; List <DCSMissionUnitGroupUnit> groupUnits = new List <DCSMissionUnitGroupUnit>(); foreach (DBEntryUnit unitBP in unitsBP) { if (unitBP == null) { continue; } for (int i = 0; i < unitBP.DCSIDs.Length; i++) { // Set unit coordinates and heading Coordinates unitCoordinates = coordinates; double unitHeading = groupHeading; SetUnitCoordinatesAndHeading(ref unitCoordinates, ref unitHeading, unitBP, unitIndex); // Get parking spot for the unit, if unit is parked at an airdrome int parkingSpot = 0; if (airbaseID > 0) { if (requiresParkingSpots) { parkingSpot = SpawnPointSelector.GetFreeParkingSpot(airbaseID, lastSpot, out Coordinates parkingCoordinates, requiresOpenAirParking); if (parkingSpot >= 0) { unitCoordinates = parkingCoordinates; } else { parkingSpot = 0; } lastSpot = unitCoordinates; } } else if (airbaseID == -99) //carrier code always parks 1 maybe will need more { parkingSpot = 1; } // Add unit to the list of units DCSMissionUnitGroupUnit unit = new DCSMissionUnitGroupUnit { Coordinates = unitCoordinates, Heading = unitHeading, ID = NextUnitID, Type = unitBP.DCSIDs[i], ParkingSpot = parkingSpot, Name = unitBP.ID }; groupUnits.Add(unit); unitIndex++; NextUnitID++; } } // Generate group name string groupName; UnitCallsign callsign = new UnitCallsign(); if (unitsBP[0].IsAircraft) // Aircraft group, name is a callsign { callsign = CallsignGenerator.GetCallsign(unitsBP[0].Families[0], coalition); groupName = callsign.GroupName; } else // Vehicle/ship/static group, name is a random group name { groupName = GetGroupName(unitsBP[0].Families[0]); } // Add group to the mission DCSMissionUnitGroup group = new DCSMissionUnitGroup { AirbaseID = airbaseID, CallsignLua = callsign.Lua, Category = unitsBP[0].Category, Coalition = coalition, Country = country.Value, Coordinates = airbaseID != 0? groupUnits[0].Coordinates : coordinates, Coordinates2 = coordinates2 ?? coordinates + Coordinates.CreateRandom(1, 2) * Toolbox.NM_TO_METERS, Flags = flags, GroupID = NextGroupID, LuaGroup = groupLua, Name = groupName, Skill = skill, Payload = payload, UnitID = units[0], LuaUnit = unitLua, Units = groupUnits.ToArray(), StartLocation = startLocation }; mission.UnitGroups.Add(group); NextGroupID++; DebugLog.Instance.WriteLine($"Added \"{group.Units[0].Type}\" unit group \"{group.Name}\" for coalition {group.Coalition.ToString().ToUpperInvariant()}", 2); return(group); }
/// <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> /// 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="coalitionsDB">Coalitions database entries</param> /// <param name="moving">Will the group be moving</param> /// <param name="objectiveGroup">If the group should be tracked as an objective</param> public void SpawnUnitGroups(DCSMission mission, MissionTemplate template, DBUnitGroup unitGroup, DBEntryCoalition[] coalitionsDB, Side side, Coalition coalition) { DCSMissionUnitGroupFlags flags = GeneratorTools.ShouldUnitBeHidden(unitGroup, !template.OptionsPreferences.Contains(MissionTemplatePreferences.HideEnemyUnits)) ? DCSMissionUnitGroupFlags.Hidden : 0; for (int i = 0; i < mission.Objectives.Length; i++) { // This objective requires no unit group generation if (!mission.Objectives[i].TargetFamily.HasValue) { continue; } string[] units = coalitionsDB[(int)coalition].GetRandomUnits( mission.Objectives[i].TargetFamily.Value, mission.DateTime.Decade, unitGroup.Count.GetValue(), template.OptionsUnitMods); // Pick the skill level once for each objective so not all target groups have the same skill level when a "random" skill level is chosen. DCSSkillLevel skillLevel; if (side == Side.Ally) { skillLevel = Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel); } else { skillLevel = Toolbox.IsUnitFamilyAircraft(mission.Objectives[i].TargetFamily.Value) ? Toolbox.BRSkillLevelToDCSSkillLevel(template.OppositionSkillLevelAir) : Toolbox.BRSkillLevelToDCSSkillLevel(template.OppositionSkillLevelGround); } DCSMissionUnitGroup group; DBEntryTheaterSpawnPoint?spawnPoint = null; if (unitGroup.SpawnPoints[0] != TheaterLocationSpawnPointType.Airbase) { if (unitGroup.Flags.HasFlag(DBUnitGroupFlags.DestinationObjective)) { spawnPoint = UnitMaker.SpawnPointSelector.GetRandomSpawnPoint( unitGroup.SpawnPoints, mission.Objectives[i].Coordinates, unitGroup.DistanceFromPoint); if (!spawnPoint.HasValue) { throw new Exception($"Failed to find spawn point for moving objective unit"); } } } if (unitGroup.Flags.HasFlag(DBUnitGroupFlags.EmbeddedAirDefense)) // Add "embedded" close range surface-to-air defense { if (Toolbox.GetUnitCategoryFromUnitFamily(mission.Objectives[i].TargetFamily.Value) == UnitCategory.Vehicle) // Objectives are ground vehicles, insert air defense units in the group itself { units = GeneratorTools.AddEmbeddedAirDefense(units, template.OppositionAirDefense, coalitionsDB[(int)coalition], mission.DateTime.Decade, template.OptionsUnitMods); } else // Objectives are not ground vehicles, create another group nearby { // TODO: make sure the group is not spawn in water string[] airDefenseGroupUnits = new string[0]; for (int j = 0; j < 2; j++) { airDefenseGroupUnits = GeneratorTools.AddEmbeddedAirDefense(airDefenseGroupUnits, template.OppositionAirDefense, coalitionsDB[(int)coalition], mission.DateTime.Decade, template.OptionsUnitMods); } UnitMaker.AddUnitGroup( mission, airDefenseGroupUnits, side, (spawnPoint != null ? spawnPoint.Value.Coordinates : mission.Objectives[i].Coordinates) + Coordinates.CreateRandom(0.5, 1.5) * Toolbox.NM_TO_METERS, "GroupVehicle", "UnitVehicle", skillLevel, flags); } } group = UnitMaker.AddUnitGroup( mission, units, side, spawnPoint != null? spawnPoint.Value.Coordinates : mission.Objectives[i].Coordinates, Toolbox.RandomFrom(unitGroup.LuaGroup), unitGroup.LuaUnit, skillLevel, flags, coordinates2: getDestination(unitGroup, mission, i), airbaseID: mission.Objectives[i].AirbaseID, requiresParkingSpots: mission.Objectives[i].AirbaseID > 0, requiresOpenAirParking: unitGroup.Flags.HasFlag(DBUnitGroupFlags.AvoidHardenedBunkers) ); // Something went wrong, abort mission generation, objective unit groups are required for the mission to work properly. if (group == null) { throw new Exception($"Failed to create objective unit group for objective #{i + 1} made of the following units: {string.Join(", ", units)}"); } // Add aircraft group to the queue of aircraft groups to be spawned if (!unitGroup.Flags.HasFlag(DBUnitGroupFlags.ManualActivation) && ((group.Category == UnitCategory.Helicopter) || (group.Category == UnitCategory.Plane) || unitGroup.Flags.HasFlag(DBUnitGroupFlags.DelaySpawn))) { mission.AircraftSpawnQueue.Add(new DCSMissionAircraftSpawnQueueItem(group.GroupID, true)); } if (!unitGroup.Flags.HasFlag(DBUnitGroupFlags.NotObjectiveTarget)) { if (mission.ObjectiveIsStatic) { mission.CoreLuaScript += $"briefingRoom.mission.objectives[{i + 1}].groupID = {group.Units[0].ID}\r\n"; } else { // Add the ID of the unit group associated with this objective to the Lua script mission.CoreLuaScript += $"briefingRoom.mission.objectives[{i + 1}].groupID = {group.GroupID}\r\n"; } } } }
/// <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="coalitionsDB">Coalitions database entries</param> /// <param name="moving">Will the group be moving</param> /// <param name="objectiveGroup">If the group should be tracked as an objective</param> public void SpawnUnitGroups(DCSMission mission, MissionTemplate template, DBUnitGroup unitGroup, DBEntryCoalition[] coalitionsDB, Side side, Coalition coalition) { DCSMissionUnitGroupFlags flags = GeneratorTools.ShouldUnitBeHidden(unitGroup, !template.OptionsPreferences.Contains(MissionTemplatePreferences.HideEnemyUnits)) ? DCSMissionUnitGroupFlags.Hidden : 0; for (int i = 0; i < mission.Objectives.Length; i++) { // This objective requires no unit group generation if (!mission.Objectives[i].TargetFamily.HasValue) { continue; } string[] units = coalitionsDB[(int)coalition].GetRandomUnits( mission.Objectives[i].TargetFamily.Value, mission.DateTime.Decade, unitGroup.Count.GetValue(), template.OptionsUnitMods); if (unitGroup.Flags.HasFlag(DBUnitGroupFlags.EmbeddedAirDefense) && coalition != mission.CoalitionPlayer && (Toolbox.GetUnitCategoryFromUnitFamily(mission.Objectives[i].TargetFamily.Value) == UnitCategory.Vehicle)) { units = GeneratorTools.AddEmbeddedAirDefense(units, template.OppositionAirDefense, coalitionsDB[(int)coalition], mission.DateTime.Decade, template.OptionsUnitMods); } // Pick the skill level once for each objective so not all target groups have the same skill level when a // "random" skill level is chosen. DCSSkillLevel skillLevel = Toolbox.IsUnitFamilyAircraft(mission.Objectives[i].TargetFamily.Value) ? Toolbox.BRSkillLevelToDCSSkillLevel(template.OppositionSkillLevelAir) : Toolbox.BRSkillLevelToDCSSkillLevel(template.OppositionSkillLevelGround); DCSMissionUnitGroup group; DBEntryTheaterSpawnPoint?spawnPoint = null; if (unitGroup.Flags.HasFlag(DBUnitGroupFlags.DestinationObjective)) { spawnPoint = UnitMaker.SpawnPointSelector.GetRandomSpawnPoint( unitGroup.SpawnPoints, mission.Objectives[i].Coordinates, unitGroup.DistanceFromPoint); if (!spawnPoint.HasValue) { throw new Exception($"Failed to find spawn point for moving objective unit"); } } group = UnitMaker.AddUnitGroup( mission, units, side, spawnPoint != null? spawnPoint.Value.Coordinates : mission.Objectives[i].Coordinates, Toolbox.RandomFrom(unitGroup.LuaGroup), unitGroup.LuaUnit, skillLevel, flags, coordinates2: getDestination(unitGroup, mission, i)); // Something went wrong, abort mission generation, objective unit groups are required for the mission to work properly. if (group == null) { throw new Exception($"Failed to create objective unit group for objective #{i + 1} made of the following units: {string.Join(", ", units)}"); } // Add aircraft group to the queue of aircraft groups to be spawned if ((group.Category == UnitCategory.Helicopter) || (group.Category == UnitCategory.Plane) || unitGroup.Flags.HasFlag(DBUnitGroupFlags.DelaySpawn)) { mission.AircraftSpawnQueue.Add(new DCSMissionAircraftSpawnQueueItem(group.GroupID, true)); } if (!unitGroup.Flags.HasFlag(DBUnitGroupFlags.NotObjectiveTarget)) { if (mission.ObjectiveIsStatic) { mission.CoreLuaScript += $"briefingRoom.mission.objectives[{i + 1}].groupID = {group.Units[0].ID}\r\n"; } else { // Add the ID of the unit group associated with this objective to the Lua script mission.CoreLuaScript += $"briefingRoom.mission.objectives[{i + 1}].groupID = {group.GroupID}\r\n"; } } } }