void Start()
        {
            for (int i = 0; i < FlightGlobals.Bodies?.Count; i++)
            {
                body = FlightGlobals.Bodies[i];

                Debug.Log("SigmaDimensions.Start", "> Planet: " + body.name + (body.name != body.displayName.Replace("^N", "") ? (", (A.K.A.: " + body.displayName.Replace("^N", "") + ")") : "") + (body.name != body.transform.name ? (", (A.K.A.: " + body.transform.name + ")") : ""));


                // Sigma Dimensions Settings
                resize          = body.Has("resize") ? body.Get <double>("resize") : 1;
                landscape       = body.Has("landscape") ? body.Get <double>("landscape") : 1;
                resizeBuildings = body.Has("resizeBuildings") ? body.Get <double>("resizeBuildings") : 1;


                // All PQSCity mods
                PQSCity[] cities = body.GetComponentsInChildren <PQSCity>(true);

                for (int j = 0; j < cities?.Length; j++)
                {
                    CityFixer(cities[j]);
                    cities[j].Orientate();
                }


                // All PQSCity2 mods
                PQSCity2[] cities2 = body.GetComponentsInChildren <PQSCity2>(true);

                for (int j = 0; j < cities2?.Length; j++)
                {
                    City2Fixer(cities2[j]);
                    cities2[j].Orientate();
                }
            }
        }
    private void Start()
    {
        //Setup line renderers
        lrForVector               = orbitingBody.GetComponentsInChildren <LineRenderer>()[0];
        lrForVector.material      = matForwardVector;
        lrForVector.positionCount = 2;
        lrForVector.startWidth    = 0.3f;
        lrForVector.endWidth      = 0.3f;

        lrNorVector               = orbitingBody.GetComponentsInChildren <LineRenderer>()[1];
        lrNorVector.material      = matNormalVector;
        lrNorVector.positionCount = 2;
        lrNorVector.startWidth    = 0.3f;
        lrNorVector.endWidth      = 0.3f;
    }
Example #3
0
        internal static void SetTerrainRescales()
        {
            CelestialBody homeWorld = ConfigUtil.GetCelestialBody("HomeWorld");

            if (homeWorld.bodyName == "Kerbin")
            {
                planetRescale = Math.Round(homeWorld.pqsController.radius, 0) / 600000;

                foreach (var pqs in homeWorld.GetComponentsInChildren <PQSCity>(true))
                {
                    if (pqs.SurfaceObjectName == "KSC")
                    {
                        var myheight = Math.Round(homeWorld.pqsController.GetSurfaceHeight(pqs.repositionRadial) - homeWorld.pqsController.radius, 1);
                        Log.Normal("Found KSC at: " + Math.Round(myheight, 1) + "m above sealevel");
                        Log.Normal("body radius: " + Math.Round(homeWorld.pqsController.radius, 1) + "m");

                        terrainRescale = myheight / 64.8d;
                    }
                }
                globalTerrainRescale = planetRescale * terrainRescale;
            }
            else
            {
                Log.Warning("our homeworld is " + homeWorld.name + ", nothing to do");
            }
        }
        private static Location KSCLocation()
        {
            CelestialBody home = FlightGlobals.Bodies.Where(cb => cb.isHomeWorld).First();
            PQSCity       ksc  = home.GetComponentsInChildren <PQSCity>(true).Where(pqs => pqs.name == "KSC").First();

            return(new Location(home, home.GetLatitude(ksc.transform.position), home.GetLongitude(ksc.transform.position)));
        }
 private PQS GetOcean()
 {
     if (_ocean == null || mainBody != part.vessel.mainBody)
     {
         mainBody = part.vessel.mainBody;
         _ocean = mainBody.GetComponentsInChildren<PQS>(true).FirstOrDefault(p => p.name == mainBody.transform.name + "Ocean");
     }
     return _ocean;
 }
 private PQS GetOcean()
 {
     if (_ocean == null || mainBody != part.vessel.mainBody)
     {
         mainBody = part.vessel.mainBody;
         _ocean   = mainBody.GetComponentsInChildren <PQS>(true).FirstOrDefault(p => p.name == mainBody.transform.name + "Ocean");
     }
     return(_ocean);
 }
Example #7
0
        private static void LocationModeGUI()
        {
            int TOP_HEADER_WIDTH = 96;
            int TOP_EDIT_WIDTH   = 160;
            int CITY_WIDTH       = 124;
            int HEADING_WIDTH    = 400;

            Vessel        v    = FlightGlobals.ActiveVessel;
            CelestialBody body = v.mainBody;

            GUILayout.BeginHorizontal();
            GUILayout.Label("Latitude", headerLabel, GUILayout.Width(TOP_HEADER_WIDTH));
            GUILayout.TextField(v.latitude.ToString(), GUILayout.Width(TOP_EDIT_WIDTH));
            GUILayout.Label("Longitude", headerLabel, GUILayout.Width(TOP_HEADER_WIDTH));
            GUILayout.TextField(v.longitude.ToString(), GUILayout.Width(TOP_EDIT_WIDTH));
            GUILayout.EndHorizontal();
            GUILayout.BeginHorizontal();
            GUILayout.Label("Height", headerLabel, GUILayout.Width(TOP_HEADER_WIDTH));
            double height = v.altitude - LocationUtil.TerrainHeight(v.latitude, v.longitude, body);

            GUILayout.TextField(height.ToString(), GUILayout.Width(TOP_EDIT_WIDTH));
            GUILayout.Label("Altitude", headerLabel, GUILayout.Width(TOP_HEADER_WIDTH));
            GUILayout.TextField(v.altitude.ToString(), GUILayout.Width(TOP_EDIT_WIDTH));
            GUILayout.EndHorizontal();

            GUILayout.Space(8);

            GUILayout.BeginHorizontal();
            GUILayout.Label("PQS City", headerLabel, GUILayout.Width(CITY_WIDTH));
            GUILayout.Label("PQS Offset", headerLabel, GUILayout.Width(HEADING_WIDTH));
            GUILayout.EndHorizontal();

            scrollPosition2 = GUILayout.BeginScrollView(scrollPosition2, GUILayout.Width(550), GUILayout.ExpandHeight(true));
            foreach (PQSCity city in body.GetComponentsInChildren <PQSCity>(true))
            {
                // Translate to a position in the PQSCity's coordinate system
                Vector3d vPos = FlightGlobals.ActiveVessel.transform.position - city.transform.position;
                Vector3d pos  = new Vector3d(Vector3d.Dot(vPos, city.transform.right), Vector3d.Dot(vPos, city.transform.forward), Vector3d.Dot(vPos, city.transform.up));

                GUILayout.BeginHorizontal();

                GUILayout.Label(new GUIContent(city.name, city.name), clippedLabel, GUILayout.Width(CITY_WIDTH));
                GUILayout.TextField(pos.x.ToString() + ", " + pos.y.ToString() + ", " + pos.z.ToString(), GUILayout.Width(HEADING_WIDTH));

                GUILayout.EndHorizontal();
            }
            GUILayout.EndScrollView();

            if (Event.current.type == EventType.Repaint)
            {
                tooltip = GUI.tooltip;
            }
        }
Example #8
0
        private PQS GetOcean()
        {
            if (_ocean != null && _mainBody == Part.vessel.mainBody)
            {
                return(_ocean);
            }

            _mainBody = Part.vessel.mainBody;
            _ocean    = _mainBody.GetComponentsInChildren <PQS>(true)
                        .FirstOrDefault(p => p.name == _mainBody.transform.name + "Ocean");

            return(_ocean);
        }
        /// <summary>
        /// Uses the PQS System to query the color of the undergound
        /// </summary>
        /// <param name="body"></param>
        /// <param name="lat"></param>
        /// <param name="lon"></param>
        /// <returns></returns>
        public static Color GetSurfaceColorPQS(CelestialBody body, Double lat, Double lon)
        {
            // Tell the PQS that our actions are not supposed to end up in the terrain
            body.pqsController.isBuildingMaps = true;
            body.pqsController.isFakeBuild    = true;

            // Create the vertex information
            PQS.VertexBuildData data = new PQS.VertexBuildData
            {
                directionFromCenter = body.GetRelSurfaceNVector(lat, lon).normalized,
                //vertHeight = body.pqsController.radius
                vertHeight = body.pqsController.GetSurfaceHeight(body.GetRelSurfaceNVector(lat, lon).normalized *body.Radius)
            };

            // Fetch all enabled Mods
            //PQSMod[] mods = body.GetComponentsInChildren<PQSMod>(true).Where(m => m.modEnabled && m.sphere == body.pqsController).ToArray();
            List <PQSMod> modsList = body.GetComponentsInChildren <PQSMod>(true).Where(m => m.modEnabled && m.sphere == body.pqsController).ToList();

            modsList.Sort(delegate(PQSMod first, PQSMod second)
            {
                return(first.order.CompareTo(second.order));
            });
            PQSMod[] mods = modsList.ToArray();


            // Iterate over them and build the height at this point
            // This is neccessary for mods that use the terrain height to
            // color the terrain (like HeightColorMap)
            foreach (PQSMod mod in mods)
            {
                mod.OnVertexBuildHeight(data);
            }

            // Iterate over the mods again, this time build the color component
            foreach (PQSMod mod in mods)
            {
                mod.OnVertexBuild(data);
            }

            // Reset the PQS
            body.pqsController.isBuildingMaps = false;
            body.pqsController.isFakeBuild    = false;

            Color vertColor = data.vertColor;

            vertColor  += new Color(0.01f, 0.01f, 0.02f);
            vertColor.a = 1;
            // The terrain color is now stored in data.vertColor.
            // For getting the height at this point you can use data.vertHeight
            return(vertColor);
        }
 Vector3?GetCenter(ConfigNode node, CelestialBody body)
 {
     if (node.HasValue("CentralPQSCity"))
     {
         return(body.GetComponentsInChildren <PQSCity>(true).FirstOrDefault(p => p.name == node.GetValue("CentralPQSCity")).repositionRadial);
     }
     else if (node.HasValue("CentralPQSCity2"))
     {
         return(body.GetComponentsInChildren <PQSCity2>(true).First(p => p.name == node.GetValue("CentralPQSCity2")).PlanetRelativePosition);
     }
     else if (node.HasValue("CentralPosition"))
     {
         Vector3Parser v = new Vector3Parser();
         v.SetFromString(node.GetValue("CentralPosition"));
         return(v);
     }
     else if (node.HasValue("CentralLAT") && node.HasValue("CentralLON"))
     {
         NumericParser <double> LAT = new NumericParser <double>();
         NumericParser <double> LON = new NumericParser <double>();
         LAT.SetFromString(node.GetValue("CentralLAT"));
         LON.SetFromString(node.GetValue("CentralLON"));
         return(Utility.LLAtoECEF(LAT, LON, 1, 1));
     }
     else if (node.HasValue("PQSCity"))
     {
         return(body.GetComponentsInChildren <PQSCity>(true).FirstOrDefault(p => p.name == node.GetValue("PQSCity")).repositionRadial);
     }
     else if (node.HasValue("PQSCity2"))
     {
         return(body.GetComponentsInChildren <PQSCity2>(true).First(p => p.name == node.GetValue("PQSCity2")).PlanetRelativePosition);
     }
     else
     {
         return(null);
     }
 }
