/// <summary> /// Generates a new asteroid name appropriate for the chosen set. /// </summary> /// /// <param name="group">The set to which the asteroid belongs.</param> /// <returns>A randomly generated name. The name will be prefixed by a population-specific /// name if custom names are enabled, or by "Ast." if they are disabled.</returns> /// /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot /// generate valid names. The program state will be unchanged in the event of an /// exception.</exception> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> is /// null.</exception> private static string makeName(AsteroidSet group) { string name = DiscoverableObjectsUtil.GenerateAsteroidName(); if (AsteroidManager.getOptions().getRenameOption()) { GroupCollection parsed = astName.Match(name).Groups; if (parsed [0].Success) { string newBase = group.getAsteroidName(); if (!newBase.Contains("<<1>>")) { newBase += " <<1>>"; } string id = parsed ["id"].ToString(); name = Localizer.Format(newBase, id); } // if asteroid name doesn't match expected format, leave it as-is #if DEBUG Debug.Log("[CustomAsteroids]: " + Localizer.Format("#autoLOC_CustomAsteroids_LogRename", name)); #endif } return(name); }
/// <summary> /// Generates any vessel modules needed by a particular asteroid. /// </summary> /// <param name="group">The asteroid group to which the asteroid belongs.</param> /// <param name="partList">The part(s) from which the asteroid is made.</param> /// <param name="size">The asteroid's size class.</param> /// <returns>A <c>VesselModules</c> node containing all vessel modules besides /// the defaults (currently <c>FlightIntegrator</c> and <c>AxisGroupModule</c>).</returns> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> or <c>partList</c> is /// null.</exception> /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot generate /// valid data, or if <c>partList</c> does not contain exactly one part, or contains an /// unsupported part. The program state will be unchanged in the event of an exception.</exception> private ConfigNode makeVesselModules(AsteroidSet group, ConfigNode[] partList, UntrackedObjectClass size) { if (partList.Length != 1) { throw new InvalidOperationException( Localizer.Format("#autoLOC_CustomAsteroids_ErrorMultiParts", partList)); } ConfigNode wrapper = new ConfigNode("VESSELMODULES"); // CometVessel seems to be removed if there's no ModuleComet, but filter just in case if (isComet(partList)) { string cometType = group.getCometOrbit(); bool cometName = group.getUseCometName(); CometOrbitType stockClass = CometManager.GetCometOrbitType(cometType); if (stockClass is null) { throw new InvalidOperationException(Localizer.Format("#autoLOC_CustomAsteroids_ErrorNoCometType", cometType)); } // As far as I can tell the object class is used to scale the activity level ConfigNode moduleComet = CometManager.GenerateDefinition(stockClass, size, new System.Random().Next()) .CreateVesselNode(false, 0.0f, !cometName); wrapper.AddNode(moduleComet); } return(wrapper); }
/// <summary> /// Creates a new asteroid from the specific asteroid set. The asteroid will be given /// appropriate properties as specified in the set's config node, and added to the game as /// an untracked object. /// </summary> /// <remarks>Based heavily on Kopernicus's <c>DiscoverableObjects.SpawnAsteroid</c> by /// ThomasKerman. Thanks for reverse-engineering everything!</remarks> /// /// <param name="group">The set to which the asteroid belongs.</param> /// <returns>The asteroid that was added.</returns> /// /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot /// generate valid data. The program state will be unchanged in the event of an /// exception.</exception> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> is /// null.</exception> private ProtoVessel spawnAsteroid(AsteroidSet group) { Orbit orbit = makeOrbit(group); string name = makeName(group); UntrackedObjectClass size = group.drawAsteroidSize(); ConfigNode [] partList = makeAsteroidParts(group); ConfigNode [] extraNodes = new ConfigNode [] { new ConfigNode("ACTIONGROUPS"), makeDiscoveryInfo(group, size), makeVesselModules(group, partList, size), }; // Stock spawner reports its module name, so do the same for custom spawns Debug.Log($"[{GetType ().Name}]: " + Localizer.Format("#autoLOC_CustomAsteroids_LogSpawn", name, group)); ConfigNode vessel = ProtoVessel.CreateVesselNode(name, VesselType.SpaceObject, orbit, 0, partList, extraNodes); ProtoVessel newVessel = HighLogic.CurrentGame.AddVessel(vessel); // For some reason you can always retrieve a CometVessel for any Vessel; check parts instead if (isComet(partList)) { GameEvents.onCometSpawned.Fire(newVessel.vesselRef); } else { GameEvents.onAsteroidSpawned.Fire(newVessel.vesselRef); } return(newVessel); }
/// <summary> /// Generates tracking station info appropriate for the chosen set. /// </summary> /// /// <param name="group">The set to which the asteroid belongs.</param> /// <param name="size">The asteroid's size class.</param> /// <returns>A ConfigNode storing the asteroid's DiscoveryInfo object.</returns> /// /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot /// generate valid data. The program state will be unchanged in the event of an /// exception.</exception> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> is /// null.</exception> private static ConfigNode makeDiscoveryInfo(AsteroidSet group, UntrackedObjectClass size) { Tuple <double, double> lifetimes = group.drawTrackingTime(); ConfigNode trackingInfo = ProtoVessel.CreateDiscoveryNode( DiscoveryLevels.Presence, size, lifetimes.Item1, lifetimes.Item2); return(trackingInfo); }
/// <summary> /// Generates tracking station info appropriate for the chosen set. /// </summary> /// /// <param name="group">The set to which the asteroid belongs.</param> /// <returns>A ConfigNode storing the asteroid's DiscoveryInfo object.</returns> /// /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot /// generate valid data. The program state will be unchanged in the event of an /// exception.</exception> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> is /// null.</exception> private static ConfigNode makeDiscoveryInfo(AsteroidSet group) { Pair <float, float> trackTimes = AsteroidManager.getOptions().getUntrackedTimes(); double lifetime = UnityEngine.Random.Range(trackTimes.first, trackTimes.second) * SECONDS_PER_EARTH_DAY; double maxLifetime = trackTimes.second * SECONDS_PER_EARTH_DAY; UntrackedObjectClass size = (UntrackedObjectClass)(int) (stockSizeCurve.Evaluate(UnityEngine.Random.Range(0.0f, 1.0f)) * Enum.GetNames(typeof(UntrackedObjectClass)).Length); ConfigNode trackingInfo = ProtoVessel.CreateDiscoveryNode( DiscoveryLevels.Presence, size, lifetime, maxLifetime); return(trackingInfo); }
/// <summary> /// Generates vessel parts and resources appropriate for the chosen set. /// </summary> /// /// <param name="group">The set to which the asteroid belongs.</param> /// <returns>An array of ConfigNodes storing the asteroid's parts. The first element MUST /// be the root part.</returns> /// /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot /// generate valid parts. The program state will be unchanged in the event of an /// exception.</exception> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> is /// null.</exception> private static ConfigNode [] makeAsteroidParts(AsteroidSet group) { // The same "seed" that shows up in ProceduralAsteroid? uint seed = (uint)UnityEngine.Random.Range(0, Int32.MaxValue); string part = group.drawAsteroidType(); try { ConfigNode potato = ProtoVessel.CreatePartNode(part, seed); return(new [] { potato }); } catch (Exception e) { // Really? That's what CreatePartNode throws? throw new InvalidOperationException( Localizer.Format("#autoLOC_CustomAsteroids_ErrorTypeBadPart", part), e); } }
/// <summary> /// Creates a new asteroid from the specific asteroid set. The asteroid will be given /// appropriate properties as specified in the set's config node, and added to the game as /// an untracked object. /// </summary> /// <remarks>Based heavily on Kopernicus's <c>DiscoverableObjects.SpawnAsteroid</c> by /// ThomasKerman. Thanks for reverse-engineering everything!</remarks> /// /// <param name="group">The set to which the asteroid belongs.</param> /// <returns>The asteroid that was added.</returns> /// /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot /// generate valid data. The program state will be unchanged in the event of an /// exception.</exception> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> is /// null.</exception> private ProtoVessel spawnAsteroid(AsteroidSet group) { Orbit orbit = makeOrbit(group); string name = makeName(group); ConfigNode trackingInfo = makeDiscoveryInfo(group); ConfigNode [] partList = makeAsteroidParts(group); // Stock spawner reports its module name, so do the same for custom spawns Debug.Log($"[{GetType ().Name}]: " + Localizer.Format("#autoLOC_CustomAsteroids_LogSpawn", name, group)); ConfigNode vessel = ProtoVessel.CreateVesselNode(name, VesselType.SpaceObject, orbit, 0, partList, new ConfigNode("ACTIONGROUPS"), trackingInfo); return(HighLogic.CurrentGame.AddVessel(vessel)); }
/// <summary> /// Counts the number of asteroids from a specific set. /// </summary> /// <param name="group">The AsteroidSet of interest.</param> /// <returns>The number of asteroids known to have been spawned from <c>group</c>.</returns> internal static int countAsteroidsInSet(AsteroidSet group) { int count = 0; string id = group.getName(); foreach (Vessel v in FlightGlobals.Vessels) { if (v.vesselType == VesselType.SpaceObject) { AsteroidInfo info = CustomAsteroidRegistry.Instance.LookupAsteroid(v); if (info != null && info.parentSet == id) { count++; } } } return(count); }
/// <summary> /// <para>Creates a new asteroid from a randomly chosen asteroid set. The asteroid will be /// given the properties specified by that set, and added to the game as an untracked /// object.</para> /// <para>If asteroid creation failed for any reason, this method will log the error rather /// than propagating the exception into client code.</para> /// </summary> /// <returns>the newly created asteroid, or null if no asteroid was created. May be used as /// a hook by spawners that need more control over asteroid properties. Clients should /// assume the returned vessel is already registered in the game.</returns> /// <remarks>At least one population must have a positive spawn rate.</remarks> protected ProtoVessel spawnAsteroid() { try { AsteroidSet group = AsteroidManager.drawAsteroidSet(); ProtoVessel asteroid = spawnAsteroid(group); try { registerAsteroid(asteroid, group); } catch (ArgumentException e) { Debug.LogWarning("[CustomAsteroids]: Duplicate entry in CustomAsteroidRegistry."); Debug.LogException(e); } return(asteroid); } catch (Exception e) { Util.errorToPlayer(e, Localizer.Format("#autoLOC_CustomAsteroids_ErrorSpawnFail")); Debug.LogException(e); return(null); } }
/// <summary> ///Generates a new asteroid orbit appropriate for the chosen set. /// </summary> /// /// <param name="group">The set to which the asteroid belongs.</param> /// <returns>A randomly generated orbit.</returns> /// /// <exception cref="System.InvalidOperationException">Thrown if <c>group</c> cannot /// generate valid orbits. The program state will be unchanged in the event of an /// exception.</exception> /// <exception cref="System.NullReferenceException">Thrown if <c>group</c> is null.</exception> private static Orbit makeOrbit(AsteroidSet group) { return(group.drawOrbit()); }
/// <summary> /// Factory method obtaining Custom Asteroids settings from KSP config state. /// </summary> /// /// <returns>A newly constructed PopulationLoader object containing a full list /// of all valid asteroid groups in asteroid config files.</returns> /// /// <exception cref="TypeInitializationException">Thrown if the PopulationLoader /// object could not be constructed. The program is in a consistent state in the event of /// an exception.</exception> internal static PopulationLoader load() { try { // Start with an empty population list PopulationLoader allPops = new PopulationLoader(); // Search for populations in all config files UrlDir.UrlConfig [] configList = GameDatabase.Instance.GetConfigs("AsteroidSets"); foreach (UrlDir.UrlConfig curSet in configList) { foreach (ConfigNode curNode in curSet.config.nodes) { #if DEBUG Debug.Log("[CustomAsteroids]: " + Localizer.Format("#autoLOC_CustomAsteroids_LogConfig", curNode)); #endif try { AsteroidSet pop = null; switch (curNode.name) { case "ASTEROIDGROUP": pop = new Population(); break; case "INTERCEPT": pop = new Flyby(); break; case "DEFAULT": #pragma warning disable 0618 // DefaultAsteroids is deprecated pop = new DefaultAsteroids(); #pragma warning restore 0618 break; // silently ignore any other nodes present } if (pop != null) { ConfigNode.LoadObjectFromConfig(pop, curNode); allPops.asteroidPops.Add(pop); } } catch (Exception e) { var nodeName = curNode.GetValue("name"); var error = Localizer.Format( "#autoLOC_CustomAsteroids_ErrorLoadGroup", nodeName); Debug.LogError($"[CustomAsteroids]: " + error); Debug.LogException(e); Util.errorToPlayer(e, error); } // Attempt to parse remaining populations } } #if DEBUG foreach (AsteroidSet x in allPops.asteroidPops) { Debug.Log("[CustomAsteroids]: " + Localizer.Format("#autoLOC_CustomAsteroids_LogLoadGroup", x)); } #endif if (allPops.asteroidPops.Count == 0) { Debug.LogWarning("[CustomAsteroids]: " + Localizer.Format("#autoLOC_CustomAsteroids_ErrorNoConfig1")); ScreenMessages.PostScreenMessage( Localizer.Format("#autoLOC_CustomAsteroids_ErrorNoConfig1") + "\n" + Localizer.Format("#autoLOC_CustomAsteroids_ErrorNoConfig2"), 10.0f, ScreenMessageStyle.UPPER_CENTER); } return(allPops); } catch (Exception e) { throw new TypeInitializationException( "Starstrider42.CustomAsteroids.PopulationLoader", e); } }
/// <summary> /// Registers basic information on an asteroid in <see cref="CustomAsteroidRegistry"/>. /// </summary> /// <param name="asteroid">The asteroid to register.</param> /// <param name="group">The AsteroidSet from which the asteroid was drawn.</param> /// <remarks>This method must only be called by <see cref="spawnAsteroid()"/>, and is /// provided as part of the API for reference. It stores the following information: /// <list type="bullet"> /// <item> /// <term>parentSet</term> /// <description>The unique ID of <paramref name="group"/>.</description> /// </item> /// </list> /// </remarks> static void registerAsteroid(ProtoVessel asteroid, AsteroidSet group) { CustomAsteroidRegistry.Instance.RegisterAsteroid( asteroid, new AsteroidInfo(asteroid, group.getName())); }