Merge branch 'dev' into hawkbar-colliders

This commit is contained in:
Joshua Thome 2025-04-15 22:58:15 -05:00
commit 4e9ec84427
22 changed files with 189 additions and 39 deletions

View File

@ -100,6 +100,15 @@ namespace NewHorizons.Builder.Atmosphere
atmoGO.transform.position = planetGO.transform.TransformPoint(Vector3.zero);
atmoGO.SetActive(true);
// CullGroups have already set up their renderers when this is done so we need to add ourself to it
// TODO: There are probably other builders where this is relevant
// This in particular was a bug affecting hazy dreams
if (sector != null && sector.gameObject.GetComponent<CullGroup>() is CullGroup cullGroup)
{
cullGroup.RecursivelyAddRenderers(atmoGO.transform, true);
cullGroup.SetVisible(cullGroup.IsVisible());
}
return atmoGO;
}
}

View File

@ -60,14 +60,22 @@ namespace NewHorizons.Builder.Body
}
}
public static void Make(GameObject planetGO, Sector sector, CometTailModule cometTailModule, PlanetConfig config)
public static void Make(GameObject planetGO, Sector sector, CometTailModule cometTailModule, PlanetConfig config, AstroObject ao)
{
if (config.Orbit.primaryBody == null)
var primaryBody = ao.GetPrimaryBody();
if (!string.IsNullOrEmpty(config.Orbit.primaryBody)) primaryBody = AstroObjectLocator.GetAstroObject(config.Orbit.primaryBody);
if (primaryBody == null)
{
NHLogger.LogError($"Comet {planetGO.name} does not orbit anything. That makes no sense");
return;
}
if (string.IsNullOrEmpty(cometTailModule.primaryBody))
cometTailModule.primaryBody = !string.IsNullOrEmpty(config.Orbit.primaryBody) ? config.Orbit.primaryBody
: primaryBody.GetKey();
var rootObj = new GameObject("CometRoot");
rootObj.SetActive(false);
rootObj.transform.parent = sector?.transform ?? planetGO.transform;
@ -79,13 +87,11 @@ namespace NewHorizons.Builder.Body
if (cometTailModule.rotationOverride != null) controller.SetRotationOverride(cometTailModule.rotationOverride);
if (string.IsNullOrEmpty(cometTailModule.primaryBody)) cometTailModule.primaryBody = config.Orbit.primaryBody;
Delay.FireOnNextUpdate(() =>
{
controller.SetPrimaryBody(
AstroObjectLocator.GetAstroObject(cometTailModule.primaryBody).transform,
AstroObjectLocator.GetAstroObject(config.Orbit.primaryBody).GetAttachedOWRigidbody()
AstroObjectLocator.GetAstroObject(cometTailModule.primaryBody).transform,
primaryBody.GetAttachedOWRigidbody()
);
});

View File

@ -203,7 +203,7 @@ namespace NewHorizons.Builder.Body
if (body.Config.CometTail != null)
{
CometTailBuilder.Make(proxy, null, body.Config.CometTail, body.Config);
CometTailBuilder.Make(proxy, null, body.Config.CometTail, body.Config, planetGO.GetComponent<AstroObject>());
}
if (body.Config.Props?.proxyDetails != null)

View File

@ -473,10 +473,14 @@ namespace NewHorizons.Builder.Props
{
// These flood toggles are to disable flooded docks on the Stranger
// Presumably the user isn't making one of those
foreach (var toggle in dock.GetComponents<FloodToggle>())
foreach (var toggle in dock.GetComponents<FloodToggle>().Concat(dock.GetComponentsInChildren<FloodToggle>()))
{
Component.DestroyImmediate(toggle);
}
foreach (var floodSensor in dock.GetComponents<RingRiverFloodSensor>().Concat(dock.GetComponentsInChildren<RingRiverFloodSensor>()))
{
Component.DestroyImmediate(floodSensor);
}
}
}

View File