Example #11
0
        protected override void OnLoad(ConfigNode configNode)
        {
            base.OnLoad(configNode);

            foreach (ConfigNode child in configNode.GetNodes("WAYPOINT"))
            {
                // Read all the waypoint data
                WaypointData wpData = new WaypointData();
                wpData.type      = child.GetValue("type");
                wpData.parameter = ConfigNodeUtil.ParseValue <List <string> >(child, "parameter", new List <string>());
                wpData.names     = ConfigNodeUtil.ParseValue <List <string> >(child, "names", new List <string>());
                wpData.waypoint.celestialName = child.GetValue("celestialName");
                wpData.waypoint.name          = child.GetValue("name");
                wpData.waypoint.id            = child.GetValue("icon");
                wpData.waypoint.latitude      = Convert.ToDouble(child.GetValue("latitude"));
                wpData.waypoint.longitude     = Convert.ToDouble(child.GetValue("longitude"));
                wpData.waypoint.altitude      = Convert.ToDouble(child.GetValue("altitude"));
                wpData.waypoint.index         = Convert.ToInt32(child.GetValue("index"));
                wpData.waypoint.visible       = !(ConfigNodeUtil.ParseValue <bool?>(child, "hidden", (bool?)false).Value);
                wpData.waypoint.isClustered   = ConfigNodeUtil.ParseValue <bool?>(child, "clustered", (bool?)false).Value;

                string pqsCityName = ConfigNodeUtil.ParseValue <string>(child, "pqsCity", null);
                if (pqsCityName != null)
                {
                    CelestialBody body = FlightGlobals.Bodies.Where(b => b.name == wpData.waypoint.celestialName).First();
                    wpData.pqsCity   = body.GetComponentsInChildren <PQSCity>(true).Where(pqs => pqs.name == pqsCityName).First();
                    wpData.pqsOffset = ConfigNodeUtil.ParseValue <Vector3d>(child, "pqsOffset");
                }

                wpData.randomAltitude = ConfigNodeUtil.ParseValue <bool?>(child, "randomAltitude", (bool?)false).Value;
                wpData.underwater     = ConfigNodeUtil.ParseValue <bool?>(child, "underwater", (bool?)false).Value;

                // Set contract data
                wpData.SetContract(contract);

                // Create additional waypoint details
                string paramID = wpData.parameter.FirstOrDefault();
                if (wpData.waypoint.visible && (!wpData.parameter.Any() || contract.AllParameters.
                                                Where(p => p.ID == paramID && p.State == ParameterState.Complete).Any()))
                {
                    AddWayPoint(wpData);
                }

                // Add to the global list
                waypoints.Add(wpData);
            }
        }
Example #12
0
        // Patch various references to point to the nearest star
        private static void PatchStarReferences(CelestialBody body)
        {
            GameObject star = KopernicusStar.GetNearest(body).gameObject;

            if (body.afg != null)
            {
                body.afg.sunLight = star;
            }
            if (body.scaledBody.GetComponent <MaterialSetDirection>() != null)
            {
                body.scaledBody.GetComponent <MaterialSetDirection>().target = star.transform;
            }
            foreach (PQSMod_MaterialSetDirection msd in
                     body.GetComponentsInChildren <PQSMod_MaterialSetDirection>(true))
            {
                msd.target = star.transform;
            }
        }
Example #13
0
 public SCANanomaly[] getAnomalies()
 {
     if (anomalies == null)
     {
         PQSCity[] sites = body.GetComponentsInChildren <PQSCity>(true);
         anomalies = new SCANanomaly[sites.Length];
         for (int i = 0; i < sites.Length; ++i)
         {
             anomalies[i] = new SCANanomaly(sites[i].name, body.GetLongitude(sites[i].transform.position), body.GetLatitude(sites[i].transform.position), sites[i]);
         }
     }
     for (int i = 0; i < anomalies.Length; ++i)
     {
         anomalies[i].known  = isCovered(anomalies[i].longitude, anomalies[i].latitude, SCANtype.Anomaly);
         anomalies[i].detail = isCovered(anomalies[i].longitude, anomalies[i].latitude, SCANtype.AnomalyDetail);
     }
     return(anomalies);
 }
        public override bool Load(ConfigNode configNode)
        {
            // Before loading, verify the SCANsat version
            if (!SCANsatUtil.VerifySCANsatVersion())
            {
                return(false);
            }

            // Load base class
            bool valid = base.Load(configNode);

            // Do not check the requirement on active contracts.  Otherwise when they scan the
            // contract is invalidated, which is usually not what's meant.
            checkOnActiveContract = false;

            valid &= ConfigNodeUtil.ParseValue <string>(configNode, "scanType", x => scanType = x, this, "Anomaly", SCANsatUtil.ValidateSCANname);
            valid &= ConfigNodeUtil.ParseValue <double>(configNode, "latitude", x => latitude = x, this, 0.0);
            valid &= ConfigNodeUtil.ParseValue <double>(configNode, "longitude", x => longitude = x, this, 0.0);

            valid &= ConfigNodeUtil.MutuallyExclusive(configNode, new string[] { "latitude", "longitude" }, new string[] { "pqsCity" }, this);
            valid &= ValidateTargetBody(configNode);

            string pqsName = null;

            valid &= ConfigNodeUtil.ParseValue <string>(configNode, "pqsCity", x => pqsName = x, this, (string)null);
            if (pqsName != null)
            {
                try
                {
                    CelestialBody body = FlightGlobals.Bodies.Where(b => b == targetBody).First();
                    pqsCity = body.GetComponentsInChildren <PQSCity>(true).Where(pqs => pqs.name == pqsName).First();
                }
                catch (Exception e)
                {
                    LoggingUtil.LogError(this, "Couldn't load PQSCity with name '" + pqsCity + "'");
                    LoggingUtil.LogException(e);
                    valid = false;
                }
            }

            return(valid);
        }
Example #15
0
 void ActivateAndReleaseBody()
 {
     if (_capturedBody)
     {
         _capturedBody.HideOrbit();
         if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
         {
             _capturedBody.Attractor = null;
             _capturedBody.Velocity  = Vector2.zero;
         }
         _capturedBody.enabled     = true;
         _capturedBody.IsDrawOrbit = ShowOrbits;
         var coll = _capturedBody.GetComponentsInChildren <Collider2D>();
         foreach (var c in coll)
         {
             c.enabled = true;
         }
         _capturedBody = null;
     }
 }
