Merge branch 'dev' into dockedRafts

This commit is contained in:
Noah Pilarski 2025-04-18 23:56:48 -04:00
commit 6d480546c8
15 changed files with 156 additions and 26 deletions

View File

@ -473,10 +473,14 @@ namespace NewHorizons.Builder.Props
{ {
// These flood toggles are to disable flooded docks on the Stranger // These flood toggles are to disable flooded docks on the Stranger
// Presumably the user isn't making one of those // 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); 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 _autoPrefab;
private static GameObject _visionTorchDetectorPrefab; private static GameObject _visionTorchDetectorPrefab;
private static GameObject _standingVisionTorchPrefab; private static GameObject _standingVisionTorchPrefab;
private static GameObject _standingVisionTorchCleanPrefab;
private static readonly int EmissionMap = Shader.PropertyToID("_EmissionMap"); private static readonly int EmissionMap = Shader.PropertyToID("_EmissionMap");
private static bool _isInit; private static bool _isInit;
@ -90,13 +91,28 @@ namespace NewHorizons.Builder.Props
_visionTorchDetectorPrefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true; _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) 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(); _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) if (_standingVisionTorchPrefab == null)
NHLogger.LogWarning($"Tried to make standing vision torch prefab but couldn't. Do you have the DLC installed?"); NHLogger.LogWarning($"Tried to make standing vision torch prefab but couldn't. Do you have the DLC installed?");
else else
{
_standingVisionTorchPrefab.AddComponent<DestroyOnDLC>()._destroyOnDLCNotOwned = true; _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(); InitPrefabs();
if (_standingVisionTorchPrefab == null) return null; if (_standingVisionTorchPrefab == null || _standingVisionTorchCleanPrefab == null) return null;
// Spawn the torch itself // 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) if (standingTorch == null)
{ {

View File

@ -471,7 +471,7 @@ namespace NewHorizons.Builder.Props.TranslatorText
if (info.arcInfo != null && info.arcInfo.Count() != dict.Values.Count()) if (info.arcInfo != null && info.arcInfo.Count() != dict.Values.Count())
{ {
NHLogger.LogError($"Can't make NomaiWallText, arcInfo length [{info.arcInfo.Count()}] doesn't equal number of TextBlocks [{dict.Values.Count()}] in the xml"); NHLogger.LogError($"Can't make NomaiWallText [{info.xmlFile}], arcInfo length [{info.arcInfo.Count()}] doesn't equal number of TextBlocks [{dict.Values.Count()}] in the xml");
return; return;
} }

View File

@ -1,5 +1,6 @@
using NewHorizons.Components.Volumes; using NewHorizons.Components.Volumes;
using NewHorizons.External.Modules.Volumes.VolumeInfos; using NewHorizons.External.Modules.Volumes.VolumeInfos;
using OWML.Common;
using OWML.Utils; using OWML.Utils;
using UnityEngine; using UnityEngine;
@ -7,12 +8,13 @@ namespace NewHorizons.Builder.Volumes
{ {
internal static class CreditsVolumeBuilder 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); var volume = VolumeBuilder.Make<LoadCreditsVolume>(planetGO, sector, info);
volume.gameOver = info.gameOver; volume.gameOver = info.gameOver;
volume.deathType = info.deathType == null ? null : EnumUtils.Parse(info.deathType.ToString(), DeathType.Default); volume.deathType = info.deathType == null ? null : EnumUtils.Parse(info.deathType.ToString(), DeathType.Default);
volume.mod = mod;
return volume; return volume;
} }

View File

@ -209,7 +209,7 @@ namespace NewHorizons.Builder.Volumes
{ {
foreach (var creditsVolume in config.Volumes.creditsVolume) 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.Modules;
using NewHorizons.External.SerializableEnums; using NewHorizons.External.SerializableEnums;
using NewHorizons.Handlers; using NewHorizons.Handlers;
using NewHorizons.Patches.CreditsScenePatches;
using NewHorizons.Utility.Files;
using NewHorizons.Utility.OWML; using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -15,14 +19,14 @@ namespace NewHorizons.Components
/// Mod unique id to game over module list /// Mod unique id to game over module list
/// Done as a dictionary so that Reload Configs can overwrite entries per mod /// Done as a dictionary so that Reload Configs can overwrite entries per mod
/// </summary> /// </summary>
public static Dictionary<string, GameOverModule[]> gameOvers = new(); public static Dictionary<IModBehaviour, GameOverModule[]> gameOvers = new();
public static NHGameOverManager Instance { get; private set; } public static NHGameOverManager Instance { get; private set; }
private GameOverController _gameOverController; private GameOverController _gameOverController;
private PlayerCameraEffectController _playerCameraEffectController; private PlayerCameraEffectController _playerCameraEffectController;
private GameOverModule[] _gameOvers; private (IModBehaviour mod, GameOverModule gameOver)[] _gameOvers;
private bool _gameOverSequenceStarted; private bool _gameOverSequenceStarted;
@ -36,25 +40,35 @@ namespace NewHorizons.Components
_gameOverController = FindObjectOfType<GameOverController>(); _gameOverController = FindObjectOfType<GameOverController>();
_playerCameraEffectController = FindObjectOfType<PlayerCameraEffectController>(); _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() public void TryHijackDeathSequence()
{ {
var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.condition) && DialogueConditionManager.SharedInstance.GetConditionState(x.condition)); var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.gameOver.condition)
if (!_gameOverSequenceStarted && gameOver != null && !Locator.GetDeathManager()._finishedDLC) && 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; _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); OWInput.ChangeInputMode(InputMode.None);
ReticleController.Hide(); ReticleController.Hide();
@ -104,12 +118,12 @@ namespace NewHorizons.Components
yield return new WaitUntil(ReadytoLoadCreditsScene); yield return new WaitUntil(ReadytoLoadCreditsScene);
} }
LoadCreditsScene(gameOver); LoadCreditsScene(gameOver, mod);
} }
private bool ReadytoLoadCreditsScene() => _gameOverController._fadedOutText && _gameOverController._textAnimator.IsComplete(); 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}"); NHLogger.LogVerbose($"Load credits {gameOver.creditsType}");
@ -125,6 +139,9 @@ namespace NewHorizons.Components
TimelineObliterationController.s_hasRealityEnded = true; TimelineObliterationController.s_hasRealityEnded = true;
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack); LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack);
break; break;
case NHCreditsType.Custom:
LoadCustomCreditsScene(gameOver, mod);
break;
default: default:
// GameOverController disables post processing // GameOverController disables post processing
_gameOverController._flashbackCamera.postProcessing.enabled = true; _gameOverController._flashbackCamera.postProcessing.enabled = true;
@ -134,5 +151,42 @@ namespace NewHorizons.Components
break; 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 NewHorizons.External.Modules;
using OWML.Common;
using UnityEngine; using UnityEngine;
@ -8,12 +9,13 @@ namespace NewHorizons.Components.Volumes
{ {
public GameOverModule gameOver; public GameOverModule gameOver;
public DeathType? deathType; public DeathType? deathType;
public IModBehaviour mod;
public override void OnTriggerVolumeEntry(GameObject hitObj) public override void OnTriggerVolumeEntry(GameObject hitObj)
{ {
if (hitObj.CompareTag("PlayerDetector") && enabled && (string.IsNullOrEmpty(gameOver.condition) || DialogueConditionManager.SharedInstance.GetConditionState(gameOver.condition))) 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> /// </summary>
public string condition; 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> /// <summary>
/// The type of credits that will run after the game over message is shown /// The type of credits that will run after the game over message is shown
/// </summary> /// </summary>

View File

@ -82,7 +82,7 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
[DefaultValue("sevenSlides")] public SlideReelType reelModel = SlideReelType.SevenSlides; [DefaultValue("sevenSlides")] public SlideReelType reelModel = SlideReelType.SevenSlides;
/// <summary> /// <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> /// </summary>
[DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique; [DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique;

View File

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

View File

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

View File

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

View File

@ -1,17 +1,30 @@
using HarmonyLib; using HarmonyLib;
using NewHorizons.Handlers; using NewHorizons.Handlers;
using System;
namespace NewHorizons.Patches.CreditsScenePatches namespace NewHorizons.Patches.CreditsScenePatches
{ {
[HarmonyPatch(typeof(Credits))] [HarmonyPatch(typeof(Credits))]
public static class CreditsPatches public static class CreditsPatches
{ {
public static event EventHandler CreditsBuilt; // Used in NHGameOverManager to patch credits music and scroll speed
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(nameof(Credits.Start))] [HarmonyPatch(nameof(Credits.Start))]
public static void Credits_Start(Credits __instance) public static void Credits_Start(Credits __instance)
{ {
CreditsHandler.AddCredits(__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._clipArrayLength = 0;
source._clipSelectionOnPlay = OWAudioSource.ClipSelectionOnPlay.MANUAL; source._clipSelectionOnPlay = OWAudioSource.ClipSelectionOnPlay.MANUAL;
source.clip = clip; source.clip = clip;
NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was loaded from a file");
return; return;
} }
catch catch
{ {
NHLogger.LogError($"Could not load file {audio}"); NHLogger.LogError($"[{nameof(AudioUtilities)}] : Could not load file {audio}");
} }
} }
if (EnumUtils.TryParse(audio, out AudioType type)) if (EnumUtils.TryParse(audio, out AudioType type))
{ {
source._audioLibraryClip = type; source._audioLibraryClip = type;
NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was an AudioType enum");
} }
else else
{ {
var audioClip = SearchUtilities.FindResourceOfTypeAndName<AudioClip>(audio); var audioClip = SearchUtilities.FindResourceOfTypeAndName<AudioClip>(audio);
if (audioClip == null) NHLogger.Log($"Couldn't find audio clip {audio}"); if (audioClip == null)
else source.clip = audioClip; {
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

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