@ -43,6 +43,7 @@ namespace NewHorizons.Builder.Props
private static GameObject _autoPrefab;
private static GameObject _visionTorchDetectorPrefab;
private static GameObject _standingVisionTorchPrefab;
private static GameObject _standingVisionTorchCleanPrefab;
private static readonly int EmissionMap = Shader.PropertyToID("_EmissionMap");
private static bool _isInit;
@ -90,13 +91,28 @@ namespace NewHorizons.Builder.Props
_visionTorchDetectorPrefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
}
if (_standingVisionTorchCleanPrefab == null)
{
_standingVisionTorchCleanPrefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_4/Interactibles_DreamZone_4_Upper/Prefab_IP_VisionTorchProjector")?.gameObject?.InstantiateInactive()?.Rename("Prefab_DW_VisionTorchProjector")?.DontDestroyOnLoad();
if (_standingVisionTorchCleanPrefab == null)
NHLogger.LogWarning($"Tried to make standing vision torch prefab but couldn't. Do you have the DLC installed?");
else
{
_standingVisionTorchCleanPrefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
GameObject.DestroyImmediate(_standingVisionTorchCleanPrefab.FindChild("Prefab_IP_Reel_PrisonPeephole_Vision"));
}
}
if (_standingVisionTorchPrefab == null)
{
_standingVisionTorchPrefab = SearchUtilities.Find("RingWorld_Body/Sector_RingWorld/Sector_SecretEntrance/Interactibles_SecretEntrance/Experiment_1/VisionTorchApparatus/VisionTorchRoot/Prefab_IP_VisionTorchProjector")?.gameObject?.InstantiateInactive()?.Rename("Prefab_IP_VisionTorchProjector")?.DontDestroyOnLoad();
if (_standingVisionTorchPrefab == null)
NHLogger.LogWarning($"Tried to make standing vision torch prefab but couldn't. Do you have the DLC installed?");
else
{
_standingVisionTorchPrefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true;
GameObject.Instantiate(_standingVisionTorchCleanPrefab.FindChild("Effects_IP_SIM_VisionTorch"), _standingVisionTorchPrefab.transform, false).Rename("Effects_IP_SIM_VisionTorch");
}
}
}
@ -446,10 +462,11 @@ namespace NewHorizons.Builder.Props
{
InitPrefabs();
if (_standingVisionTorchPrefab == null) return null;
if (_standingVisionTorchPrefab == null || _standingVisionTorchCleanPrefab == null) return null;
// Spawn the torch itself
var standingTorch = DetailBuilder.Make(planetGO, sector, mod, _standingVisionTorchPrefab, new DetailInfo(info));
var prefab = info.reelCondition == ProjectionInfo.SlideReelCondition.Pristine ? _standingVisionTorchCleanPrefab : _standingVisionTorchPrefab;
var standingTorch = DetailBuilder.Make(planetGO, sector, mod, prefab, new DetailInfo(info));
if (standingTorch == null)
{

View File

@ -1,5 +1,6 @@
using NewHorizons.Components.Volumes;
using NewHorizons.External.Modules.Volumes.VolumeInfos;
using OWML.Common;
using OWML.Utils;
using UnityEngine;
@ -7,12 +8,13 @@ namespace NewHorizons.Builder.Volumes
{
internal static class CreditsVolumeBuilder
{
public static LoadCreditsVolume Make(GameObject planetGO, Sector sector, LoadCreditsVolumeInfo info)
public static LoadCreditsVolume Make(GameObject planetGO, Sector sector, LoadCreditsVolumeInfo info, IModBehaviour mod)
{
var volume = VolumeBuilder.Make<LoadCreditsVolume>(planetGO, sector, info);
volume.gameOver = info.gameOver;
volume.deathType = info.deathType == null ? null : EnumUtils.Parse(info.deathType.ToString(), DeathType.Default);
volume.mod = mod;
volume.gameObject.SetActive(true);

View File

@ -247,7 +247,7 @@ namespace NewHorizons.Builder.Volumes
{
foreach (var creditsVolume in config.Volumes.creditsVolume)
{
CreditsVolumeBuilder.Make(go, sector, creditsVolume);
CreditsVolumeBuilder.Make(go, sector, creditsVolume, mod);
}
}
}

View File

@ -1,7 +1,11 @@
using NewHorizons.External.Modules;
using NewHorizons.External.SerializableEnums;
using NewHorizons.Handlers;
using NewHorizons.Patches.CreditsScenePatches;
using NewHorizons.Utility.Files;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -15,14 +19,14 @@ namespace NewHorizons.Components
/// Mod unique id to game over module list
/// Done as a dictionary so that Reload Configs can overwrite entries per mod
/// </summary>
public static Dictionary<string, GameOverModule[]> gameOvers = new();
public static Dictionary<IModBehaviour, GameOverModule[]> gameOvers = new();
public static NHGameOverManager Instance { get; private set; }
private GameOverController _gameOverController;
private PlayerCameraEffectController _playerCameraEffectController;
private GameOverModule[] _gameOvers;
private (IModBehaviour mod, GameOverModule gameOver)[] _gameOvers;
private bool _gameOverSequenceStarted;
@ -36,25 +40,35 @@ namespace NewHorizons.Components
_gameOverController = FindObjectOfType<GameOverController>();
_playerCameraEffectController = FindObjectOfType<PlayerCameraEffectController>();
_gameOvers = gameOvers.SelectMany(x => x.Value).ToArray();
var gameOverList = new List<(IModBehaviour, GameOverModule)>();
foreach (var gameOverPair in gameOvers)
{
var mod = gameOverPair.Key;
foreach (var gameOver in gameOverPair.Value)
{
gameOverList.Add((mod, gameOver));
}
}
_gameOvers = gameOverList.ToArray();
}
public void TryHijackDeathSequence()
{
var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.condition) && DialogueConditionManager.SharedInstance.GetConditionState(x.condition));
if (!_gameOverSequenceStarted && gameOver != null && !Locator.GetDeathManager()._finishedDLC)
var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.gameOver.condition)
&& DialogueConditionManager.SharedInstance.GetConditionState(x.gameOver.condition));
if (!_gameOverSequenceStarted && gameOver != default && !Locator.GetDeathManager()._finishedDLC)
{
StartGameOverSequence(gameOver, null);
StartGameOverSequence(gameOver.gameOver, null, gameOver.mod);
}
}
public void StartGameOverSequence(GameOverModule gameOver, DeathType? deathType)
public void StartGameOverSequence(GameOverModule gameOver, DeathType? deathType, IModBehaviour mod)
{
_gameOverSequenceStarted = true;
Delay.StartCoroutine(GameOver(gameOver, deathType));
Delay.StartCoroutine(GameOver(gameOver, deathType, mod));
}
private IEnumerator GameOver(GameOverModule gameOver, DeathType? deathType)
private IEnumerator GameOver(GameOverModule gameOver, DeathType? deathType, IModBehaviour mod)
{
OWInput.ChangeInputMode(InputMode.None);
ReticleController.Hide();
@ -104,12 +118,12 @@ namespace NewHorizons.Components
yield return new WaitUntil(ReadytoLoadCreditsScene);
}
LoadCreditsScene(gameOver);
LoadCreditsScene(gameOver, mod);
}
private bool ReadytoLoadCreditsScene() => _gameOverController._fadedOutText && _gameOverController._textAnimator.IsComplete();
private void LoadCreditsScene(GameOverModule gameOver)
private void LoadCreditsScene(GameOverModule gameOver, IModBehaviour mod)
{
NHLogger.LogVerbose($"Load credits {gameOver.creditsType}");
@ -125,6 +139,9 @@ namespace NewHorizons.Components
TimelineObliterationController.s_hasRealityEnded = true;
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack);
break;
case NHCreditsType.Custom:
LoadCustomCreditsScene(gameOver, mod);
break;
default:
// GameOverController disables post processing
_gameOverController._flashbackCamera.postProcessing.enabled = true;
@ -134,5 +151,42 @@ namespace NewHorizons.Components
break;
}
}
private void LoadCustomCreditsScene(GameOverModule gameOver, IModBehaviour mod)
{
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack);
// Unfortunately we can't make this a private method, as EventArgs/EventHandler enforces the (sender, e) parameters, which prevents us from passing in gameOver and mod, which we need.
EventHandler onCreditsBuilt = null; // needs to be done so we can unsubscribe from within the lambda.
onCreditsBuilt = (sender, e) =>
{
// Unsubscribe first, playing it safe in case it NREs
CreditsPatches.CreditsBuilt -= onCreditsBuilt;
// Patch new music clip
var musicSource = Locator.FindObjectsOfType<OWAudioSource>().Where(x => x.name == "AudioSource").Single(); // AudioSource that plays the credits music is literally called "AudioSource", luckily it's the only one called that. Lazy OW devs do be lazy.
if (!string.IsNullOrEmpty(gameOver.audio)) // string.Empty is default value for "audio" in GameOverModule, means no audio is specified.
{
AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); // Load audio if specified
}
else
{
musicSource.AssignAudioLibraryClip(AudioType.PLACEHOLDER); // Otherwise default custom credits are silent - AudioType.PLACEHOLDER is silence (apparently)
}
musicSource.loop = gameOver.audioLooping;
musicSource._maxSourceVolume = gameOver.audioVolume;
// Override fade in
musicSource.Stop();
musicSource.Play();
// Patch scroll duration
var creditsScroll = Locator.FindObjectOfType<CreditsScrollSection>();
creditsScroll._scrollDuration = gameOver.length;
};
CreditsPatches.CreditsBuilt += onCreditsBuilt;
}
}
}