Example #16
0
        /// <summary>
        /// Changes the parameters of the body
        /// </summary>
        public override Boolean Tweak(CelestialBody body)
        {
            // Is the body a GasPlanet or a Star?
            if (!body.hasSolidSurface || body.scaledBody.GetComponentsInChildren <SunShaderController>().Length != 0)
            {
                return(false);
            }

            // Is the body our homeworld?
            if (body.isHomeWorld)
            {
                return(false);
            }

            // Does the body have an ocean? This could end badly
            if (body.ocean)
            {
                return(false);
            }

            // Process previous states
            if (toRestore.Contains(body))
            {
                // Enable the Atmosphere from Ground
                AtmosphereFromGround[] afgs = body.GetComponentsInChildren <AtmosphereFromGround>();
                foreach (AtmosphereFromGround afg in afgs)
                {
                    afg.gameObject.SetActive(true);
                }

                // Enable the Light controller
                MaterialSetDirection[] msds = body.GetComponentsInChildren <MaterialSetDirection>();
                foreach (MaterialSetDirection msd in msds)
                {
                    msd.gameObject.SetActive(true);
                }

                // Atmosphere \o/
                body.atmosphere = true;

                // Restore the old material
                GameObject backupGameObject = body.scaledBody.GetChild("Backup");
                backupGameObject.SetActive(false);
                body.scaledBody.GetComponent <Renderer>().sharedMaterial =
                    backupGameObject.GetComponent <MeshRenderer>().material;
                UnityEngine.Object.Destroy(backupGameObject);

                // Update state
                toRestore.Remove(body);
            }
            else if (toDelete.Contains(body))
            {
                // Remove the Atmosphere from Ground
                AtmosphereFromGround[] afgs = body.GetComponentsInChildren <AtmosphereFromGround>();
                foreach (AtmosphereFromGround afg in afgs)
                {
                    UnityEngine.Object.Destroy(afg.gameObject);
                }

                // Disable the Light controller
                MaterialSetDirection[] msds = body.GetComponentsInChildren <MaterialSetDirection>();
                foreach (MaterialSetDirection msd in msds)
                {
                    UnityEngine.Object.Destroy(msd.gameObject);
                }

                // No Atmosphere :(
                body.atmosphere = false;

                // Restore the old material
                GameObject backupGameObject = body.scaledBody.GetChild("Backup");
                backupGameObject.SetActive(false);
                body.scaledBody.GetComponent <Renderer>().sharedMaterial =
                    backupGameObject.GetComponent <MeshRenderer>().material;
                UnityEngine.Object.Destroy(backupGameObject);

                // Update state
                toDelete.Remove(body);
            }

            // For all other bodies, there is a small chance (5%) that their existing
            // atmosphere is toggled, or a new one gets added
            if (GetRandom(HighLogic.CurrentGame.Seed, 0, 100) < 5)
            {
                //Report::Report.fetch.SetPrefix("PlanetaryBody: " + body.bodyDisplayName);
                //Report::Report.fetch.ReportLine("Atmosphere toggled");

                ToggleAtmosphere(body);

                //Report::Report.fetch.ReportSection();
                return(true);
            }
            //Report::Report.fetch.ReportSection();
            // Did we tweak something?
            return(false);
        }
Example #17
0
        /// <summary>
        /// Gets called when the users switches from one game scene to another one.
        /// </summary>
        void OnGameSceneSwitchRequested(GameEvents.FromToAction <GameScenes, GameScenes> action)
        {
            // Are we loading a game?
            if (action.from == GameScenes.MAINMENU && action.to == GameScenes.SPACECENTER)
            {
                // Get a sorted list of bodies
                List <CelestialBody> bodies = Utility.GetSortedBodies();

                // Tweak it!
                for (Int32 j = 0; j < bodies.Count; j++)
                {
                    // Get the Body
                    CelestialBody body = bodies[j];

                    // Is this body blacklisted?
                    if (bodyBlacklist != null)
                    {
                        if (bodyBlacklist.GetValues("blacklist").Any(b => body.bodyName == b))
                        {
                            continue;
                        }
                    }

                    // Was the body edited?
                    Boolean edited = false;

                    // Tweak the PQS itself
                    for (Int32 i = 0; i < PQSTweakers.Count; i++)
                    {
                        // Tweaker
                        IPQSTweaker tweaker = PQSTweakers[i];

                        // Check the config
                        ConfigNode config = ConfigCache[tweaker.GetConfig()];

                        // Is the tweak group enabled?
                        if (!config.HasValue("enabled"))
                        {
                            continue;
                        }
                        if (!Boolean.TryParse(config.GetValue("enabled"), out Boolean isEnabled) || !isEnabled)
                        {
                            continue;
                        }

                        // Is the tweak itself enabled?
                        String setting = tweaker.GetSetting();
                        if (setting != null)
                        {
                            if (!config.HasValue(setting))
                            {
                                continue;
                            }
                            if (!Boolean.TryParse(config.GetValue(setting), out isEnabled) || !isEnabled)
                            {
                                continue;
                            }
                        }

                        // Tweak it
                        if (tweaker.Tweak(body, body.pqsController))
                        {
                            edited = true;
                        }
                    }

                    // Get the PQSMods
                    PQSMod[] mods = body.GetComponentsInChildren <PQSMod>(true);

                    for (Int32 i = 0; i < PQSModTweakers.Count; i++)
                    {
                        // Tweaker
                        IPQSModTweaker tweaker = PQSModTweakers[i];

                        // Check the config
                        ConfigNode config = ConfigCache[tweaker.GetConfig()];

                        // Is the tweak group enabled?
                        if (!config.HasValue("enabled"))
                        {
                            continue;
                        }
                        if (!Boolean.TryParse(config.GetValue("enabled"), out Boolean isEnabled) || !isEnabled)
                        {
                            continue;
                        }

                        // Is the tweak itself enabled?
                        String setting = tweaker.GetSetting();
                        if (setting != null)
                        {
                            if (!config.HasValue(setting))
                            {
                                continue;
                            }
                            if (!Boolean.TryParse(config.GetValue(setting), out isEnabled) || !isEnabled)
                            {
                                continue;
                            }
                        }

                        // Tweak them
                        foreach (PQSMod mod in mods)
                        {
                            if (tweaker.Tweak(body, mod))
                            {
                                edited = true;
                                mod.OnSetup();
                            }
                        }
                    }

                    // The body was edited, we should update it's scaled space
                    if (edited && scaledSpaceUpdate.All(b => b.name != body.name))
                    {
                        scaledSpaceUpdate.Add(body);
                    }
                }

                // Tweak Celestial Bodies
                for (Int32 i = 0; i < CBTweakers.Count; i++)
                {
                    // Tweaker
                    ICelestialBodyTweaker tweaker = CBTweakers[i];

                    // Check the config
                    ConfigNode config = ConfigCache[tweaker.GetConfig()];

                    // Is the tweak group enabled?
                    if (!config.HasValue("enabled"))
                    {
                        continue;
                    }
                    if (!Boolean.TryParse(config.GetValue("enabled"), out Boolean isEnabled) || !isEnabled)
                    {
                        continue;
                    }

                    // Is the tweak itself enabled?
                    String setting = tweaker.GetSetting();
                    if (setting != null)
                    {
                        if (!config.HasValue(setting))
                        {
                            continue;
                        }
                        if (!Boolean.TryParse(config.GetValue(setting), out isEnabled) || !isEnabled)
                        {
                            continue;
                        }
                    }

                    // Tweak it!
                    for (Int32 j = 0; j < bodies.Count; j++)
                    {
                        // Get the Body
                        CelestialBody body = bodies[j];

                        // Tweak it
                        tweaker.Tweak(body);
                    }
                }
            }

            // Are we leaving the game?
            if (action.to == GameScenes.MAINMENU)
            {
                if (PSystemManager.Instance?.localBodies == null)
                {
                    return;
                }

                // Reset the random
                RandomProvider.Reset();

                // Kill the PQS so it definitly rebuilds when we load a game
                for (Int32 i = 0; i < PSystemManager.Instance.localBodies.Count; i++)
                {
                    CelestialBody body = PSystemManager.Instance.localBodies[i];
                    body.pqsController?.ResetSphere();
                }
            }
        }
