mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Added feature for custom credits music and scroll length (#1073)
Built a new feature to specify custom credits audio and scroll length within GameOverModule. - Added new creditsType: "custom" - Added "audio", "audioVolume", "audioLooping", and "length" to GameOverModule, which only work with custom creditsType. Some code notes: - I needed to bring an IModBehaviour down into NHGameOverManager.LoadCreditsScene() to patch in the custom audio clip, so there are some changes in other files to get it there. If there's a better way to do this, let me know :)
This commit is contained in:
commit
53d20525bd
@ -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;
|
||||
|
||||
return volume;
|
||||
}
|
||||
|
||||
@ -209,7 +209,7 @@ namespace NewHorizons.Builder.Volumes
|
||||
{
|
||||
foreach (var creditsVolume in config.Volumes.creditsVolume)
|
||||
{
|
||||
CreditsVolumeBuilder.Make(go, sector, creditsVolume);
|
||||
CreditsVolumeBuilder.Make(go, sector, creditsVolume, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,35 @@ 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.
|
||||
AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
NewHorizons/External/Modules/GameOverModule.cs
vendored
29
NewHorizons/External/Modules/GameOverModule.cs
vendored
@ -22,7 +22,34 @@ namespace NewHorizons.External.Modules
|
||||
/// Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over.
|
||||
/// Note this is a regular dialogue condition, not a persistent condition.
|
||||
/// </summary>
|
||||
public string condition;
|
||||
public string condition;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the audio file to use as custom music for the credits.
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the credits music should loop.
|
||||
/// Note: only applies when creditsType is set to "custom".
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
public bool audioLooping;
|
||||
|
||||
/// <summary>
|
||||
/// Duration of the credits scroll in seconds.
|
||||
/// Note: only applies when creditsType is set to "custom".
|
||||
/// </summary>
|
||||
[DefaultValue(120f)]
|
||||
public float length;
|
||||
|
||||
/// <summary>
|
||||
/// The type of credits that will run after the game over message is shown
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(AppData)\OuterWildsModManager\OWML\Mods\xen.NewHorizons</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user