diff --git a/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs b/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs index 1dd0dade..3b3cef88 100644 --- a/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs +++ b/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs @@ -19,14 +19,18 @@ namespace NewHorizons.Builder.Orbital var lineRenderer = orbitGO.AddComponent(); - - OrbitLine orbitLinePrefab; - orbitLinePrefab = GameObject.Find("GiantsDeep_Body/OrbitLine_GD").GetComponent(); - //orbitLinePrefab = GameObject.Find("HearthianMapSatellite_Body/OrbitLine").GetComponent(); - lineRenderer.material = orbitLinePrefab.GetComponent().material; + lineRenderer.material = config.Orbit.DottedOrbitLine ? GameObject.Find("HearthianMapSatellite_Body/OrbitLine").GetComponent().material : GameObject.Find("OrbitLine_CO").GetComponent().material; + lineRenderer.textureMode = config.Orbit.DottedOrbitLine ? LineTextureMode.RepeatPerSegment : LineTextureMode.Stretch; + + var width = config.Orbit.DottedOrbitLine ? 100 : 50; + lineRenderer.startWidth = width; + lineRenderer.endWidth = width; lineRenderer.useWorldSpace = false; lineRenderer.loop = false; + var numVerts = config.Orbit.DottedOrbitLine ? 128 : 256; + lineRenderer.positionCount = numVerts; + var ecc = config.Orbit.Eccentricity; var parentGravity = astroObject.GetPrimaryBody()?.GetGravityVolume(); @@ -65,9 +69,10 @@ namespace NewHorizons.Builder.Orbital orbitLine._astroObject = astroObject; orbitLine._fade = fade; + orbitLine._lineWidth = 0.2f; - orbitLine._numVerts = (int)Mathf.Clamp(config.Orbit.SemiMajorAxis / 1000f, 128, 4096); + orbitLine._numVerts = (int)Mathf.Clamp(config.Orbit.SemiMajorAxis / 1000f, numVerts, 4096); Main.Instance.ModHelper.Events.Unity.FireOnNextUpdate(orbitLine.InitializeLineRenderer); } diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs index a25ac70f..e07c8ef9 100644 --- a/NewHorizons/Builder/Props/DetailBuilder.cs +++ b/NewHorizons/Builder/Props/DetailBuilder.cs @@ -85,9 +85,23 @@ namespace NewHorizons.Builder.Props if (component is GhostIK) (component as GhostIK).enabled = false; if (component is GhostEffects) (component as GhostEffects).enabled = false; - // If it's not a moving anglerfish make sure the anim controller is off + // If it's not a moving anglerfish make sure the anim controller is regular if(component is AnglerfishAnimController && component.GetComponentInParent() == null) - Main.Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => (component as AnglerfishAnimController).enabled = false); + Main.Instance.ModHelper.Events.Unity.RunWhen(() => Main.IsSystemReady, () => + { + Logger.Log("Enabling anglerfish animation"); + var angler = (component as AnglerfishAnimController); + // Remove any reference to its angler + if(angler._anglerfishController) + { + angler._anglerfishController.OnChangeAnglerState -= angler.OnChangeAnglerState; + angler._anglerfishController.OnAnglerTurn -= angler.OnAnglerTurn; + angler._anglerfishController.OnAnglerSuspended -= angler.OnAnglerSuspended; + angler._anglerfishController.OnAnglerUnsuspended -= angler.OnAnglerUnsuspended; + } + angler.enabled = true; + angler.OnChangeAnglerState(AnglerfishController.AnglerState.Lurking); + }); if (component is Animator) Main.Instance.ModHelper.Events.Unity.RunWhen(() => Main.IsSystemReady, () => (component as Animator).enabled = true); if (component is Collider) Main.Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => (component as Collider).enabled = true); @@ -134,6 +148,8 @@ namespace NewHorizons.Builder.Props if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.forward); if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.up); + front = rot * front; + prop.transform.LookAt(prop.transform.position + front, up); } diff --git a/NewHorizons/Builder/Props/DialogueBuilder.cs b/NewHorizons/Builder/Props/DialogueBuilder.cs index 4c9e6bd8..ce80363f 100644 --- a/NewHorizons/Builder/Props/DialogueBuilder.cs +++ b/NewHorizons/Builder/Props/DialogueBuilder.cs @@ -16,10 +16,16 @@ namespace NewHorizons.Builder.Props { public static void Make(GameObject go, Sector sector, PropModule.DialogueInfo info, IModBehaviour mod) { + // In stock I think they disable dialogue stuff with conditions + // Here we just don't make it at all if (info.blockAfterPersistentCondition != null && PlayerData._currentGameSave.GetPersistentCondition(info.blockAfterPersistentCondition)) return; var dialogue = MakeConversationZone(go, sector, info, mod.ModHelper); if (info.remoteTriggerPosition != null) MakeRemoteDialogueTrigger(go, sector, info, dialogue); + + // Make the character look at the player + // Useful for dialogue replacement + if (!string.IsNullOrEmpty(info.pathToAnimController)) MakePlayerTrackingZone(go, dialogue, info); } public static void MakeRemoteDialogueTrigger(GameObject go, Sector sector, PropModule.DialogueInfo info, CharacterDialogueTree dialogue) @@ -81,6 +87,83 @@ namespace NewHorizons.Builder.Props return dialogueTree; } + public static void MakePlayerTrackingZone(GameObject go, CharacterDialogueTree dialogue, PropModule.DialogueInfo info) + { + var character = go.transform.Find(info.pathToAnimController); + + // At most one of these should ever not be null + var nomaiController = character.GetComponent(); + var controller = character.GetComponent(); + + var lookOnlyWhenTalking = info.lookAtRadius <= 0; + + // To have them look when you start talking + if (controller != null) + { + controller._dialogueTree = dialogue; + controller.lookOnlyWhenTalking = lookOnlyWhenTalking; + } + else if(nomaiController != null) + { + if (lookOnlyWhenTalking) + { + dialogue.OnStartConversation += nomaiController.StartWatchingPlayer; + dialogue.OnEndConversation += nomaiController.StopWatchingPlayer; + } + } + else + { + // TODO: make a custom controller for basic characters to just turn them to face you + } + + if (info.lookAtRadius > 0) + { + GameObject playerTrackingZone = new GameObject("PlayerTrackingZone"); + playerTrackingZone.SetActive(false); + + playerTrackingZone.layer = LayerMask.NameToLayer("BasicEffectVolume"); + playerTrackingZone.SetActive(false); + + var sphereCollider = playerTrackingZone.AddComponent(); + sphereCollider.radius = info.lookAtRadius; + sphereCollider.isTrigger = true; + + playerTrackingZone.AddComponent(); + + var triggerVolume = playerTrackingZone.AddComponent(); + + if (controller) + { + // Since the Awake method is CharacterAnimController was already called + if (controller.playerTrackingZone) + { + controller.playerTrackingZone.OnEntry -= controller.OnZoneEntry; + controller.playerTrackingZone.OnExit -= controller.OnZoneExit; + } + // Set it to use the new zone + controller.playerTrackingZone = triggerVolume; + triggerVolume.OnEntry += controller.OnZoneEntry; + triggerVolume.OnExit += controller.OnZoneExit; + } + // Simpler for the Nomai + else if(nomaiController) + { + triggerVolume.OnEntry += (_) => nomaiController.StartWatchingPlayer(); + triggerVolume.OnExit += (_) => nomaiController.StopWatchingPlayer(); + } + // No controller + else + { + // TODO + } + + playerTrackingZone.transform.parent = dialogue.gameObject.transform; + playerTrackingZone.transform.localPosition = Vector3.zero; + + playerTrackingZone.SetActive(true); + } + } + private static void AddTranslation(string xml) { XmlDocument xmlDocument = new XmlDocument(); diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs index 3678956c..1029de37 100644 --- a/NewHorizons/Builder/Props/PropBuildManager.cs +++ b/NewHorizons/Builder/Props/PropBuildManager.cs @@ -46,16 +46,17 @@ namespace NewHorizons.Builder.Props { foreach(var tornadoInfo in config.Props.Tornados) { - TornadoBuilder.Make(go, sector, tornadoInfo, config.Atmosphere?.Cloud != null); + //TornadoBuilder.Make(go, sector, tornadoInfo, config.Atmosphere?.Cloud != null); } } - if (config.Props.Volcanos != null) + if (config.Props.Volcanoes != null) { - foreach (var volcanoInfo in config.Props.Volcanos) + foreach (var volcanoInfo in config.Props.Volcanoes) { VolcanoBuilder.Make(go, sector, volcanoInfo); } } + // Reminder that dialogue has to be built after props if they're going to be using CharacterAnimController stuff if (config.Props.Dialogue != null) { foreach(var dialogueInfo in config.Props.Dialogue) diff --git a/NewHorizons/Builder/Props/SignalBuilder.cs b/NewHorizons/Builder/Props/SignalBuilder.cs index d95e775c..6a531f80 100644 --- a/NewHorizons/Builder/Props/SignalBuilder.cs +++ b/NewHorizons/Builder/Props/SignalBuilder.cs @@ -161,7 +161,7 @@ namespace NewHorizons.Builder.Props { try { - clip = AudioUtility.LoadAudio(mod.ModHelper.Manifest.ModFolderPath + "/" + info.AudioFilePath); + clip = AudioUtilities.LoadAudio(mod.ModHelper.Manifest.ModFolderPath + "/" + info.AudioFilePath); } catch(Exception e) { @@ -198,7 +198,8 @@ namespace NewHorizons.Builder.Props _customCurve = GameObject.Find("Moon_Body/Sector_THM/Characters_THM/Villager_HEA_Esker/Signal_Whistling").GetComponent().GetCustomCurve(AudioSourceCurveType.CustomRolloff); source.SetCustomCurve(AudioSourceCurveType.CustomRolloff, _customCurve); - source.playOnAwake = false; + // If it can be heard regularly then we play it immediately + source.playOnAwake = !info.OnlyAudibleToScope; source.spatialBlend = 1f; source.volume = 0.5f; source.dopplerLevel = 0; diff --git a/NewHorizons/Builder/Props/VolcanoBuilder.cs b/NewHorizons/Builder/Props/VolcanoBuilder.cs index 6526c6b3..172f45a1 100644 --- a/NewHorizons/Builder/Props/VolcanoBuilder.cs +++ b/NewHorizons/Builder/Props/VolcanoBuilder.cs @@ -17,6 +17,7 @@ namespace NewHorizons.Builder.Props var launcherGO = prefab.InstantiateInactive(); launcherGO.transform.parent = sector.transform; launcherGO.transform.localPosition = info.position == null ? Vector3.zero : (Vector3) info.position; + launcherGO.transform.rotation = Quaternion.FromToRotation(launcherGO.transform.TransformDirection(Vector3.up), ((Vector3)info.position).normalized).normalized; launcherGO.name = "MeteorLauncher"; var meteorLauncher = launcherGO.GetComponent(); @@ -24,30 +25,49 @@ namespace NewHorizons.Builder.Props meteorLauncher._detectableFluid = null; meteorLauncher._detectableField = null; - meteorLauncher._launchDirection = info.position == null ? Vector3.up : ((Vector3)info.position).normalized; + meteorLauncher._launchDirection = Vector3.up; - var meteorPrefab = GameObject.Instantiate(meteorLauncher._meteorPrefab); - FixMeteor(meteorPrefab, info); + meteorLauncher._dynamicProbability = 0f; - meteorLauncher._meteorPrefab = meteorPrefab; + meteorLauncher._minLaunchSpeed = info.minLaunchSpeed; + meteorLauncher._maxLaunchSpeed = info.maxLaunchSpeed; + meteorLauncher._minInterval = info.minInterval; + meteorLauncher._maxInterval = info.maxInterval; launcherGO.SetActive(true); - // Kill the prefab when its done with it - Main.Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => meteorPrefab.SetActive(false)); + // Have to null check else it breaks on reload configs + Main.Instance.ModHelper.Events.Unity.RunWhen(() => Main.IsSystemReady && meteorLauncher._meteorPool != null, () => { + foreach (var meteor in meteorLauncher._meteorPool) + { + FixMeteor(meteor, info); + } + }); } - private static void FixMeteor(GameObject meteor, PropModule.VolcanoInfo info) + private static void FixMeteor(MeteorController meteor, PropModule.VolcanoInfo info) { var mat = meteor.GetComponentInChildren().material; mat.SetColor("_Color", info.stoneTint == null ? defaultStoneTint : info.stoneTint.ToColor()); mat.SetColor("_EmissionColor", info.lavaTint == null ? defaultLavaTint : info.lavaTint.ToColor()); - var detectors = meteor.transform.Find("ConstantDetectors"); + var detectors = meteor.transform.Find("ConstantDetectors").gameObject; GameObject.Destroy(detectors.GetComponent()); GameObject.Destroy(detectors.GetComponent()); - detectors.gameObject.AddComponent(); + var forceDetector = detectors.gameObject.AddComponent(); detectors.gameObject.AddComponent(); + + detectors.layer = LayerMask.NameToLayer("BasicDetector"); + + var sphere = detectors.AddComponent(); + sphere.radius = 1; + + var sphere2 = detectors.AddComponent(); + sphere2._collisionMode = Shape.CollisionMode.Detector; + sphere2.radius = 1; + + forceDetector._collider = sphere; + forceDetector._shape = sphere2; } } } diff --git a/NewHorizons/External/OrbitModule.cs b/NewHorizons/External/OrbitModule.cs index 674990c6..038be883 100644 --- a/NewHorizons/External/OrbitModule.cs +++ b/NewHorizons/External/OrbitModule.cs @@ -23,6 +23,7 @@ namespace NewHorizons.External public bool IsTidallyLocked { get; set; } public MVector3 AlignmentAxis { get; set; } public bool ShowOrbitLine { get; set; } = true; + public bool DottedOrbitLine { get; set; } = false; public bool IsStatic { get; set; } public MColor Tint { get; set; } public bool TrackingOrbitLine { get; set; } = false; diff --git a/NewHorizons/External/PropModule.cs b/NewHorizons/External/PropModule.cs index a92827ac..0f66b2c7 100644 --- a/NewHorizons/External/PropModule.cs +++ b/NewHorizons/External/PropModule.cs @@ -14,7 +14,7 @@ namespace NewHorizons.External public RaftInfo[] Rafts; public GeyserInfo[] Geysers; public TornadoInfo[] Tornados; - public VolcanoInfo[] Volcanos; + public VolcanoInfo[] Volcanoes; public DialogueInfo[] Dialogue; public RevealInfo[] Reveal; public EntryLocationInfo[] EntryLocation; @@ -33,8 +33,8 @@ namespace NewHorizons.External public class DetailInfo { public string path; - public string objFilePath; - public string mtlFilePath; + public string objFilePath; //obsolete DO NOT DOCUMENT + public string mtlFilePath; //obsolete public string assetBundle; public MVector3 position; public MVector3 rotation; @@ -67,6 +67,10 @@ namespace NewHorizons.External public MVector3 position = null; public MColor stoneTint = null; public MColor lavaTint = null; + public float minLaunchSpeed = 50f; + public float maxLaunchSpeed = 150f; + public float minInterval = 5f; + public float maxInterval = 20f; } public class DialogueInfo @@ -76,6 +80,8 @@ namespace NewHorizons.External public string xmlFile; public MVector3 remoteTriggerPosition; public string blockAfterPersistentCondition; + public string pathToAnimController; + public float lookAtRadius; } public class RevealInfo diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 806c6ebf..1cf8d9cc 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -81,6 +81,8 @@ namespace NewHorizons OnStarSystemLoaded = new StarSystemEvent(); SceneManager.sceneLoaded += OnSceneLoaded; + SceneManager.sceneUnloaded += OnSceneUnloaded; + Instance = this; GlobalMessenger.AddListener("PlayerDeath", OnDeath); GlobalMessenger.AddListener("WakeUp", new Callback(OnWakeUp)); @@ -109,9 +111,7 @@ namespace NewHorizons Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single)); Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => _firstLoad = false); Instance.ModHelper.Menus.PauseMenu.OnInit += DebugReload.InitializePauseMenu; - } - - + } public void OnDestroy() { @@ -127,12 +127,17 @@ namespace NewHorizons Instance.OnStarSystemLoaded?.Invoke(Instance.CurrentStarSystem); } - void OnSceneLoaded(Scene scene, LoadSceneMode mode) + private void OnSceneUnloaded(Scene scene) { - Logger.Log($"Scene Loaded: {scene.name} {mode}"); - SearchUtilities.ClearCache(); ImageUtilities.ClearCache(); + AudioUtilities.ClearCache(); + IsSystemReady = false; + } + + private void OnSceneLoaded(Scene scene, LoadSceneMode mode) + { + Logger.Log($"Scene Loaded: {scene.name} {mode}"); _isChangingStarSystem = false; diff --git a/NewHorizons/Utility/AudioUtility.cs b/NewHorizons/Utility/AudioUtilities.cs similarity index 61% rename from NewHorizons/Utility/AudioUtility.cs rename to NewHorizons/Utility/AudioUtilities.cs index d33d27a9..ec2204fe 100644 --- a/NewHorizons/Utility/AudioUtility.cs +++ b/NewHorizons/Utility/AudioUtilities.cs @@ -9,18 +9,38 @@ using UnityEngine.Networking; namespace NewHorizons.Utility { - public static class AudioUtility + public static class AudioUtilities { - public static AudioClip LoadAudio(string filePath) + private static Dictionary _loadedAudioClips = new Dictionary(); + + public static AudioClip LoadAudio(string path) { - var task = Task.Run(async () => await GetAudioClip(filePath)); + if (_loadedAudioClips.ContainsKey(path)) + { + Logger.Log($"Already loaded audio at path: {path}"); + return _loadedAudioClips[path]; + } + Logger.Log($"Loading audio at path: {path}"); + var task = Task.Run(async () => await GetAudioClip(path)); task.Wait(); + _loadedAudioClips.Add(path, task.Result); return task.Result; } + public static void ClearCache() + { + Logger.Log("Clearing audio cache"); + + foreach (var audioClip in _loadedAudioClips.Values) + { + if (audioClip == null) continue; + UnityEngine.Object.Destroy(audioClip); + } + _loadedAudioClips.Clear(); + } + private static async Task GetAudioClip(string filePath) { - var extension = filePath.Split(new char[] { '.' }).Last(); UnityEngine.AudioType audioType; diff --git a/NewHorizons/Utility/ImageUtilities.cs b/NewHorizons/Utility/ImageUtilities.cs index 6fc800d8..2d39f06f 100644 --- a/NewHorizons/Utility/ImageUtilities.cs +++ b/NewHorizons/Utility/ImageUtilities.cs @@ -8,6 +8,46 @@ namespace NewHorizons.Utility { static class ImageUtilities { + private static Dictionary _loadedTextures = new Dictionary(); + private static List _generatedTextures = new List(); + + public static Texture2D GetTexture(IModBehaviour mod, string filename) + { + // Copied from OWML but without the print statement lol + var path = mod.ModHelper.Manifest.ModFolderPath + filename; + if (_loadedTextures.ContainsKey(path)) + { + Logger.Log($"Already loaded image at path: {path}"); + return _loadedTextures[path]; + } + Logger.Log($"Loading image at path: {path}"); + var data = File.ReadAllBytes(path); + var texture = new Texture2D(2, 2); + texture.name = Path.GetFileNameWithoutExtension(path); + texture.LoadImage(data); + _loadedTextures.Add(path, texture); + return texture; + } + + public static void ClearCache() + { + Logger.Log("Cleaing image cache"); + + foreach (var texture in _loadedTextures.Values) + { + if (texture == null) continue; + UnityEngine.Object.Destroy(texture); + } + _loadedTextures.Clear(); + + foreach(var texture in _generatedTextures) + { + if (texture == null) continue; + UnityEngine.Object.Destroy(texture); + } + _generatedTextures.Clear(); + } + public static Texture2D MakeOutline(Texture2D texture, Color color, int thickness) { var outline = new Texture2D(texture.width, texture.height, TextureFormat.ARGB32, false); @@ -32,6 +72,8 @@ namespace NewHorizons.Utility outline.SetPixels(outlinePixels); outline.Apply(); + _generatedTextures.Add(outline); + return outline; } @@ -67,6 +109,9 @@ namespace NewHorizons.Utility newImage.name = image.name + "Tinted"; newImage.SetPixels(pixels); newImage.Apply(); + + _generatedTextures.Add(newImage); + return newImage; } @@ -84,15 +129,10 @@ namespace NewHorizons.Utility newImage.name = image.name + "LerpedGrayscale"; newImage.SetPixels(pixels); newImage.Apply(); - return newImage; - } - public static Texture2D LoadImage(string filepath) - { - Texture2D tex = new Texture2D(2, 2); - tex.name = Path.GetFileNameWithoutExtension(filepath); - tex.LoadRawTextureData(File.ReadAllBytes(filepath)); - return tex; + _generatedTextures.Add(newImage); + + return newImage; } public static Texture2D ClearTexture(int width, int height) @@ -107,6 +147,9 @@ namespace NewHorizons.Utility } tex.SetPixels(fillPixels); tex.Apply(); + + _generatedTextures.Add(tex); + return tex; } @@ -124,39 +167,12 @@ namespace NewHorizons.Utility } tex.SetPixels(fillPixels); tex.Apply(); + + _generatedTextures.Add(tex); + return tex; } - private static Dictionary _loadedTextures = new Dictionary(); - - public static Texture2D GetTexture(IModBehaviour mod, string filename) - { - // Copied from OWML but without the print statement lol - var path = mod.ModHelper.Manifest.ModFolderPath + filename; - if (_loadedTextures.ContainsKey(path)) - { - Logger.Log($"Already loaded image at path: {path}"); - return _loadedTextures[path]; - } - Logger.Log($"Loading image at path: {path}"); - var data = File.ReadAllBytes(path); - var texture = new Texture2D(2, 2); - texture.name = Path.GetFileNameWithoutExtension(path); - texture.LoadImage(data); - _loadedTextures.Add(path, texture); - return texture; - } - - public static void ClearCache() - { - foreach(var texture in _loadedTextures.Values) - { - if (texture == null) continue; - UnityEngine.Object.Destroy(texture); - } - _loadedTextures.Clear(); - } - public static Color GetAverageColor(Texture2D src) { var pixels = src.GetPixels32(); diff --git a/NewHorizons/Utility/SearchUtilities.cs b/NewHorizons/Utility/SearchUtilities.cs index f3b3017f..f6eafbce 100644 --- a/NewHorizons/Utility/SearchUtilities.cs +++ b/NewHorizons/Utility/SearchUtilities.cs @@ -14,6 +14,7 @@ namespace NewHorizons.Utility public static void ClearCache() { + Logger.Log("Clearing search cache"); CachedGameObjects.Clear(); } diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json index 131d6245..5e9e90c9 100644 --- a/NewHorizons/manifest.json +++ b/NewHorizons/manifest.json @@ -3,7 +3,7 @@ "author": "xen, Bwc9876, & Book", "name": "New Horizons", "uniqueName": "xen.NewHorizons", - "version": "0.10.3", + "version": "0.11.0", "owmlVersion": "2.1.0", "conflicts": [ "Raicuparta.QuantumSpaceBuddies", "Vesper.OuterWildsMMO", "Vesper.AutoResume" ], "pathsToPreserve": [ "planets", "systems", "translations" ] diff --git a/NewHorizons/schema.json b/NewHorizons/schema.json index 5fbd70f2..759b3db3 100644 --- a/NewHorizons/schema.json +++ b/NewHorizons/schema.json @@ -363,6 +363,10 @@ "default": false, "description": "Referring to the orbit line in the map screen." }, + "dottedOrbitLine": { + "type": "boolean", + "default": false + }, "isStatic": { "type": "boolean", "default": false, @@ -603,14 +607,6 @@ "type": "string", "description": "Relative filepath to an asset-bundle" }, - "objFilePath": { - "type": "string", - "description": "Relative filepath to the obj file" - }, - "mtlFilePath": { - "type": "string", - "description": "Relative filepath to the mtl file of the obj file" - }, "position": { "$ref": "#/$defs/vector3" }, @@ -663,6 +659,15 @@ "blockAfterPersistentCondition": { "type": "string", "description": "Prevents the dialogue from being created after a specific persistent condition is set. Useful for remote dialogue triggers that you want to have happen only once." + }, + "pathToAnimController": { + "type": "string", + "description": "If this dialogue is meant for a character, this is the relative path from the planet to that character's CharacterAnimController or SolanumAnimController." + }, + "lookAtRadius": { + "type": "number", + "default": 0, + "description": "If a pathToAnimController is supplied, if you are within this distance the character will look at you. If it is set to 0, they will only look at you when spoken to." } } } @@ -747,6 +752,48 @@ } } } + }, + "volcanoes": { + "type": "array", + "description": "A set of meteor-spewing volcanoes", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "position": { + "$ref": "#/$defs/vector3", + "description": "The position of this volcano." + }, + "stoneTint": { + "$ref": "#/$defs/color", + "description": "The colour of the meteor's stone." + }, + "lavaTint": { + "$ref": "#/$defs/color", + "description": "The colour of the meteor's lava." + }, + "minLaunchSpeed": { + "type": "number", + "description": "Minimum random speed at which meteors are launched.", + "default": 50 + }, + "maxLaunchSpeed": { + "type": "number", + "description": "Maximum random speed at which meteors are launched.", + "default": 150 + }, + "minInterval": { + "type": "number", + "description": "Minimum time between meteor launches.", + "default": 5 + }, + "maxInterval": { + "type": "number", + "description": "Maximum time between meteor launches.", + "default": 20 + } + } + } } } }, diff --git a/README.md b/README.md index 832fa928..33ff6fc0 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Check the ship's log for how to use your warp drive to travel between star syste - Separate solar system scenes accessible via wormhole OR via the ship's new warp drive feature accessible via the ship's log - Remove existing planets - Create planets from heightmaps/texturemaps -- Create stars, comets, asteroid belts, satellites, geysers, cloak fields +- Create stars, comets, asteroid belts, satellites, geysers, cloak fields, meteor-launching volcanoes - Binary orbits - Signalscope signals and custom frequencies - Surface scatter: rocks, trees, etc, using in-game models, or custom ones @@ -51,7 +51,6 @@ Check the ship's log for how to use your warp drive to travel between star syste - Implement all planet features: - Tornados + floating islands - Let any star go supernova - - Meteors - Pocket dimensions - Timed position/velocity changes - Implement custom Nomai scrolls diff --git a/docs/Pipfile.lock b/docs/Pipfile.lock index 5f48b06d..6952676f 100644 --- a/docs/Pipfile.lock +++ b/docs/Pipfile.lock @@ -50,11 +50,11 @@ }, "click": { "hashes": [ - "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e", - "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72" + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" ], "markers": "python_version >= '3.7'", - "version": "==8.1.2" + "version": "==8.1.3" }, "colorama": { "hashes": [ @@ -74,11 +74,11 @@ }, "elementpath": { "hashes": [ - "sha256:2a432775e37a19e4362443078130a7dbfc457d7d093cd421c03958d9034cc08b", - "sha256:3a27aaf3399929fccda013899cb76d3ff111734abf4281e5f9d3721ba0b9ffa3" + "sha256:1f41f1160aaae66bc25a8cb9451e5b31ca4553b6dccd9b57045205b005e5406e", + "sha256:c556a9b9dde47fdf05bb3ad525dfb43fc6becb95532a053f6a73024e586ead37" ], "markers": "python_version >= '3.7'", - "version": "==2.5.0" + "version": "==2.5.1" }, "htmlmin": { "hashes": [ @@ -96,11 +96,11 @@ }, "jinja2": { "hashes": [ - "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", - "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" ], "markers": "python_version >= '3.7'", - "version": "==3.1.1" + "version": "==3.1.2" }, "json-minify": { "hashes": [ @@ -114,7 +114,7 @@ "sha256:66784a3d37c8f730588524cc8f103448847533f067ba8b5d76e7667675ee31f1", "sha256:ed900db6b19b41bf681513c48ae5e403632878745775ddfc8d5b73438d2930fe" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_version >= '3.7' and python_version < '4.0'", "version": "==0.40.2" }, "jsonschema": { @@ -135,11 +135,11 @@ }, "markdown2": { "hashes": [ - "sha256:8f4ac8d9a124ab408c67361090ed512deda746c04362c36c2ec16190c720c2b0", - "sha256:91113caf23aa662570fe21984f08fe74f814695c0a0ea8e863a8b4c4f63f9f6e" + "sha256:412520c7b6bba540c2c2067d6be3a523ab885703bf6a81d93963f848b55dfb9a", + "sha256:f344d4adfba5d1de821f7850b36e3507f583468a7eb47e6fa191765ed0b9c66b" ], - "markers": "python_version >= '3.5' and python_version < '4'", - "version": "==2.4.2" + "markers": "python_version >= '3.5' and python_version < '4.0'", + "version": "==2.4.3" }, "markupsafe": { "hashes": [ @@ -222,7 +222,7 @@ "sha256:5053fc5ca7b8a281081274702ebf1584e341f40a68e6ab8f6b4b79f4b3fdf18e", "sha256:8e8226f15c0b25565aa391797963b78c95930e12efc40e905153130783e766be" ], - "markers": "python_version >= '3.8' and python_version < '4'", + "markers": "python_version >= '3.8' and python_version < '4.0'", "version": "==0.1.0" }, "packaging": { @@ -453,7 +453,7 @@ "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", "version": "==1.26.9" }, "xmlschema": { diff --git a/docs/content/pages/api.md b/docs/content/pages/api.md index b87fb847..98713d2e 100644 --- a/docs/content/pages/api.md +++ b/docs/content/pages/api.md @@ -2,6 +2,7 @@ Title: API Sort_Priority: 40 ## How to use the API +___ First create the following interface in your mod: diff --git a/docs/content/pages/details.md b/docs/content/pages/details.md index e9c6d468..01326340 100644 --- a/docs/content/pages/details.md +++ b/docs/content/pages/details.md @@ -2,6 +2,7 @@ Title: Detailing Sort_Priority: 90 ## Details/Scatterer +___ For physical objects there are currently two ways of setting them up: specify an asset bundle and path to load a custom asset you created, or specify the path to the item you want to copy from the game in the scene hierarchy. Use the [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer){ target="_blank" } mod to find an object you want to copy onto your new body. Some objects work better than others for this. Good luck. Some pointers: - Use "Object Explorer" to search @@ -9,6 +10,7 @@ For physical objects there are currently two ways of setting them up: specify an - Generally you can find planets by writing their name with no spaces/punctuation followed by "_Body". ## Asset Bundles +___ Here is a template project: [Outer Wilds Unity Template](https://github.com/xen-42/outer-wilds-unity-template){ target="_blank" } @@ -47,10 +49,12 @@ public class CreateAssetBundles 6. Copy the asset bundle and asset bundle .manifest files from StreamingAssets into your mod's "planets" folder. If you did everything properly they should work in game. To double-check everything is included, open the .manifest file in a text editor to see the files included and their paths. ## Importing a planet's surface from Unity +___ Making a planet's entire surface from a Unity prefab is the exact same thing as adding one single big detail at position (0, 0, 0). ## Examples +___ To add a Mars rover to the red planet in [RSS](https://github.com/xen-42/outer-wilds-real-solar-system), its model was put in an asset bundle as explained above, and then the following was put into the `Props` module: diff --git a/docs/content/pages/dialogue.md b/docs/content/pages/dialogue.md index f2d135a0..24adbdb3 100644 --- a/docs/content/pages/dialogue.md +++ b/docs/content/pages/dialogue.md @@ -2,6 +2,7 @@ Title: Dialogue Sort_Priority: 50 ## Dialogue +___ Here's an example dialogue XML: diff --git a/docs/content/pages/home.md b/docs/content/pages/home.md index 373831fc..1005af27 100644 --- a/docs/content/pages/home.md +++ b/docs/content/pages/home.md @@ -5,10 +5,12 @@ Sort_Priority: 100 ![New Horizons Logo]({{ 'images/home/home_logo.webp'|static }}) # Outer Wilds New Horizons +___ This is the official documentation for [New Horizons](https://github.com/xen-42/outer-wilds-new-horizons){ target="_blank" } ## Getting Started +___ Before starting, go into your in-game mod settings for New Horizons and switch Debug mode on. This allows you to: @@ -114,11 +116,13 @@ To see all the different things you can put into a config file check out the [Ce Check out the rest of the site for how to format [star system]({{ 'Star System Schema'|route}}), [dialogue]({{ 'Dialogue Schema'|route}}), [ship log]({{ 'Shiplog Schema'|route}}), and [translation]({{ 'Translation Schema'|route}}) files! ## Publishing Your Mod +___ Once your mod is complete, you can use the [addon creation tool](https://outerwildsmods.com/custom-worlds/create/){ target="_blank" } to upload your mod to the database. Alternatively, you can use the [planet creation template](https://github.com/xen-42/ow-new-horizons-config-template#readme){ target="_blank" } GitHub template if you're familiar with Git and GitHub ## Helpful Resources +___ The texturemap/heightmap feature was inspired by the Kerbal Space Program mod Kopernicus. A lot of the same techniques that apply to planet creation there apply to New Horizons. If you need help with planetary texturing, check out [The KSP texturing guide](https://forum.kerbalspaceprogram.com/index.php?/topic/165285-planetary-texturing-guide-repository/){ target="_blank" }. diff --git a/docs/content/pages/secret.md b/docs/content/pages/secret.md index b9277458..31faba0b 100644 --- a/docs/content/pages/secret.md +++ b/docs/content/pages/secret.md @@ -3,6 +3,7 @@ Description: Hehe funny secret Hide_In_Nav: True # Hello!! +___ Uh idk what to put here thought it would be funny haha diff --git a/docs/content/pages/ship_log.md b/docs/content/pages/ship_log.md index 112d6109..b9073911 100644 --- a/docs/content/pages/ship_log.md +++ b/docs/content/pages/ship_log.md @@ -3,9 +3,11 @@ Description: A guide to editing the ship log in New Horizons Sort_Priority: 70 # Intro +___ Welcome! this page outlines how to create a custom ship log. ## Helpful Mods +___ These mods are useful when developing your ship log @@ -14,6 +16,7 @@ These mods are useful when developing your ship log - [Save Editor](https://outerwildsmods.com/mods/saveeditor){ target="_blank" } ## Helpful Tools +___ These tools/references are highly recommended @@ -24,10 +27,12 @@ These tools/references are highly recommended - [The Examples Mod](https://github.com/xen-42/ow-new-horizons-examples){ target="_blank" } # Understanding Ship Logs +___ First thing's first, I'll define some terminology regarding ship logs in the game, and how ship logs are structured. ## Entries +___ An entry is a card you see in rumor mode, it represents a specific area or concept in the game, such as Timber Hearth's village or the southern observatory on Brittle Hollow. @@ -53,6 +58,7 @@ Entries can be children of other entries, meaning they'll be smaller. *The murals at the old settlement on Brittle Hollow are examples of child entries* ## Rumor Facts +___ A rumor fact represents the information you might hear about a specific area or concept, usually, you get these through dialogue or maybe by observing a faraway planet. @@ -60,18 +66,21 @@ dialogue or maybe by observing a faraway planet. ![rumorFactExample]({{ "images/ship_log/rumor_example.webp"|static }}) ## Explore Facts +___ Explore facts represent the information you learn about a specific area or concept. ![exploreFactExample]({{ "images/ship_log/explore_example.webp"|static }}) # The XML +___ Now that we know some terminology, let's get into how the XML works. Every planet in the ship log is represented by a single XML file, you can see this if you use the unity explorer mod and navigate to ShipLogManager. ## Example File +___ ```xml @@ -150,6 +159,7 @@ navigate to ShipLogManager. ``` ## Using The Schema +___ In the example XML, you may notice something like `xsi:noNamespaceSchemaLocation` at the top, this tells whatever editor you're using that the file at that link is the schema. The game simply ignores this though, so it won't be able to catch @@ -158,6 +168,7 @@ Some editors may require you to [Trust](https://code.visualstudio.com/docs/edito the schema file. Doing this varies per-editor, and you may also have to right-click the link and click download. ## Loading The File +___ You can load your XML file to your planet by doing adding the following to your planet's config @@ -172,6 +183,7 @@ You can load your XML file to your planet by doing adding the following to your # Rumor Mode Options ## Entry Layout +___ By default, entries in rumor mode are laid out by rows, where each row is one planet. This will not make for a perfect layout, so you can use the `entryPositions` property to change them @@ -205,6 +217,7 @@ For example, if I want to change an entry with the ID of `EXAMPLE_ENTRY` and ano *A set of entries laid out with auto mode* ## Images +___ Custom entry images are a bit different from other custom images, instead of pointing to each file for each entry, you point to a folder: @@ -224,6 +237,7 @@ you set alternate sprites by making a file with the entry's ID and `_ALT` at the would be `EXAMPLE_ENTRY_ALT.png`. ## Curiosity Colors +___ Colors for each curiosity is given in a list, so if I wanted the curiosity `EXAMPLE_ENTRY` to have a color of blue: @@ -256,8 +270,10 @@ Colors for each curiosity is given in a list, so if I wanted the curiosity `EXAM *The curiosity's color is changed to blue* # Map Mode Options +___ ## Layout +___ Layout in map mode can be handled in two different ways, either manual or automatic, if you try to mix them you'll get an error. @@ -424,10 +440,12 @@ between Ash Twin and Ember Twin) As you can see, they have similar properties to planets, with the addition of rotation # Revealing Facts +___ Of course, having a custom ship log is neat and all, but what use is it if the player can't unlock it? ## Initial Reveal +___ You can set facts to reveal as soon as the player enters the system by adding the `initialReveal` property @@ -443,6 +461,7 @@ You can set facts to reveal as soon as the player enters the system by adding th ``` ## Signal Discovery +___ You can set a fact to reveal as soon as a signal is identified by editing the signal's `Reveals` attribute @@ -463,6 +482,7 @@ You can set a fact to reveal as soon as a signal is identified by editing the si ``` ## Dialogue +___ You can set a fact to reveal in dialogue with the `` tag @@ -484,6 +504,7 @@ You can set a fact to reveal in dialogue with the `` tag ``` ## Reveal Volumes +___ Reveal volumes are triggers/colliders in the world that can unlock facts from a variety of actions. Reveal volumes are specified in the `Props` module, its key is `reveal`. @@ -551,6 +572,7 @@ trigger the reveal ``` # Setting Entry Locations +___ Entry locations are the "Mark On HUD" option you see when in map mode, this allows the player to go back to where they were in the event of the big funny. diff --git a/docs/content/pages/translation.md b/docs/content/pages/translation.md index 4b80fee4..0ad0e362 100644 --- a/docs/content/pages/translation.md +++ b/docs/content/pages/translation.md @@ -2,6 +2,7 @@ Title: Translations Sort_Priority: 60 ## Translations +___ There are 12 supported languages in Outer Wilds: english, spanish_la, german, french, italian, polish, portuguese_br, japanese, russian, chinese_simple, korean, and turkish. diff --git a/docs/content/pages/update_existing.md b/docs/content/pages/update_existing.md index b0eaf921..3b956314 100644 --- a/docs/content/pages/update_existing.md +++ b/docs/content/pages/update_existing.md @@ -2,6 +2,7 @@ Title: Update Planets Sort_Priority: 80 ## Update Existing Planets +___ Similar to above, make a config where "Name" is the name of the planet. The name should be able to just match their in-game english names, however if you encounter any issues with that here are the in-code names for planets that are guaranteed to work: `SUN`, `CAVE_TWIN` (Ember Twin), `TOWER_TWIN` (Ash Twin), `TIMBER_HEARTH`, `BRITTLE_HOLLOW`, `GIANTS_DEEP`, `DARK_BRAMBLE`, `COMET` (Interloper), `WHITE_HOLE`, `WHITE_HOLE_TARGET` (Whitehole station I believe), `QUANTUM_MOON`, `ORBITAL_PROBE_CANNON`, `TIMBER_MOON` (Attlerock), `VOLCANIC_MOON` (Hollow's Lantern), `DREAMWORLD`, `MapSatellite`, `RINGWORLD` (the Stranger). @@ -20,6 +21,7 @@ You can also delete parts of an existing planet. Here's part of an example confi In `childrenToDestroy` you list the relative paths for the children of the planet's gameObject that you want to delete. ## Destroy Existing Planets +___ You do this (but with the appropriate name) as its own config. ```json