Example #18
0
        /// <summary>
        /// Disables an existing atmosphere, or adds a new one if there is none
        /// </summary>
        private void ToggleAtmosphere(CelestialBody body)
        {
            // Disable an existing atmosphere
            if (body.atmosphere)
            {
                // Disable the Atmosphere from Ground
                AtmosphereFromGround[] afgs = body.GetComponentsInChildren <AtmosphereFromGround>();
                foreach (AtmosphereFromGround afg in afgs)
                {
                    afg.gameObject.SetActive(false);
                }

                // Disable the Light controller
                MaterialSetDirection[] msds = body.GetComponentsInChildren <MaterialSetDirection>();
                foreach (MaterialSetDirection msd in msds)
                {
                    msd.gameObject.SetActive(false);
                }

                // No Atmosphere :(
                body.atmosphere = false;

                // Get the material
                Renderer  renderer   = body.scaledBody.GetComponent <Renderer>();
                Material  material   = renderer.sharedMaterial;
                Texture2D diffuseMap = (Texture2D)material.GetTexture("_MainTex");
                Texture2D bumpMap    = (Texture2D)material.GetTexture("_BumpMap");

                // Create a new scaled material
                Material newMaterial = new Material(Shader.Find("Terrain/Scaled Planet (Simple)"));
                newMaterial.SetTexture("_MainTex", diffuseMap);
                newMaterial.SetTexture("_BumpMap", bumpMap);
                newMaterial.SetFloat("_Shininess", material.GetFloat("_Shininess")); // TODO: Investigate
                newMaterial.SetColor("_SpecColor", material.GetColor("_SpecColor")); // TODO: Investigate

                // Apply the material
                renderer.sharedMaterial = newMaterial;

                // Backup the old material
                GameObject backupGameObject = new GameObject("Backup");
                backupGameObject.SetActive(false);
                backupGameObject.AddComponent <MeshRenderer>().material = material;
                backupGameObject.transform.parent = body.scaledBody.transform;

                // Update state
                toRestore.Add(body);
            }
            else
            {
                // Add a new atmosphere, this could get funny
                // We will just copy Laythe for the most parts
                body.atmosphere = true;
                body.atmosphereContainsOxygen = GetRandom(HighLogic.CurrentGame.Seed, 0, 99) < 10; // Oxygen is rare
                body.atmosphereDepth          = (body.pqsController.radiusMax / 10) * GetRandomDouble(HighLogic.CurrentGame.Seed, 0.8, 1.2);
                body.atmosphereAdiabaticIndex =
                    1.39999997615814 * GetRandomDouble(HighLogic.CurrentGame.Seed, 0.8, 1.2);
                body.atmosphereGasMassLapseRate =
                    4.84741125702493 * GetRandomDouble(HighLogic.CurrentGame.Seed, 0.8, 1.2);
                body.atmosphereMolarMass = 0.0289644002914429 * GetRandomDouble(HighLogic.CurrentGame.Seed, 0.8, 1.2);
                Double multiplier = GetRandomDouble(HighLogic.CurrentGame.Seed, 0, 1);
                body.atmospherePressureSeaLevel    = (595 * multiplier) + 5;
                body.atmosphereTemperatureSeaLevel = (270 * multiplier) + 240;
                body.atmDensityASL = (6.9 * multiplier) + 0.1;
                body.atmosphereTemperatureLapseRate         = GetRandomDouble(HighLogic.CurrentGame.Seed, 0.004, 0.005);
                body.atmospherePressureCurveIsNormalized    = true;
                body.atmosphereTemperatureCurveIsNormalized = true;
                body.atmosphereUsePressureCurve             = true;
                body.atmosphereUseTemperatureCurve          = true;

                // Select a curve template
                KeyValuePair <FloatCurve, FloatCurve> template =
                    GetRandomElement(HighLogic.CurrentGame.Seed, CurveTemplates.Atmospheres);
                body.atmospherePressureCurve    = template.Key;
                body.atmosphereTemperatureCurve = template.Value;

                // Now add the visuals
                GameObject scaledVersion = body.scaledBody;

                // Add the material light direction behavior
                MaterialSetDirection materialLightDirection = scaledVersion.AddComponent <MaterialSetDirection>();
                materialLightDirection.valueName = "_localLightDirection";

                // Create the atmosphere shell game object
                GameObject scaledAtmosphere = new GameObject("Atmosphere");
                scaledAtmosphere.transform.parent        = scaledVersion.transform;
                scaledAtmosphere.transform.position      = scaledVersion.transform.position;
                scaledAtmosphere.transform.localPosition = Vector3.zero;
                scaledAtmosphere.layer = 9;
                MeshRenderer mrenderer = scaledAtmosphere.AddComponent <MeshRenderer>();
                mrenderer.sharedMaterial = new Material(Shader.Find("AtmosphereFromGround"));
                MeshFilter meshFilter = scaledAtmosphere.AddComponent <MeshFilter>();
                meshFilter.sharedMesh = Templates.ReferenceGeosphere;
                AtmosphereFromGround atmosphereFromGround = scaledAtmosphere.AddComponent <AtmosphereFromGround>();

                // Get the average color of the current texture
                Renderer  renderer    = body.scaledBody.GetComponent <Renderer>();
                Material  material    = renderer.sharedMaterial;
                Texture2D diffuseMap  = Utility.CreateReadable((Texture2D)material.GetTexture("_MainTex"));
                Texture2D bumpMap     = (Texture2D)material.GetTexture("_BumpMap");
                Color     average     = Utility.GetAverageColor(diffuseMap);
                Color     altered     = AlterColor(average);
                Color     darkAltered = Utility.Dark(altered);

                body.afg = atmosphereFromGround;
                atmosphereFromGround.planet     = body;
                atmosphereFromGround.sunLight   = Planetarium.fetch.Sun.gameObject;
                atmosphereFromGround.mainCamera = PlanetariumCamera.fetch.transform;
                atmosphereFromGround.waveLength = new Color(1 - darkAltered.r, 1 - darkAltered.g, 1 - darkAltered.b, 0.5f);

                // Ambient Light
                body.atmosphericAmbientColor = altered;

                // Scaled Material
                Material newMaterial = new Material(Shader.Find("Terrain/Scaled Planet (RimAerial)"));
                newMaterial.SetTexture("_MainTex", diffuseMap);
                newMaterial.SetTexture("_BumpMap", bumpMap);
                newMaterial.SetFloat("_Shininess", material.GetFloat("_Shininess")); // TODO: Investigate
                newMaterial.SetColor("_SpecColor", material.GetColor("_SpecColor")); // TODO: Investigate
                newMaterial.SetFloat("_rimPower", (Single)GetRandomDouble(HighLogic.CurrentGame.Seed, 3.8, 5));
                newMaterial.SetFloat("_rimBlend", 1f);

                // Generate the atmosphere rim texture
                Gradient gradient = new Gradient();
                gradient.Add(0f, altered);
                gradient.Add(0.2f, new Color(0.0549f, 0.0784f, 0.141f, 1f));
                gradient.Add(1f, new Color(0.0196f, 0.0196f, 0.0196f, 1f));

                // Generate the ramp from a gradient
                Texture2D ramp   = new Texture2D(512, 1);
                Color[]   colors = ramp.GetPixels(0);
                for (Int32 i = 0; i < colors.Length; i++)
                {
                    // Compute the position in the gradient
                    Single k = (Single)i / colors.Length;
                    colors[i] = gradient.ColorAt(k);
                }
                ramp.SetPixels(colors, 0);
                ramp.Apply(true, false);
                ramp.wrapMode   = TextureWrapMode.Clamp;
                ramp.mipMapBias = 0.0f;

                // Set the color ramp
                newMaterial.SetTexture("_rimColorRamp", ramp);

                // Apply the material
                renderer.sharedMaterial = newMaterial;

                // Backup the old material
                GameObject backupGameObject = new GameObject("Backup");
                backupGameObject.SetActive(false);
                backupGameObject.AddComponent <MeshRenderer>().material = material;
                backupGameObject.transform.parent = scaledVersion.transform;

                // Update state
                toDelete.Add(body);
            }
        }
