Merge branch 'common-resources' of github.com:xen-42/outer-wilds-new-horizons into common-resources

This commit is contained in:
JoJo 2022-01-01 23:31:43 -08:00
commit f3bf6e945c
24 changed files with 1172 additions and 211 deletions

View File

@ -6,7 +6,7 @@ namespace NewHorizons.Atmosphere
{
static class AtmosphereBuilder
{
public static void Make(GameObject body, AtmosphereModule atmosphereModule)
public static void Make(GameObject body, AtmosphereModule atmosphereModule, float surfaceSize)
{
GameObject atmoGO = new GameObject("Atmosphere");
atmoGO.SetActive(false);
@ -14,66 +14,19 @@ namespace NewHorizons.Atmosphere
if (atmosphereModule.HasAtmosphere)
{
var mat = GameObject.Find("TimberHearth_Body/Atmosphere_TH/AtmoSphere/Atmosphere_LOD0").GetComponent<MeshRenderer>().material;
GameObject atmo = GameObject.Instantiate(GameObject.Find("Atmosphere_TH/AtmoSphere"));
GameObject atmo = GameObject.Instantiate(GameObject.Find("TimberHearth_Body/Atmosphere_TH/AtmoSphere"));
atmo.transform.parent = atmoGO.transform;
atmo.transform.localPosition = Vector3.zero;
atmo.transform.localScale = Vector3.one * atmosphereModule.Size;
atmo.transform.localScale = Vector3.one * atmosphereModule.Size * 1.2f;
foreach(var meshRenderer in atmo.GetComponentsInChildren<MeshRenderer>())
{
meshRenderer.material.SetFloat("_InnerRadius", atmosphereModule.Cloud != null ? atmosphereModule.Size : surfaceSize);
meshRenderer.material.SetFloat("_OuterRadius", atmosphereModule.Size * 1.2f);
if(atmosphereModule.AtmosphereTint != null)
meshRenderer.material.SetColor("_SkyColor", atmosphereModule.AtmosphereTint.ToColor32());
}
atmo.SetActive(true);
/*
GameObject lod0 = new GameObject();
lod0.transform.parent = atmo.transform;
lod0.transform.localPosition = Vector3.zero;
MeshFilter f0 = lod0.AddComponent<MeshFilter>();
f0.mesh = GameObject.Find("Atmosphere_LOD0").GetComponent<MeshFilter>().mesh;
MeshRenderer r0 = lod0.AddComponent<MeshRenderer>();
r0.material = mat;
GameObject lod1 = new GameObject();
lod1.transform.parent = atmo.transform;
lod1.transform.localPosition = Vector3.zero;
MeshFilter f1 = lod1.AddComponent<MeshFilter>();
f1.mesh = GameObject.Find("Atmosphere_LOD1").GetComponent<MeshFilter>().mesh;
MeshRenderer r1 = lod1.AddComponent<MeshRenderer>();
r1.material = mat;
GameObject lod2 = new GameObject();
lod2.transform.parent = atmo.transform;
lod2.transform.localPosition = Vector3.zero;
MeshFilter f2 = lod2.AddComponent<MeshFilter>();
f2.mesh = GameObject.Find("Atmosphere_LOD2").GetComponent<MeshFilter>().mesh;
MeshRenderer r2 = lod2.AddComponent<MeshRenderer>();
r2.material = mat;
GameObject lod3 = new GameObject();
lod3.transform.parent = atmo.transform;
lod3.transform.localPosition = Vector3.zero;
MeshFilter f3 = lod3.AddComponent<MeshFilter>();
f3.mesh = GameObject.Find("Atmosphere_LOD3").GetComponent<MeshFilter>().mesh;
MeshRenderer r3 = lod3.AddComponent<MeshRenderer>();
r3.material = mat;
LODGroup lodg = atmo.AddComponent<LODGroup>();
LOD[] lodlist = new LOD[4];
Renderer[] t0 = { r0 };
Renderer[] t1 = { r1 };
Renderer[] t2 = { r2 };
Renderer[] t3 = { r3 };
LOD one = new LOD(1, t0);
LOD two = new LOD(0.7f, t1);
LOD three = new LOD(0.27f, t2);
LOD four = new LOD(0.08f, t3);
lodlist[0] = one;
lodlist[1] = two;
lodlist[2] = three;
lodlist[3] = four;
lodg.SetLODs(lodlist);
lodg.fadeMode = LODFadeMode.None;
*/
}
atmoGO.transform.localPosition = Vector3.zero;

View File

@ -14,79 +14,98 @@ namespace NewHorizons.Builder.Body
static class StarBuilder
{
private static Texture2D _colorOverTime;
public static void Make(GameObject body, Sector sector, StarModule starModule)
public static StarController Make(GameObject body, Sector sector, StarModule starModule)
{
if (_colorOverTime == null) _colorOverTime = Main.Instance.ModHelper.Assets.GetTexture("AssetBundle/StarColorOverTime.png");
var sunGO = new GameObject("Star");
sunGO.transform.parent = body.transform;
var starGO = new GameObject("Star");
starGO.transform.parent = body.transform;
var sunSurface = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Geometry_SUN/Surface"), sunGO.transform);
var sunSurface = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Geometry_SUN/Surface"), starGO.transform);
sunSurface.transform.localPosition = Vector3.zero;
sunSurface.transform.localScale = Vector3.one;
sunSurface.name = "Surface";
//var sunLight = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight"), sunGO.transform);
var sunLight = new GameObject();
sunLight.transform.parent = sunGO.transform;
sunLight.transform.parent = starGO.transform;
sunLight.transform.localPosition = Vector3.zero;
sunLight.transform.localScale = Vector3.one;
sunLight.name = "StarLight";
var light = sunLight.AddComponent<Light>();
light.CopyPropertiesFrom(GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<Light>());
light.intensity *= starModule.SolarLuminosity;
light.range *= Mathf.Sqrt(starModule.SolarLuminosity);
var solarFlareEmitter = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SolarFlareEmitter"), sunGO.transform);
var faceActiveCamera = sunLight.AddComponent<FaceActiveCamera>();
faceActiveCamera.CopyPropertiesFrom(GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<FaceActiveCamera>());
var csmTextureCacher = sunLight.AddComponent<CSMTextureCacher>();
csmTextureCacher.CopyPropertiesFrom(GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<CSMTextureCacher>());
csmTextureCacher._light = light;
var proxyShadowLight = sunLight.AddComponent<ProxyShadowLight>();
proxyShadowLight.CopyPropertiesFrom(GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<ProxyShadowLight>());
proxyShadowLight._light = light;
var solarFlareEmitter = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SolarFlareEmitter"), starGO.transform);
solarFlareEmitter.transform.localPosition = Vector3.zero;
solarFlareEmitter.transform.localScale = Vector3.one;
solarFlareEmitter.name = "SolarFlareEmitter";
var sunAudio = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Audio_SUN"), sunGO.transform);
var sunAudio = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Audio_SUN"), starGO.transform);
sunAudio.transform.localPosition = Vector3.zero;
sunAudio.transform.localScale = Vector3.one;
sunAudio.transform.Find("SurfaceAudio_Sun").GetComponent<AudioSource>().maxDistance = starModule.Size * 2f;
var surfaceAudio = sunAudio.GetComponentInChildren<SunSurfaceAudioController>();
surfaceAudio.SetSector(sector);
surfaceAudio._sunController = null;
sunAudio.name = "Audio_Star";
var sunAtmosphere = GameObject.Instantiate(GameObject.Find("Sun_Body/Atmosphere_SUN"), sunGO.transform);
var sunAtmosphere = GameObject.Instantiate(GameObject.Find("Sun_Body/Atmosphere_SUN"), starGO.transform);
sunAtmosphere.transform.localPosition = Vector3.zero;
sunAtmosphere.transform.localScale = Vector3.one;
sunAtmosphere.name = "Atmosphere_Star";
var ambientLightGO = GameObject.Instantiate(GameObject.Find("Sun_Body/AmbientLight_SUN"), sunGO.transform);
var ambientLightGO = GameObject.Instantiate(GameObject.Find("Sun_Body/AmbientLight_SUN"), starGO.transform);
ambientLightGO.transform.localPosition = Vector3.zero;
ambientLightGO.name = "AmbientLight_Star";
var heatVolume = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Volumes_SUN/HeatVolume"), sunGO.transform);
var heatVolume = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Volumes_SUN/HeatVolume"), starGO.transform);
heatVolume.transform.localPosition = Vector3.zero;
heatVolume.transform.localScale = Vector3.one;
heatVolume.GetComponent<SphereShape>().radius = 1f;
heatVolume.name = "HeatVolume";
var deathVolume = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Volumes_SUN/ScaledVolumesRoot/DestructionFluidVolume"), sunGO.transform);
var deathVolume = GameObject.Instantiate(GameObject.Find("Sun_Body/Sector_SUN/Volumes_SUN/ScaledVolumesRoot/DestructionFluidVolume"), starGO.transform);
deathVolume.transform.localPosition = Vector3.zero;
deathVolume.transform.localScale = Vector3.one;
deathVolume.GetComponent<SphereCollider>().radius = 1f;
deathVolume.name = "DestructionVolume";
PlanetaryFogController fog = sunAtmosphere.transform.Find("FogSphere").GetComponent<PlanetaryFogController>();
TessellatedSphereRenderer surface = sunSurface.GetComponent<TessellatedSphereRenderer>();
Light ambientLight = ambientLightGO.GetComponent<Light>();
//GameObject.Destroy(sunLight.GetComponent<FaceActiveCamera>());
//GameObject.Destroy(sunLight.GetComponent<CSMTextureCacher>());
//GameObject.Destroy(sunLight.GetComponent<ProxyShadowLight>());
//SunLightController sunLightController = sunLight.GetComponent<SunLightController>();
//GameObject.Destroy(sunLight.GetComponent<SunLightParamUpdater>());
//GameObject.Destroy(sunLightController);
Color lightColour = light.color;
if (starModule.LightTint != null) lightColour = starModule.LightTint.ToColor32();
if (lightColour == null && starModule.Tint != null)
{
// Lighten it a bit
var r = Mathf.Clamp01(starModule.Tint.R * 1.5f / 255f);
var g = Mathf.Clamp01(starModule.Tint.G * 1.5f / 255f);
var b = Mathf.Clamp01(starModule.Tint.B * 1.5f / 255f);
lightColour = new Color(r, g, b);
}
if (lightColour != null) light.color = (Color)lightColour;
light.color = lightColour;
ambientLight.color = lightColour;
fog.fogRadius = starModule.Size * 1.2f;
if(starModule.Tint != null)
{
var colour = starModule.Tint.ToColor32();
//sunLightController.sunColor = colour;
//ambientLight.color = colour;
fog.fogTint = colour;
var sun = GameObject.Find("Sun_Body");
@ -94,8 +113,9 @@ namespace NewHorizons.Builder.Body
var giantMaterial = sun.GetComponent<SunController>().GetValue<Material>("_endSurfaceMaterial");
surface.sharedMaterial = new Material(starModule.Size >= 3000 ? giantMaterial : mainSequenceMaterial);
surface.sharedMaterial.color = new Color(colour.r * 8f / 255f, colour.g * 8f / 255f, colour.b * 8f / 255f);
surface.sharedMaterial.SetTexture("_ColorRamp", Utility.ImageUtilities.TintImage(_colorOverTime, colour));
var mod = 8f * starModule.SolarLuminosity / 255f;
surface.sharedMaterial.color = new Color(colour.r * mod, colour.g * mod, colour.b * mod);
surface.sharedMaterial.SetTexture("_ColorRamp", ImageUtilities.TintImage(_colorOverTime, colour));
sunAtmosphere.transform.Find("AtmoSphere").transform.localScale = Vector3.one * (starModule.Size + 1000)/starModule.Size;
foreach (var lod in sunAtmosphere.transform.Find("AtmoSphere").GetComponentsInChildren<MeshRenderer>())
@ -109,8 +129,19 @@ namespace NewHorizons.Builder.Body
if(starModule.SolarFlareTint != null)
solarFlareEmitter.GetComponent<SolarFlareEmitter>().tint = starModule.SolarFlareTint.ToColor32();
sunGO.transform.localPosition = Vector3.zero;
sunGO.transform.localScale = starModule.Size * Vector3.one;
starGO.transform.localPosition = Vector3.zero;
starGO.transform.localScale = starModule.Size * Vector3.one;
var starController = body.AddComponent<StarController>();
starController.Light = light;
starController.AmbientLight = ambientLight;
starController.FaceActiveCamera = faceActiveCamera;
starController.CSMTextureCacher = csmTextureCacher;
starController.ProxyShadowLight = proxyShadowLight;
starController.Intensity = starModule.SolarLuminosity;
starController.SunColor = lightColour;
return starController;
}
}
}

View File

@ -1,5 +1,7 @@
using NewHorizons.External;
using NewHorizons.Utility;
using OWML.Utils;
using System;
using System.Reflection;
using UnityEngine;
using Logger = NewHorizons.Utility.Logger;
@ -8,8 +10,15 @@ namespace NewHorizons.Builder.General
{
static class GravityBuilder
{
public static GravityVolume Make(GameObject body, AstroObject ao, float surfaceAccel, float sphereOfInfluence, float surface, string falloffType)
public static GravityVolume Make(GameObject body, AstroObject ao, IPlanetConfig config)
{
var exponent = config.Base.GravityFallOff.Equals("linear") ? 1f : 2f;
var GM = config.Base.SurfaceGravity * Mathf.Pow(config.Base.SurfaceSize, exponent);
// Gravity limit will be when the acceleration it would cause is less than 0.1 m/s^2
var gravityRadius = GM / 0.1f;
if (exponent == 2f) gravityRadius = Mathf.Sqrt(gravityRadius);
GameObject gravityGO = new GameObject("GravityWell");
gravityGO.transform.parent = body.transform;
gravityGO.transform.localPosition = Vector3.zero;
@ -18,7 +27,7 @@ namespace NewHorizons.Builder.General
SphereCollider SC = gravityGO.AddComponent<SphereCollider>();
SC.isTrigger = true;
SC.radius = sphereOfInfluence;
SC.radius = gravityRadius;
OWCollider OWC = gravityGO.AddComponent<OWCollider>();
OWC.SetLODActivationMask(DynamicOccupant.Player);
@ -27,14 +36,20 @@ namespace NewHorizons.Builder.General
GravityVolume GV = gravityGO.AddComponent<GravityVolume>();
GV.SetValue("_cutoffAcceleration", 0.1f);
GV.SetValue("_falloffType", GV.GetType().GetNestedType("FalloffType", BindingFlags.NonPublic).GetField(falloffType).GetValue(GV));
GV.SetValue("_alignmentRadius", 1.5f * surface);
GV.SetValue("_upperSurfaceRadius", surface);
GravityVolume.FalloffType falloff = GravityVolume.FalloffType.linear;
if (config.Base.GravityFallOff.ToUpper().Equals("LINEAR")) falloff = GravityVolume.FalloffType.linear;
else if (config.Base.GravityFallOff.ToUpper().Equals("INVERSESQUARED")) falloff = GravityVolume.FalloffType.inverseSquared;
else Logger.LogError($"Couldn't set gravity type {config.Base.GravityFallOff}. Must be either \"linear\" or \"inverseSquared\". Defaulting to linear.");
GV._falloffType = falloff;
GV.SetValue("_alignmentRadius", config.Base.SurfaceGravity != 0 ? 1.5f * config.Base.SurfaceSize : 0f);
GV.SetValue("_upperSurfaceRadius", config.Base.SurfaceSize);
GV.SetValue("_lowerSurfaceRadius", 0);
GV.SetValue("_layer", 3);
GV.SetValue("_priority", 0);
GV.SetValue("_alignmentPriority", 0);
GV.SetValue("_surfaceAcceleration", surfaceAccel);
GV.SetValue("_surfaceAcceleration", config.Base.SurfaceGravity);
GV.SetValue("_inheritable", false);
GV.SetValue("_isPlanetGravityVolume", true);
GV.SetValue("_cutoffRadius", 0f);

View File

@ -49,17 +49,17 @@ namespace NewHorizons.Builder.General
if (ao.GetAstroObjectName() == AstroObject.Name.CaveTwin || ao.GetAstroObjectName() == AstroObject.Name.TowerTwin)
{
if (ao.GetAstroObjectName() == AstroObject.Name.TowerTwin)
GameObject.Find("TimeLoopRing_Body").SetActive(false);
var focalBody = GameObject.Find("FocalBody");
if (focalBody != null) focalBody.SetActive(false);
}
if (ao.GetAstroObjectName() == AstroObject.Name.MapSatellite)
else if (ao.GetAstroObjectName() == AstroObject.Name.MapSatellite)
{
var msb = GameObject.Find("MapSatellite_Body");
if (msb != null) msb.SetActive(false);
}
if (ao.GetAstroObjectName() == AstroObject.Name.TowerTwin)
GameObject.Find("TimeLoopRing_Body").SetActive(false);
if (ao.GetAstroObjectName() == AstroObject.Name.ProbeCannon)
else if(ao.GetAstroObjectName() == AstroObject.Name.ProbeCannon)
{
GameObject.Find("NomaiProbe_Body").SetActive(false);
GameObject.Find("CannonMuzzle_Body").SetActive(false);
@ -68,11 +68,11 @@ namespace NewHorizons.Builder.General
GameObject.Find("FakeCannonBarrel_Body (1)").SetActive(false);
GameObject.Find("Debris_Body (1)").SetActive(false);
}
if (ao.GetAstroObjectName() == AstroObject.Name.SunStation)
else if(ao.GetAstroObjectName() == AstroObject.Name.SunStation)
{
GameObject.Find("SS_Debris_Body").SetActive(false);
}
if (ao.GetAstroObjectName() == AstroObject.Name.GiantsDeep)
else if(ao.GetAstroObjectName() == AstroObject.Name.GiantsDeep)
{
GameObject.Find("BrambleIsland_Body").SetActive(false);
GameObject.Find("GabbroIsland_Body").SetActive(false);
@ -80,15 +80,41 @@ namespace NewHorizons.Builder.General
GameObject.Find("StatueIsland_Body").SetActive(false);
GameObject.Find("ConstructionYardIsland_Body").SetActive(false);
}
if (ao.GetAstroObjectName() == AstroObject.Name.WhiteHole)
else if(ao.GetAstroObjectName() == AstroObject.Name.WhiteHole)
{
GameObject.Find("WhiteholeStation_Body").SetActive(false);
GameObject.Find("WhiteholeStationSuperstructure_Body").SetActive(false);
}
if (ao.GetAstroObjectName() == AstroObject.Name.TimberHearth)
else if(ao.GetAstroObjectName() == AstroObject.Name.TimberHearth)
{
GameObject.Find("MiningRig_Body").SetActive(false);
}
else if(ao.GetAstroObjectName() == AstroObject.Name.Sun)
{
var starController = ao.gameObject.GetComponent<StarController>();
Main.Instance.StarLightController.RemoveStar(starController);
GameObject.Destroy(starController);
var audio = ao.GetComponentInChildren<SunSurfaceAudioController>();
GameObject.Destroy(audio);
foreach(var owAudioSource in ao.GetComponentsInChildren<OWAudioSource>())
{
owAudioSource.Stop();
GameObject.Destroy(owAudioSource);
}
foreach (var audioSource in ao.GetComponentsInChildren<AudioSource>())
{
audioSource.Stop();
GameObject.Destroy(audioSource);
}
}
else if(ao.GetAstroObjectName() == AstroObject.Name.DreamWorld)
{
GameObject.Find("BackRaft_Body").SetActive(false);
GameObject.Find("SealRaft_Body").SetActive(false);
}
// Deal with proxies
foreach (var p in GameObject.FindObjectsOfType<ProxyOrbiter>())

View File

@ -8,24 +8,40 @@ using UnityEngine;
using Random = UnityEngine.Random;
using Logger = NewHorizons.Utility.Logger;
using System.Reflection;
using NewHorizons.Utility;
namespace NewHorizons.Builder.Props
{
public static class PropBuilder
{
public static void Make(GameObject body, string propToClone, Vector3 position, Sector sector)
public static void Make(GameObject go, Sector sector, IPlanetConfig config)
{
var prefab = GameObject.Find(propToClone);
Make(body, prefab, position, sector);
if (config.Props.Scatter != null) PropBuilder.Scatter(go, config.Props.Scatter, config.Base.SurfaceSize, sector);
if(config.Props.Details != null)
{
foreach(var detail in config.Props.Details)
{
MakeDetail(go, sector, detail.path, detail.position, detail.rotation, detail.scale);
}
}
}
public static void Make(GameObject body, GameObject prefab, Vector3 position, Sector sector)
public static GameObject MakeDetail(GameObject go, Sector sector, string propToClone, MVector3 position, MVector3 rotation, float scale)
{
if (prefab == null) return;
var prefab = GameObject.Find(propToClone);
return MakeDetail(go, sector, prefab, position, rotation, scale);
}
public static GameObject MakeDetail(GameObject go, Sector sector, GameObject prefab, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal = false)
{
if (prefab == null) return null;
GameObject prop = GameObject.Instantiate(prefab, sector.transform);
prop.transform.localPosition = position;
prop.transform.rotation = Quaternion.FromToRotation(prop.transform.TransformDirection(Vector3.up), position.normalized);
prop.transform.localPosition = position == null ? prefab.transform.localPosition : (Vector3)position;
Quaternion rot = rotation == null ? prefab.transform.localRotation : Quaternion.Euler((Vector3)rotation);
if (alignWithNormal) rot = Quaternion.FromToRotation(prop.transform.TransformDirection(Vector3.up), ((Vector3)position).normalized);
prop.transform.rotation = rot;
prop.transform.localScale = scale != 0 ? Vector3.one * scale : prefab.transform.localScale;
prop.SetActive(false);
List<string> assetBundles = new List<string>();
@ -43,7 +59,6 @@ namespace NewHorizons.Builder.Props
sector.OnOccupantEnterSector += ((SectorDetector sd) => StreamingManager.LoadStreamingAssets(assetBundle));
}
/*
foreach(var component in prop.GetComponentsInChildren<Component>())
{
try
@ -61,40 +76,30 @@ namespace NewHorizons.Builder.Props
Logger.Log($"Found a _sector field in {component}");
sectorField.SetValue(component, sector);
}
else
{
if(component is Campfire)
{
Logger.Log("CAMPFIRE");
Campfire campfire = component as Campfire;
if (campfire._sector != null)
campfire._sector.OnSectorOccupantsUpdated -= campfire.OnSectorOccupantsUpdated;
campfire._sector = sector;
campfire._sector.OnSectorOccupantsUpdated += campfire.OnSectorOccupantsUpdated;
}
}
}
catch (Exception e) { Logger.Log($"{e.Message}, {e.StackTrace}"); }
}
*/
prop.SetActive(true);
return prop;
}
public static void Scatter(GameObject body, PropModule.ScatterInfo[] scatterInfo, float radius, Sector sector)
private static void Scatter(GameObject go, PropModule.ScatterInfo[] scatterInfo, float radius, Sector sector)
{
var area = 4f * Mathf.PI * radius * radius;
var points = FibonacciSphere((int)area);
foreach (var scatterer in scatterInfo)
foreach (var propInfo in scatterInfo)
{
var prefab = GameObject.Find(scatterer.path);
for(int i = 0; i < scatterer.count; i++)
var prefab = GameObject.Find(propInfo.path);
for(int i = 0; i < propInfo.count; i++)
{
var randomInd = (int)Random.Range(0, points.Count);
var point = points[randomInd];
Make(body, prefab, point.normalized * radius, sector);
var prop = MakeDetail(go, sector, prefab, (MVector3)(point.normalized * radius), null, 0f, true);
if(propInfo.offset != null) prop.transform.localPosition += prop.transform.TransformVector(propInfo.offset);
if(propInfo.rotation != null) prop.transform.rotation *= Quaternion.Euler(propInfo.rotation);
points.RemoveAt(randomInd);
if (points.Count == 0) return;
}

View File

@ -0,0 +1,188 @@
using NewHorizons.Components;
using NewHorizons.External;
using NewHorizons.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.Builder.Props
{
public static class SignalBuilder
{
private static AnimationCurve _customCurve = null;
private static Dictionary<SignalName, string> _customSignalNames;
private static Stack<SignalName> _availableSignalNames;
public static Dictionary<SignalFrequency, string> SignalFrequencyOverrides;
public static void Reset()
{
_customSignalNames = new Dictionary<SignalName, string>();
_availableSignalNames = new Stack<SignalName> (new SignalName[]
{
(SignalName)17,
(SignalName)18,
(SignalName)19,
(SignalName)26,
(SignalName)27,
(SignalName)28,
(SignalName)29,
(SignalName)33,
(SignalName)34,
(SignalName)35,
(SignalName)36,
(SignalName)37,
(SignalName)38,
(SignalName)39,
(SignalName)50,
(SignalName)51,
(SignalName)52,
(SignalName)53,
(SignalName)54,
(SignalName)55,
(SignalName)56,
(SignalName)57,
(SignalName)58,
(SignalName)59,
SignalName.WhiteHole_WH,
SignalName.WhiteHole_SS_Receiver,
SignalName.WhiteHole_CT_Receiver,
SignalName.WhiteHole_CT_Experiment,
SignalName.WhiteHole_TT_Receiver,
SignalName.WhiteHole_TT_TimeLoopCore,
SignalName.WhiteHole_TH_Receiver,
SignalName.WhiteHole_BH_NorthPoleReceiver,
SignalName.WhiteHole_BH_ForgeReceiver,
SignalName.WhiteHole_GD_Receiver,
});
SignalFrequencyOverrides = new Dictionary<SignalFrequency, string>() {
{ SignalFrequency.Statue, "NOMAI STATUE" },
{ SignalFrequency.Default, "???" },
{ SignalFrequency.WarpCore, "ANTI-GRAVITON FLUX" }
};
}
public static SignalName AddSignalName(string str)
{
if (_availableSignalNames.Count == 0)
{
Logger.LogWarning($"There are no more available SignalName spots. Cannot use name [{str}].");
return SignalName.Default;
}
Logger.Log($"Registering new signal name [{str}]");
var newName = _availableSignalNames.Pop();
_customSignalNames.Add(newName, str.ToUpper());
return newName;
}
public static string GetCustomSignalName(SignalName signalName)
{
string name = null;
_customSignalNames.TryGetValue(signalName, out name);
return name;
}
public static void Make(GameObject body, Sector sector, SignalModule module)
{
foreach(var info in module.Signals)
{
Make(body, sector, info);
}
}
public static void Make(GameObject body, Sector sector, SignalModule.SignalInfo info)
{
var signalGO = new GameObject($"Signal_{info.Name}");
signalGO.SetActive(false);
signalGO.transform.parent = body.transform;
signalGO.transform.localPosition = info.Position != null ? (Vector3)info.Position : Vector3.zero;
signalGO.layer = LayerMask.NameToLayer("AdvancedEffectVolume");
var source = signalGO.AddComponent<AudioSource>();
var owAudioSource = signalGO.AddComponent<OWAudioSource>();
AudioSignal audioSignal = null;
if (info.InsideCloak) audioSignal = signalGO.AddComponent<CloakedAudioSignal>();
else audioSignal = signalGO.AddComponent<AudioSignal>();
var frequency = StringToFrequency(info.Frequency);
var name = StringToSignalName(info.Name);
AudioClip clip = SearchUtilities.FindResourceOfTypeAndName<AudioClip>(info.AudioClip);
if (clip == null) return;
audioSignal.SetSector(sector);
audioSignal._frequency = frequency;
if (name == SignalName.Default)
{
name = AddSignalName(info.Name);
if(name == SignalName.Default) audioSignal._preventIdentification = true;
}
audioSignal._name = name;
audioSignal._sourceRadius = info.SourceRadius;
audioSignal._onlyAudibleToScope = info.OnlyAudibleToScope;
audioSignal._identificationDistance = info.IdentificationRadius;
source.clip = clip;
source.loop = true;
source.minDistance = 0;
source.maxDistance = 30;
source.velocityUpdateMode = AudioVelocityUpdateMode.Fixed;
source.rolloffMode = AudioRolloffMode.Custom;
if(_customCurve == null)
_customCurve = GameObject.Find("Moon_Body/Sector_THM/Characters_THM/Villager_HEA_Esker/Signal_Whistling").GetComponent<AudioSource>().GetCustomCurve(AudioSourceCurveType.CustomRolloff);
source.SetCustomCurve(AudioSourceCurveType.CustomRolloff, _customCurve);
source.playOnAwake = false;
source.spatialBlend = 1f;
source.volume = 0.5f;
source.dopplerLevel = 0;
owAudioSource.SetTrack(OWAudioMixer.TrackName.Signal);
// Frequency detection trigger volume
var signalDetectionGO = new GameObject($"SignalDetectionTrigger_{info.Name}");
signalDetectionGO.SetActive(false);
signalDetectionGO.transform.parent = body.transform;
signalDetectionGO.transform.localPosition = info.Position != null ? (Vector3)info.Position : Vector3.zero;
signalDetectionGO.layer = LayerMask.NameToLayer("AdvancedEffectVolume");
var sphereShape = signalDetectionGO.AddComponent<SphereShape>();
var owTriggerVolume = signalDetectionGO.AddComponent<OWTriggerVolume>();
var audioSignalDetectionTrigger = signalDetectionGO.AddComponent<AudioSignalDetectionTrigger>();
sphereShape.radius = info.DetectionRadius == 0 ? info.SourceRadius + 30 : info.DetectionRadius;
audioSignalDetectionTrigger._signal = audioSignal;
audioSignalDetectionTrigger._trigger = owTriggerVolume;
signalGO.SetActive(true);
signalDetectionGO.SetActive(true);
}
private static SignalFrequency StringToFrequency(string str)
{
foreach(SignalFrequency freq in Enum.GetValues(typeof(SignalFrequency)))
{
if (str.Equals(freq.ToString())) return freq;
}
return SignalFrequency.Default;
}
private static SignalName StringToSignalName(string str)
{
foreach (SignalName name in Enum.GetValues(typeof(SignalName)))
{
if (str.Equals(name.ToString())) return name;
}
return SignalName.Default;
}
}
}

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Components
{
public class CloakedAudioSignal : AudioSignal
{
public new void UpdateSignalStrength(Signalscope scope, float distToClosestScopeObstruction)
{
this._canBePickedUpByScope = false;
if (!PlayerState.InCloakingField())
{
this._signalStrength = 0f;
this._degreesFromScope = 180f;
return;
}
if (this._sunController != null && this._sunController.IsPointInsideSupernova(base.transform.position))
{
this._signalStrength = 0f;
this._degreesFromScope = 180f;
return;
}
if (Locator.GetQuantumMoon() != null && Locator.GetQuantumMoon().IsPlayerInside() && this._name != SignalName.Quantum_QM)
{
this._signalStrength = 0f;
this._degreesFromScope = 180f;
return;
}
if (!this._active || !base.gameObject.activeInHierarchy || this._outerFogWarpVolume != PlayerState.GetOuterFogWarpVolume() || (scope.GetFrequencyFilter() & this._frequency) != this._frequency)
{
this._signalStrength = 0f;
this._degreesFromScope = 180f;
return;
}
this._scopeToSignal = base.transform.position - scope.transform.position;
this._distToScope = this._scopeToSignal.magnitude;
if (this._outerFogWarpVolume == null && distToClosestScopeObstruction < 1000f && this._distToScope > 1000f)
{
this._signalStrength = 0f;
this._degreesFromScope = 180f;
return;
}
this._canBePickedUpByScope = true;
if (this._distToScope < this._sourceRadius)
{
this._signalStrength = 1f;
}
else
{
this._degreesFromScope = Vector3.Angle(scope.GetScopeDirection(), this._scopeToSignal);
float t = Mathf.InverseLerp(2000f, 1000f, this._distToScope);
float a = Mathf.Lerp(45f, 90f, t);
float a2 = 57.29578f * Mathf.Atan2(this._sourceRadius, this._distToScope);
float b = Mathf.Lerp(Mathf.Max(a2, 5f), Mathf.Max(a2, 1f), scope.GetZoomFraction());
this._signalStrength = Mathf.Clamp01(Mathf.InverseLerp(a, b, this._degreesFromScope));
}
if (this._distToScope < this._identificationDistance + this._sourceRadius && this._signalStrength > 0.9f)
{
if (!PlayerData.KnowsFrequency(this._frequency) && !this._preventIdentification)
{
this.IdentifyFrequency();
}
if (!PlayerData.KnowsSignal(this._name) && !this._preventIdentification)
{
this.IdentifySignal();
}
if (this._revealFactID.Length > 0)
{
Locator.GetShipLogManager().RevealFact(this._revealFactID, true, true);
}
}
}
}
}

View File

@ -21,5 +21,6 @@ namespace NewHorizons.External
public bool HasSnow { get; set; }
public bool HasOxygen { get; set; }
public bool HasAtmosphere { get; set; }
public MColor32 AtmosphereTint { get; set; }
}
}

View File

@ -18,5 +18,6 @@ namespace NewHorizons.External
FocalPointModule FocalPoint { get; }
PropModule Props { get; }
SpawnModule Spawn { get; }
SignalModule Signal { get; }
}
}

123
NewHorizons/External/NewHorizonsData.cs vendored Normal file
View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.External
{
public static class NewHorizonsData
{
private static NewHorizonsSaveFile _saveFile;
private static NewHorizonsProfile _activeProfile;
private static string _activeProfileName;
private static string _fileName = "save.json";
public static void Load()
{
_activeProfileName = StandaloneProfileManager.SharedInstance.currentProfile.profileName;
try
{
_saveFile = Main.Instance.ModHelper.Storage.Load<NewHorizonsSaveFile>(_fileName);
if (!_saveFile.Profiles.ContainsKey(_activeProfileName)) _saveFile.Profiles.Add(_activeProfileName, new NewHorizonsProfile());
_activeProfile = _saveFile.Profiles[_activeProfileName];
Logger.Log($"Loaded save data for {_activeProfileName}");
}
catch(Exception)
{
try
{
Logger.Log($"Couldn't load save data from {_fileName}, creating a new file");
_saveFile = new NewHorizonsSaveFile();
_saveFile.Profiles.Add(_activeProfileName, new NewHorizonsProfile());
_activeProfile = _saveFile.Profiles[_activeProfileName];
Main.Instance.ModHelper.Storage.Save(_saveFile, _fileName);
Logger.Log($"Loaded save data for {_activeProfileName}");
}
catch(Exception e)
{
Logger.LogError($"Couldn't create save data {e.Message}, {e.StackTrace}");
}
}
}
public static void Save()
{
if (_saveFile == null) return;
Main.Instance.ModHelper.Storage.Save(_saveFile, _fileName);
}
public static void Reset()
{
if (_saveFile == null || _activeProfile == null)
{
Load();
}
Logger.Log($"Reseting save data for {_activeProfileName}");
_activeProfile = new NewHorizonsProfile();
_saveFile.Profiles[_activeProfileName] = _activeProfile;
Save();
}
public static bool KnowsFrequency(string frequency)
{
if (_activeProfile == null) return true;
return _activeProfile.KnownFrequencies.Contains(frequency);
}
public static void LearnFrequency(string frequency)
{
if (_activeProfile == null) return;
if (!KnowsFrequency(frequency))
{
_activeProfile.KnownFrequencies.Add(frequency);
Save();
}
}
public static bool KnowsSignal(string signal)
{
if (_activeProfile == null) return true;
return _activeProfile.KnownSignals.Contains(signal);
}
public static void LearnSignal(string signal)
{
if (_activeProfile == null) return;
if (!KnowsSignal(signal))
{
_activeProfile.KnownSignals.Add(signal);
Save();
}
}
public static bool KnowsMultipleFrequencies()
{
return (_activeProfile != null && _activeProfile.KnownFrequencies.Count > 0);
}
private class NewHorizonsSaveFile
{
public NewHorizonsSaveFile()
{
Profiles = new Dictionary<string, NewHorizonsProfile>();
}
public Dictionary<string, NewHorizonsProfile> Profiles { get; set; }
}
private class NewHorizonsProfile
{
public NewHorizonsProfile()
{
KnownFrequencies = new List<string>();
KnownSignals = new List<string>();
}
public List<string> KnownFrequencies { get; set; }
public List<string> KnownSignals { get; set; }
}
}
}

View File

@ -22,6 +22,7 @@ namespace NewHorizons.External
public FocalPointModule FocalPoint { get; set; }
public PropModule Props { get; set; }
public SpawnModule Spawn { get; set; }
public SignalModule Signal { get; set; }
public PlanetConfig(Dictionary<string, object> dict)
{

View File

@ -10,12 +10,23 @@ namespace NewHorizons.External
public class PropModule : Module
{
public ScatterInfo[] Scatter;
public DetailInfo[] Details;
public MVector3[] Rafts;
public class ScatterInfo
{
public string path;
public int count;
public MVector3 offset;
public MVector3 rotation;
}
public class DetailInfo
{
public string path;
public MVector3 position;
public MVector3 rotation;
public float scale;
}
}
}

27
NewHorizons/External/SignalModule.cs vendored Normal file
View File

@ -0,0 +1,27 @@
using NewHorizons.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External
{
public class SignalModule : Module
{
public SignalInfo[] Signals;
public class SignalInfo
{
public MVector3 Position;
public string Frequency;
public string Name;
public string AudioClip;
public float SourceRadius = 1f;
public float DetectionRadius = 0f;
public float IdentificationRadius = 10f;
public bool OnlyAudibleToScope = true;
public bool InsideCloak = false;
}
}
}

View File

@ -9,10 +9,10 @@ namespace NewHorizons.External
{
public class StarModule : Module
{
public float Size { get; set; }
public MColor32 Tint { get; set; }
public float Size { get; set; } = 2000f;
public MColor32 Tint { get; set; }
public MColor32 SolarFlareTint { get; set; }
public MColor32 LightTint { get; set; }
public float SolarLuminosity { get; set; }
public float SolarLuminosity { get; set; } = 1f;
}
}

View File

@ -30,7 +30,9 @@ namespace NewHorizons
public static List<NewHorizonsBody> BodyList = new List<NewHorizonsBody>();
public static List<NewHorizonsBody> NextPassBodies = new List<NewHorizonsBody>();
public static float FurthestOrbit = 50000f;
public static float FurthestOrbit { get; set; } = 50000f;
public StarLightController StarLightController { get; private set; }
public override object GetApi()
{
@ -51,7 +53,7 @@ namespace NewHorizons
{
LoadConfigs(this);
}
catch(Exception)
catch (Exception)
{
Logger.LogWarning("Couldn't find planets folder");
}
@ -66,29 +68,65 @@ namespace NewHorizons
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Logger.Log($"Scene Loaded: {scene.name} {mode}");
HeavenlyBodyBuilder.Reset();
if (scene.name != "SolarSystem") { return; }
NewHorizonsData.Load();
// Need to manage this when there are multiple stars
var sun = GameObject.Find("Sun_Body");
var starController = sun.AddComponent<StarController>();
starController.Light = GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<Light>();
starController.AmbientLight = GameObject.Find("Sun_Body/AmbientLight_SUN").GetComponent<Light>();
starController.FaceActiveCamera = GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<FaceActiveCamera>();
starController.CSMTextureCacher = GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<CSMTextureCacher>();
starController.ProxyShadowLight = GameObject.Find("Sun_Body/Sector_SUN/Effects_SUN/SunLight").GetComponent<ProxyShadowLight>();
starController.Intensity = 0.9859f;
starController.SunColor = new Color(1f, 0.8845f, 0.6677f, 1f);
var starLightGO = GameObject.Instantiate(sun.GetComponentInChildren<SunLightController>().gameObject);
foreach(var comp in starLightGO.GetComponents<Component>())
{
if(!(comp is SunLightController) && !(comp is SunLightParamUpdater) && !(comp is Light) && !(comp is Transform))
{
GameObject.Destroy(comp);
}
}
GameObject.Destroy(starLightGO.GetComponent<Light>());
starLightGO.name = "StarLightController";
StarLightController = starLightGO.AddComponent<StarLightController>();
StarLightController.AddStar(starController);
starLightGO.SetActive(true);
// TODO: Make this configurable probably
Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => Locator.GetPlayerBody().gameObject.AddComponent<DebugRaycaster>());
// Some builders need to be reset each time
SignalBuilder.Reset();
// We do our own AstroObject tracking
AstroObjectLocator.RefreshList();
foreach(AstroObject ao in GameObject.FindObjectsOfType<AstroObject>())
foreach (AstroObject ao in GameObject.FindObjectsOfType<AstroObject>())
{
AstroObjectLocator.AddAstroObject(ao);
}
// Stars then planets then moons
var toLoad = BodyList.OrderBy(b =>
(b.Config.BuildPriority != -1 ? b.Config.BuildPriority :
(b.Config.FocalPoint != null ? 0 :
// Stars then planets then moons (not necessary but probably speeds things up, maybe)
var toLoad = BodyList.OrderBy(b =>
(b.Config.BuildPriority != -1 ? b.Config.BuildPriority :
(b.Config.FocalPoint != null ? 0 :
(b.Config.Star != null) ? 0 :
(b.Config.Orbit.IsMoon ? 2 : 1)
))).ToList();
var count = 0;
while(toLoad.Count != 0)
var passCount = 0;
while (toLoad.Count != 0)
{
Logger.Log($"Starting body loading pass #{++count}");
Logger.Log($"Starting body loading pass #{++passCount}");
var flagNoneLoadedThisPass = true;
foreach (var body in toLoad)
{
@ -98,7 +136,7 @@ namespace NewHorizons
{
Logger.LogWarning("No objects were loaded this pass");
// Try again but default to sun
foreach(var body in toLoad)
foreach (var body in toLoad)
{
if (LoadBody(body, true)) flagNoneLoadedThisPass = false;
}
@ -114,7 +152,7 @@ namespace NewHorizons
NextPassBodies = new List<NewHorizonsBody>();
// Infinite loop failsafe
if (count > 10)
if (passCount > 10)
{
Logger.Log("Something went wrong");
break;
@ -125,6 +163,15 @@ namespace NewHorizons
// I don't know what these do but they look really weird from a distance
Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => PlanetDestroyer.RemoveDistantProxyClones());
var map = GameObject.FindObjectOfType<MapController>();
if (map != null) map._maxPanDistance = FurthestOrbit * 1.5f;
/*
foreach(var cam in GameObject.FindObjectsOfType<OWCamera>())
{
cam.farClipPlane = FurthestOrbit * 3f;
}
*/
}
private bool LoadBody(NewHorizonsBody body, bool defaultPrimaryToSun = false)
@ -136,15 +183,15 @@ namespace NewHorizons
if (stringID.Equals("EMBER_TWIN")) stringID = "CAVE_TWIN";
if (stringID.Equals("INTERLOPER")) stringID = "COMET";
AstroObject existingPlanet = null;
GameObject existingPlanet = null;
try
{
existingPlanet = AstroObjectLocator.GetAstroObject(stringID);
if (existingPlanet == null) existingPlanet = AstroObjectLocator.GetAstroObject(body.Config.Name.Replace(" ", ""));
existingPlanet = AstroObjectLocator.GetAstroObject(stringID).gameObject;
if (existingPlanet == null) existingPlanet = AstroObjectLocator.GetAstroObject(body.Config.Name.Replace(" ", "")).gameObject;
}
catch (Exception e)
{
Logger.LogWarning($"Error when looking for {body.Config.Name}: {e.Message}, {e.StackTrace}");
existingPlanet = GameObject.Find(body.Config.Name.Replace(" ", "") + "_Body");
}
if (existingPlanet != null)
@ -153,7 +200,9 @@ namespace NewHorizons
{
if (body.Config.Destroy)
{
Instance.ModHelper.Events.Unity.FireInNUpdates(() => PlanetDestroyer.RemoveBody(existingPlanet), 2);
var ao = existingPlanet.GetComponent<AstroObject>();
if (ao != null) Instance.ModHelper.Events.Unity.FireInNUpdates(() => PlanetDestroyer.RemoveBody(ao), 2);
else Instance.ModHelper.Events.Unity.FireInNUpdates(() => existingPlanet.SetActive(false), 2);
}
else UpdateBody(body, existingPlanet);
}
@ -180,7 +229,6 @@ namespace NewHorizons
return true;
}
public void LoadConfigs(IModBehaviour mod)
{
var folder = mod.ModHelper.Manifest.ModFolderPath;
@ -199,23 +247,21 @@ namespace NewHorizons
}
}
public static GameObject UpdateBody(NewHorizonsBody body, AstroObject ao)
public GameObject UpdateBody(NewHorizonsBody body, GameObject go)
{
Logger.Log($"Updating existing AstroObject {ao}");
var go = ao.gameObject;
Logger.Log($"Updating existing Object {go.name}");
var sector = go.GetComponentInChildren<Sector>();
var rb = go.GetAttachedOWRigidbody();
// Do stuff that's shared between generating new planets and updating old ones
return SharedGenerateBody(body, go, sector, rb, ao);
return SharedGenerateBody(body, go, sector, rb);
}
public static GameObject GenerateBody(NewHorizonsBody body, bool defaultPrimaryToSun = false)
public GameObject GenerateBody(NewHorizonsBody body, bool defaultPrimaryToSun = false)
{
AstroObject primaryBody;
if(body.Config.Orbit.PrimaryBody != null)
if (body.Config.Orbit.PrimaryBody != null)
{
primaryBody = AstroObjectLocator.GetAstroObject(body.Config.Orbit.PrimaryBody);
if (primaryBody == null)
@ -242,7 +288,7 @@ namespace NewHorizons
var go = new GameObject(body.Config.Name.Replace(" ", "").Replace("'", "") + "_Body");
go.SetActive(false);
if(body.Config.Base.GroundSize != 0) GeometryBuilder.Make(go, body.Config.Base.GroundSize);
if (body.Config.Base.GroundSize != 0) GeometryBuilder.Make(go, body.Config.Base.GroundSize);
var atmoSize = body.Config.Atmosphere != null ? body.Config.Atmosphere.Size : 0f;
float sphereOfInfluence = Mathf.Max(atmoSize, body.Config.Base.SurfaceSize * 2f);
@ -253,9 +299,9 @@ namespace NewHorizons
GravityVolume gv = null;
if (body.Config.Base.SurfaceGravity != 0)
gv = GravityBuilder.Make(go, ao, body.Config.Base.SurfaceGravity, sphereOfInfluence * (body.Config.Star != null ? 10f : 1f), body.Config.Base.SurfaceSize, body.Config.Base.GravityFallOff);
if(body.Config.Base.HasReferenceFrame)
gv = GravityBuilder.Make(go, ao, body.Config);
if (body.Config.Base.HasReferenceFrame)
RFVolumeBuilder.Make(go, owRigidBody, sphereOfInfluence);
if (body.Config.Base.HasMapMarker)
@ -278,20 +324,18 @@ namespace NewHorizons
if (body.Config.Base.BlackHoleSize != 0)
BlackHoleBuilder.Make(go, body.Config.Base, sector);
if (body.Config.Star != null)
StarBuilder.Make(go, sector, body.Config.Star);
if (body.Config.Star != null) StarLightController.AddStar(StarBuilder.Make(go, sector, body.Config.Star));
if (body.Config.FocalPoint != null)
FocalPointBuilder.Make(go, body.Config.FocalPoint);
// Do stuff that's shared between generating new planets and updating old ones
go = SharedGenerateBody(body, go, sector, owRigidBody, ao);
go = SharedGenerateBody(body, go, sector, owRigidBody);
body.Object = go;
// Now that we're done move the planet into place
go.transform.parent = Locator.GetRootTransform();
go.transform.position = OrbitalHelper.GetCartesian(new OrbitalHelper.Gravity(1, 100), body.Config.Orbit).Item1 + (primaryBody == null ? Vector3.zero : primaryBody.transform.position);
if (go.transform.position.magnitude > FurthestOrbit)
@ -319,7 +363,7 @@ namespace NewHorizons
return go;
}
private static GameObject SharedGenerateBody(NewHorizonsBody body, GameObject go, Sector sector, OWRigidbody rb, AstroObject ao)
private GameObject SharedGenerateBody(NewHorizonsBody body, GameObject go, Sector sector, OWRigidbody rb)
{
if (body.Config.Ring != null)
RingBuilder.Make(go, body.Config.Ring, body.Assets);
@ -329,14 +373,11 @@ namespace NewHorizons
if (body.Config.Base.HasCometTail)
CometTailBuilder.Make(go, body.Config.Base, go.GetComponent<AstroObject>().GetPrimaryBody());
if(body.Config.Base != null)
{
if (body.Config.Base.LavaSize != 0)
LavaBuilder.Make(go, sector, rb, body.Config.Base.LavaSize);
if (body.Config.Base.WaterSize != 0)
WaterBuilder.Make(go, sector, rb, body.Config.Base.WaterSize);
}
if (body.Config.Base.LavaSize != 0)
LavaBuilder.Make(go, sector, rb, body.Config.Base.LavaSize);
if (body.Config.Base.WaterSize != 0)
WaterBuilder.Make(go, sector, rb, body.Config.Base.WaterSize);
if (body.Config.Atmosphere != null)
{
@ -354,22 +395,14 @@ namespace NewHorizons
if (body.Config.Atmosphere.FogSize != 0)
FogBuilder.Make(go, sector, body.Config.Atmosphere);
AtmosphereBuilder.Make(go, body.Config.Atmosphere);
AtmosphereBuilder.Make(go, body.Config.Atmosphere, body.Config.Base.SurfaceSize);
}
if (body.Config.Props != null)
{
if (body.Config.Props.Scatter != null) PropBuilder.Scatter(go, body.Config.Props.Scatter, body.Config.Base.SurfaceSize, sector);
/*
if (body.Config.Props.Rafts != null)
{
foreach(var v in body.Config.Props.Rafts)
{
Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => RaftBuilder.Make(go, v, sector, rb, ao));
}
}
*/
}
PropBuilder.Make(go, sector, body.Config);
if (body.Config.Signal != null)
SignalBuilder.Make(go, sector, body.Config.Signal);
return go;
}

View File

@ -7,12 +7,12 @@ using UnityEngine;
namespace NewHorizons.OrbitalPhysics
{
public class TrackingOrbitLine : OrbitLine
{
public class TrackingOrbitLine : OrbitLine
{
private Vector3[] _vertices;
private float _timer;
private bool _firstTimeEnabled = true;
public float TrailTime = 120f;
@ -44,7 +44,7 @@ namespace NewHorizons.OrbitalPhysics
public override void OnEnterMapView()
{
if (_firstTimeEnabled)
if (_firstTimeEnabled)
{
ResetLineVertices();
_firstTimeEnabled = false;
@ -65,8 +65,8 @@ namespace NewHorizons.OrbitalPhysics
_timer += Time.deltaTime;
var updateTime = (TrailTime / (float)_numVerts);
if(_timer > updateTime)
{
if (_timer > updateTime)
{
for (int i = _numVerts - 1; i > 0; i--)
{
var v = _vertices[i - 1];
@ -95,12 +95,12 @@ namespace NewHorizons.OrbitalPhysics
var dist1 = Vector3.Distance(point, _vertices[0]);
var dist2 = Vector3.Distance(point, _vertices[(int)(_numVerts / 2)]);
var dist3 = Vector3.Distance(point, _vertices[_numVerts - 1]);
return Mathf.Min(new float[] { dist1, dist2, dist3 });
}
public void ResetLineVertices()
{
{
var primary = _astroObject.GetPrimaryBody();
Vector3 origin = primary == null ? Locator.GetRootTransform().position : primary.transform.position;
@ -114,4 +114,4 @@ namespace NewHorizons.OrbitalPhysics
_lineRenderer.SetPositions(_vertices);
}
}
}
}

View File

@ -29,7 +29,8 @@ namespace NewHorizons.Utility
var direction = Locator.GetActiveCamera().transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(origin, direction, out RaycastHit hitInfo, 100f, layerMask))
{
Logger.Log($"Raycast hit [{hitInfo.transform.InverseTransformPoint(hitInfo.point)}] on [{hitInfo.transform.gameObject.name}]");
var pos = hitInfo.transform.InverseTransformPoint(hitInfo.point);
Logger.Log($"Raycast hit {{\"x\": {pos.x}, \"y\": {pos.y}, \"z\": {pos.z}}} on [{hitInfo.transform.gameObject.name}]");
}
_rb.EnableCollisionDetection();
}

View File

@ -90,7 +90,14 @@ namespace NewHorizons.Utility
continue;
}
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
try
{
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
} catch(Exception)
{
Logger.LogWarning($"Couldn't copy property {targetProperty.Name} from {source} to {destination}");
}
}
}
}

View File

@ -1,4 +1,7 @@
using OWML.Common;
using NewHorizons.Builder.Props;
using NewHorizons.Components;
using NewHorizons.External;
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,9 +15,35 @@ namespace NewHorizons.Utility
{
public static void Apply()
{
// Prefixes
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<ReferenceFrame>("GetHUDDisplayName", typeof(Patches), nameof(Patches.GetHUDDisplayName));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<PlayerState>("CheckShipOutsideSolarSystem", typeof(Patches), nameof(Patches.CheckShipOutersideSolarSystem));
Main.Instance.ModHelper.HarmonyHelper.AddPostfix<EllipticOrbitLine>("Start", typeof(Patches), nameof(Patches.OnEllipticOrbitLineStart));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<SunLightParamUpdater>("LateUpdate", typeof(Patches), nameof(Patches.OnSunLightParamUpdaterLateUpdate));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<SunSurfaceAudioController>("Update", typeof(Patches), nameof(Patches.OnSunSurfaceAudioControllerUpdate));
// Lot of audio signal stuff
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<AudioSignal>("SignalNameToString", typeof(Patches), nameof(Patches.OnAudioSignalSignalNameToString));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<AudioSignal>("IndexToFrequency", typeof(Patches), nameof(Patches.OnAudioSignalIndexToFrequency));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<AudioSignal>("FrequencyToIndex", typeof(Patches), nameof(Patches.OnAudioSignalFrequencyToIndex));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<AudioSignal>("FrequencyToString", typeof(Patches), nameof(Patches.OnAudioSignalFrequencyToString));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<Signalscope>("Awake", typeof(Patches), nameof(Patches.OnSignalscopeAwake));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<Signalscope>("SwitchFrequencyFilter", typeof(Patches), nameof(Patches.OnSignalscopeSwitchFrequencyFilter));
Main.Instance.ModHelper.HarmonyHelper.AddPrefix<AudioSignal>("UpdateSignalStrength", typeof(Patches), nameof(Patches.OnAudioSignalUpdateSignalStrength));
var playerDataKnowsSignal = typeof(PlayerData).GetMethod("KnowsSignal");
Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataKnowsSignal, typeof(Patches), nameof(Patches.OnPlayerDataKnowsSignal));
var playerDataLearnSignal = typeof(PlayerData).GetMethod("LearnSignal");
Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataLearnSignal, typeof(Patches), nameof(Patches.OnPlayerDataLearnSignal));
var playerDataKnowsFrequency = typeof(PlayerData).GetMethod("KnowsFrequency");
Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataKnowsFrequency, typeof(Patches), nameof(Patches.OnPlayerDataKnowsFrequency));
var playerDataLearnFrequency = typeof(PlayerData).GetMethod("LearnFrequency");
Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataLearnFrequency, typeof(Patches), nameof(Patches.OnPlayerDataLearnFrequency));
var playerDataKnowsMultipleFrequencies = typeof(PlayerData).GetMethod("KnowsMultipleFrequencies");
Main.Instance.ModHelper.HarmonyHelper.AddPrefix(playerDataKnowsMultipleFrequencies, typeof(Patches), nameof(Patches.OnPlayerDataKnowsMultipleFrequencies));
var playerDataResetGame = typeof(PlayerData).GetMethod("ResetGame");
Main.Instance.ModHelper.HarmonyHelper.AddPostfix(playerDataResetGame, typeof(Patches), nameof(Patches.OnPlayerDataResetGame));
// Postfixes
Main.Instance.ModHelper.HarmonyHelper.AddPostfix<MapController>("Awake", typeof(Patches), nameof(Patches.OnMapControllerAwake));
Main.Instance.ModHelper.HarmonyHelper.AddPostfix<OWCamera>("Awake", typeof(Patches), nameof(Patches.OnOWCameraAwake));
}
@ -30,31 +59,252 @@ namespace NewHorizons.Utility
return true;
}
public static bool CheckShipOutersideSolarSystem(PlayerState __instance, bool __result)
public static bool CheckShipOutersideSolarSystem(PlayerState __instance, ref bool __result)
{
__result = false;
return false;
}
public static void OnEllipticOrbitLineStart(EllipticOrbitLine __instance, ref Vector3 ____upAxisDir, AstroObject ____astroObject)
{
if (____astroObject.GetAstroObjectName() == AstroObject.Name.Comet) return;
// For some reason other planets do this idk
____upAxisDir *= -1f;
}
public static void OnMapControllerAwake(MapController __instance, ref float ____maxPanDistance, ref float ____maxZoomDistance, ref float ____minPitchAngle, ref float ____zoomSpeed)
{
____maxPanDistance *= 4f;
____maxPanDistance = Main.FurthestOrbit * 1.5f;
____maxZoomDistance *= 6f;
____minPitchAngle = -90f;
____zoomSpeed *= 4f;
__instance._mapCamera.farClipPlane = Main.FurthestOrbit * 3f;
}
public static void OnOWCameraAwake(OWCamera __instance)
{
__instance.farClipPlane *= 4f;
}
public static bool OnSunLightParamUpdaterLateUpdate(SunLightParamUpdater __instance)
{
if (__instance.sunLight)
{
Vector3 position = __instance.transform.position;
float w = 2000f;
if (__instance._sunController != null)
{
w = (__instance._sunController.HasSupernovaStarted() ? __instance._sunController.GetSupernovaRadius() : __instance._sunController.GetSurfaceRadius());
}
float range = __instance.sunLight.range;
Color color = (__instance._sunLightController != null) ? __instance._sunLightController.sunColor : __instance.sunLight.color;
float w2 = (__instance._sunLightController != null) ? __instance._sunLightController.sunIntensity : __instance.sunLight.intensity;
Shader.SetGlobalVector(__instance._propID_SunPosition, new Vector4(position.x, position.y, position.z, w));
Shader.SetGlobalVector(__instance._propID_OWSunPositionRange, new Vector4(position.x, position.y, position.z, 1f / (range * range)));
Shader.SetGlobalVector(__instance._propID_OWSunColorIntensity, new Vector4(color.r, color.g, color.b, w2));
}
return false;
}
public static bool OnSunSurfaceAudioControllerUpdate(SunSurfaceAudioController __instance)
{
if (__instance._sunController != null) return true;
var surfaceRadius = __instance.transform.parent.parent.localScale.magnitude;
float value = Mathf.Max(0f, Vector3.Distance(Locator.GetPlayerCamera().transform.position, __instance.transform.position) - surfaceRadius);
float num = Mathf.InverseLerp(1600f, 100f, value);
__instance._audioSource.SetLocalVolume(num * num * __instance._fade);
return false;
}
#region AudioSignal
public static bool OnAudioSignalSignalNameToString(SignalName __0, ref string __result)
{
var customSignalName = SignalBuilder.GetCustomSignalName(__0);
if (customSignalName == null) return true;
else
{
__result = customSignalName;
return false;
}
}
public static bool OnAudioSignalIndexToFrequency(int __0, ref SignalFrequency __result) {
switch (__0)
{
case 1:
__result = SignalFrequency.Traveler;
break;
case 2:
__result = SignalFrequency.Quantum;
break;
case 3:
__result = SignalFrequency.EscapePod;
break;
case 4:
__result = SignalFrequency.WarpCore;
break;
case 5:
__result = SignalFrequency.HideAndSeek;
break;
case 6:
__result = SignalFrequency.Radio;
break;
case 7:
__result = SignalFrequency.Statue;
break;
default:
__result = SignalFrequency.Default;
break;
}
return false;
}
public static bool OnAudioSignalFrequencyToIndex(SignalFrequency __0, ref int __result)
{
var frequency = __0;
if (frequency <= SignalFrequency.EscapePod)
{
if(frequency == SignalFrequency.Default)
{
__result = 0;
}
else if (frequency == SignalFrequency.Traveler)
{
__result = 1;
}
else if (frequency == SignalFrequency.Quantum)
{
__result = 2;
}
else if (frequency == SignalFrequency.EscapePod)
{
__result = 3;
}
}
else
{
if (frequency == SignalFrequency.WarpCore)
{
__result = 4;
}
else if (frequency == SignalFrequency.HideAndSeek)
{
__result = 5;
}
else if (frequency == SignalFrequency.Radio)
{
__result = 6;
}
else if (frequency == SignalFrequency.Statue)
{
__result = 7;
}
}
return false;
}
public static bool OnAudioSignalFrequencyToString(SignalFrequency __0, ref string __result)
{
SignalBuilder.SignalFrequencyOverrides.TryGetValue(__0, out string customName);
if (customName != null)
{
if (NewHorizonsData.KnowsFrequency(customName)) __result = customName;
else __result = UITextLibrary.GetString(UITextType.SignalFreqUnidentified);
return false;
}
return true;
}
public static bool OnAudioSignalUpdateSignalStrength(AudioSignal __instance, Signalscope __0, float __1)
{
// I hate this
if(__instance is CloakedAudioSignal)
{
((CloakedAudioSignal)__instance).UpdateSignalStrength(__0, __1);
return false;
}
return true;
}
#endregion
#region Signalscope
public static bool OnSignalscopeAwake(Signalscope __instance, ref AudioSignal[] ____strongestSignals)
{
____strongestSignals = new AudioSignal[8];
return true;
}
public static bool OnSignalscopeSwitchFrequencyFilter(Signalscope __instance, int __0)
{
var increment = __0;
var count = Enum.GetValues(typeof(SignalFrequency)).Length;
__instance._frequencyFilterIndex += increment;
__instance._frequencyFilterIndex = ((__instance._frequencyFilterIndex >= count) ? 0 : __instance._frequencyFilterIndex);
__instance._frequencyFilterIndex = ((__instance._frequencyFilterIndex < 0) ? count - 1 : __instance._frequencyFilterIndex);
SignalFrequency signalFrequency = AudioSignal.IndexToFrequency(__instance._frequencyFilterIndex);
if (!PlayerData.KnowsFrequency(signalFrequency) && (!__instance._isUnknownFreqNearby || __instance._unknownFrequency != signalFrequency))
{
__instance.SwitchFrequencyFilter(increment);
}
return false;
}
#endregion
#region PlayerData
public static bool OnPlayerDataKnowsFrequency(SignalFrequency __0, ref bool __result)
{
SignalBuilder.SignalFrequencyOverrides.TryGetValue(__0, out string freqString);
if (freqString != null)
{
__result = NewHorizonsData.KnowsFrequency(freqString);
return false;
}
return true;
}
public static bool OnPlayerDataLearnFrequency(SignalFrequency __0)
{
SignalBuilder.SignalFrequencyOverrides.TryGetValue(__0, out string freqString);
if (freqString != null)
{
NewHorizonsData.LearnFrequency(freqString);
return false;
}
return true;
}
public static bool OnPlayerDataKnowsSignal(SignalName __0, ref bool __result)
{
var customSignalName = SignalBuilder.GetCustomSignalName(__0);
if (customSignalName != null)
{
__result = NewHorizonsData.KnowsSignal(customSignalName);
return false;
}
return true;
}
public static bool OnPlayerDataLearnSignal(SignalName __0)
{
var customSignalName = SignalBuilder.GetCustomSignalName(__0);
if (customSignalName != null)
{
if (!NewHorizonsData.KnowsSignal(customSignalName)) NewHorizonsData.LearnSignal(customSignalName);
return false;
}
return true;
}
public static bool OnPlayerDataKnowsMultipleFrequencies(ref bool __result)
{
if (NewHorizonsData.KnowsMultipleFrequencies())
{
__result = true;
return false;
}
return true;
}
public static void OnPlayerDataResetGame()
{
NewHorizonsData.Reset();
}
#endregion
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Object = UnityEngine.Object;
namespace NewHorizons.Utility
{
public static class SearchUtilities
{
public static List<T> FindObjectsOfTypeAndName<T>(string name) where T : MonoBehaviour
{
T[] firstList = GameObject.FindObjectsOfType<T>();
List<T> finalList = new List<T>();
for (var i = 0; i < firstList.Length; i++)
{
if (firstList[i].name == name)
{
finalList.Add(firstList[i]);
}
}
return finalList;
}
public static List<T> FindResourcesOfTypeAndName<T>(string name) where T : Object
{
T[] firstList = Resources.FindObjectsOfTypeAll<T>();
List<T> finalList = new List<T>();
for (var i = 0; i < firstList.Length; i++)
{
if (firstList[i].name == name)
{
finalList.Add(firstList[i]);
}
}
return finalList;
}
public static T FindResourceOfTypeAndName<T>(string name) where T : Object
{
T[] firstList = Resources.FindObjectsOfTypeAll<T>();
for (var i = 0; i < firstList.Length; i++)
{
if (firstList[i].name == name)
{
return firstList[i];
}
}
return null;
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Utility
{
public class StarController : MonoBehaviour
{
public Light Light;
public Light AmbientLight;
public FaceActiveCamera FaceActiveCamera;
public CSMTextureCacher CSMTextureCacher;
public ProxyShadowLight ProxyShadowLight;
public float Intensity;
public Color SunColor;
public void Awake()
{
Disable();
}
public void Disable()
{
if (FaceActiveCamera != null) FaceActiveCamera.enabled = false;
if (CSMTextureCacher != null) CSMTextureCacher.enabled = false;
if (ProxyShadowLight != null) ProxyShadowLight.enabled = false;
}
public void Enable()
{
if (FaceActiveCamera != null) FaceActiveCamera.enabled = true;
if (CSMTextureCacher != null) CSMTextureCacher.enabled = true;
if (ProxyShadowLight != null) ProxyShadowLight.enabled = true;
}
}
}

View File

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.Utility
{
[RequireComponent(typeof(SunLightController))]
[RequireComponent(typeof(SunLightParamUpdater))]
public class StarLightController : MonoBehaviour
{
private List<StarController> _stars = new List<StarController>();
private StarController _activeStar;
private SunLightController _sunLightController;
private SunLightParamUpdater _sunLightParamUpdater;
public void Awake()
{
_sunLightController = GetComponent<SunLightController>();
_sunLightController.enabled = true;
_sunLightParamUpdater = GetComponent<SunLightParamUpdater>();
_sunLightParamUpdater._sunLightController = _sunLightController;
}
public void AddStar(StarController star)
{
Logger.Log($"Adding new star to list: {star.gameObject.name}");
_stars.Add(star);
}
public void RemoveStar(StarController star)
{
if (_stars.Contains(star))
{
if (_activeStar.Equals(star)) _activeStar = null;
_stars.Remove(star);
}
}
public void Update()
{
if (_activeStar == null || !_activeStar.gameObject.activeInHierarchy)
{
if (_stars.Contains(_activeStar)) _stars.Remove(_activeStar);
if (_stars.Count > 0) ChangeActiveStar(_stars[0]);
else gameObject.SetActive(false);
return;
}
foreach(var star in _stars)
{
if (star == null) continue;
// Player is always at 0,0,0 more or less so if they arent using the map camera then wtv
var origin = Vector3.zero;
if (PlayerState.InMapView())
{
origin = Locator.GetActiveCamera().transform.position;
}
if (star.Intensity * (star.transform.position - origin).sqrMagnitude < star.Intensity * (_activeStar.transform.position - origin).sqrMagnitude)
{
ChangeActiveStar(star);
break;
}
}
}
private void ChangeActiveStar(StarController star)
{
if (_sunLightController == null || _sunLightParamUpdater == null) return;
if(_activeStar != null) _activeStar.Disable();
Logger.Log($"Switching active star: {star.gameObject.name}");
_activeStar = star;
star.Enable();
_sunLightController._sunBaseColor = star.SunColor;
_sunLightController._sunBaseIntensity = star.Intensity;
_sunLightController._sunLight = star.Light;
_sunLightController._ambientLight = star.AmbientLight;
_sunLightParamUpdater.sunLight = star.Light;
_sunLightParamUpdater._sunController = star.transform.GetComponent<SunController>();
_sunLightParamUpdater._propID_SunPosition = Shader.PropertyToID("_SunPosition");
_sunLightParamUpdater._propID_OWSunPositionRange = Shader.PropertyToID("_OWSunPositionRange");
_sunLightParamUpdater._propID_OWSunColorIntensity = Shader.PropertyToID("_OWSunColorIntensity");
// For the param thing to work it wants this to be on the star idk
this.transform.parent = star.transform;
this.transform.localPosition = Vector3.zero;
}
}
}

View File

@ -3,7 +3,6 @@
"author": "xen",
"name": "New Horizons",
"uniqueName": "xen.NewHorizons",
"version": "0.2.0",
"owmlVersion": "2.1.0",
"dependencies": [ "PacificEngine.OW_CommonResources" ]
"version": "0.3.1",
"owmlVersion": "2.1.0"
}

View File

@ -24,19 +24,29 @@ Planets are created using a JSON file format structure, and placed in the `plane
<!-- /TOC -->
## Roadmap
- Heightmaps/texturemaps (Done)
- Remove existing planets (Done)
- Stars (Done)
- Binary orbits (Done)
- Comets (Done)
- Signalscope signals (Done)
- Asteroid belts (Done)
- Procedurally terrain generation (started)
- Asteroid belts (started, needs more customization)
- "Quantum" orbits
- Better terrain and water LOD
- Support satellites (using custom models in the assets folder or in-game ones)
- Surface scatter: rocks, trees, etc, using in-game models (done) or custom ones
- Load planet meshes from asset bundle
- "Quantum" planet parameters
- Better terrain and water LOD
- Edit existing planet orbits
- Black hole / white hole pairs
- Separate solar system scenes accessible via wormhole
- Implement all planet features:
- Tornados
- Sand funnels (water? lava? star?)
- Variable surface height (sand/water/lava/star)
- Let any star go supernova
- Geysers
- Meteors
## How to create your own planets using configs