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)); }
/// <summary> /// Constructor. /// </summary> public DatabaseCommon() { EnemyAirDefense = new DatabaseCommonAirDefenseInfo[Toolbox.EnumCount <AmountN>()]; EnemyAirDefenseDistanceFromObjectives = new MinMaxD[Toolbox.EnumCount <AirDefenseRange>()]; EnemyAirDefenseDistanceFromTakeOffLocation = new int[Toolbox.EnumCount <AirDefenseRange>()]; AllyAirDefense = new DatabaseCommonAirDefenseInfo[Toolbox.EnumCount <AmountN>()]; AllyAirDefenseDistanceFromObjectives = new int[Toolbox.EnumCount <AirDefenseRange>()]; AllyAirDefenseDistanceFromTakeOffLocation = new MinMaxD[Toolbox.EnumCount <AirDefenseRange>()]; EnemyCAPRelativePower = new double[Toolbox.EnumCount <AmountN>()]; }
private Coordinates?GetLandCoordinates( SpawnPointType[] validTypes, Coordinates distanceOrigin1, MinMaxD distanceFrom1, Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null, Coalition?coalition = null ) { var validSP = (from DBEntryTheaterSpawnPoint pt in SpawnPoints where validTypes.Contains(pt.PointType) select pt); Coordinates?[] distanceOrigin = new Coordinates?[] { distanceOrigin1, distanceOrigin2 }; MinMaxD?[] distanceFrom = new MinMaxD?[] { distanceFrom1, distanceFrom2 }; for (int i = 0; i < 2; i++) // Remove spawn points too far or too close from distanceOrigin1 and distanceOrigin2 { if (validSP.Count() == 0) { return(null); } if (!distanceFrom[i].HasValue || !distanceOrigin[i].HasValue) { continue; } var searchRange = distanceFrom[i].Value * Toolbox.NM_TO_METERS; // convert distance to meters IEnumerable <DBEntryTheaterSpawnPoint> validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP select s); int iterationsLeft = MAX_RADIUS_SEARCH_ITERATIONS; do { Coordinates origin = distanceOrigin[i].Value; validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP where searchRange.Contains(origin.GetDistanceFrom(s.Coordinates)) && CheckNotInHostileCoords(s.Coordinates, coalition) select s); searchRange = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1)); validSP = (from DBEntryTheaterSpawnPoint s in validSPInRange select s); iterationsLeft--; } while ((validSPInRange.Count() == 0) && (iterationsLeft > 0)); } if (validSP.Count() == 0) { return(null); } DBEntryTheaterSpawnPoint selectedSpawnPoint = Toolbox.RandomFrom(validSP.ToArray()); SpawnPoints.Remove(selectedSpawnPoint); // Remove spawn point so it won't be used again return(selectedSpawnPoint.Coordinates); }
internal Coordinates?GetRandomSpawnPoint( SpawnPointType[] validTypes, Coordinates distanceOrigin1, MinMaxD distanceFrom1, Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null, Coalition?coalition = null) { if (validTypes.Contains(SpawnPointType.Air) || validTypes.Contains(SpawnPointType.Sea)) { return(GetAirOrSeaCoordinates(validTypes, distanceOrigin1, distanceFrom1, distanceOrigin2, distanceFrom2, coalition)); } return(GetLandCoordinates(validTypes, distanceOrigin1, distanceFrom1, distanceOrigin2, distanceFrom2, coalition)); }
/// <summary> /// Constructor. /// </summary> /// <param name="ini">Objective database entry ini file</param> /// <param name="section">Ini section to load from</param> public DBUnitGroup(INIFile ini, string section) { Count = ini.GetValue <MinMaxI>(section, "Count"); DistanceFromPoint = ini.GetValue <MinMaxD>(section, "DistanceFromPoint"); Families = ini.GetValueArray <UnitFamily>(section, "Families"); if (Families.Length > 0) // If the group has at least one unit family (no families mean the group is disabled)... { // ...make sure all families belong to the same category UnitFamily family0 = Families[0]; // Families don't have to be Distinct() because one may want to mention the same familiy several times // so it appears more often Families = (from UnitFamily family in Families where Toolbox.GetUnitCategoryFromUnitFamily(family) == Toolbox.GetUnitCategoryFromUnitFamily(family0) select family).ToArray(); } Flags = ini.GetValueArrayAsEnumFlags <DBUnitGroupFlags>(section, "Flags"); LuaGroup = ini.GetValueArray <string>(section, "Lua.Group"); LuaUnit = ini.GetValue <string>(section, "Lua.Unit"); // TODO: check LuaGroup and LuaUnit Lua files exist in Include/Lua List <TheaterLocationSpawnPointType> spList = new List <TheaterLocationSpawnPointType>(ini.GetValueArray <TheaterLocationSpawnPointType>(section, "SpawnPoints")); if (spList.Count == 0) { spList.AddRange(Toolbox.GetEnumValues <TheaterLocationSpawnPointType>()); // No spawn point type means all spawn point types } // LandSmall implies LandLarge and LandMedium, LandMedium implies LandLarge (larger spots can handle smaller units) if (spList.Contains(TheaterLocationSpawnPointType.LandSmall)) { spList.AddRange(new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.LandMedium, TheaterLocationSpawnPointType.LandLarge }); } if (spList.Contains(TheaterLocationSpawnPointType.LandMedium)) { spList.Add(TheaterLocationSpawnPointType.LandLarge); } SpawnPoints = spList.Distinct().ToArray(); if (Families.Length > 0) { foreach (string lGroup in LuaGroup) { if (!File.Exists($"{BRPaths.INCLUDE_LUA_UNITS}{lGroup}.lua")) { DebugLog.Instance.WriteLine($"File \"Include\\Lua\\Units\\{lGroup}.lua\" doesn't exist.", 1, DebugLogMessageErrorLevel.Warning); } } if (!File.Exists($"{BRPaths.INCLUDE_LUA_UNITS}{LuaUnit}.lua")) { DebugLog.Instance.WriteLine($"File \"Include\\Lua\\Units\\{LuaUnit}.lua\" doesn't exist.", 1, DebugLogMessageErrorLevel.Warning); } } }
/// <summary> /// Constructor. Loads data from a .ini file. /// </summary> /// <param name="ini">The .ini file to load from.</param> /// <param name="section">The .ini section to load from.</param> /// <param name="key">The top level .ini key to load from.</param> public LibraryCommonSettingsDistanceToObjective(INIFile ini, string section, string key) { DistanceFromStartLocation = ini.GetValue <MinMaxD>(section, $"{key}.DistanceFromStartLocation"); DistanceFromStartLocation = new MinMaxD( Math.Max(0, DistanceFromStartLocation.Min), Math.Max(0, DistanceFromStartLocation.Max)) * HQTools.NM_TO_METERS; DistanceBetweenTargets = ini.GetValue <MinMaxD>(section, $"{key}.DistanceBetweenTargets"); DistanceBetweenTargets = new MinMaxD( Math.Max(0, DistanceBetweenTargets.Min), Math.Max(0, DistanceBetweenTargets.Max)) * HQTools.NM_TO_METERS; }
private Coordinates?GetAirOrSeaCoordinates( SpawnPointType[] validTypes, Coordinates distanceOrigin1, MinMaxD distanceFrom1, Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null, Coalition?coalition = null) { var searchRange = distanceFrom1 * Toolbox.NM_TO_METERS; MinMaxD?secondSearchRange = null; if (distanceOrigin2.HasValue && distanceFrom2.HasValue) { secondSearchRange = distanceFrom2.Value * Toolbox.NM_TO_METERS; } var iterations = 0; do { var coordOptionsLinq = Enumerable.Range(0, 50) .Select(x => Coordinates.CreateRandom(distanceOrigin1, searchRange)) .Where(x => CheckNotInHostileCoords(x, coalition) && CheckNotInNoSpawnCoords(x)); if (secondSearchRange.HasValue) { coordOptionsLinq = coordOptionsLinq.Where(x => secondSearchRange.Value.Contains(distanceOrigin2.Value.GetDistanceFrom(x))); } if (validTypes.First() == SpawnPointType.Sea) //sea position { coordOptionsLinq = coordOptionsLinq.Where(x => ShapeManager.IsPosValid(x, TheaterDB.WaterCoordinates, TheaterDB.WaterExclusionCoordinates)); } var coordOptions = coordOptionsLinq.ToList(); if (coordOptions.Count > 0) { return(Toolbox.RandomFrom(coordOptions)); } searchRange = new MinMaxD(searchRange.Min * 0.9, searchRange.Max * 1.1); if (secondSearchRange.HasValue) { secondSearchRange = new MinMaxD(secondSearchRange.Value.Min * 0.9, secondSearchRange.Value.Max * 1.1); } iterations++; } while (iterations < MAX_RADIUS_SEARCH_ITERATIONS); return(null); }
/// <summary> /// Constructor. Load data from a .ini file. /// </summary> /// <param name="ini">The .ini file to load from.</param> /// <param name="section">The .ini file section.</param> /// <param name="key">The top level .ini key to load from.</param> public LibraryCommonSettingsEnemyAirDefenseDistance(INIFile ini, string section, string key) { key = (key == null) ? "" : $"{key}."; DistanceFromObjective = ini.GetValue <MinMaxD>(section, $"{key}DistanceFromTarget"); DistanceFromObjective = new MinMaxD(Math.Max(0.0, DistanceFromObjective.Min), Math.Max(0.0, DistanceFromObjective.Max)) * HQTools.NM_TO_METERS; MinDistanceFromTakeOffLocation = Math.Max(0, ini.GetValue <double>(section, $"{key}MinDistanceFromTakeOffLocation")) * HQTools.NM_TO_METERS; NodeTypes = ini.GetValueArray <TheaterLocationSpawnPointType>(section, $"{key}SpawnPointTypes").Distinct().ToArray(); if (NodeTypes.Length == 0) { NodeTypes = new TheaterLocationSpawnPointType[] { TheaterLocationSpawnPointType.LandMedium, TheaterLocationSpawnPointType.LandLarge } } ; } }
internal DBCommonAirDefense() { INIFile ini = new($"{BRPaths.DATABASE}AirDefense.ini"); AirDefenseLevels = new DBCommonAirDefenseLevel[Toolbox.EnumCount <AmountNR>()]; for (var i = 0; i < Toolbox.EnumCount <AmountNR>(); i++) { AirDefenseLevels[i] = new DBCommonAirDefenseLevel(ini, (AmountNR)i); } DistanceFromCenter = new MinMaxD[2, Toolbox.EnumCount <AirDefenseRange>()]; MinDistanceFromOpposingPoint = new double[2, Toolbox.EnumCount <AirDefenseRange>()]; foreach (Side side in Toolbox.GetEnumValues <Side>()) { foreach (AirDefenseRange airDefenseRange in Toolbox.GetEnumValues <AirDefenseRange>()) { DistanceFromCenter[(int)side, (int)airDefenseRange] = ini.GetValue <MinMaxD>($"AirDefenseRange.{side}", $"{airDefenseRange}.DistanceFromCenter"); MinDistanceFromOpposingPoint[(int)side, (int)airDefenseRange] = ini.GetValue <double>($"AirDefenseRange.{side}", $"{airDefenseRange}.MinDistanceFromOpposingPoint"); } } }
/// <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); }
/// <summary> /// Gets a random spawn point around a given point. /// </summary> /// <param name="validTypes">Valid spawn point types</param> /// <param name="distanceOrigin1">Origin point distance must be computed from</param> /// <param name="distanceFrom1">Min/max distance from origin point, in nautical miles</param> /// <param name="distanceOrigin2">Second origin point distance must be computed from</param> /// <param name="distanceFrom2">Min/max distance from second origin point, in nautical miles</param> /// <param name="coalition">Which coalition should the spawn point belong to?</param> /// <returns>A spawn point, or null if none found matching the provided criteria</returns> public DBEntryTheaterSpawnPoint?GetRandomSpawnPoint( TheaterLocationSpawnPointType[] validTypes = null, Coordinates?distanceOrigin1 = null, MinMaxD?distanceFrom1 = null, Coordinates?distanceOrigin2 = null, MinMaxD?distanceFrom2 = null, Coalition?coalition = null) { // Select all spoint points IEnumerable <DBEntryTheaterSpawnPoint> validSP = from DBEntryTheaterSpawnPoint pt in SpawnPoints select pt; if (validTypes != null) // Remove spawn points of invalid types { validSP = (from DBEntryTheaterSpawnPoint pt in validSP where validTypes.Contains(pt.PointType) select pt); } if (coalition.HasValue) // Select spawn points belonging to the proper coalition { IEnumerable <DBEntryTheaterSpawnPoint> coalitionValidSP = coalitionValidSP = (from DBEntryTheaterSpawnPoint sp in validSP where sp.Coalition == coalition.Value select sp); // At least one spawn point found, only use SP for the preferred coalition if (coalitionValidSP.Count() > 0) { validSP = coalitionValidSP; } } Coordinates?[] distanceOrigin = new Coordinates?[] { distanceOrigin1, distanceOrigin2 }; MinMaxD?[] distanceFrom = new MinMaxD?[] { distanceFrom1, distanceFrom2 }; for (int i = 0; i < 2; i++) // Remove spawn points too far or too close from distanceOrigin1 and distanceOrigin2 { if (validSP.Count() == 0) { return(null); } if (!distanceFrom[i].HasValue || !distanceOrigin[i].HasValue) { continue; } MinMaxD searchRange = distanceFrom[i].Value * Toolbox.NM_TO_METERS; // convert distance to meters IEnumerable <DBEntryTheaterSpawnPoint> validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP select s); int iterationsLeft = MAX_RADIUS_SEARCH_ITERATIONS; do { Coordinates origin = distanceOrigin[i].Value; validSPInRange = (from DBEntryTheaterSpawnPoint s in validSP where searchRange.Contains(origin.GetDistanceFrom(s.Coordinates)) select s); searchRange = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1)); iterationsLeft--; } while ((validSPInRange.Count() == 0) && (iterationsLeft > 0)); validSP = (from DBEntryTheaterSpawnPoint s in validSPInRange select s); } if (validSP.Count() == 0) { return(null); } DBEntryTheaterSpawnPoint selectedSpawnPoint = Toolbox.RandomFrom(validSP.ToArray()); SpawnPoints.Remove(selectedSpawnPoint); // Remove spawn point so it won't be used again return(selectedSpawnPoint); }
/// <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(); }
public DefinitionTheaterSpawnPoint?GetRandomSpawnPoint( TheaterLocationSpawnPointType[] validTypes = null, DCSCountry[] validCountries = null, MinMaxD?distanceFrom = null, Coordinates?distanceOrigin = null, MinMaxD?distanceFrom2 = null, Coordinates?distanceOrigin2 = null) { IEnumerable <DefinitionTheaterSpawnPoint> validSP = (from DefinitionTheaterSpawnPoint s in SpawnPoints where !UsedSpawnPointsID.Contains(s.UniqueID) select s); if (validTypes != null) { validSP = (from DefinitionTheaterSpawnPoint s in validSP where validTypes.Contains(s.PointType) select s); } if (validCountries != null) { validSP = (from DefinitionTheaterSpawnPoint s in validSP where validCountries.Contains(s.Country) select s); } if (distanceFrom.HasValue && distanceOrigin.HasValue) { if (validSP.Count() == 0) { return(null); } MinMaxD searchRange = distanceFrom.Value; IEnumerable <DefinitionTheaterSpawnPoint> validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP select s); do { validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP where searchRange.Contains(distanceOrigin.Value.GetDistanceFrom(s.Coordinates)) select s); searchRange = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1)); } while (validSPInRange.Count() == 0); validSP = (from DefinitionTheaterSpawnPoint s in validSPInRange select s); } if (distanceFrom2.HasValue && distanceOrigin2.HasValue) { if (validSP.Count() == 0) { return(null); } MinMaxD searchRange = distanceFrom2.Value; IEnumerable <DefinitionTheaterSpawnPoint> validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP select s); do { validSPInRange = (from DefinitionTheaterSpawnPoint s in validSP where searchRange.Contains(distanceOrigin2.Value.GetDistanceFrom(s.Coordinates)) select s); searchRange = new MinMaxD(searchRange.Min * 0.9, Math.Max(100, searchRange.Max * 1.1)); } while (validSPInRange.Count() == 0); validSP = (from DefinitionTheaterSpawnPoint s in validSPInRange select s); } if (validSP.Count() == 0) { return(null); } DefinitionTheaterSpawnPoint selectedSpawnPoint = HQTools.RandomFrom(validSP.ToArray()); UsedSpawnPointsID.Add(selectedSpawnPoint.UniqueID); return(selectedSpawnPoint); }
public DCSMission Generate(MissionTemplate template, out string errorMessage) { int i; errorMessage = ""; // Clear log, begin timing then create an instance of the HQ mission class Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); DCSMission mission = new DCSMission(); DebugLog.Instance.Clear(); DebugLog.Instance.Log($"STARTING MISSION GENERATION AT {DateTime.Now.ToLongTimeString()}..."); DebugLog.Instance.Log(new string('=', DebugLog.Instance.GetLastMessage().Length)); DebugLog.Instance.Log(); try { using (MissionGeneratorTemplateChecker templateChecker = new MissionGeneratorTemplateChecker()) { templateChecker.CheckTemplate(template); } if (template.GetPlayerCount() < 1) { throw new HQ4DCSException("Mission must include at least one player-controlled aircraft."); } // Pick definitions DefinitionCoalition[] coalitions = new DefinitionCoalition[2]; coalitions[(int)Coalition.Blue] = Library.Instance.GetDefinition <DefinitionCoalition>(template.ContextCoalitionBlue); coalitions[(int)Coalition.Red] = Library.Instance.GetDefinition <DefinitionCoalition>(template.ContextCoalitionRed); DefinitionLanguage languageDef = Library.Instance.GetDefinition <DefinitionLanguage>(template.PreferencesLanguage.ToLowerInvariant()); DefinitionObjective objectiveDef = Library.Instance.GetDefinition <DefinitionObjective>(template.ObjectiveType.ToLowerInvariant()); DefinitionTheater theaterDef = Library.Instance.GetDefinition <DefinitionTheater>(template.ContextTheater); theaterDef.ResetUsedSpawnPoints(); // Create a list of all available objective names List <string> objectiveNames = languageDef.GetStringArray("Mission", "Waypoint.ObjectiveNames").ToList(); // Create unit generators MissionGeneratorCallsign callsignGenerator = new MissionGeneratorCallsign(coalitions[(int)Coalition.Blue].NATOCallsigns, coalitions[(int)Coalition.Red].NATOCallsigns); MissionGeneratorUnitGroups unitGroupsGenerator = new MissionGeneratorUnitGroups(languageDef, callsignGenerator); // Copy values from the template to the mission mission.TheaterDefinition = template.ContextTheater; mission.ObjectiveDefinition = template.ObjectiveType; mission.Language = template.PreferencesLanguage; mission.CoalitionPlayer = template.ContextPlayerCoalition; mission.SinglePlayer = (template.GetPlayerCount() < 2); mission.UseNATOCallsigns = coalitions[(int)template.ContextPlayerCoalition].NATOCallsigns; // Make sure no countries are shared between both coalitions mission.Countries[(int)Coalition.Blue] = coalitions[(int)Coalition.Blue].Countries.ToArray(); mission.Countries[(int)Coalition.Red] = coalitions[(int)Coalition.Red].Countries.Except(coalitions[(int)Coalition.Blue].Countries).ToArray(); if (mission.Countries[(int)Coalition.Red].Length == 0) { throw new HQ4DCSException("Red and blue coalitions cannot share the same countries."); } switch (template.BriefingUnits) { case UnitSystem.ByCoalition: mission.BriefingImperialUnits = (coalitions[(int)mission.CoalitionPlayer].UnitSystem == UnitSystem.Imperial); break; case UnitSystem.Imperial: mission.BriefingImperialUnits = true; break; case UnitSystem.Metric: mission.BriefingImperialUnits = false; break; } // Generate mission environment parameters (weather, time of day, date...) using (MissionGeneratorEnvironment environment = new MissionGeneratorEnvironment()) { environment.GenerateMissionDate(mission, template.ContextTimePeriod, template.EnvironmentSeason); environment.GenerateMissionTime(mission, template.EnvironmentTimeOfDay, theaterDef); environment.GenerateWeather(mission, template.EnvironmentWeather, theaterDef); environment.GenerateWind(mission, template.EnvironmentWind, theaterDef); } // Randomly select players' airbase DefinitionTheaterAirbase missionAirbase = HQTools.RandomFrom((from DefinitionTheaterAirbase ab in theaterDef.Airbases where ab.Coalition == template.ContextPlayerCoalition select ab).ToArray()); // Randomly select objective spawn points int objectiveCount = (int)template.ObjectiveCount; if (objectiveCount == 0) { objectiveCount = HQTools.RandomFrom(1, 1, 1, 2, 2, 3, 3, 4, 5); // Random objective count } //AmountR objectiveDistance = template.ObjectiveDistance; //if (objectiveDistance == AmountR.Random) objectiveDistance = // HQTools.RandomFrom(AmountR.VeryLow, AmountR.VeryLow, AmountR.Low, AmountR.Low, AmountR.Low, AmountR.Average, AmountR.Average, AmountR.Average, AmountR.High, AmountR.High, AmountR.VeryHigh); List <DCSMissionObjectiveLocation> objectivesList = new List <DCSMissionObjectiveLocation>(); List <DCSMissionWaypoint> waypointsList = new List <DCSMissionWaypoint>(); for (i = 0; i < objectiveCount; i++) { // If this is the first objective, measure distance from the airbase. Else measure distance from the previous objective. Coordinates previousPoint = (i == 0) ? missionAirbase.Coordinates : objectivesList[i - 1].Coordinates; MinMaxD distanceFromLastPoint = new MinMaxD(template.ObjectiveDistance * 0.75, template.ObjectiveDistance * 1.25) * HQTools.NM_TO_METERS; if (i > 0) { distanceFromLastPoint /= 4.0; } DefinitionTheaterSpawnPoint?spawnPoint = theaterDef.GetRandomSpawnPoint(objectiveDef.SpawnPointType, null, distanceFromLastPoint, previousPoint); if (!spawnPoint.HasValue) // No valid spawn point, throw an error { throw new HQ4DCSException($"Cannot find a valid spawn point for objective #{i + 1}"); } // Select a random name for the objective string objName; if (objectiveNames.Count == 0) { objName = $"OBJECTIVE{(i + 1).ToString("00")}"; } else { objName = HQTools.RandomFrom(objectiveNames); objectiveNames.Remove(objName); } objectivesList.Add(new DCSMissionObjectiveLocation(spawnPoint.Value.Coordinates, objName, objectiveDef.WaypointOnGround ? 0.0 : 1.0, 0)); // Add a waypoint for each objective waypointsList.Add(new DCSMissionWaypoint(spawnPoint.Value.Coordinates + Coordinates.CreateRandomInaccuracy(objectiveDef.WaypointInaccuracy), objName)); } // If required, add additional waypoints on the way to & from the objectives if (template.PreferencesExtraWaypoints && (waypointsList.Count > 0)) { Coordinates firstWPCoos = waypointsList.First().Coordinates; Coordinates lastWPCoos = waypointsList.Last().Coordinates; int wpBeforeCount = HQTools.RandomMinMax(1, 3); for (i = 0; i < wpBeforeCount; i++) { waypointsList.Insert(i, new DCSMissionWaypoint( Coordinates.Lerp(missionAirbase.Coordinates, firstWPCoos, (double)(i + 1) / (wpBeforeCount + 1)) + Coordinates.CreateRandomInaccuracy(firstWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.05, firstWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.15), $"WP{(i + 1).ToString()}")); } int wpAfterCount = HQTools.RandomMinMax(1, 2); for (i = 0; i < wpAfterCount; i++) { waypointsList.Add( new DCSMissionWaypoint( Coordinates.Lerp(lastWPCoos, missionAirbase.Coordinates, (double)(i + 1) / (wpAfterCount + 1)) + Coordinates.CreateRandomInaccuracy(lastWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.05, lastWPCoos.GetDistanceFrom(missionAirbase.Coordinates) * 0.15), $"WP{(waypointsList.Count + 1).ToString()}")); } } mission.Objectives = objectivesList.ToArray(); mission.Waypoints = waypointsList.ToArray(); mission.TotalFlightPlanDistance = 0.0; for (i = 0; i <= mission.Waypoints.Length; i++) { if (i == 0) // first point, add distance between the takeoff airbase and the first waypoint { mission.TotalFlightPlanDistance += missionAirbase.Coordinates.GetDistanceFrom(mission.Waypoints.First().Coordinates); } else if (i == mission.Waypoints.Length) // last point, add distance between last waypoint and landing airbase { mission.TotalFlightPlanDistance += missionAirbase.Coordinates.GetDistanceFrom(mission.Waypoints.Last().Coordinates); } else // any other point, add distance between this waypoint and the last one { mission.TotalFlightPlanDistance += mission.Waypoints[i].Coordinates.GetDistanceFrom(mission.Waypoints[i - 1].Coordinates); } } // Create a list of used player aircraft types, so the proper kneeboard subdirectories can be created in the .miz file mission.UsedPlayerAircraftTypes = (from MissionTemplatePlayerFlightGroup pfg in template.FlightPackagePlayers select pfg.AircraftType).Distinct().OrderBy(x => x).ToArray(); // Generate bullseyes and map center mission.MapCenter = Coordinates.GetCenter( (from DCSMissionObjectiveLocation o in mission.Objectives select o.Coordinates).Union(new Coordinates[] { missionAirbase.Coordinates }).ToArray()); mission.Bullseye = new Coordinates[2]; for (i = 0; i < 2; i++) { mission.Bullseye[i] = mission.MapCenter + Coordinates.CreateRandomInaccuracy(10000, 20000); } // Copy scripts //mission.ScriptsMission = missionObjective.ScriptMission.ToList(); //mission.ScriptsObjective = missionObjective.ScriptObjective.ToList(); mission.RealismAllowExternalViews = template.RealismAllowExternalViews; mission.RealismBirdStrikes = template.RealismBirdStrikes; mission.RealismRandomFailures = template.RealismRandomFailures; // Create list of airbase alignment from the theater definition mission.AirbasesCoalition.Clear(); foreach (DefinitionTheaterAirbase ab in theaterDef.Airbases) { if (mission.AirbasesCoalition.ContainsKey(ab.DCSID)) { continue; } Coalition airbaseCoalition = ab.Coalition; switch (template.ContextCountriesCoalitions) { case CountriesCoalition.AllBlue: airbaseCoalition = Coalition.Blue; break; case CountriesCoalition.AllRed: airbaseCoalition = Coalition.Red; break; case CountriesCoalition.Inverted: airbaseCoalition = (Coalition)(1 - (int)ab.Coalition); break; } mission.AirbasesCoalition.Add(ab.DCSID, airbaseCoalition); } // Make sure the starting airbase belongs to the players' coalition no matter which coalition other airbases belong to if (mission.AirbasesCoalition.ContainsKey(missionAirbase.DCSID)) { mission.AirbasesCoalition[missionAirbase.DCSID] = template.ContextPlayerCoalition; } List <string> oggFilesList = new List <string>(); oggFilesList.AddRange(Library.Instance.Common.SharedOggFiles); // Default wave files oggFilesList.AddRange(objectiveDef.IncludeOgg); // Objective wave files mission.OggFiles = oggFilesList.Distinct().ToArray(); //mission.Scripts /* * // Generate mission flight plan * using (GeneratorFlightPlan flightPlan = new GeneratorFlightPlan(Library, language, csGenerator)) * { * flightPlan.SelectTakeoffAndLandingAirbases(mission, theater); * mission.MapCenter = mission.Airbases[0].Coordinates; // Center the map on the starting airdrome * flightPlan.GenerateObjectiveLocations(mission, template, theater, missionObjective); * flightPlan.GenerateWaypoints(mission, template, theater, missionObjective); * flightPlan.GenerateBullseye(mission); * } */ // Generate units AmountNR selectedEnemyAirDefense, selectedEnemyCAP; // We have to store these values here because they're used in the briefing remarks using (MissionGeneratorUnitGroups unitGenerator = new MissionGeneratorUnitGroups(languageDef, callsignGenerator)) { foreach (MissionTemplatePlayerFlightGroup pfg in template.FlightPackagePlayers) { unitGenerator.AddPlayerFlightGroup(mission, template, pfg, objectiveDef, missionAirbase); } //unitGroups.GeneratePlayerFlightGroups(mission, template, missionObjective); //unitGroups.GenerateAIEscortFlightGroups(mission, template, coalitions, template.FlightGroupsAICAP, UnitFamily.PlaneFighter, "GroupPlaneEscortCAP", AircraftPayloadType.A2A, DCSAircraftTask.CAP); //unitGroups.GenerateAIEscortFlightGroups(mission, template, coalitions, template.FlightGroupsAISEAD, UnitFamily.PlaneSEAD, "GroupPlaneEscortSEAD", AircraftPayloadType.SEAD, DCSAircraftTask.SEAD); unitGenerator.AddObjectiveUnitGroupsAtEachObjective(mission, template, objectiveDef, coalitions); //unitGroups.GenerateObjectiveUnitGroupsAtCenter(mission, template, missionObjective, coalitions); unitGenerator.AddFriendlySupportAircraft(mission, template, coalitions[(int)mission.CoalitionPlayer], theaterDef, missionAirbase); unitGenerator.AddEnemyAirDefenseUnits(mission, template, theaterDef, objectiveDef, coalitions, missionAirbase, out selectedEnemyAirDefense); unitGenerator.AddFriendlyAirDefenseUnits(mission, template, theaterDef, objectiveDef, coalitions, missionAirbase, out AmountNR selectedFriendlyAirDefense); unitGenerator.AddCombatAirPatrolUnits(mission, template, theaterDef, coalitions, missionAirbase, out AmountNR selectedFriendlyCAP, out selectedEnemyCAP); } using (MissionGeneratorBriefing briefingGenerator = new MissionGeneratorBriefing(languageDef)) { // Add briefing remarks for (i = 0; i < objectiveDef.BriefingRemarks.Length; i++) { mission.BriefingRemarks.Add(languageDef.GetStringRandom("Briefing", $"Remark.{objectiveDef.BriefingRemarks}")); } mission.BriefingRemarks.Add(languageDef.GetStringRandom("Briefing", $"Remark.EnemyAirDefense.{selectedEnemyAirDefense}")); mission.BriefingRemarks.Add(languageDef.GetStringRandom("Briefing", $"Remark.EnemyCAP.{selectedEnemyCAP}")); mission.BriefingTasks.Add(languageDef.GetString("Briefing", "Task.TakeOffFrom", "Airbase", missionAirbase.Name)); for (i = 0; i < mission.Objectives.Length; i++) { mission.BriefingTasks.Add(languageDef.GetString("Briefing", $"Task.{objectiveDef.BriefingTask}", "Objective", mission.Objectives[i].Name.ToUpperInvariant())); } mission.BriefingTasks.Add(languageDef.GetString("Briefing", "Task.LandAt", "Airbase", missionAirbase.Name)); briefingGenerator.GenerateMissionName(mission, template.BriefingName); briefingGenerator.GenerateMissionDescription(mission, template.BriefingDescription, objectiveDef); /* * briefing.GenerateMissionTasks(mission, template, missionObjective); * briefing.GenerateMissionRemarks(mission, template, missionObjective); */ briefingGenerator.GenerateRawTextBriefing(mission, template /*, missionObjective*/); briefingGenerator.GenerateHTMLBriefing(mission, template /*, missionObjective*/); } stopwatch.Stop(); DebugLog.Instance.Log(); DebugLog.Instance.Log($"COMPLETED MISSION GENERATION AT {DateTime.Now.ToLongTimeString()} (TOOK {stopwatch.Elapsed.TotalMilliseconds.ToString("F0")} MILLISECONDS)."); DebugLog.Instance.Log(); mission.GenerationLog = DebugLog.Instance.GetFullLog(); } #if DEBUG catch (HQ4DCSException e) #else catch (Exception e) #endif { stopwatch.Stop(); DebugLog.Instance.Log($"ERROR: {e.Message}"); DebugLog.Instance.Log(); DebugLog.Instance.Log($"MISSION GENERATION FAILED."); DebugLog.Instance.Log(); errorMessage = e.Message; mission.Dispose(); mission = null; } DebugLog.Instance.SaveToFileAndClear("MissionGeneration"); return(mission); }