Example #19
0
        public static WaypointGenerator Create(ConfigNode configNode, WaypointGeneratorFactory factory)
        {
            WaypointGenerator wpGenerator = new WaypointGenerator();

            // Waypoint Manager integration
            EventData <string> onWaypointIconAdded = GameEvents.FindEvent <EventData <string> >("OnWaypointIconAdded");

            bool valid = true;
            int  index = 0;

            foreach (ConfigNode child in ConfigNodeUtil.GetChildNodes(configNode))
            {
                DataNode dataNode = new DataNode("WAYPOINT_" + index, factory.dataNode, factory);
                try
                {
                    ConfigNodeUtil.SetCurrentDataNode(dataNode);
                    dataNode["type"] = child.name;

                    double?      altitude = null;
                    WaypointData wpData   = new WaypointData(child.name);

                    // Use an expression to default - then it'll work for dynamic contracts
                    if (!child.HasValue("targetBody"))
                    {
                        child.AddValue("targetBody", "@/targetBody");
                    }
                    valid &= ConfigNodeUtil.ParseValue <CelestialBody>(child, "targetBody", x => wpData.waypoint.celestialName = x != null ? x.name : "", factory);

                    valid &= ConfigNodeUtil.ParseValue <List <string> >(child, "name", x => wpData.names = x, factory, new List <string>());
                    valid &= ConfigNodeUtil.ParseValue <double?>(child, "altitude", x => altitude = x, factory, (double?)null);
                    valid &= ConfigNodeUtil.ParseValue <List <string> >(child, "parameter", x => wpData.parameter = x, factory, new List <string>());
                    valid &= ConfigNodeUtil.ParseValue <bool>(child, "hidden", x => wpData.waypoint.visible = !x, factory, false);

                    Action <string> assignWaypoint = (x) =>
                    {
                        wpData.waypoint.id = x;
                        if (onWaypointIconAdded != null)
                        {
                            onWaypointIconAdded.Fire(x);
                        }
                    };
                    if (!wpData.waypoint.visible)
                    {
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "icon", assignWaypoint, factory, "");
                    }
                    else
                    {
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "icon", assignWaypoint, factory);
                    }

                    valid &= ConfigNodeUtil.ParseValue <bool>(child, "underwater", x => wpData.underwater = x, factory, false);
                    valid &= ConfigNodeUtil.ParseValue <bool>(child, "clustered", x => wpData.waypoint.isClustered = x, factory, false);

                    // Track the index
                    wpData.waypoint.index = index++;

                    // Get altitude
                    if (altitude == null)
                    {
                        wpData.waypoint.altitude = 0.0;
                        wpData.randomAltitude    = true;
                    }
                    else
                    {
                        wpData.waypoint.altitude = altitude.Value;
                    }

                    // Get settings that differ by type
                    if (child.name == "WAYPOINT")
                    {
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "latitude", x => wpData.waypoint.latitude = x, factory);
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "longitude", x => wpData.waypoint.longitude = x, factory);
                    }
                    else if (child.name == "RANDOM_WAYPOINT")
                    {
                        // Get settings for randomization
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "waterAllowed", x => wpData.waterAllowed = x, factory, true);
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "forceEquatorial", x => wpData.forceEquatorial = x, factory, false);
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "count", x => wpData.count = x, factory, 1, x => Validation.GE(x, 1));
                    }
                    else if (child.name == "RANDOM_WAYPOINT_NEAR")
                    {
                        // Get settings for randomization
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "waterAllowed", x => wpData.waterAllowed = x, factory, true);

                        // Get near waypoint details
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "nearIndex", x => wpData.nearIndex = x, factory,
                                                                 x => Validation.GE(x, 0) && Validation.LT(x, wpGenerator.waypoints.Count));
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "chained", x => wpData.chained = x, factory, false);
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "count", x => wpData.count = x, factory, 1, x => Validation.GE(x, 1));

                        // Get distances
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "minDistance", x => wpData.minDistance = x, factory, 0.0, x => Validation.GE(x, 0.0));
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "maxDistance", x => wpData.maxDistance = x, factory, x => Validation.GT(x, 0.0));
                    }
                    else if (child.name == "PQS_CITY")
                    {
                        wpData.randomAltitude = false;
                        string dummy = null;
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "pqsCity", x => dummy = x, factory, x =>
                        {
                            bool v = true;
                            if (!string.IsNullOrEmpty(wpData.waypoint.celestialName))
                            {
                                try
                                {
                                    CelestialBody body = FlightGlobals.Bodies.Where(b => b.name == wpData.waypoint.celestialName).First();
                                    wpData.pqsCity     = body.GetComponentsInChildren <PQSCity>(true).Where(pqs => pqs.name == x).First();
                                }
                                catch (Exception e)
                                {
                                    LoggingUtil.LogError(typeof(WaypointGenerator), "Couldn't load PQSCity with name '{0}'", x);
                                    LoggingUtil.LogException(e);
                                    v = false;
                                }
                            }
                            else
                            {
                                // Force this to get re-run when the targetBody is loaded
                                throw new DataNode.ValueNotInitialized("/targetBody");
                            }
                            return(v);
                        });
                        valid &= ConfigNodeUtil.ParseValue <Vector3d>(child, "pqsOffset", x => wpData.pqsOffset = x, factory, new Vector3d());
                    }
                    else if (child.name == "LAUNCH_SITE")
                    {
                        wpData.randomAltitude = false;
                        string dummy = null;
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "launchSite", x => dummy = x, factory, x =>
                        {
                            bool v = true;
                            if (!string.IsNullOrEmpty(wpData.waypoint.celestialName))
                            {
                                try
                                {
                                    wpData.launchSite = ConfigNodeUtil.ParseLaunchSiteValue(x);
                                }
                                catch (Exception e)
                                {
                                    LoggingUtil.LogError(typeof(WaypointGenerator), "Couldn't load Launch Site with name '{0}'", x);
                                    LoggingUtil.LogException(e);
                                    v = false;
                                }
                            }
                            else
                            {
                                // Force this to get re-run when the targetBody is loaded
                                throw new DataNode.ValueNotInitialized("/targetBody");
                            }
                            return(v);
                        });
                        valid &= ConfigNodeUtil.ParseValue <Vector3d>(child, "pqsOffset", x => wpData.pqsOffset = x, factory, new Vector3d());
                    }
                    else
                    {
                        LoggingUtil.LogError(factory, "Unrecognized waypoint node: '{0}'", child.name);
                        valid = false;
                    }

                    // Check for unexpected values
                    valid &= ConfigNodeUtil.ValidateUnexpectedValues(child, factory);

                    // Copy waypoint data
                    WaypointData old = wpData;
                    wpData = new WaypointData(old, null);
                    wpGenerator.waypoints.Add(wpData);
                }
                finally
                {
                    ConfigNodeUtil.SetCurrentDataNode(factory.dataNode);
                }
            }

            return(valid ? wpGenerator : null);
        }
        void Start()
        {
            foreach (CelestialBody cb in FlightGlobals.Bodies)
            {
                body = cb;
                resize = body.Has("resize") ? body.Get<double>("resize") : 1;
                landscape = body.Has("landscape") ? body.Get<double>("landscape") : 1;
                resizeBuildings = body.Has("resizeBuildings") ? body.Get<double>("resizeBuildings") : 1;

                foreach (PQSCity mod in body.GetComponentsInChildren<PQSCity>(true))
                {
                    CityFixer(mod);
                }

                foreach (PQSCity2 mod in body.GetComponentsInChildren<PQSCity2>(true))
                {
                    City2Fixer(mod);
                }
            }
        }
Example #21
0
        public static void AtmosphereToggler()
        {
            // Only run in TrackingStation or MapView
            if (!HighLogic.LoadedSceneHasPlanetarium && !MapView.MapIsEnabled)
            {
                return;
            }
            // Only work when the player is focused on a planet
            if (PlanetariumCamera.fetch.target.celestialBody == null)
            {
                return;
            }

            // When the user presses Ctrl+A in the Tracking Station, toggle the atmosphere of the currently selected body

            // Get the body
            CelestialBody body = PSystemManager.Instance.localBodies.Find(b => b.transform.name == PlanetariumCamera.fetch.target.celestialBody.transform.name);     // Yes this is silly, but it shows how to find a body by its name

            // Toggle Atmosphere
            if (atmosphericBodies.Contains(body.transform.name))
            {
                // Disable the Atmosphere from Ground
                AtmosphereFromGround[] afgs = body.GetComponentsInChildren <AtmosphereFromGround>();
                foreach (AtmosphereFromGround afg in afgs)
                {
                    afg.gameObject.SetActive(false);
                }

                // Disable the Light controller
                MaterialSetDirection[] msds = body.GetComponentsInChildren <MaterialSetDirection>();
                foreach (MaterialSetDirection msd in msds)
                {
                    msd.gameObject.SetActive(false);
                }

                // No Atmosphere :(
                body.atmosphere = false;

                // Message
                ScreenMessages.PostScreenMessage("Atmosphere Disabled " + body.displayName.Replace("^N", ""), 2f, ScreenMessageStyle.UPPER_RIGHT);
            }
            else
            {
                // Enable the Atmosphere from Ground
                AtmosphereFromGround[] AtmoFromGround = body.GetComponentsInChildren <AtmosphereFromGround>();
                foreach (AtmosphereFromGround afg in AtmoFromGround)
                {
                    afg.gameObject.SetActive(true);
                }

                // Enable the Light controller
                MaterialSetDirection[] materialSetDir = body.GetComponentsInChildren <MaterialSetDirection>();
                foreach (MaterialSetDirection msd in materialSetDir)
                {
                    msd.gameObject.SetActive(true);
                }

                // Atmosphere \o/
                body.atmosphere = true;

                // Message
                ScreenMessages.PostScreenMessage("Atmosphere Enabled " + body.displayName.Replace("^N", ""), 2f, ScreenMessageStyle.UPPER_RIGHT);
            }
        }
        public static WaypointGenerator Create(ConfigNode configNode, WaypointGeneratorFactory factory)
        {
            WaypointGenerator wpGenerator = new WaypointGenerator();

            bool valid = true;
            int  index = 0;

            foreach (ConfigNode child in ConfigNodeUtil.GetChildNodes(configNode))
            {
                DataNode dataNode = new DataNode("WAYPOINT_" + index, factory.dataNode, factory);
                try
                {
                    ConfigNodeUtil.SetCurrentDataNode(dataNode);
                    dataNode["type"] = child.name;

                    double?      altitude = null;
                    WaypointData wpData   = new WaypointData(child.name);

                    // Use an expression to default - then it'll work for dynamic contracts
                    if (!child.HasValue("targetBody"))
                    {
                        child.AddValue("targetBody", "@/targetBody");
                    }
                    valid &= ConfigNodeUtil.ParseValue <CelestialBody>(child, "targetBody", x => wpData.waypoint.celestialName = x != null ? x.name : "", factory);

                    valid &= ConfigNodeUtil.ParseValue <List <string> >(child, "name", x => wpData.names = x, factory, new List <string>());
                    valid &= ConfigNodeUtil.ParseValue <double?>(child, "altitude", x => altitude = x, factory, (double?)null);
                    valid &= ConfigNodeUtil.ParseValue <List <string> >(child, "parameter", x => wpData.parameter = x, factory, new List <string>());
                    valid &= ConfigNodeUtil.ParseValue <bool>(child, "hidden", x => wpData.waypoint.visible = !x, factory, false);
                    if (!wpData.waypoint.visible)
                    {
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "icon", x => wpData.waypoint.id = x, factory, "");
                    }
                    else
                    {
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "icon", x => wpData.waypoint.id = x, factory);
                    }

                    // The FinePrint logic is such that it will only look in Squad/Contracts/Icons for icons.
                    // Cheat this by hacking the path in the game database.
                    if (wpData.waypoint.id.Contains("/"))
                    {
                        GameDatabase.TextureInfo texInfo = GameDatabase.Instance.databaseTexture.Where(t => t.name == wpData.waypoint.id).FirstOrDefault();
                        if (texInfo != null)
                        {
                            texInfo.name = "Squad/Contracts/Icons/" + wpData.waypoint.id;
                        }
                    }

                    valid &= ConfigNodeUtil.ParseValue <bool>(child, "underwater", x => wpData.underwater = x, factory, false);
                    valid &= ConfigNodeUtil.ParseValue <bool>(child, "clustered", x => wpData.waypoint.isClustered = x, factory, false);

                    // Track the index
                    wpData.waypoint.index = index++;

                    // Get altitude
                    if (altitude == null)
                    {
                        wpData.waypoint.altitude = 0.0;
                        wpData.randomAltitude    = true;
                    }
                    else
                    {
                        wpData.waypoint.altitude = altitude.Value;
                    }

                    // Get settings that differ by type
                    if (child.name == "WAYPOINT")
                    {
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "latitude", x => wpData.waypoint.latitude = x, factory);
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "longitude", x => wpData.waypoint.longitude = x, factory);
                    }
                    else if (child.name == "RANDOM_WAYPOINT")
                    {
                        // Get settings for randomization
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "waterAllowed", x => wpData.waterAllowed = x, factory, true);
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "forceEquatorial", x => wpData.forceEquatorial = x, factory, false);
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "count", x => wpData.count = x, factory, 1, x => Validation.GE(x, 1));
                    }
                    else if (child.name == "RANDOM_WAYPOINT_NEAR")
                    {
                        // Get settings for randomization
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "waterAllowed", x => wpData.waterAllowed = x, factory, true);

                        // Get near waypoint details
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "nearIndex", x => wpData.nearIndex = x, factory,
                                                                 x => Validation.GE(x, 0) && Validation.LT(x, wpGenerator.waypoints.Count));
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "chained", x => wpData.chained = x, factory, false);
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "count", x => wpData.count = x, factory, 1, x => Validation.GE(x, 1));

                        // Get distances
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "minDistance", x => wpData.minDistance = x, factory, 0.0, x => Validation.GE(x, 0.0));
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "maxDistance", x => wpData.maxDistance = x, factory, x => Validation.GT(x, 0.0));
                    }
                    else if (child.name == "PQS_CITY")
                    {
                        wpData.randomAltitude = false;
                        string dummy = null;
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "pqsCity", x => dummy = x, factory, x =>
                        {
                            bool v = true;
                            if (!string.IsNullOrEmpty(wpData.waypoint.celestialName))
                            {
                                try
                                {
                                    CelestialBody body = FlightGlobals.Bodies.Where(b => b.name == wpData.waypoint.celestialName).First();
                                    wpData.pqsCity     = body.GetComponentsInChildren <PQSCity>(true).Where(pqs => pqs.name == x).First();
                                }
                                catch (Exception e)
                                {
                                    LoggingUtil.LogError(typeof(WaypointGenerator), "Couldn't load PQSCity with name '" + x + "'");
                                    LoggingUtil.LogException(e);
                                    v = false;
                                }
                            }
                            else
                            {
                                // Force this to get re-run when the targetBody is loaded
                                throw new DataNode.ValueNotInitialized("/targetBody");
                            }
                            return(v);
                        });
                        valid &= ConfigNodeUtil.ParseValue <Vector3d>(child, "pqsOffset", x => wpData.pqsOffset = x, factory, new Vector3d());
                    }
                    else
                    {
                        LoggingUtil.LogError(factory, "Unrecognized waypoint node: '" + child.name + "'");
                        valid = false;
                    }

                    // Check for unexpected values
                    valid &= ConfigNodeUtil.ValidateUnexpectedValues(child, factory);

                    // Copy waypoint data
                    WaypointData old = wpData;
                    for (int i = 0; i < old.count; i++)
                    {
                        wpData = new WaypointData(old, null);
                        wpGenerator.waypoints.Add(wpData);

                        if (old.parameter.Any())
                        {
                            wpData.parameter = new List <string>();
                            wpData.parameter.Add(old.parameter.Count() == 1 ? old.parameter.First() : old.parameter.ElementAtOrDefault(i));
                        }

                        // Set the name
                        if (old.names.Any())
                        {
                            wpData.waypoint.name = (old.names.Count() == 1 ? old.names.First() : old.names.ElementAtOrDefault(i));
                        }
                        if (string.IsNullOrEmpty(wpData.waypoint.name) || wpData.waypoint.name.ToLower() == "site")
                        {
                            wpData.waypoint.name = StringUtilities.GenerateSiteName(random.Next(), wpData.waypoint.celestialBody, !wpData.waterAllowed);
                        }

                        // Handle waypoint chaining
                        if (wpData.chained && i != 0)
                        {
                            wpData.nearIndex = wpGenerator.waypoints.Count - 2;
                        }
                    }
                }
                finally
                {
                    ConfigNodeUtil.SetCurrentDataNode(factory.dataNode);
                }
            }

            return(valid ? wpGenerator : null);
        }
