/// <summary> /// Creates the flight group player will lead 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="objectiveDB">Mission objective database entry</param> /// <returns>A <see cref="UnitFlightGroupBriefingDescription"/> describing the flight group, to be used in the briefing</returns> private UnitFlightGroupBriefingDescription GenerateSinglePlayerFlightGroup(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB) { var playerFlightGroup = template.PlayerFlightGroups[0]; bool isCarrier = !string.IsNullOrEmpty(playerFlightGroup.Carrier); DebugLog.Instance.WriteLine($"{playerFlightGroup.Carrier} -> {string.Join(",",mission.Carriers.Select(x => x.Name).ToArray())}"); DCSMissionUnitGroup group = UnitMaker.AddUnitGroup( mission, Enumerable.Repeat(playerFlightGroup.Aircraft, playerFlightGroup.Count).ToArray(), Side.Ally, isCarrier? mission.Carriers.First(x => x.Units[0].Name == playerFlightGroup.Carrier).Coordinates : mission.InitialPosition, isCarrier? "GroupAircraftPlayerCarrier" : "GroupAircraftPlayer", "UnitAircraft", Toolbox.BRSkillLevelToDCSSkillLevel(template.SituationFriendlyAISkillLevel), DCSMissionUnitGroupFlags.FirstUnitIsPlayer, objectiveDB.Payload, null, isCarrier? -99 : mission.InitialAirbaseID, true, country: playerFlightGroup.Country, startLocation: playerFlightGroup.StartLocation ); if (group == null) { throw new Exception($"Failed to create group of player aircraft of type \"{playerFlightGroup.Aircraft}\"."); } if (isCarrier) { group.CarrierId = mission.Carriers.First(x => x.Units[0].Name == playerFlightGroup.Carrier).Units[0].ID; } return(new UnitFlightGroupBriefingDescription( group.Name, group.Units.Length, playerFlightGroup.Aircraft, objectiveDB.BriefingTaskFlightGroup, Database.Instance.GetEntry <DBEntryUnit>(playerFlightGroup.Aircraft).AircraftData.GetRadioAsString())); }
/// <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> /// Creates the flight group player will lead 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="objectiveDB">Mission objective database entry</param> /// <returns>A <see cref="UnitFlightGroupBriefingDescription"/> describing the flight group, to be used in the briefing</returns> private UnitFlightGroupBriefingDescription GenerateSinglePlayerFlightGroup(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB) { DCSMissionUnitGroup group = UnitMaker.AddUnitGroup( mission, Enumerable.Repeat(template.PlayerSPAircraft, template.PlayerSPWingmen + 1).ToArray(), Side.Ally, mission.Carrier != null ? mission.Carrier.Coordinates : mission.InitialPosition, mission.Carrier != null ? "GroupAircraftPlayerCarrier" : "GroupAircraftPlayer", "UnitAircraft", Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel), DCSMissionUnitGroupFlags.FirstUnitIsPlayer, objectiveDB.Payload, null, mission.Carrier != null ? -99 : mission.InitialAirbaseID, true); if (group == null) { throw new Exception($"Failed to create group of player aircraft of type \"{template.PlayerSPAircraft}\"."); } return(new UnitFlightGroupBriefingDescription( group.Name, group.Units.Length, template.PlayerSPAircraft, objectiveDB.BriefingTaskFlightGroup, Database.Instance.GetEntry <DBEntryUnit>(template.PlayerSPAircraft).AircraftData.GetRadioAsString())); }
/// <summary> /// /// </summary> /// <param name="mission"></param> /// <param name="template"></param> /// <param name="playerCoalitionDB"></param> /// <param name="windDirection0">Wind direction at altitude 0, in degrees. Used by carrier groups to make sure carriers sail into the wind.</param> /// <returns></returns> public void GenerateCarriers(DCSMission mission, MissionTemplate template, DBEntryCoalition playerCoalitionDB) { var carriers = new string[] {}; if (template.GetMissionType() == MissionType.SinglePlayer) { if (string.IsNullOrEmpty(template.PlayerSPCarrier)) { return; } carriers = carriers.Append(template.PlayerSPCarrier).ToArray(); } else { carriers = template.PlayerMPFlightGroups.Aggregate(new string[] {}, (acc, x) => !string.IsNullOrEmpty(x.Carrier)? acc.Append(x.Carrier).ToArray() : acc); } if (carriers.Length == 0) { return; } foreach (string carrier in carriers) { DBEntryTheaterSpawnPoint?spawnPoint = UnitMaker.SpawnPointSelector.GetRandomSpawnPoint( // If spawn point types are specified, use them. Else look for spawn points of any type new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.Sea }, // Select spawn points at a proper distance from last location (previous objective or home airbase) mission.InitialPosition, new MinMaxD(10, 50), // Make sure no objective is too close to the initial location null, null, GeneratorTools.GetAllySpawnPointCoalition(template)); if (!spawnPoint.HasValue) { throw new Exception($"Failed to find a spawn point for Carrier"); } Coordinates position = mission.InitialPosition; DCSMissionUnitGroup group; string[] ships = new string[] { carrier }; foreach (var ship in new UnitFamily[] { UnitFamily.ShipFrigate, UnitFamily.ShipFrigate, UnitFamily.ShipCruiser, UnitFamily.ShipCruiser, UnitFamily.ShipTransport }) { ships = ships.Append(playerCoalitionDB.GetRandomUnits(ship, mission.DateTime.Decade, 1, template.OptionsUnitMods)[0]).ToArray(); } DebugLog.Instance.WriteLine($"Ships to be spawned {ships.Aggregate((acc, x) => $"{acc}, {x}")}", 1, DebugLogMessageErrorLevel.Warning); group = UnitMaker.AddUnitGroup( mission, ships, Side.Ally, spawnPoint.Value.Coordinates, "GroupCarrier", "UnitShip", Toolbox.BRSkillLevelToDCSSkillLevel(template.PlayerAISkillLevel)); if (group == null) { DebugLog.Instance.WriteLine($"Failed to create AI Carrier with ship of type \"{carrier}\".", 1, DebugLogMessageErrorLevel.Warning); } else { //set all units against the wind int windDirection = mission.Weather.WindDirection[0]; double WindSpeed = mission.Weather.WindSpeed[0]; double windOverDeck = 12.8611; // 25kts group.Speed = windOverDeck - WindSpeed; if (group.Speed <= 2.6) { group.Speed = 2.57222; //5kts } double heading = Toolbox.ClampAngle((windDirection + 180) * Toolbox.DEGREES_TO_RADIANS); foreach (DCSMissionUnitGroupUnit unit in group.Units) { unit.Heading = heading; } group.Units[0].Coordinates = group.Coordinates; group.Coordinates2 = Coordinates.FromAngleAndDistance(group.Coordinates, (group.Speed * Toolbox.METERS_PER_SECOND_TO_KNOTS) * Toolbox.NM_TO_METERS, heading); } string cvnId = mission.Carriers.Length > 0? (mission.Carriers.Length + 1).ToString() : ""; group.TACAN = new Tacan(74 + mission.Carriers.Length, $"CVN{cvnId}"); group.ILS = 11 + mission.Carriers.Length; group.RadioFrequency = 127.5f + mission.Carriers.Length; mission.Carriers = mission.Carriers.Append(group).ToArray(); } return; }
/// <summary> /// 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)} }}"; } } }
/// <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> /// 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"; } } } }
/// <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 }