View File

@ -1,4 +1,5 @@
using NewHorizons.External.Modules;
using OWML.Common;
using UnityEngine;
@ -8,12 +9,13 @@ namespace NewHorizons.Components.Volumes
{
public GameOverModule gameOver;
public DeathType? deathType;
public IModBehaviour mod;
public override void OnTriggerVolumeEntry(GameObject hitObj)
{
if (hitObj.CompareTag("PlayerDetector") && enabled && (string.IsNullOrEmpty(gameOver.condition) || DialogueConditionManager.SharedInstance.GetConditionState(gameOver.condition)))
{
NHGameOverManager.Instance.StartGameOverSequence(gameOver, deathType);
NHGameOverManager.Instance.StartGameOverSequence(gameOver, deathType, mod);
}
}

View File

@ -24,6 +24,31 @@ namespace NewHorizons.External.Modules
/// </summary>
public string condition;
/// <summary>
/// The audio to use for the credits music. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
/// Credits will be silent unless this attribute is specified.
/// Note: only applies when creditsType is set to "custom".
/// </summary>
public string audio;
/// <summary>
/// The length of the fade in and out for the credits music.
/// Note: only applies when creditsType is set to "custom".
/// </summary>
[DefaultValue(1f)] public float audioVolume = 1f;
/// <summary>
/// Determines if the credits music should loop.
/// Note: only applies when creditsType is set to "custom".
/// </summary>
[DefaultValue(false)] public bool audioLooping = false;
/// <summary>
/// Duration of the credits scroll in seconds.
/// Note: only applies when creditsType is set to "custom".
/// </summary>
[DefaultValue(120f)] public float length = 120f;
/// <summary>
/// The type of credits that will run after the game over message is shown
/// </summary>

View File

@ -82,7 +82,7 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
[DefaultValue("sevenSlides")] public SlideReelType reelModel = SlideReelType.SevenSlides;
/// <summary>
/// Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.
/// Exclusive to the slide reel and standing vision torch type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted (exclusive to slide reels) is a burned reel.
/// </summary>
[DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique;

View File

@ -13,6 +13,8 @@ namespace NewHorizons.External.SerializableEnums
[EnumMember(Value = @"kazoo")] Kazoo = 2,
[EnumMember(Value = @"none")] None = 3
[EnumMember(Value = @"custom")] Custom = 3,
[EnumMember(Value = @"none")] None = 4
}
}