Example #23
0
 private static IEnumerable <Transform> FindInterestingThings(CelestialBody body)
 {
     return(body.GetComponentsInChildren <Transform>().Where(child => !InterestingThingIgnores.Any(i => child.name.StartsWith(i)) && !child.name.StartsWith(body.name)));
 }
Example #24
0
        public static WaypointGenerator Create(ConfigNode configNode, CelestialBody defaultBody, WaypointGeneratorFactory factory)
        {
            WaypointGenerator wpGenerator = new WaypointGenerator();

            bool valid = true;
            int  index = 0;

            foreach (ConfigNode child in ConfigNodeUtil.GetChildNodes(configNode))
            {
                double?      altitude = null;
                WaypointData wpData   = new WaypointData(child.name);

                valid &= ConfigNodeUtil.ParseValue <string>(child, "targetBody", x => wpData.waypoint.celestialName = x, factory, defaultBody != null ? defaultBody.name : null, Validation.NotNull);
                valid &= ConfigNodeUtil.ParseValue <string>(child, "name", x => wpData.waypoint.name = x, factory, (string)null);
                valid &= ConfigNodeUtil.ParseValue <double?>(child, "altitude", x => altitude = x, factory, (double?)null);
                valid &= ConfigNodeUtil.ParseValue <string>(child, "parameter", x => wpData.parameter = x, factory, "");
                valid &= ConfigNodeUtil.ParseValue <bool>(child, "hidden", x => wpData.hidden = x, factory, false);
                if (wpData.hidden)
                {
                    valid &= ConfigNodeUtil.ParseValue <string>(child, "icon", x => wpData.waypoint.id = x, factory, "");
                }
                else
                {
                    valid &= ConfigNodeUtil.ParseValue <string>(child, "icon", x => wpData.waypoint.id = x, factory);
                }

                // The FinePrint logic is such that it will only look in Squad/Contracts/Icons for icons.
                // Cheat this by hacking the path in the game database.
                if (wpData.waypoint.id.Contains("/"))
                {
                    GameDatabase.TextureInfo texInfo = GameDatabase.Instance.databaseTexture.Where(t => t.name == wpData.waypoint.id).FirstOrDefault();
                    if (texInfo != null)
                    {
                        texInfo.name = "Squad/Contracts/Icons/" + wpData.waypoint.id;
                    }
                }

                // Track the index
                wpData.waypoint.index = index++;

                // Get altitude
                if (altitude == null)
                {
                    wpData.waypoint.altitude = 0.0;
                    wpData.randomAltitude    = true;
                }
                else
                {
                    wpData.waypoint.altitude = altitude.Value;
                }

                DataNode dataNode = new DataNode("WAYPOINT_" + (index - 1), factory.dataNode, factory);
                try
                {
                    ConfigNodeUtil.SetCurrentDataNode(dataNode);

                    // Get settings that differ by type
                    if (child.name == "WAYPOINT")
                    {
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "latitude", x => wpData.waypoint.latitude = x, factory);
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "longitude", x => wpData.waypoint.longitude = x, factory);
                    }
                    else if (child.name == "RANDOM_WAYPOINT")
                    {
                        // Get settings for randomization
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "waterAllowed", x => wpData.waterAllowed = x, factory, true);
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "forceEquatorial", x => wpData.forceEquatorial = x, factory, false);
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "count", x => wpData.count = x, factory, 1, x => Validation.GE(x, 1));
                    }
                    else if (child.name == "RANDOM_WAYPOINT_NEAR")
                    {
                        // Get settings for randomization
                        valid &= ConfigNodeUtil.ParseValue <bool>(child, "waterAllowed", x => wpData.waterAllowed = x, factory, true);

                        // Get near waypoint details
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "nearIndex", x => wpData.nearIndex = x, factory, x => Validation.GE(x, 0));
                        valid &= ConfigNodeUtil.ParseValue <int>(child, "count", x => wpData.count = x, factory, 1, x => Validation.GE(x, 1));

                        // Get distances
                        valid &= ConfigNodeUtil.ParseValue <double>(child, "minDistance", x => wpData.minDistance = x, factory, 0.0, x => Validation.GE(x, 0.0));

                        // To be deprecated
                        if (child.HasValue("nearDistance"))
                        {
                            valid &= ConfigNodeUtil.ParseValue <double>(child, "nearDistance", x => wpData.maxDistance = x, factory, x => Validation.GT(x, 0.0));
                            LoggingUtil.LogWarning(factory, "The 'nearDistance' attribute is obsolete as of Contract Configurator 0.7.4.  It will be removed in 1.0.0 in favour of minDistance/maxDistance.");
                        }
                        else
                        {
                            valid &= ConfigNodeUtil.ParseValue <double>(child, "maxDistance", x => wpData.maxDistance = x, factory, x => Validation.GT(x, wpData.minDistance));
                        }
                    }
                    else if (child.name == "PQS_CITY")
                    {
                        wpData.randomAltitude = false;
                        string pqsCity = null;
                        valid &= ConfigNodeUtil.ParseValue <string>(child, "pqsCity", x => pqsCity = x, factory);
                        if (pqsCity != null)
                        {
                            try
                            {
                                CelestialBody body = FlightGlobals.Bodies.Where(b => b.name == wpData.waypoint.celestialName).First();
                                wpData.pqsCity = body.GetComponentsInChildren <PQSCity>(true).Where(pqs => pqs.name == pqsCity).First();
                            }
                            catch (Exception e)
                            {
                                LoggingUtil.LogError(typeof(WaypointGenerator), "Couldn't load PQSCity with name '" + pqsCity + "'");
                                LoggingUtil.LogException(e);
                                valid = false;
                            }
                        }
                        valid &= ConfigNodeUtil.ParseValue <Vector3d>(child, "pqsOffset", x => wpData.pqsOffset = x, factory, new Vector3d());
                    }
                    else
                    {
                        LoggingUtil.LogError(factory, "Unrecognized waypoint node: '" + child.name + "'");
                        valid = false;
                    }
                }
                finally
                {
                    ConfigNodeUtil.SetCurrentDataNode(factory.dataNode);
                }

                // Add to the list
                wpGenerator.waypoints.Add(wpData);
            }

            return(valid ? wpGenerator : null);
        }
