internal UnitMakerSpawnPointSelector(DBEntryTheater theaterDB, DBEntrySituation situationDB, bool invertCoalition) { TheaterDB = theaterDB; SituationDB = situationDB; AirbaseParkingSpots = new Dictionary <int, List <DBEntryAirbaseParkingSpot> >(); SpawnPoints = new List <DBEntryTheaterSpawnPoint>(); InvertCoalition = invertCoalition; if (TheaterDB.SpawnPoints is not null) { SpawnPoints.AddRange(TheaterDB.SpawnPoints.Where(x => CheckNotInNoSpawnCoords(x.Coordinates)).ToList()); } foreach (DBEntryAirbase airbase in SituationDB.GetAirbases(InvertCoalition)) { if (airbase.ParkingSpots.Length < 1) { continue; } if (AirbaseParkingSpots.ContainsKey(airbase.DCSID)) { continue; } AirbaseParkingSpots.Add(airbase.DCSID, airbase.ParkingSpots.ToList()); } }
public UnitMaker(DBEntryCoalition[] coalitionsDB, DBEntryTheater theaterDB) { CallsignGenerator = new UnitMakerCallsignGenerator(coalitionsDB); SpawnPointSelector = new UnitMakerSpawnPointSelector(theaterDB); NextGroupID = 1; NextUnitID = 1; }
private static void GenerateFOB( UnitMaker unitMaker, ZoneMaker zoneMaker, MissionTemplateFlightGroupRecord flightGroup, Dictionary <string, UnitMakerGroupInfo> carrierDictionary, DCSMission mission, MissionTemplateRecord template, Coordinates landbaseCoordinates, Coordinates objectivesCenter) { DBEntryTheater theaterDB = Database.Instance.GetEntry <DBEntryTheater>(template.ContextTheater); if (theaterDB == null) { return; // Theater doesn't exist. Should never happen. } Coordinates?spawnPoint = unitMaker.SpawnPointSelector.GetRandomSpawnPoint( new SpawnPointType[] { SpawnPointType.LandLarge }, landbaseCoordinates, new MinMaxD(5, template.FlightPlanObjectiveDistance), objectivesCenter, new MinMaxD(10, template.FlightPlanObjectiveDistance / 2), template.ContextPlayerCoalition); if (!spawnPoint.HasValue) { BriefingRoom.PrintToLog($"No spawn point found for FOB air defense unit groups", LogMessageErrorLevel.Warning); return; } DBEntryUnit unitDB = Database.Instance.GetEntry <DBEntryUnit>(flightGroup.Carrier); if (unitDB == null) { return; // Unit doesn't exist or is not a carrier } double radioFrequency = 127.5 + carrierDictionary.Count; var FOBNames = new List <string> { "FOB_London", "FOB_Dallas", "FOB_Paris", "FOB_Moscow", "FOB_Berlin" }; UnitMakerGroupInfo?groupInfo = unitMaker.AddUnitGroup( unitDB.Families[0], 1, Side.Ally, "GroupStatic", "UnitStaticFOB", spawnPoint.Value, 0, "FOBCallSignIndex".ToKeyValuePair(FOBNames.IndexOf(flightGroup.Carrier) + 1), "RadioBand".ToKeyValuePair((int)RadioModulation.AM), "RadioFrequency".ToKeyValuePair(GeneratorTools.GetRadioFrenquency(radioFrequency))); if (!groupInfo.HasValue || (groupInfo.Value.UnitsID.Length == 0)) { return; // Couldn't generate group } zoneMaker.AddCTLDPickupZone(spawnPoint.Value, true); mission.Briefing.AddItem( DCSMissionBriefingItemType.Airbase, $"{unitDB.UIDisplayName}\t-\t{GeneratorTools.FormatRadioFrequency(radioFrequency)}\t\t"); carrierDictionary.Add(flightGroup.Carrier, groupInfo.Value); // This bit limits FOBS to one per game think about how we can fix this }
private static Tuple <Coordinates, Coordinates> GetSpawnAndDestination( UnitMaker unitMaker, MissionTemplateRecord template, DBEntryTheater theaterDB, List <Coordinates> usedCoordinates, Coordinates landbaseCoordinates, Coordinates objectivesCenter, double carrierPathDeg) { var travelMinMax = new MinMaxD(Database.Instance.Common.CarrierGroup.CourseLength, Database.Instance.Common.CarrierGroup.CourseLength * 2); Coordinates?carrierGroupCoordinates = null; Coordinates?destinationPath = null; var iteration = 0; var maxDistance = 25; while (iteration < 100) { carrierGroupCoordinates = unitMaker.SpawnPointSelector.GetRandomSpawnPoint( new SpawnPointType[] { SpawnPointType.Sea }, landbaseCoordinates, new MinMaxD(10, maxDistance), objectivesCenter, new MinMaxD(10, 99999), GeneratorTools.GetSpawnPointCoalition(template, Side.Ally)); if (!carrierGroupCoordinates.HasValue) { maxDistance += 25; continue; } var minDist = usedCoordinates.Aggregate(99999999.0, (acc, x) => x.GetDistanceFrom(carrierGroupCoordinates.Value) < acc ? x.GetDistanceFrom(carrierGroupCoordinates.Value) : acc); if (minDist < Database.Instance.Common.CarrierGroup.ShipSpacing) { continue; } destinationPath = Coordinates.FromAngleAndDistance(carrierGroupCoordinates.Value, travelMinMax, carrierPathDeg); if (ShapeManager.IsPosValid(destinationPath.Value, theaterDB.WaterCoordinates, theaterDB.WaterExclusionCoordinates)) { break; } iteration++; if (iteration > 10) { maxDistance += 1; } } if (!carrierGroupCoordinates.HasValue) { throw new BriefingRoomException($"Carrier spawnpoint could not be found."); } if (!destinationPath.HasValue) { throw new BriefingRoomException($"Carrier destination could not be found."); } if (!ShapeManager.IsPosValid(destinationPath.Value, theaterDB.WaterCoordinates, theaterDB.WaterExclusionCoordinates)) { throw new BriefingRoomException($"Carrier waypoint is on shore"); } return(new(carrierGroupCoordinates.Value, destinationPath.Value)); }
internal DrawingMaker( DCSMission mission, MissionTemplateRecord template, DBEntryTheater theaterDB, DBEntrySituation situationDB) { Mission = mission; Template = template; TheaterDB = theaterDB; SituationDB = situationDB; Clear(); AddTheaterZones(); }
/// <summary> /// Generates weather settings (precipitation, cloud coverage, temperature...) for the mission. /// Must be called after mission date has been set because min/max temperature changes every month. /// </summary> /// <param name="mission">The mission.</param> /// <param name="weather">The preferred type of weather (clear, cloudy, storm...).</param> /// <param name="theaterDB">Theater database entry</param> public void GenerateWeather(DCSMission mission, Weather weather, DBEntryTheater theaterDB) { mission.Weather.Temperature = theaterDB.Temperature[(int)mission.DateTime.Month].GetValue(); mission.Weather.WeatherLevel = (weather == Weather.Random) ? Toolbox.RandomFrom(RANDOM_WEATHER) : weather; DebugLog.Instance.WriteLine($"Weather set to {mission.Weather.WeatherLevel}", 1); int weatherIndex = (int)mission.Weather.WeatherLevel; // Clouds and precipitations mission.Weather.CloudBase = theaterDB.Weather[weatherIndex].CloudsBase.GetValue(); mission.Weather.CloudsDensity = theaterDB.Weather[weatherIndex].CloudsDensity.GetValue(); mission.Weather.CloudsPrecipitation = Toolbox.RandomFrom(theaterDB.Weather[weatherIndex].CloudsPrecipitation); mission.Weather.CloudsThickness = theaterDB.Weather[weatherIndex].CloudsThickness.GetValue(); // Dust mission.Weather.DustEnabled = Toolbox.RandomFrom(theaterDB.Weather[weatherIndex].DustEnabled); mission.Weather.DustDensity = mission.Weather.DustEnabled ? theaterDB.Weather[weatherIndex].DustDensity.GetValue() : 0; // Fog mission.Weather.FogEnabled = Toolbox.RandomFrom(theaterDB.Weather[weatherIndex].FogEnabled); mission.Weather.FogThickness = mission.Weather.FogEnabled ? theaterDB.Weather[weatherIndex].FogThickness.GetValue() : 0; mission.Weather.FogVisibility = mission.Weather.FogEnabled ? theaterDB.Weather[weatherIndex].FogVisibility.GetValue() : 0; // Pressure, turbulence and visiblity mission.Weather.QNH = theaterDB.Weather[weatherIndex].QNH.GetValue(); mission.Weather.Turbulence = theaterDB.Weather[weatherIndex].Turbulence.GetValue(); mission.Weather.Visibility = theaterDB.Weather[(int)mission.Weather.WeatherLevel].Visibility.GetValue(); DebugLog.Instance.WriteLine($"Cloud base altitude set to {mission.Weather.CloudBase} m", 1); DebugLog.Instance.WriteLine($"Cloud density set to {mission.Weather.CloudBase}", 1); DebugLog.Instance.WriteLine($"Precipitation set to {mission.Weather.CloudsPrecipitation}", 1); DebugLog.Instance.WriteLine($"Cloud thickness set to {mission.Weather.CloudsThickness} m", 1); DebugLog.Instance.WriteLine($"Dust set to {mission.Weather.DustEnabled}", 1); DebugLog.Instance.WriteLine($"Dust density set to {mission.Weather.DustDensity}", 1); DebugLog.Instance.WriteLine($"Fog set to {mission.Weather.FogEnabled}", 1); DebugLog.Instance.WriteLine($"Fog thickness set to {mission.Weather.FogThickness}", 1); DebugLog.Instance.WriteLine($"Fog visibility set to {mission.Weather.FogVisibility} m", 1); DebugLog.Instance.WriteLine($"QNH set to {mission.Weather.QNH}", 1); DebugLog.Instance.WriteLine($"Turbulence set to {mission.Weather.Turbulence} m/s", 1); DebugLog.Instance.WriteLine($"Visibility set to {mission.Weather.Visibility} m", 1); }
internal UnitMaker( DCSMission mission, MissionTemplateRecord template, DBEntryCoalition[] coalitionsDB, DBEntryTheater theaterDB, DBEntrySituation situationDB, Coalition playerCoalition, Country[][] coalitionsCountries, bool singlePlayerMission) { CallsignGenerator = new UnitMakerCallsignGenerator(coalitionsDB); SpawnPointSelector = new UnitMakerSpawnPointSelector(theaterDB, situationDB, template.OptionsMission.Contains("InvertCountriesCoalitions")); Mission = mission; Template = template; CoalitionsDB = coalitionsDB; PlayerCoalition = playerCoalition; CoalitionsCountries = coalitionsCountries; SinglePlayerMission = singlePlayerMission; GroupID = 1; UnitID = 1; }
/// <summary> /// Constructor. /// </summary> /// <param name="theaterDB">Theater database entry to use</param> public UnitMakerSpawnPointSelector(DBEntryTheater theaterDB) { TheaterDB = theaterDB; AirbaseParkingSpots = new Dictionary <int, List <DBEntryTheaterAirbaseParkingSpot> >(); SpawnPoints = new List <DBEntryTheaterSpawnPoint>(); SpawnPoints.AddRange(theaterDB.SpawnPoints); foreach (DBEntryTheaterAirbase airbase in theaterDB.Airbases) { if (airbase.ParkingSpots.Length < 1) { continue; } if (AirbaseParkingSpots.ContainsKey(airbase.DCSID)) { continue; } AirbaseParkingSpots.Add(airbase.DCSID, airbase.ParkingSpots.ToList()); } }
/// <summary> /// Generates wind settings for the mission. /// Must be called once mission weather level has been set, as weather is used for auto wind. /// </summary> /// <param name="mission">The mission.</param> /// <param name="wind">The preferred wind speed.</param> /// <param name="theaterDB">Theater database entry</param> public void GenerateWind(DCSMission mission, Wind wind, DBEntryTheater theaterDB) { // If auto, speed depends on weather, so we never end up with no wind in a storm mission.Weather.WindLevel = (wind == Wind.Auto) ? (Wind)Toolbox.Clamp((int)mission.Weather.WeatherLevel + Toolbox.RandomMinMax(-1, 1), 0, (int)Wind.StrongGale) : wind; DebugLog.Instance.WriteLine($"Wind speed level set to {mission.Weather.WindLevel}", 1); int windIndex = (int)mission.Weather.WindLevel; for (int i = 0; i < 3; i++) { mission.Weather.WindSpeed[i] = Math.Max(0, theaterDB.Wind[windIndex].Wind.GetValue()); mission.Weather.WindDirection[i] = (mission.Weather.WindSpeed[i] > 0) ? Toolbox.RandomInt(0, 360) : 0; DebugLog.Instance.WriteLine($"Wind speed at {WIND_ALTITUDE[i]} meters set to {mission.Weather.WindSpeed[i]} m/s, direction of {mission.Weather.WindDirection[i]}", 1); } // Turbulence is equals to max(weatherTurbulence, windTurbulence) mission.Weather.Turbulence = Math.Max(mission.Weather.Turbulence, theaterDB.Wind[windIndex].Turbulence.GetValue()); DebugLog.Instance.WriteLine($"Turbulence updated to {mission.Weather.Turbulence} m/s", 1); }
public static string MakeBriefingStringReplacements(string briefingString, DCSMission mission, DBEntryCoalition[] coalitionsDB, int objectiveIndex = 0) { DBEntryTheater theaterDB = Database.Instance.GetEntry <DBEntryTheater>(mission.Theater); briefingString = briefingString.Replace("$ALLYADJ$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.Adjective])); briefingString = briefingString.Replace("$ENEMYADJ$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionEnemy].BriefingElements[(int)CoalitionBriefingElement.Adjective])); briefingString = briefingString.Replace("$RECON$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.Recon])); briefingString = briefingString.Replace("$STRCOMMAND$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.StrategicCommand])); briefingString = briefingString.Replace("$TACCOMMAND$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.TacticalCommand])); briefingString = briefingString.Replace("$THEATER$", Toolbox.RandomFrom(theaterDB.BriefingNames)); briefingString = briefingString.Replace("$THEALLIES$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionPlayer].BriefingElements[(int)CoalitionBriefingElement.TheCoalition])); briefingString = briefingString.Replace("$THEENEMIES$", Toolbox.RandomFrom(coalitionsDB[(int)mission.CoalitionEnemy].BriefingElements[(int)CoalitionBriefingElement.TheCoalition])); briefingString = briefingString.Replace("$OBJECTIVE$", mission.Objectives[objectiveIndex].Name); briefingString = briefingString.Replace("$UNITFAMILIES$", mission.Objectives[objectiveIndex].TargetFamily.HasValue ? GetBriefingStringForUnitFamily(mission.Objectives[objectiveIndex].TargetFamily.Value, true) : "units"); briefingString = briefingString.Replace("$UNITFAMILY$", mission.Objectives[objectiveIndex].TargetFamily.HasValue ? GetBriefingStringForUnitFamily(mission.Objectives[objectiveIndex].TargetFamily.Value, false) : "unit"); return(Toolbox.ICase(SanitizeString(briefingString))); }
/// <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> /// Generates the <see cref="DCSMissionObjective"/>. /// </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> public void CreateObjectives(DCSMission mission, MissionTemplate template, DBEntryObjective objectiveDB, DBEntryTheater theaterDB) { // Set the array for the proper number of objective mission.Objectives = new DCSMissionObjective[template.ObjectiveCount]; GenerateObjectivesData(mission, template, objectiveDB, theaterDB); GenerateObjectivesScript(mission); }
internal static void GenerateMissionTime(DCSMission mission, MissionTemplateRecord template, DBEntryTheater theaterDB, Month month) { double totalMinutes; int hour, minute; switch (template.EnvironmentTimeOfDay) { default: // case TimeOfDay.Random totalMinutes = Toolbox.RandomInt(Toolbox.MINUTES_PER_DAY); break; case TimeOfDay.RandomDaytime: totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Max - 60); break; case TimeOfDay.Dawn: totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Min + 120); break; case TimeOfDay.Noon: totalMinutes = Toolbox.RandomInt( (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 - 90, (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 + 90); break; case TimeOfDay.Twilight: totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Max - 120, theaterDB.DayTime[(int)month].Max + 30); break; case TimeOfDay.Night: totalMinutes = Toolbox.RandomInt(0, theaterDB.DayTime[(int)month].Min - 120); break; } hour = Toolbox.Clamp((int)Math.Floor(totalMinutes / 60), 0, 23); minute = Toolbox.Clamp((int)Math.Floor((totalMinutes - hour * 60) / 15) * 15, 0, 45); mission.SetValue("BriefingTime", $"{hour:00}:{minute:00}"); mission.SetValue("StartTime", hour * 3600 + minute * 60); // DCS World time is stored in seconds since midnight }
/// <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> /// Sets the coalition to which the various airbases on the theater belong. /// </summary> /// <param name="mission">Mission for which airbase coalitions must be set</param> /// <param name="theaterAirbasesCoalitions">Airbase coalition setting</param> /// <param name="theaterDB">Theater database entry</param> public void SetupAirbasesCoalitions(DCSMission mission, CountryCoalition theaterAirbasesCoalitions, DBEntryTheater theaterDB) { mission.AirbasesCoalition.Clear(); foreach (DBEntryTheaterAirbase ab in theaterDB.Airbases) { // Airbase ID already exists in the mission if (mission.AirbasesCoalition.ContainsKey(ab.DCSID)) { continue; } // Airbase is the player starting airbase, always set it to the player coalition if (ab.DCSID == mission.InitialAirbaseID) { mission.AirbasesCoalition.Add(ab.DCSID, mission.CoalitionPlayer); continue; } // Other airbases are assigned to a coalition according to the theater and the template settings Coalition airbaseCoalition = ab.Coalition; switch (theaterAirbasesCoalitions) { case CountryCoalition.AllBlue: airbaseCoalition = Coalition.Blue; break; case CountryCoalition.AllRed: airbaseCoalition = Coalition.Red; break; case CountryCoalition.Inverted: airbaseCoalition = (Coalition)(1 - (int)ab.Coalition); break; } mission.AirbasesCoalition.Add(ab.DCSID, airbaseCoalition); } }
internal static Dictionary <string, UnitMakerGroupInfo> GenerateCarrierGroup( UnitMaker unitMaker, ZoneMaker zoneMaker, DCSMission mission, MissionTemplateRecord template, Coordinates landbaseCoordinates, Coordinates objectivesCenter, double windSpeedAtSeaLevel, double windDirectionAtSeaLevel) { Dictionary <string, UnitMakerGroupInfo> carrierDictionary = new Dictionary <string, UnitMakerGroupInfo>(StringComparer.InvariantCultureIgnoreCase); DBEntryTheater theaterDB = Database.Instance.GetEntry <DBEntryTheater>(template.ContextTheater); double carrierSpeed = Math.Max( Database.Instance.Common.CarrierGroup.MinimumCarrierSpeed, Database.Instance.Common.CarrierGroup.IdealWindOfDeck - windSpeedAtSeaLevel); if (windSpeedAtSeaLevel == 0) // No wind? Pick a random direction so carriers don't always go to a 0 course when wind is calm. { windDirectionAtSeaLevel = Toolbox.RandomDouble(Toolbox.TWO_PI); } var carrierPathDeg = ((windDirectionAtSeaLevel + Math.PI) % Toolbox.TWO_PI) * Toolbox.RADIANS_TO_DEGREES; var usedCoordinates = new List <Coordinates>(); foreach (MissionTemplateFlightGroupRecord flightGroup in template.PlayerFlightGroups) { if (string.IsNullOrEmpty(flightGroup.Carrier)) { continue; // No carrier for } if (carrierDictionary.ContainsKey(flightGroup.Carrier)) { continue; // Carrier type already added } if (flightGroup.Carrier.StartsWith("FOB")) { //It Carries therefore carrier not because I can't think of a name to rename this lot GenerateFOB(unitMaker, zoneMaker, flightGroup, carrierDictionary, mission, template, landbaseCoordinates, objectivesCenter); continue; } DBEntryUnit unitDB = Database.Instance.GetEntry <DBEntryUnit>(flightGroup.Carrier); if ((unitDB == null) || !unitDB.Families.Any(x => x.IsCarrier())) { continue; // Unit doesn't exist or is not a carrier } var(shipCoordinates, shipDestination) = GetSpawnAndDestination(unitMaker, template, theaterDB, usedCoordinates, landbaseCoordinates, objectivesCenter, carrierPathDeg); usedCoordinates.Add(shipCoordinates); string cvnID = carrierDictionary.Count > 0 ? (carrierDictionary.Count + 1).ToString() : ""; int ilsChannel = 11 + carrierDictionary.Count; double radioFrequency = 127.5 + carrierDictionary.Count; string tacanCallsign = $"CVN{cvnID}"; int tacanChannel = 74 + carrierDictionary.Count; UnitMakerGroupInfo?groupInfo = unitMaker.AddUnitGroup( new string[] { unitDB.ID }, Side.Ally, unitDB.Families[0], "GroupShipCarrier", "UnitShip", shipCoordinates, 0, "GroupX2".ToKeyValuePair(shipDestination.X), "GroupY2".ToKeyValuePair(shipDestination.Y), "ILS".ToKeyValuePair(ilsChannel), "RadioBand".ToKeyValuePair((int)RadioModulation.AM), "RadioFrequency".ToKeyValuePair(GeneratorTools.GetRadioFrenquency(radioFrequency)), "Speed".ToKeyValuePair(carrierSpeed), "TACANCallsign".ToKeyValuePair(tacanCallsign), "TACANChannel".ToKeyValuePair(tacanChannel), "TACANFrequency".ToKeyValuePair(GeneratorTools.GetTACANFrequency(tacanChannel, 'X', false)), "TACANMode".ToKeyValuePair("X")); if (!groupInfo.HasValue || (groupInfo.Value.UnitsID.Length == 0)) { continue; // Couldn't generate group } mission.Briefing.AddItem( DCSMissionBriefingItemType.Airbase, $"{unitDB.UIDisplayName}\t-\t{GeneratorTools.FormatRadioFrequency(radioFrequency)}\t{ilsChannel}\t{tacanCallsign}, {tacanChannel}X"); carrierDictionary.Add(flightGroup.Carrier, groupInfo.Value); } return(carrierDictionary); }
/// <summary> /// Picks a starting airbase for the player(s) /// </summary> /// <param name="mission">Mission for which the starting airbase must be set</param> /// <param name="template">Mission template to use</param> /// <param name="theaterDB">Theater database entry</param> /// <param name="lastCoordinates">Last location for referance</param> /// <param name="distance">Base Distance Range</param> /// <param name="first">is first objective</param> /// <returns>Information about the starting airbase</returns> public DBEntryTheaterAirbase SelectObjectiveAirbase(DCSMission mission, MissionTemplate template, DBEntryTheater theaterDB, Coordinates lastCoordinates, MinMaxD distance, bool first = false) { List <DBEntryTheaterAirbase> airbasesList = new List <DBEntryTheaterAirbase>(); // Select all airbases with enough parking spots, trying to match the preferred coalition for enemy unit location, if any if ((template.OptionsTheaterCountriesCoalitions == CountryCoalition.AllBlue) || (template.OptionsTheaterCountriesCoalitions == CountryCoalition.AllRed) || (template.OptionsEnemyUnitsLocation == SpawnPointPreferredCoalition.Any)) { airbasesList.AddRange((from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE select ab).ToArray()); } else { Coalition preferredCoalition; if (template.OptionsEnemyUnitsLocation == SpawnPointPreferredCoalition.Blue) { preferredCoalition = (template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted) ? Coalition.Red : Coalition.Blue; } else { preferredCoalition = (template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted) ? Coalition.Blue : Coalition.Red; } airbasesList.AddRange( (from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE && ab.Coalition == preferredCoalition select ab).ToArray()); if (airbasesList.Count == 0) { airbasesList.AddRange((from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE select ab).ToArray()); } } // Remove players' home airbase and airbases already used by other objectives from the list of available airbases List <int> airbasesInUse = (from DCSMissionObjective objective in mission.Objectives select objective.AirbaseID).ToList(); airbasesInUse.Add(mission.InitialAirbaseID); airbasesList = (from DBEntryTheaterAirbase ab in airbasesList where !airbasesInUse.Contains(ab.DCSID) select ab).ToList(); if (airbasesList.Count == 0) { throw new Exception($"No airbase found with at least {Toolbox.MAXIMUM_FLIGHT_GROUP_SIZE} parking spots to use as an objective."); } int distanceMultiplier = 1; do { MinMaxD searchDistance = new MinMaxD(first ? distance.Min : 0, distance.Max * distanceMultiplier); List <DBEntryTheaterAirbase> airbasesInRange = airbasesList.FindAll(x => searchDistance.Contains(x.Coordinates.GetDistanceFrom(lastCoordinates) * Toolbox.METERS_TO_NM)); if (airbasesInRange.Count > 0) { DBEntryTheaterAirbase selectedAirbase = Toolbox.RandomFrom(airbasesInRange); mission.AirbasesCoalition[selectedAirbase.DCSID] = mission.CoalitionEnemy; return(selectedAirbase); } distanceMultiplier++; if (distanceMultiplier > 128) { throw new Exception($"No target airbase found within range, try a larger objective range."); } } while (true); }
internal static int GenerateWeather(DCSMission mission, MissionTemplateRecord template, DBEntryTheater theaterDB, Month month, DBEntryAirbase playerAirbase) { var baseAlt = template.OptionsMission.Contains("SeaLevelRefCloud") ? 0.0 : playerAirbase.Elevation; if (template.OptionsMission.Contains("HighCloud")) { baseAlt += 2000; } DBEntryWeatherPreset weatherDB; if (string.IsNullOrEmpty(template.EnvironmentWeatherPreset)) // Random weather { weatherDB = Toolbox.RandomFrom(Database.Instance.GetAllEntries <DBEntryWeatherPreset>()); } else { weatherDB = Database.Instance.GetEntry <DBEntryWeatherPreset>(template.EnvironmentWeatherPreset); } mission.SetValue("WeatherName", weatherDB.BriefingDescription); mission.SetValue("WeatherCloudsBase", weatherDB.CloudsBase.GetValue() + baseAlt); mission.SetValue("WeatherCloudsPreset", Toolbox.RandomFrom(weatherDB.CloudsPresets)); mission.SetValue("WeatherCloudsThickness", weatherDB.CloudsThickness.GetValue()); mission.SetValue("WeatherDust", weatherDB.Dust); mission.SetValue("WeatherDustDensity", weatherDB.DustDensity.GetValue()); mission.SetValue("WeatherFog", weatherDB.Fog); mission.SetValue("WeatherFogThickness", weatherDB.FogThickness.GetValue()); mission.SetValue("WeatherFogVisibility", weatherDB.FogVisibility.GetValue()); mission.SetValue("WeatherQNH", weatherDB.QNH.GetValue()); mission.SetValue("WeatherTemperature", theaterDB.Temperature[(int)month].GetValue()); mission.SetValue("WeatherVisibility", weatherDB.Visibility.GetValue()); return(weatherDB.Turbulence.GetValue()); }
/// <summary> /// Picks a starting airbase for the player(s) /// </summary> /// <param name="mission">Mission for which the starting airbase must be set</param> /// <param name="template">Mission template to use</param> /// <param name="theaterDB">Theater database entry</param> /// <param name="objectiveDB">Objective database entry</param> /// <returns>Information about the starting airbase</returns> public DBEntryTheaterAirbase SelectStartingAirbase(DCSMission mission, MissionTemplate template, DBEntryTheater theaterDB, DBEntryObjective objectiveDB) { List <DBEntryTheaterAirbase[]> airbasesList = new List <DBEntryTheaterAirbase[]>(); // Select all airbases with enough parking spots int requiredParkingSpots = template.GetMissionPackageRequiredParkingSpots(); airbasesList.Add((from DBEntryTheaterAirbase ab in theaterDB.Airbases where ab.ParkingSpots.Length >= requiredParkingSpots select ab).ToArray()); // Select all airbases belonging to the proper coalition (unless all airbase belong to the same coalition) if ((template.OptionsTheaterCountriesCoalitions == CountryCoalition.Default) || (template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted)) { Coalition requiredCoalition = template.OptionsTheaterCountriesCoalitions == CountryCoalition.Inverted ? mission.CoalitionEnemy : mission.CoalitionPlayer; airbasesList.Add((from DBEntryTheaterAirbase ab in airbasesList.Last() where ab.Coalition == requiredCoalition select ab).ToArray()); } // If mission must start near water, or some player start on a carrier, select all airbases near water if (objectiveDB.Flags.HasFlag(DBEntryObjectiveFlags.MustStartNearWater) || !string.IsNullOrEmpty(template.PlayerFlightGroups[0].Carrier)) { airbasesList.Add((from DBEntryTheaterAirbase ab in airbasesList.Last() where ab.Flags.HasFlag(DBEntryTheaterAirbaseFlag.NearWater) select ab).ToArray()); } // If a particular airbase name has been specified and an airbase with this name exists, pick it if (!string.IsNullOrEmpty(template.FlightPlanTheaterStartingAirbase)) { airbasesList.Add((from DBEntryTheaterAirbase airbase in theaterDB.Airbases where airbase.Name.ToLowerInvariant() == template.FlightPlanTheaterStartingAirbase.ToLowerInvariant() select airbase).ToArray()); if (airbasesList.Last().Length == 0) { DebugLog.Instance.WriteLine($"Airbase \"{template.FlightPlanTheaterStartingAirbase}\" not found or airbase doesn't have enough parking spots. Selecting a random airbase instead.", 1, DebugLogMessageErrorLevel.Warning); } } // Check for valid airbases in all list, starting from the last one (with the most criteria filtered, and go back to the previous ones // as long as no airbase is found. for (int i = airbasesList.Count - 1; i >= 0; i--) { if (airbasesList[i].Length > 0) { return(Toolbox.RandomFrom(airbasesList[i])); } } throw new Exception($"No airbase found with {requiredParkingSpots} parking spots, cannot spawn all player aircraft."); }
/// <summary> /// Picks a starting time for the mission. /// Must be called after <see cref="GenerateMissionDate(DCSMission, MissionTemplate, DBEntryCoalition[])"/> because sunrise/sunset /// time depends on the selected date. /// </summary> /// <param name="mission">The mission.</param> /// <param name="template">Mission template to use</param> /// <param name="theaterDB">Theater database entry</param> public void GenerateMissionTime(DCSMission mission, MissionTemplate template, DBEntryTheater theaterDB) { Month month = mission.DateTime.Month; double totalMinutes; int hour, minute; switch (template.EnvironmentTimeOfDay) { default: // case TimeOfDay.Random totalMinutes = Toolbox.RandomInt(Toolbox.MINUTES_PER_DAY); break; case TimeOfDay.RandomDaytime: totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Max - 60); break; case TimeOfDay.Dawn: totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Min, theaterDB.DayTime[(int)month].Min + 120); break; case TimeOfDay.Noon: totalMinutes = Toolbox.RandomInt( (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 - 90, (theaterDB.DayTime[(int)month].Min + theaterDB.DayTime[(int)month].Max) / 2 + 90); break; case TimeOfDay.Twilight: totalMinutes = Toolbox.RandomInt(theaterDB.DayTime[(int)month].Max - 120, theaterDB.DayTime[(int)month].Max + 30); break; case TimeOfDay.Night: totalMinutes = Toolbox.RandomInt(0, theaterDB.DayTime[(int)month].Min - 120); break; } hour = Toolbox.Clamp((int)Math.Floor(totalMinutes / 60), 0, 23); minute = Toolbox.Clamp((int)Math.Floor((totalMinutes - hour * 60) / 15) * 15, 0, 45); mission.DateTime.Hour = hour; mission.DateTime.Minute = minute; DebugLog.Instance.WriteLine($"Starting time set to {mission.DateTime.ToTimeString()}", 1); }