Eye sequence fixes

This commit is contained in:
Joshua Thome 2025-01-18 17:26:02 -06:00
parent 63fbc8afeb
commit e0f79ae3ea
9 changed files with 153 additions and 58 deletions

View File

@ -18,6 +18,11 @@ namespace NewHorizons.Builder.Props
{ {
var go = DetailBuilder.Make(planetGO, sector, nhBody.Mod, info); var go = DetailBuilder.Make(planetGO, sector, nhBody.Mod, info);
if (string.IsNullOrEmpty(info.name))
{
info.name = go.name;
}
var travelerController = go.GetAddComponent<TravelerEyeController>(); var travelerController = go.GetAddComponent<TravelerEyeController>();
if (!string.IsNullOrEmpty(info.startPlayingCondition)) if (!string.IsNullOrEmpty(info.startPlayingCondition))
{ {
@ -34,6 +39,8 @@ namespace NewHorizons.Builder.Props
if (info.dialogue != null) if (info.dialogue != null)
{ {
var (dialogueTree, remoteTrigger) = DialogueBuilder.Make(planetGO, sector, info.dialogue, nhBody.Mod); var (dialogueTree, remoteTrigger) = DialogueBuilder.Make(planetGO, sector, info.dialogue, nhBody.Mod);
dialogueTree.transform.SetParent(travelerController.transform, false);
dialogueTree.transform.localPosition = Vector3.zero;
if (travelerController._dialogueTree != null) if (travelerController._dialogueTree != null)
{ {
travelerController._dialogueTree.OnStartConversation -= travelerController.OnStartConversation; travelerController._dialogueTree.OnStartConversation -= travelerController.OnStartConversation;
@ -54,17 +61,20 @@ namespace NewHorizons.Builder.Props
{ {
var signalInfo = new SignalInfo() var signalInfo = new SignalInfo()
{ {
name = info.name,
audio = info.loopAudio, audio = info.loopAudio,
detectionRadius = 0, detectionRadius = 10f,
identificationRadius = 10f, identificationRadius = 10f,
onlyAudibleToScope = false,
frequency = string.IsNullOrEmpty(info.frequency) ? "Traveler" : info.frequency, frequency = string.IsNullOrEmpty(info.frequency) ? "Traveler" : info.frequency,
parentPath = go.transform.GetPath(),
isRelativeToParent = true,
position = Vector3.up * 0.5f,
}; };
var signalGO = SignalBuilder.Make(planetGO, sector, signalInfo, nhBody.Mod); var signalGO = SignalBuilder.Make(planetGO, sector, signalInfo, nhBody.Mod);
signalGO.transform.SetParent(travelerController.transform, false);
signalGO.transform.localPosition = Vector3.zero;
var signal = signalGO.GetComponent<AudioSignal>(); var signal = signalGO.GetComponent<AudioSignal>();
travelerController._signal = signal; travelerController._signal = signal;
signal.SetSignalActivation(false);
loopAudioSource = signal.GetOWAudioSource(); loopAudioSource = signal.GetOWAudioSource();
} }
else if (travelerController._signal == null) else if (travelerController._signal == null)
@ -80,11 +90,14 @@ namespace NewHorizons.Builder.Props
{ {
audio = info.finaleAudio, audio = info.finaleAudio,
track = External.SerializableEnums.NHAudioMixerTrackName.Music, track = External.SerializableEnums.NHAudioMixerTrackName.Music,
volume = 1f,
}; };
finaleAudioSource = GeneralAudioBuilder.Make(planetGO, sector, finaleAudioInfo, nhBody.Mod); finaleAudioSource = GeneralAudioBuilder.Make(planetGO, sector, finaleAudioInfo, nhBody.Mod);
finaleAudioSource.SetTrack(finaleAudioInfo.track.ConvertToOW()); finaleAudioSource.SetTrack(finaleAudioInfo.track.ConvertToOW());
finaleAudioSource.loop = false; finaleAudioSource.loop = false;
finaleAudioSource.spatialBlend = 0f; finaleAudioSource.spatialBlend = 0f;
finaleAudioSource.playOnAwake = false;
finaleAudioSource.gameObject.SetActive(true);
} }
var travelerData = EyeSceneHandler.GetOrCreateEyeTravelerData(info.id); var travelerData = EyeSceneHandler.GetOrCreateEyeTravelerData(info.id);
@ -111,6 +124,7 @@ namespace NewHorizons.Builder.Props
go.GetAddComponent<InteractReceiver>(); go.GetAddComponent<InteractReceiver>();
var quantumInstrument = go.GetAddComponent<QuantumInstrument>(); var quantumInstrument = go.GetAddComponent<QuantumInstrument>();
quantumInstrument._gatherWithScope = info.gatherWithScope; quantumInstrument._gatherWithScope = info.gatherWithScope;
ArrayHelpers.Append(ref quantumInstrument._deactivateObjects, go);
var trigger = go.AddComponent<QuantumInstrumentTrigger>(); var trigger = go.AddComponent<QuantumInstrumentTrigger>();
trigger.gatherCondition = info.gatherCondition; trigger.gatherCondition = info.gatherCondition;
@ -124,15 +138,15 @@ namespace NewHorizons.Builder.Props
{ {
var signalInfo = new SignalInfo() var signalInfo = new SignalInfo()
{ {
name = travelerData.info.name,
audio = travelerData.info.loopAudio, audio = travelerData.info.loopAudio,
detectionRadius = 0, detectionRadius = 0,
identificationRadius = 0, identificationRadius = 0,
frequency = string.IsNullOrEmpty(travelerData.info.frequency) ? "Traveler" : travelerData.info.frequency, frequency = string.IsNullOrEmpty(travelerData.info.frequency) ? "Traveler" : travelerData.info.frequency,
parentPath = go.transform.GetPath(),
isRelativeToParent = true,
position = Vector3.zero,
}; };
var signalGO = SignalBuilder.Make(planetGO, sector, signalInfo, nhBody.Mod); var signalGO = SignalBuilder.Make(planetGO, sector, signalInfo, nhBody.Mod);
signalGO.transform.SetParent(quantumInstrument.transform, false);
signalGO.transform.localPosition = Vector3.zero;
} }
else else
{ {

View File

@ -1,3 +1,4 @@
using NewHorizons.Utility.OWML;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -16,6 +17,7 @@ namespace NewHorizons.Components.EyeOfTheUniverse
{ {
src.loop = false; src.loop = false;
src.SetLocalVolume(1f); src.SetLocalVolume(1f);
src.Stop();
_loopSources.Add(src); _loopSources.Add(src);
} }
@ -23,6 +25,7 @@ namespace NewHorizons.Components.EyeOfTheUniverse
{ {
src.loop = false; src.loop = false;
src.SetLocalVolume(1f); src.SetLocalVolume(1f);
src.Stop();
_finaleSources.Add(src); _finaleSources.Add(src);
} }
@ -36,61 +39,85 @@ namespace NewHorizons.Components.EyeOfTheUniverse
public void TransitionToFinale() public void TransitionToFinale()
{ {
_transitionToFinale = true; _transitionToFinale = true;
}
private IEnumerator DoLoop() var cosmicInflationController = FindObjectOfType<CosmicInflationController>();
{
// Initial delay to ensure audio system has time to schedule all loops for the same tick
double timeBufferWindow = 0.5;
// Track when the next audio (loop or finale) should play, in audio system time // Schedule finale for as soon as the current segment loop ends
double nextAudioEventTime = AudioSettings.dspTime + timeBufferWindow; double finaleAudioTime = _segmentEndAudioTime;
float finaleGameTime = _segmentEndGameTime;
// Determine timing using the first loop audio clip (should be Riebeck's banjo loop) // Cancel loop audio
var referenceLoopClip = _loopSources.First().clip; foreach (var loopSrc in _loopSources)
double loopDuration = referenceLoopClip.samples / (double)referenceLoopClip.frequency;
double segmentDuration = loopDuration / 4.0;
while (!_transitionToFinale)
{ {
// Play loops in sync loopSrc._audioSource.SetScheduledEndTime(finaleAudioTime);
var loopStartTime = nextAudioEventTime;
foreach (var loopSrc in _loopSources)
{
loopSrc._audioSource.PlayScheduled(loopStartTime);
}
nextAudioEventTime += loopDuration;
// Handle loop segments (the current musical measure will always finish playing before transitioning to the finale)
for (int i = 0; i < 4; i++)
{
// Interrupting the upcoming segment for the finale
if (_transitionToFinale)
{
// End the loop at the start time of the upcoming segment
var loopStopTime = loopStartTime + segmentDuration * (i + 1);
// Cancel scheduled upcoming loop
foreach (var loopSrc in _loopSources)
{
loopSrc._audioSource.SetScheduledEndTime(loopStopTime);
}
// Schedule finale for as soon as the loop ends
nextAudioEventTime = loopStopTime;
break;
}
// Wait until shortly before the next segment (`nextAudioEventTime` will be ahead of current time by `timeBufferWindow`)
yield return new WaitForSecondsRealtime((float)segmentDuration);
}
} }
// Set quantum sphere inflation timer
var finaleDuration = cosmicInflationController._travelerFinaleSource.clip.length;
cosmicInflationController._startFormationTime = Time.time;
cosmicInflationController._finishFormationTime = finaleGameTime + finaleDuration - 4f;
// Play finale in sync // Play finale in sync
foreach (var finaleSrc in _finaleSources) foreach (var finaleSrc in _finaleSources)
{ {
finaleSrc._audioSource.PlayScheduled(nextAudioEventTime); finaleSrc._audioSource.PlayScheduled(finaleAudioTime);
}
}
// Delay between game logic and audio to ensure audio system has time to schedule all loops for the same tick
const double TIME_BUFFER_WINDOW = 0.5;
private double _segmentEndAudioTime;
private float _segmentEndGameTime;
private IEnumerator DoLoop()
{
// Determine timing using the first loop audio clip (should be Riebeck's banjo loop)
var referenceLoopClip = _loopSources.First().clip;
double loopDuration = referenceLoopClip.samples / (double)referenceLoopClip.frequency;
// Vanilla audio divides the loop into 4 segments, but that actually causes weird key shifting during the crossfade
int segmentCount = 2;
double segmentDuration = loopDuration / segmentCount;
// Track when the next loop will play, in both audio system time and game time
double nextLoopAudioTime = AudioSettings.dspTime + TIME_BUFFER_WINDOW;
float nextLoopGameTime = Time.time + (float)TIME_BUFFER_WINDOW;
while (!_transitionToFinale)
{
// Play loops in sync
double loopStartAudioTime = nextLoopAudioTime;
float loopStartGameTime = nextLoopGameTime;
foreach (var loopSrc in _loopSources)
{
if (!loopSrc.gameObject.activeInHierarchy) continue;
if (loopSrc.loop) continue;
// We only need to schedule once and then Unity will loop it for us
loopSrc._audioSource.PlayScheduled(loopStartAudioTime);
loopSrc.loop = true;
}
// Schedule next loop
nextLoopAudioTime += loopDuration;
nextLoopGameTime += (float)loopDuration;
// Track loop segment timing (the current musical verse should always finish playing before the finale)
for (int i = 0; i < segmentCount; i++)
{
_segmentEndAudioTime = loopStartAudioTime + segmentDuration * (i + 1);
_segmentEndGameTime = loopStartGameTime + (float)(segmentDuration * (i + 1));
// Wait until the next segment
while (Time.time < _segmentEndGameTime && !_transitionToFinale)
{
yield return null;
}
// Interrupt the remaining segments for the finale
if (_transitionToFinale) break;
}
} }
} }
} }

View File

@ -11,6 +11,11 @@ namespace NewHorizons.External.Modules.Props.EyeOfTheUniverse
/// </summary> /// </summary>
public string id; public string id;
/// <summary>
/// The name to display for this traveler's signals. Defaults to the name of the detail.
/// </summary>
public string name;
/// <summary> /// <summary>
/// The dialogue condition that will trigger the traveler to start playing their instrument. Must be unique for each traveler. /// The dialogue condition that will trigger the traveler to start playing their instrument. Must be unique for each traveler.
/// </summary> /// </summary>

View File

@ -19,6 +19,36 @@ public static class EyeDetailCacher
foreach (var body in Main.BodyDict["EyeOfTheUniverse"]) foreach (var body in Main.BodyDict["EyeOfTheUniverse"])
{ {
NHLogger.LogVerbose($"{nameof(EyeDetailCacher)}: {body.Config.name}"); NHLogger.LogVerbose($"{nameof(EyeDetailCacher)}: {body.Config.name}");
if (body.Config?.EyeOfTheUniverse?.eyeTravelers != null)
{
foreach (var detail in body.Config.EyeOfTheUniverse.eyeTravelers)
{
if (!string.IsNullOrEmpty(detail.assetBundle)) continue;
AddPathToCache(detail.path);
}
}
if (body.Config?.EyeOfTheUniverse?.instrumentZones != null)
{
foreach (var detail in body.Config.EyeOfTheUniverse.instrumentZones)
{
if (!string.IsNullOrEmpty(detail.assetBundle)) continue;
AddPathToCache(detail.path);
}
}
if (body.Config?.EyeOfTheUniverse?.quantumInstruments != null)
{
foreach (var detail in body.Config.EyeOfTheUniverse.quantumInstruments)
{
if (!string.IsNullOrEmpty(detail.assetBundle)) continue;
AddPathToCache(detail.path);
}
}
if (body.Config?.Props?.details != null) if (body.Config?.Props?.details != null)
{ {
foreach (var detail in body.Config.Props.details) foreach (var detail in body.Config.Props.details)

View File

@ -180,6 +180,8 @@ namespace NewHorizons.Handlers
public static void SetUpEyeCampfireSequence() public static void SetUpEyeCampfireSequence()
{ {
if (!GetCustomEyeTravelers().Any()) return;
_eyeMusicController = new GameObject("EyeMusicController").AddComponent<EyeMusicController>(); _eyeMusicController = new GameObject("EyeMusicController").AddComponent<EyeMusicController>();
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>(); var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();
@ -196,6 +198,8 @@ namespace NewHorizons.Handlers
{ {
if (eyeTraveler.controller != null) if (eyeTraveler.controller != null)
{ {
eyeTraveler.controller.gameObject.SetActive(false);
ArrayHelpers.Append(ref quantumCampsiteController._travelerControllers, eyeTraveler.controller); ArrayHelpers.Append(ref quantumCampsiteController._travelerControllers, eyeTraveler.controller);
eyeTraveler.controller.OnStartPlaying += quantumCampsiteController.OnTravelerStartPlaying; eyeTraveler.controller.OnStartPlaying += quantumCampsiteController.OnTravelerStartPlaying;
@ -211,6 +215,7 @@ namespace NewHorizons.Handlers
if (eyeTraveler.loopAudioSource != null) if (eyeTraveler.loopAudioSource != null)
{ {
eyeTraveler.loopAudioSource.GetComponent<AudioSignal>().SetSignalActivation(false);
_eyeMusicController.RegisterLoopSource(eyeTraveler.loopAudioSource); _eyeMusicController.RegisterLoopSource(eyeTraveler.loopAudioSource);
} }
if (eyeTraveler.finaleAudioSource != null) if (eyeTraveler.finaleAudioSource != null)
@ -227,6 +232,7 @@ namespace NewHorizons.Handlers
if (ancestorInstrumentZone == null) if (ancestorInstrumentZone == null)
{ {
// Quantum instrument is not a child of an instrument zone, so treat it like its own zone // Quantum instrument is not a child of an instrument zone, so treat it like its own zone
quantumInstrument.gameObject.SetActive(false);
ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, quantumInstrument.gameObject); ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, quantumInstrument.gameObject);
} }
} }
@ -237,11 +243,13 @@ namespace NewHorizons.Handlers
ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, instrumentZone.gameObject); ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, instrumentZone.gameObject);
} }
} }
UpdateTravelerPositions();
} }
public static void UpdateTravelerPositions() public static void UpdateTravelerPositions()
{ {
//if (!GetCustomEyeTravelers().Any()) return; if (!GetCustomEyeTravelers().Any()) return;
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>(); var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();

View File

@ -438,7 +438,6 @@ namespace NewHorizons
_playerAwake = true; _playerAwake = true;
EyeSceneHandler.Init(); EyeSceneHandler.Init();
EyeSceneHandler.OnSceneLoad(); EyeSceneHandler.OnSceneLoad();
EyeSceneHandler.SetUpEyeCampfireSequence();
} }
if (isSolarSystem || isEyeOfTheUniverse) if (isSolarSystem || isEyeOfTheUniverse)
@ -537,6 +536,8 @@ namespace NewHorizons
IsWarpingFromVessel = false; IsWarpingFromVessel = false;
DidWarpFromVessel = false; DidWarpFromVessel = false;
DidWarpFromShip = false; DidWarpFromShip = false;
EyeSceneHandler.SetUpEyeCampfireSequence();
} }
//Stop starfield from disappearing when there is no lights //Stop starfield from disappearing when there is no lights

View File

@ -1,6 +1,7 @@
using HarmonyLib; using HarmonyLib;
using NewHorizons.Handlers; using NewHorizons.Handlers;
using NewHorizons.Utility.OWML; using NewHorizons.Utility.OWML;
using System.Linq;
namespace NewHorizons.Patches.EyeScenePatches namespace NewHorizons.Patches.EyeScenePatches
{ {
@ -11,7 +12,7 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(nameof(CosmicInflationController.UpdateFormation))] [HarmonyPatch(nameof(CosmicInflationController.UpdateFormation))]
public static void CosmicInflationController_UpdateFormation(CosmicInflationController __instance) public static void CosmicInflationController_UpdateFormation(CosmicInflationController __instance)
{ {
if (__instance._waitForCrossfade) if (__instance._waitForCrossfade && EyeSceneHandler.GetCustomEyeTravelers().Any())
{ {
NHLogger.Log($"Hijacking finale cross-fade, NH will handle it"); NHLogger.Log($"Hijacking finale cross-fade, NH will handle it");
__instance._waitForCrossfade = false; __instance._waitForCrossfade = false;

View File

@ -30,10 +30,13 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(nameof(QuantumCampsiteController.AreAllTravelersGathered))] [HarmonyPatch(nameof(QuantumCampsiteController.AreAllTravelersGathered))]
public static bool QuantumCampsiteController_AreAllTravelersGathered(QuantumCampsiteController __instance, ref bool __result) public static bool QuantumCampsiteController_AreAllTravelersGathered(QuantumCampsiteController __instance, ref bool __result)
{ {
if (!EyeSceneHandler.GetCustomEyeTravelers().Any())
{
return true;
}
bool gatheredAllHearthianTravelers = __instance._travelerControllers.Take(4).All(t => t.gameObject.activeInHierarchy); bool gatheredAllHearthianTravelers = __instance._travelerControllers.Take(4).All(t => t.gameObject.activeInHierarchy);
if (!gatheredAllHearthianTravelers) if (!gatheredAllHearthianTravelers)
{ {
NHLogger.LogVerbose("");
__result = false; __result = false;
return false; return false;
} }
@ -73,7 +76,7 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(nameof(QuantumCampsiteController.OnTravelerStartPlaying))] [HarmonyPatch(nameof(QuantumCampsiteController.OnTravelerStartPlaying))]
public static void OnTravelerStartPlaying(QuantumCampsiteController __instance) public static void OnTravelerStartPlaying(QuantumCampsiteController __instance)
{ {
if (!__instance._hasJamSessionStarted) if (!__instance._hasJamSessionStarted && EyeSceneHandler.GetCustomEyeTravelers().Any())
{ {
NHLogger.Log($"NH is handling Eye sequence music"); NHLogger.Log($"NH is handling Eye sequence music");
// Jam session is starting, start our custom music handler // Jam session is starting, start our custom music handler

View File

@ -1,4 +1,6 @@
using HarmonyLib; using HarmonyLib;
using NewHorizons.Handlers;
using System.Linq;
namespace NewHorizons.Patches.EyeScenePatches namespace NewHorizons.Patches.EyeScenePatches
{ {
@ -9,6 +11,10 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(nameof(TravelerEyeController.OnStartCosmicJamSession))] [HarmonyPatch(nameof(TravelerEyeController.OnStartCosmicJamSession))]
public static bool TravelerEyeController_OnStartCosmicJamSession(TravelerEyeController __instance) public static bool TravelerEyeController_OnStartCosmicJamSession(TravelerEyeController __instance)
{ {
if (!EyeSceneHandler.GetCustomEyeTravelers().Any())
{
return true;
}
// Not starting the loop audio here; EyeMusicController will handle that // Not starting the loop audio here; EyeMusicController will handle that
__instance._signal.GetOWAudioSource().SetLocalVolume(0f); __instance._signal.GetOWAudioSource().SetLocalVolume(0f);
return false; return false;