Example #25
0
 private static IEnumerable<Transform> FindInterestingThings(CelestialBody body)
 {
     return body.GetComponentsInChildren<Transform>().Where(child => !InterestingThingIgnores.Any(i => child.name.StartsWith(i)) && !child.name.StartsWith(body.name));
 }
Example #26
0
            /// <summary>
            /// [HACK] Spawn a new body from the PSystem-Prefab
            /// </summary>
            public static void Instantiate(PSystemBody template, string name)
            {
                // Fix Templates
                if (template == null)
                {
                    ScreenMessages.PostScreenMessage("You need a valid Template!", 3f, ScreenMessageStyle.UPPER_CENTER);
                    return;
                }

                // Spawn Message
                ScreenMessages.PostScreenMessage("Created new Planet " + name + ", based on " + template.name + "!", 5f, ScreenMessageStyle.UPPER_CENTER);
                ScreenMessages.PostScreenMessage("This tool is meant to be used by modders, it can break mods!", 5f, ScreenMessageStyle.UPPER_CENTER);

                // Clone the Template
                GameObject  bodyObject = UnityEngine.Object.Instantiate(template.gameObject);
                PSystemBody body       = bodyObject.GetComponent <PSystemBody>();

                // Alter it's name and flight-Number
                body.name = name;
                body.celestialBody.bodyName           = name;
                body.celestialBody.transform.name     = name;
                body.celestialBody.bodyTransform.name = name;
                body.scaledVersion.name = name;
                if (body.pqsVersion != null)
                {
                    body.pqsVersion.name            = name;
                    body.pqsVersion.gameObject.name = name;
                    body.pqsVersion.transform.name  = name;
                    foreach (PQS p in body.pqsVersion.GetComponentsInChildren(typeof(PQS), true))
                    {
                        p.name = p.name.Replace(template.celestialBody.bodyName, name);
                    }
                }
                body.flightGlobalsIndex = PSystemManager.Instance.localBodies.Last().flightGlobalsIndex + 1;

                // Change it's Orbit
                body.orbitDriver.orbit                = Orbit.CreateRandomOrbitAround(PSystemManager.Instance.localBodies.First(), 4000000000, 60000000000);
                body.orbitDriver.referenceBody        = PSystemManager.Instance.localBodies.First();
                body.orbitDriver.orbit.referenceBody  = body.orbitDriver.referenceBody;
                body.orbitRenderer.lowerCamVsSmaRatio = template.orbitRenderer.lowerCamVsSmaRatio;
                body.orbitRenderer.upperCamVsSmaRatio = template.orbitRenderer.upperCamVsSmaRatio;

                // Clear it's childs
                body.children = new List <PSystemBody>();

                // Add it to the System-Prefab
                body.transform.parent = PSystemManager.Instance.systemPrefab.transform;
                PSystemManager.Instance.systemPrefab.rootBody.children.Add(body);

                // Hack^6 - Hack the PSystemManager to spawn this thing
                MethodInfo spawnBody = typeof(PSystemManager).GetMethod("SpawnBody", BindingFlags.NonPublic | BindingFlags.Instance);

                spawnBody.Invoke(PSystemManager.Instance, new object[] { PSystemManager.Instance.localBodies.First(), body });
                CelestialBody cBody = PSystemManager.Instance.localBodies.Last();

                // Add the body to FlightGlobals.Bodies
                FlightGlobals.fetch.bodies.Add(cBody);

                // Start the CelestialBody
                typeof(CelestialBody).GetMethod("Start", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(cBody, null);

                // Start the OrbitDriver
                if (cBody.orbitDriver != null)
                {
                    typeof(OrbitDriver).GetMethod("Start", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(cBody.orbitDriver, null);
                }

                // Fix and start the OrbitRenderer
                if (Resources.FindObjectsOfTypeAll <OrbitRenderer>().Count(r => r.name == cBody.name) == 1)
                {
                    OrbitRenderer renderer = Resources.FindObjectsOfTypeAll <OrbitRenderer>().First(r => r.name == cBody.name);
                    renderer.driver        = cBody.orbitDriver;
                    renderer.celestialBody = cBody;
                    typeof(OrbitRenderer).GetMethod("Start", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(renderer, null);
                }

                // Force the start of the PQS-Spheres
                foreach (PQS p in cBody.GetComponentsInChildren <PQS>(true))
                {
                    p.ForceStart();
                }

                // Fix the ScaledVersion
                if (cBody.scaledBody.GetComponents <ScaledSpaceFader>().Length == 1)
                {
                    cBody.scaledBody.GetComponent <ScaledSpaceFader>().celestialBody = cBody;
                }
                if (cBody.scaledBody.GetComponents <AtmosphereFromGround>().Length == 1)
                {
                    cBody.scaledBody.GetComponent <AtmosphereFromGround>().planet = cBody;
                }
            }
        void SaveGroups()
        {
            // LOAD SD GROUPS
            foreach (ConfigNode Group in GroupsList.Values)
            {
                string        group = Group.GetValue("name");
                CelestialBody body  = FlightGlobals.Bodies.FirstOrDefault(b => b.transform.name == Group.GetValue("body"));
                if (string.IsNullOrEmpty(group) || body == null)
                {
                    continue;
                }
                Debug.Log("PQSCityGroups.SaveGroups", ">>> Loading PQSCityGroups from config files <<<");
                Debug.Log("PQSCityGroups.SaveGroups", "> Planet: " + body.name + (body.name != body.displayName.Replace("^N", "") ? (", (A.K.A.: " + body.displayName.Replace("^N", "") + ")") : "") + (body.name != body.transform.name ? (", (A.K.A.: " + body.transform.name + ")") : ""));
                Debug.Log("PQSCityGroups.SaveGroups", "    > Group: " + group);

                bool.TryParse(Group.GetValue("exclude"), out bool exclude);

                // FIND GROUP CENTER
                Vector3Parser center = null;

                // Get Center position from the CENTER node
                if (Group.HasNode("CENTER"))
                {
                    ConfigNode C = Group.GetNode("CENTER");

                    if (C.HasValue("CentralPQSCity"))
                    {
                        if (body == FlightGlobals.GetHomeBody() && C.GetValue("CentralPQSCity") == "KSC")
                        {
                            center = new Vector3(157000, -1000, -570000);
                        }
                    }

                    if (center == null)
                    {
                        center = GetCenter(C, body);
                    }
                }

                if (!body.Has("PQSCityGroups"))
                {
                    body.Set("PQSCityGroups", new Dictionary <object, Vector3>());
                }
                Dictionary <object, Vector3> PQSList = body.Get <Dictionary <object, Vector3> >("PQSCityGroups");

                if (!body.Has("ExcludedPQSCityMods"))
                {
                    body.Set("ExcludedPQSCityMods", new List <object>());
                }
                List <object> ExcludeList = body.Get <List <object> >("ExcludedPQSCityMods");

                // If the Center position has not been found get it from the MODS node
                if (Group.HasNode("MODS"))
                {
                    ConfigNode M = Group.GetNode("MODS");

                    if (center == null)
                    {
                        center = GetCenter(M, body);
                    }
                }

                // If the Center position has not been found get it from the external groups
                if
                (
                    center == null &&
                    ExternalGroups?.ContainsKey(body) == true &&
                    ExternalGroups[body].ContainsKey(group)
                )
                {
                    center = GetPosition(ExternalGroups[body][group].FirstOrDefault());
                }

                // If the Center position has not been found stop here
                if (center == null)
                {
                    continue;
                }
                Debug.Log("PQSCityGroups.SaveGroups", "        > Center position = " + center.Value + ", (LAT: " + new SigmaDimensions.LatLon(center).lat + ", LON: " + new SigmaDimensions.LatLon(center).lon + ")");


                // ADD PQS MODS TO THE GROUP
                if (Group.HasNode("MODS"))
                {
                    ConfigNode M = Group.GetNode("MODS");

                    foreach (string city in M.GetValues("PQSCity"))
                    {
                        PQSCity mod = body.GetComponentsInChildren <PQSCity>(true).FirstOrDefault(m => m.name == city);

                        if (mod != null)
                        {
                            // If the mod has already been added overwrite it
                            // This way custom groups will overwrite external ones
                            if (PQSList.ContainsKey(mod))
                            {
                                PQSList.Remove(mod);
                            }

                            if (!exclude)
                            {
                                PQSList.Add(mod, center);
                                Debug.Log("PQSCityGroups.SaveGroups", "            > PQSCity:  " + mod.name);
                            }
                            else
                            {
                                ExcludeList.Add(mod);
                                Debug.Log("PQSCityGroups.SaveGroups", "            > Excluded PQSCity:  " + mod.name);
                            }
                        }
                    }
                    foreach (string city2 in M.GetValues("PQSCity2"))
                    {
                        PQSCity2 mod = body.GetComponentsInChildren <PQSCity2>(true).FirstOrDefault(m => m.name == city2);

                        if (mod != null)
                        {
                            // If the mod has already been added overwrite it
                            // This way custom groups will overwrite external ones
                            if (PQSList.ContainsKey(mod))
                            {
                                PQSList.Remove(mod);
                            }

                            if (!exclude)
                            {
                                PQSList.Add(mod, center);
                                Debug.Log("PQSCityGroups.SaveGroups", "            > PQSCity2:  " + mod.name);
                            }
                            else
                            {
                                ExcludeList.Add(mod);
                                Debug.Log("PQSCityGroups.SaveGroups", "            > Excluded PQSCity2:  " + mod.name);
                            }
                        }
                    }
                }


                // ADD EXTERNAL MODS TO THIS GROUP
                if
                (
                    ExternalGroups?.ContainsKey(body) == true &&
                    ExternalGroups[body].ContainsKey(group) &&
                    ExternalGroups[body][group].Where(m => m != null)?.Count() > 0
                )
                {
                    foreach (object mod in ExternalGroups[body][group].Where(m => m != null))
                    {
                        // External groups should not overwrite custom ones
                        if (PQSList.ContainsKey(mod))
                        {
                            continue;
                        }

                        if (!exclude)
                        {
                            PQSList.Add(mod, center);
                            Debug.Log("PQSCityGroups.SaveGroups", "            > external:  " + mod);
                        }
                        else
                        {
                            ExcludeList.Add(mod);
                            Debug.Log("PQSCityGroups.SaveGroups", "            > Excluded external:  " + mod);
                        }
                    }
                    ExternalGroups[body].Remove(group);
                }


                // REMOVE KSC FROM THE LIST

                PQSCity ksc = FlightGlobals.GetHomeBody().GetComponentsInChildren <PQSCity>(true).FirstOrDefault(m => m.name == "KSC");
                if (PQSList.ContainsKey(ksc))
                {
                    PQSList.Remove(ksc);
                }
                if (ExcludeList.Contains(ksc))
                {
                    ExcludeList.Remove(ksc);
                }


                body.Set("PQSCityGroups", PQSList);
                body.Set("ExcludedPQSCityMods", ExcludeList);


                // ADD THIS GROUP TO THE MOVE LIST
                if (Group.HasNode("MOVE"))
                {
                    ConfigNode C2        = Group.GetNode("MOVE");
                    Vector3?   newCenter = GetCenter(C2, body);

                    if (newCenter == null)
                    {
                        newCenter = center;
                    }
                    Debug.Log("PQSCityGroups.SaveGroups", "Move Group to position = " + newCenter.Value + ", (LAT: " + new SigmaDimensions.LatLon(newCenter.Value).lat + ", LON: " + new SigmaDimensions.LatLon(newCenter.Value).lon + ")");


                    var info = new KeyValuePair <Vector3, NumericParser <double>[]>((Vector3)newCenter, new[] { 0, 0, new NumericParser <double>() });

                    if (C2.HasValue("Rotate"))
                    {
                        info.Value[0].SetFromString(C2.GetValue("Rotate"));
                    }
                    Debug.Log("PQSCityGroups.SaveGroups", "Rotate group = " + info.Value[0].Value);
                    if (C2.HasValue("fixAltitude"))
                    {
                        info.Value[1].SetFromString(C2.GetValue("fixAltitude"));
                    }
                    Debug.Log("PQSCityGroups.SaveGroups", "Fix group altitude = " + info.Value[1].Value);
                    if (C2.HasValue("originalAltitude"))
                    {
                        info.Value[2].SetFromString(C2.GetValue("originalAltitude"));
                    }
                    else
                    {
                        info.Value[2].SetFromString("-Infinity");
                    } Debug.Log("PQSCityGroups.SaveGroups", "Original group altitude = " + (info.Value[2].Value == double.NegativeInfinity ? "[Not Specified]" : info.Value[2].Value.ToString()));


                    if (!body.Has("PQSCityGroupsMove"))
                    {
                        body.Set("PQSCityGroupsMove", new Dictionary <Vector3, KeyValuePair <Vector3, NumericParser <double>[]> >());
                    }
                    var MoveList = body.Get <Dictionary <Vector3, KeyValuePair <Vector3, NumericParser <double>[]> > >("PQSCityGroupsMove");

                    if (!MoveList.ContainsKey(center.Value))
                    {
                        MoveList.Add(center.Value, info);
                    }

                    body.Set("PQSCityGroupsMove", MoveList);
                }
            }


            // Make sure External Groups are valid
            if (ExternalGroups == null)
            {
                ExternalGroups = new Dictionary <CelestialBody, Dictionary <string, List <object> > >();
            }

            // LOAD REMAINING EXTERNAL GROUPS
            Debug.Log("PQSCityGroups.SaveGroups", ">>> Loading external PQSCityGroups <<<");
            foreach (CelestialBody planet in ExternalGroups.Keys.Where(p => p != null && ExternalGroups[p] != null))
            {
                Debug.Log("PQSCityGroups.SaveGroups", "> Planet: " + planet.name + (planet.name != planet.displayName.Replace("^N", "") ? (", (A.K.A.: " + planet.displayName.Replace("^N", "") + ")") : "") + (planet.name != planet.transform.name ? (", (A.K.A.: " + planet.transform.name + ")") : ""));
                foreach (string group in ExternalGroups[planet].Keys.Where(g => !string.IsNullOrEmpty(g) && ExternalGroups[planet][g] != null))
                {
                    if (ExternalGroups[planet][group].Count == 0)
                    {
                        continue;
                    }
                    Debug.Log("PQSCityGroups.SaveGroups", "    > Group: " + group);

                    // Since these groups are new they don't have a center
                    // Define the center as the position of the first mod in the array
                    Vector3?center = null;
                    center = GetPosition(ExternalGroups[planet][group].FirstOrDefault());
                    if (center == null)
                    {
                        continue;
                    }
                    Debug.Log("PQSCityGroups.SaveGroups", "        > Center position = " + center + ", (LAT: " + new SigmaDimensions.LatLon((Vector3)center).lat + ", LON: " + new SigmaDimensions.LatLon((Vector3)center).lon + ")");

                    if (!planet.Has("PQSCityGroups"))
                    {
                        planet.Set("PQSCityGroups", new Dictionary <object, Vector3>());
                    }
                    Dictionary <object, Vector3> PQSList = planet.Get <Dictionary <object, Vector3> >("PQSCityGroups");

                    foreach (object mod in ExternalGroups[planet][group])
                    {
                        if (!PQSList.ContainsKey(mod))
                        {
                            PQSList.Add(mod, (Vector3)center);
                            Debug.Log("PQSCityGroups.SaveGroups", "            > external: " + mod);
                        }
                    }


                    // REMOVE KSC FROM THE LIST

                    PQSCity ksc = FlightGlobals.GetHomeBody().GetComponentsInChildren <PQSCity>(true).FirstOrDefault(m => m.name == "KSC");
                    if (PQSList.ContainsKey(ksc))
                    {
                        PQSList.Remove(ksc);
                    }


                    planet.Set("PQSCityGroups", PQSList);
                }
            }

            // LOAD EXTERNAL EXCEPTIONS
            Debug.Log("PQSCityGroups.SaveGroups", ">>> Loading external PQSCityGroups exceptions <<<");
            foreach (CelestialBody planet in ExternalExceptions.Keys.Where(p => p != null && ExternalExceptions[p] != null))
            {
                Debug.Log("PQSCityGroups.SaveGroups", "> Planet: " + planet.name + (planet.name != planet.displayName.Replace("^N", "") ? (", (A.K.A.: " + planet.displayName.Replace("^N", "") + ")") : "") + (planet.name != planet.transform.name ? (", (A.K.A.: " + planet.transform.name + ")") : ""));
                foreach (string group in ExternalExceptions[planet].Keys.Where(g => !string.IsNullOrEmpty(g) && ExternalExceptions[planet][g] != null))
                {
                    if (ExternalExceptions[planet][group].Count == 0)
                    {
                        continue;
                    }
                    Debug.Log("PQSCityGroups.SaveGroups", "    > Group: " + group);

                    // Since these groups are exceptions they don't need a center

                    if (!planet.Has("ExcludedPQSCityMods"))
                    {
                        planet.Set("ExcludedPQSCityMods", new Dictionary <object, Vector3>());
                    }
                    List <object> ExcludeList = planet.Get <List <object> >("PQSCityGroups");

                    foreach (object mod in ExternalExceptions[planet][group])
                    {
                        if (!ExcludeList.Contains(mod))
                        {
                            ExcludeList.Add(mod);
                            Debug.Log("PQSCityGroups.SaveGroups", "            > excluded external: " + mod);
                        }
                    }


                    // REMOVE KSC FROM THE LIST

                    PQSCity ksc = FlightGlobals.GetHomeBody().GetComponentsInChildren <PQSCity>(true).FirstOrDefault(m => m.name == "KSC");
                    if (ExcludeList.Contains(ksc))
                    {
                        ExcludeList.Remove(ksc);
                    }


                    planet.Set("ExcludedPQSCityMods", ExcludeList);
                }
            }
        }