/// <summary> /// Generates the data for the objectives. /// </summary> /// <param name="mission">The mission for which to generate objectives</param> /// <param name="template">Mission template to use</param> /// <param name="objectiveDB">Objective database entry</param> private void GenerateObjectivesData(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB, DBEntryTheater theaterDB) { // Keep in mind the position of the last objective/player location. // Start with initial player location. Coordinates lastCoordinates = mission.InitialPosition; // Common family to use for all objectives if DBEntryObjectiveFlags.SingleTargetUnitFamily is set UnitFamily singleObjectiveUnitFamily = objectiveDB.GetRandomUnitFamily(); for (int i = 0; i < template.ObjectiveCount; i++) { // Pick a random unique name, or a waypoint number if objectives shouldn't be named string objectiveName = PickUniqueObjectiveName(); DebugLog.Instance.WriteLine($"Adding objective #{i + 1}, designated {objectiveName}", 1); // Compute a random distance from last position, in nautical miles double objectiveDistanceNM = (template.ObjectiveDistanceNM == 0) ? Toolbox.RandomInt(TemplateTools.MIN_OBJECTIVE_DISTANCE, TemplateTools.MAX_OBJECTIVE_DISTANCE) : template.ObjectiveDistanceNM; if (i > 0) // Objective is not the first one, spawn it close to the previous objective { objectiveDistanceNM /= 5.0; } MinMaxD distanceFromLast = new MinMaxD(OBJECTIVE_DISTANCE_VARIATION_MIN, OBJECTIVE_DISTANCE_VARIATION_MAX) * objectiveDistanceNM; Coordinates objectiveCoordinates; DBEntryTheaterAirbase?airbase = null; if (objectiveDB.UnitGroup.SpawnPoints[0] != TheaterLocationSpawnPointType.Airbase) { // Look for a valid spawn point DBEntryTheaterSpawnPoint?spawnPoint = SpawnPointSelector.GetRandomSpawnPoint( // If spawn point types are specified, use them. Else look for spawn points of any type (objectiveDB.UnitGroup.SpawnPoints.Length > 0) ? objectiveDB.UnitGroup.SpawnPoints : null, // Select spawn points at a proper distance from last location (previous objective or home airbase) lastCoordinates, distanceFromLast, // Make sure no objective is too close to the initial location mission.InitialPosition, new MinMaxD(objectiveDistanceNM * OBJECTIVE_DISTANCE_VARIATION_MIN, 999999999), GeneratorTools.GetEnemySpawnPointCoalition(template)); // No spawn point found for the objective, abort mission creation. if (!spawnPoint.HasValue) { throw new Exception($"Failed to find a spawn point for objective {i + 1}"); } objectiveCoordinates = spawnPoint.Value.Coordinates; } else { airbase = new MissionGeneratorAirbases().SelectObjectiveAirbase(mission, template, theaterDB, lastCoordinates, distanceFromLast, i == 0); if (!airbase.HasValue) { throw new Exception($"Failed to find a airbase point for objective {i + 1}"); } objectiveCoordinates = airbase.Value.Coordinates; } // Set the waypoint coordinates according the the inaccuracy defined in the objective database entry Coordinates waypointCoordinates = objectiveCoordinates + Coordinates.CreateRandom(objectiveDB.WaypointInaccuracy * Toolbox.NM_TO_METERS); // Select an objective family for the target if any or default to VehicleTransport. UnitFamily objectiveUnitFamily = singleObjectiveUnitFamily; if (!objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.SingleTargetUnitFamily)) { objectiveUnitFamily = objectiveDB.GetRandomUnitFamily(); } // Set the mission objective mission.Objectives[i] = new DCSMissionObjective( objectiveName, objectiveCoordinates, objectiveUnitFamily, waypointCoordinates, airbase.HasValue? airbase.Value.DCSID: 0); // Last position is now the position of this objective lastCoordinates = objectiveCoordinates; } // If the target is a static object, make sure the correct flag is enabled as it has an influence of some scripts mission.ObjectiveIsStatic = objectiveDB.UnitGroup.Category.HasValue && (objectiveDB.UnitGroup.Category.Value == UnitCategory.Static); // Make sure objectives are ordered by distance from the players' starting location mission.Objectives = mission.Objectives.OrderBy(x => mission.InitialPosition.GetDistanceFrom(x.WaypointCoordinates)).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> /// 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> /// 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 }