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

View File

@ -1,3 +1,4 @@
using NewHorizons.Utility.OWML;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -16,6 +17,7 @@ namespace NewHorizons.Components.EyeOfTheUniverse
{
src.loop = false;
src.SetLocalVolume(1f);
src.Stop();
_loopSources.Add(src);
}
@ -23,6 +25,7 @@ namespace NewHorizons.Components.EyeOfTheUniverse
{
src.loop = false;
src.SetLocalVolume(1f);
src.Stop();
_finaleSources.Add(src);
}
@ -36,61 +39,85 @@ namespace NewHorizons.Components.EyeOfTheUniverse
public void TransitionToFinale()
{
_transitionToFinale = true;
}
private IEnumerator DoLoop()
{
// Initial delay to ensure audio system has time to schedule all loops for the same tick
double timeBufferWindow = 0.5;
var cosmicInflationController = FindObjectOfType<CosmicInflationController>();
// Track when the next audio (loop or finale) should play, in audio system time
double nextAudioEventTime = AudioSettings.dspTime + timeBufferWindow;
// Schedule finale for as soon as the current segment loop ends
double finaleAudioTime = _segmentEndAudioTime;
float finaleGameTime = _segmentEndGameTime;
// 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;
double segmentDuration = loopDuration / 4.0;
while (!_transitionToFinale)
{
// Play loops in sync
var loopStartTime = nextAudioEventTime;
// Cancel loop audio
foreach (var loopSrc in _loopSources)
{
loopSrc._audioSource.PlayScheduled(loopStartTime);
loopSrc._audioSource.SetScheduledEndTime(finaleAudioTime);
}
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
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>
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>
/// The dialogue condition that will trigger the traveler to start playing their instrument. Must be unique for each traveler.
/// </summary>

View File

@ -19,6 +19,36 @@ public static class EyeDetailCacher
foreach (var body in Main.BodyDict["EyeOfTheUniverse"])
{
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)
{
foreach (var detail in body.Config.Props.details)

View File

@ -180,6 +180,8 @@ namespace NewHorizons.Handlers
public static void SetUpEyeCampfireSequence()
{
if (!GetCustomEyeTravelers().Any()) return;
_eyeMusicController = new GameObject("EyeMusicController").AddComponent<EyeMusicController>();
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();
@ -196,6 +198,8 @@ namespace NewHorizons.Handlers
{
if (eyeTraveler.controller != null)
{
eyeTraveler.controller.gameObject.SetActive(false);
ArrayHelpers.Append(ref quantumCampsiteController._travelerControllers, eyeTraveler.controller);
eyeTraveler.controller.OnStartPlaying += quantumCampsiteController.OnTravelerStartPlaying;
@ -211,6 +215,7 @@ namespace NewHorizons.Handlers
if (eyeTraveler.loopAudioSource != null)
{
eyeTraveler.loopAudioSource.GetComponent<AudioSignal>().SetSignalActivation(false);
_eyeMusicController.RegisterLoopSource(eyeTraveler.loopAudioSource);
}
if (eyeTraveler.finaleAudioSource != null)
@ -227,6 +232,7 @@ namespace NewHorizons.Handlers
if (ancestorInstrumentZone == null)
{
// 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);
}
}
@ -237,11 +243,13 @@ namespace NewHorizons.Handlers
ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, instrumentZone.gameObject);
}
}
UpdateTravelerPositions();
}
public static void UpdateTravelerPositions()
{
//if (!GetCustomEyeTravelers().Any()) return;
if (!GetCustomEyeTravelers().Any()) return;
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();

View File

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

View File

@ -1,6 +1,7 @@
using HarmonyLib;
using NewHorizons.Handlers;
using NewHorizons.Utility.OWML;
using System.Linq;
namespace NewHorizons.Patches.EyeScenePatches
{
@ -11,7 +12,7 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(nameof(CosmicInflationController.UpdateFormation))]
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");
__instance._waitForCrossfade = false;

View File

@ -30,10 +30,13 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(nameof(QuantumCampsiteController.AreAllTravelersGathered))]
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);
if (!gatheredAllHearthianTravelers)
{
NHLogger.LogVerbose("");
__result = false;
return false;
}
@ -73,7 +76,7 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(nameof(QuantumCampsiteController.OnTravelerStartPlaying))]
public static void OnTravelerStartPlaying(QuantumCampsiteController __instance)
{
if (!__instance._hasJamSessionStarted)
if (!__instance._hasJamSessionStarted && EyeSceneHandler.GetCustomEyeTravelers().Any())
{
NHLogger.Log($"NH is handling Eye sequence music");
// Jam session is starting, start our custom music handler

View File

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