View File

@ -657,7 +657,7 @@ namespace NewHorizons.Handlers
if (body.Config.CometTail != null)
{
CometTailBuilder.Make(go, sector, body.Config.CometTail, body.Config);
CometTailBuilder.Make(go, sector, body.Config.CometTail, body.Config, go.GetComponent<AstroObject>());
}
if (body.Config.Lava != null)

View File

@ -849,7 +849,7 @@ namespace NewHorizons
}
if (addonConfig.gameOver != null)
{
NHGameOverManager.gameOvers[mod.ModHelper.Manifest.UniqueName] = addonConfig.gameOver;
NHGameOverManager.gameOvers[mod] = addonConfig.gameOver;
}
AddonConfigs[mod] = addonConfig;

View File

@ -1,4 +1,4 @@
<Project>
<Project>
<PropertyGroup>
<OutputPath>$(AppData)\OuterWildsModManager\OWML\Mods\xen.NewHorizons</OutputPath>
</PropertyGroup>

View File

@ -1,17 +1,30 @@
using HarmonyLib;
using NewHorizons.Handlers;
using System;
namespace NewHorizons.Patches.CreditsScenePatches
{
[HarmonyPatch(typeof(Credits))]
public static class CreditsPatches
{
public static event EventHandler CreditsBuilt; // Used in NHGameOverManager to patch credits music and scroll speed
[HarmonyPrefix]
[HarmonyPatch(nameof(Credits.Start))]
public static void Credits_Start(Credits __instance)
{
CreditsHandler.AddCredits(__instance);
}
[HarmonyPostfix]
[HarmonyPatch(nameof(Credits.BuildCredits))]
public static void Credits_BuildCredits_Post(Credits __instance)
{
// Do things BuildCredits() normally does
// Fire event once finished
CreditsBuilt?.Invoke(__instance, new EventArgs());
}
}
}

View File

@ -27,23 +27,34 @@ namespace NewHorizons.Utility.Files
source._clipArrayLength = 0;
source._clipSelectionOnPlay = OWAudioSource.ClipSelectionOnPlay.MANUAL;
source.clip = clip;
NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was loaded from a file");
return;
}
catch
{
NHLogger.LogError($"Could not load file {audio}");
NHLogger.LogError($"[{nameof(AudioUtilities)}] : Could not load file {audio}");
}
}
if (EnumUtils.TryParse(audio, out AudioType type))
{
source._audioLibraryClip = type;
NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was an AudioType enum");
}
else
{
var audioClip = SearchUtilities.FindResourceOfTypeAndName<AudioClip>(audio);
if (audioClip == null) NHLogger.Log($"Couldn't find audio clip {audio}");
else source.clip = audioClip;
if (audioClip == null)
{
NHLogger.LogError($"[{nameof(AudioUtilities)}] : Couldn't find audio clip {audio}");
}
else
{
NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was an AudioClip resource");
// Else if this is set it will try to change the clip back when it starts playing
source._audioLibraryClip = AudioType.None;
source.clip = audioClip;
}
}
}

View File

@ -445,6 +445,11 @@ namespace NewHorizons.Utility
return globalMusicController._endTimesSource.clip.length;
}
public static string GetKey(this AstroObject ao)
{
return ao._name == AstroObject.Name.CustomString ? ao.GetCustomName() : ao._name.ToString();
}
public static CodeMatcher LogInstructions(this CodeMatcher matcher, string prefix)
{
matcher.InstructionEnumeration().LogInstructions(prefix);

View File

@ -65,7 +65,7 @@ namespace NewHorizons.Utility.OuterWilds
public static void RegisterCustomAstroObject(AstroObject ao)
{
var key = ao._name == AstroObject.Name.CustomString ? ao.GetCustomName() : ao._name.ToString();
var key = ao.GetKey();
if (_customAstroObjectDictionary.ContainsKey(key))
{
@ -81,7 +81,7 @@ namespace NewHorizons.Utility.OuterWilds
public static void DeregisterCustomAstroObject(AstroObject ao)
{
var key = ao._name == AstroObject.Name.CustomString ? ao.GetCustomName() : ao._name.ToString();
var key = ao.GetKey();
_customAstroObjectDictionary.Remove(key);
}

View File

@ -4,7 +4,7 @@
"author": "xen, Bwc9876, JohnCorby, MegaPiggy, and friends",
"name": "New Horizons",
"uniqueName": "xen.NewHorizons",
"version": "1.27.3",
"version": "1.27.4",
"owmlVersion": "2.12.1",
"dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ],
"conflicts": [ "PacificEngine.OW_CommonResources" ],

View File

@ -20,12 +20,12 @@ You can use [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) to t
## Asset Bundles
There is an [old unity template](https://github.com/xen-42/outer-wilds-unity-template) and a [new one](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki#outer-wilds-unity-assets)
There is an [old unity template](https://github.com/xen-42/outer-wilds-unity-template) and a [new one](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki/Tools-%E2%80%90-Outer-Wilds-Unity-Assets-repository)
The project contains ripped versions of all the game scripts, meaning you can put things like DirectionalForceVolumes in your Unity project to have artificial gravity volumes loaded right into the game.\
Either one works, but the new one has more tools and better versions of the scripts (in exchange for being invite-only).
Read [this guide](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki/Tutorials-%E2%80%90-Using-asset-bundles) on how to work with asset bundles in editor.
Read [this guide](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki/Tutorials-%E2%80%90-Using-AssetBundles) on how to work with asset bundles in editor.
## Importing a planet's surface from Unity

View File

@ -19,5 +19,5 @@ which interact poorly with the fluid detector and can mess up the movement of th
Either clear the .nhcache files or enable Debug mode to always regenerate the text cache.
## Prop placer is gone!
This is not a bug, actually. We removed prop placer because it was inconsistent and buggy, and no one in years cared enough to fix it.
Use the debug raycast button and Unity Explorer to place your props, or otherwise work in unity editor.
It has been moved to a [separate mod](https://outerwildsmods.com/mods/propplacer/).
Use it in addition to the debug raycast button and Unity Explorer to place your props, or otherwise work in unity editor.