mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Eye sequence props first pass
This commit is contained in:
parent
ffe41747fb
commit
63fbc8afeb
187
NewHorizons/Builder/Props/EyeOfTheUniverseBuilder.cs
Normal file
187
NewHorizons/Builder/Props/EyeOfTheUniverseBuilder.cs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
using NewHorizons.Builder.Props.Audio;
|
||||||
|
using NewHorizons.Components.EyeOfTheUniverse;
|
||||||
|
using NewHorizons.External;
|
||||||
|
using NewHorizons.External.Modules;
|
||||||
|
using NewHorizons.External.Modules.Props.Audio;
|
||||||
|
using NewHorizons.External.Modules.Props.EyeOfTheUniverse;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
using NewHorizons.Utility;
|
||||||
|
using NewHorizons.Utility.OuterWilds;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NewHorizons.Builder.Props
|
||||||
|
{
|
||||||
|
public static class EyeOfTheUniverseBuilder
|
||||||
|
{
|
||||||
|
public static TravelerEyeController MakeEyeTraveler(GameObject planetGO, Sector sector, EyeTravelerInfo info, NewHorizonsBody nhBody)
|
||||||
|
{
|
||||||
|
var go = DetailBuilder.Make(planetGO, sector, nhBody.Mod, info);
|
||||||
|
|
||||||
|
var travelerController = go.GetAddComponent<TravelerEyeController>();
|
||||||
|
if (!string.IsNullOrEmpty(info.startPlayingCondition))
|
||||||
|
{
|
||||||
|
travelerController._startPlayingCondition = info.startPlayingCondition;
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(travelerController._startPlayingCondition))
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Eye Traveler with ID \"{info.id}\" does not have a Start Playing condition set");
|
||||||
|
}
|
||||||
|
if (travelerController._animator == null)
|
||||||
|
{
|
||||||
|
travelerController._animator = go.GetComponentInChildren<Animator>();
|
||||||
|
}
|
||||||
|
if (info.dialogue != null)
|
||||||
|
{
|
||||||
|
var (dialogueTree, remoteTrigger) = DialogueBuilder.Make(planetGO, sector, info.dialogue, nhBody.Mod);
|
||||||
|
if (travelerController._dialogueTree != null)
|
||||||
|
{
|
||||||
|
travelerController._dialogueTree.OnStartConversation -= travelerController.OnStartConversation;
|
||||||
|
travelerController._dialogueTree.OnEndConversation -= travelerController.OnEndConversation;
|
||||||
|
}
|
||||||
|
travelerController._dialogueTree = dialogueTree;
|
||||||
|
travelerController._dialogueTree.OnStartConversation += travelerController.OnStartConversation;
|
||||||
|
travelerController._dialogueTree.OnEndConversation += travelerController.OnEndConversation;
|
||||||
|
}
|
||||||
|
else if (travelerController._dialogueTree == null)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Eye Traveler with ID \"{info.id}\" does not have any dialogue set");
|
||||||
|
}
|
||||||
|
|
||||||
|
OWAudioSource loopAudioSource = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.loopAudio))
|
||||||
|
{
|
||||||
|
var signalInfo = new SignalInfo()
|
||||||
|
{
|
||||||
|
audio = info.loopAudio,
|
||||||
|
detectionRadius = 0,
|
||||||
|
identificationRadius = 10f,
|
||||||
|
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 signal = signalGO.GetComponent<AudioSignal>();
|
||||||
|
travelerController._signal = signal;
|
||||||
|
loopAudioSource = signal.GetOWAudioSource();
|
||||||
|
}
|
||||||
|
else if (travelerController._signal == null)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Eye Traveler with ID \"{info.id}\" does not have any loop audio set");
|
||||||
|
}
|
||||||
|
|
||||||
|
OWAudioSource finaleAudioSource = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.finaleAudio))
|
||||||
|
{
|
||||||
|
var finaleAudioInfo = new AudioSourceInfo()
|
||||||
|
{
|
||||||
|
audio = info.finaleAudio,
|
||||||
|
track = External.SerializableEnums.NHAudioMixerTrackName.Music,
|
||||||
|
};
|
||||||
|
finaleAudioSource = GeneralAudioBuilder.Make(planetGO, sector, finaleAudioInfo, nhBody.Mod);
|
||||||
|
finaleAudioSource.SetTrack(finaleAudioInfo.track.ConvertToOW());
|
||||||
|
finaleAudioSource.loop = false;
|
||||||
|
finaleAudioSource.spatialBlend = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var travelerData = EyeSceneHandler.GetOrCreateEyeTravelerData(info.id);
|
||||||
|
travelerData.info = info;
|
||||||
|
travelerData.controller = travelerController;
|
||||||
|
travelerData.loopAudioSource = loopAudioSource;
|
||||||
|
travelerData.finaleAudioSource = finaleAudioSource;
|
||||||
|
|
||||||
|
return travelerController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QuantumInstrument MakeQuantumInstrument(GameObject planetGO, Sector sector, QuantumInstrumentInfo info, NewHorizonsBody nhBody)
|
||||||
|
{
|
||||||
|
var go = DetailBuilder.Make(planetGO, sector, nhBody.Mod, info);
|
||||||
|
go.layer = Layer.Interactible;
|
||||||
|
if (info.interactRadius > 0f)
|
||||||
|
{
|
||||||
|
var collider = go.AddComponent<SphereCollider>();
|
||||||
|
collider.radius = info.interactRadius;
|
||||||
|
collider.isTrigger = true;
|
||||||
|
go.GetAddComponent<OWCollider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
go.GetAddComponent<InteractReceiver>();
|
||||||
|
var quantumInstrument = go.GetAddComponent<QuantumInstrument>();
|
||||||
|
quantumInstrument._gatherWithScope = info.gatherWithScope;
|
||||||
|
|
||||||
|
var trigger = go.AddComponent<QuantumInstrumentTrigger>();
|
||||||
|
trigger.gatherCondition = info.gatherCondition;
|
||||||
|
|
||||||
|
var travelerData = EyeSceneHandler.GetOrCreateEyeTravelerData(info.id);
|
||||||
|
travelerData.quantumInstruments.Add(quantumInstrument);
|
||||||
|
|
||||||
|
if (travelerData.info != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(travelerData.info.loopAudio))
|
||||||
|
{
|
||||||
|
var signalInfo = new SignalInfo()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Eye Traveler with ID \"{info.id}\" does not have any loop audio set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Quantum instrument with ID \"{info.id}\" has no matching eye traveler");
|
||||||
|
}
|
||||||
|
|
||||||
|
return quantumInstrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InstrumentZone MakeInstrumentZone(GameObject planetGO, Sector sector, InstrumentZoneInfo info, NewHorizonsBody nhBody)
|
||||||
|
{
|
||||||
|
var go = DetailBuilder.Make(planetGO, sector, nhBody.Mod, info);
|
||||||
|
|
||||||
|
var instrumentZone = go.AddComponent<InstrumentZone>();
|
||||||
|
|
||||||
|
var travelerData = EyeSceneHandler.GetOrCreateEyeTravelerData(info.id);
|
||||||
|
travelerData.instrumentZones.Add(instrumentZone);
|
||||||
|
|
||||||
|
return instrumentZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Make(GameObject go, Sector sector, EyeOfTheUniverseModule module, NewHorizonsBody nhBody)
|
||||||
|
{
|
||||||
|
if (module.eyeTravelers != null)
|
||||||
|
{
|
||||||
|
foreach (var info in module.eyeTravelers)
|
||||||
|
{
|
||||||
|
MakeEyeTraveler(go, sector, info, nhBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (module.instrumentZones != null)
|
||||||
|
{
|
||||||
|
foreach (var info in module.instrumentZones)
|
||||||
|
{
|
||||||
|
MakeInstrumentZone(go, sector, info, nhBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (module.quantumInstruments != null)
|
||||||
|
{
|
||||||
|
foreach (var info in module.quantumInstruments)
|
||||||
|
{
|
||||||
|
MakeQuantumInstrument(go, sector, info, nhBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NewHorizons.Components.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
public class EyeMusicController : MonoBehaviour
|
||||||
|
{
|
||||||
|
private List<OWAudioSource> _loopSources = new();
|
||||||
|
private List<OWAudioSource> _finaleSources = new();
|
||||||
|
private bool _transitionToFinale;
|
||||||
|
private bool _isPlaying;
|
||||||
|
|
||||||
|
public void RegisterLoopSource(OWAudioSource src)
|
||||||
|
{
|
||||||
|
src.loop = false;
|
||||||
|
src.SetLocalVolume(1f);
|
||||||
|
_loopSources.Add(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFinaleSource(OWAudioSource src)
|
||||||
|
{
|
||||||
|
src.loop = false;
|
||||||
|
src.SetLocalVolume(1f);
|
||||||
|
_finaleSources.Add(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartPlaying()
|
||||||
|
{
|
||||||
|
if (_isPlaying) return;
|
||||||
|
_isPlaying = true;
|
||||||
|
StartCoroutine(DoLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Track when the next audio (loop or finale) should play, in audio system time
|
||||||
|
double nextAudioEventTime = AudioSettings.dspTime + timeBufferWindow;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play finale in sync
|
||||||
|
foreach (var finaleSrc in _finaleSources)
|
||||||
|
{
|
||||||
|
finaleSrc._audioSource.PlayScheduled(nextAudioEventTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NewHorizons.Components.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
public class InstrumentZone : MonoBehaviour
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NewHorizons.Components.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
public class QuantumInstrumentTrigger : MonoBehaviour
|
||||||
|
{
|
||||||
|
public string gatherCondition;
|
||||||
|
|
||||||
|
private QuantumInstrument _quantumInstrument;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_quantumInstrument = GetComponent<QuantumInstrument>();
|
||||||
|
_quantumInstrument.OnFinishGather += OnFinishGather;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
_quantumInstrument.OnFinishGather -= OnFinishGather;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFinishGather()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(gatherCondition))
|
||||||
|
{
|
||||||
|
DialogueConditionManager.SharedInstance.SetConditionState(gatherCondition, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
NewHorizons/External/Configs/PlanetConfig.cs
vendored
5
NewHorizons/External/Configs/PlanetConfig.cs
vendored
@ -102,6 +102,11 @@ namespace NewHorizons.External.Configs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DreamModule Dream;
|
public DreamModule Dream;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add features exclusive to the Eye of the Universe scene
|
||||||
|
/// </summary>
|
||||||
|
public EyeOfTheUniverseModule EyeOfTheUniverse;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make this body into a focal point (barycenter)
|
/// Make this body into a focal point (barycenter)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
24
NewHorizons/External/Modules/EyeOfTheUniverseModule.cs
vendored
Normal file
24
NewHorizons/External/Modules/EyeOfTheUniverseModule.cs
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using NewHorizons.External.Modules.Props.EyeOfTheUniverse;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NewHorizons.External.Modules
|
||||||
|
{
|
||||||
|
[JsonObject]
|
||||||
|
public class EyeOfTheUniverseModule
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add custom travelers to the campfire sequence
|
||||||
|
/// </summary>
|
||||||
|
public EyeTravelerInfo[] eyeTravelers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add instrument zones which contain puzzles to gather a quantum instrument. You can parent other props to these with `parentPath`
|
||||||
|
/// </summary>
|
||||||
|
public InstrumentZoneInfo[] instrumentZones;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add quantum instruments which cause their associated eye traveler to appear and instrument zones to disappear
|
||||||
|
/// </summary>
|
||||||
|
public QuantumInstrumentInfo[] quantumInstruments;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
NewHorizons/External/Modules/Props/EyeOfTheUniverse/EyeTravelerInfo.cs
vendored
Normal file
45
NewHorizons/External/Modules/Props/EyeOfTheUniverse/EyeTravelerInfo.cs
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using NewHorizons.External.Modules.Props.Dialogue;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NewHorizons.External.Modules.Props.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
[JsonObject]
|
||||||
|
public class EyeTravelerInfo : DetailInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A unique ID to associate this traveler with their corresponding quantum instruments and instrument zones. Must be unique for each traveler.
|
||||||
|
/// </summary>
|
||||||
|
public string id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The dialogue condition that will trigger the traveler to start playing their instrument. Must be unique for each traveler.
|
||||||
|
/// </summary>
|
||||||
|
public string startPlayingCondition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If specified, this dialogue condition must be set for the traveler to participate in the campfire song. Otherwise, the song will be able to start without them.
|
||||||
|
/// </summary>
|
||||||
|
public string participatingCondition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The audio to use for the traveler while playing around the campfire (and also for their paired quantum instrument). It should be 16 measures at 92 BPM (approximately 42 seconds long). Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
|
||||||
|
/// </summary>
|
||||||
|
public string loopAudio;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The audio to use for the traveler during the finale of the campfire song. It should be 8 measures of the main loop at 92 BPM followed by 2 measures of fade-out (approximately 26 seconds long in total). Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
|
||||||
|
/// </summary>
|
||||||
|
public string finaleAudio;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The frequency ID of the signal emitted by the traveler. The built-in game values are `Default`, `Traveler`, `Quantum`, `EscapePod`,
|
||||||
|
/// `Statue`, `WarpCore`, `HideAndSeek`, and `Radio`. Defaults to `Traveler`. You can also put a custom value.
|
||||||
|
/// </summary>
|
||||||
|
public string frequency;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The dialogue to use for this traveler. Omit this or set it to null if your traveler already has valid dialogue.
|
||||||
|
/// </summary>
|
||||||
|
public DialogueInfo dialogue;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
NewHorizons/External/Modules/Props/EyeOfTheUniverse/InstrumentZoneInfo.cs
vendored
Normal file
13
NewHorizons/External/Modules/Props/EyeOfTheUniverse/InstrumentZoneInfo.cs
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NewHorizons.External.Modules.Props.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
[JsonObject]
|
||||||
|
public class InstrumentZoneInfo : DetailInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The unique ID of the Eye Traveler associated with this instrument zone.
|
||||||
|
/// </summary>
|
||||||
|
public string id;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
NewHorizons/External/Modules/Props/EyeOfTheUniverse/QuantumInstrumentInfo.cs
vendored
Normal file
34
NewHorizons/External/Modules/Props/EyeOfTheUniverse/QuantumInstrumentInfo.cs
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace NewHorizons.External.Modules.Props.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
[JsonObject]
|
||||||
|
public class QuantumInstrumentInfo : DetailInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The unique ID of the Eye Traveler associated with this quantum instrument.
|
||||||
|
/// </summary>
|
||||||
|
public string id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dialogue condition to set when gathering this quantum instrument. Use it in conjunction with `activationCondition` or `deactivationCondition` on other details.
|
||||||
|
/// </summary>
|
||||||
|
public string gatherCondition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows gathering this quantum instrument using the zoomed-in signalscope, like Chert's bongos.
|
||||||
|
/// </summary>
|
||||||
|
public bool gatherWithScope;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The radius of the added sphere collider that will be used for interaction.
|
||||||
|
/// </summary>
|
||||||
|
[DefaultValue(0.5f)] public float interactRadius = 0.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The furthest distance where the player can interact with this quantum instrument.
|
||||||
|
/// </summary>
|
||||||
|
[DefaultValue(2f)] public float interactRange = 2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,57 @@
|
|||||||
using NewHorizons.Builder.General;
|
using NewHorizons.Builder.General;
|
||||||
using NewHorizons.Components.EyeOfTheUniverse;
|
using NewHorizons.Components.EyeOfTheUniverse;
|
||||||
using NewHorizons.Components.Stars;
|
using NewHorizons.Components.Stars;
|
||||||
|
using NewHorizons.External.Modules.Props.EyeOfTheUniverse;
|
||||||
using NewHorizons.External.SerializableData;
|
using NewHorizons.External.SerializableData;
|
||||||
using NewHorizons.Utility;
|
using NewHorizons.Utility;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace NewHorizons.Handlers
|
namespace NewHorizons.Handlers
|
||||||
{
|
{
|
||||||
public static class EyeSceneHandler
|
public static class EyeSceneHandler
|
||||||
{
|
{
|
||||||
|
private static Dictionary<string, EyeTravelerData> _eyeTravelers = new();
|
||||||
|
private static EyeMusicController _eyeMusicController;
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
_eyeTravelers.Clear();
|
||||||
|
_eyeMusicController = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EyeMusicController GetMusicController()
|
||||||
|
{
|
||||||
|
return _eyeMusicController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EyeTravelerData GetOrCreateEyeTravelerData(string id)
|
||||||
|
{
|
||||||
|
if (_eyeTravelers.TryGetValue(id, out EyeTravelerData traveler))
|
||||||
|
{
|
||||||
|
return traveler;
|
||||||
|
}
|
||||||
|
traveler = new EyeTravelerData()
|
||||||
|
{
|
||||||
|
id = id,
|
||||||
|
info = null,
|
||||||
|
controller = null,
|
||||||
|
loopAudioSource = null,
|
||||||
|
finaleAudioSource = null,
|
||||||
|
instrumentZones = new(),
|
||||||
|
quantumInstruments = new(),
|
||||||
|
};
|
||||||
|
_eyeTravelers[traveler.id] = traveler;
|
||||||
|
return traveler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<EyeTravelerData> GetCustomEyeTravelers()
|
||||||
|
{
|
||||||
|
return _eyeTravelers.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public static void OnSceneLoad()
|
public static void OnSceneLoad()
|
||||||
{
|
{
|
||||||
// Create astro objects for eye and vessel because they didn't have them somehow.
|
// Create astro objects for eye and vessel because they didn't have them somehow.
|
||||||
@ -134,5 +177,129 @@ namespace NewHorizons.Handlers
|
|||||||
SunLightEffectsController.AddStar(starController);
|
SunLightEffectsController.AddStar(starController);
|
||||||
SunLightEffectsController.AddStarLight(sunLight);
|
SunLightEffectsController.AddStarLight(sunLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetUpEyeCampfireSequence()
|
||||||
|
{
|
||||||
|
_eyeMusicController = new GameObject("EyeMusicController").AddComponent<EyeMusicController>();
|
||||||
|
|
||||||
|
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();
|
||||||
|
var cosmicInflationController = Object.FindObjectOfType<CosmicInflationController>();
|
||||||
|
|
||||||
|
_eyeMusicController.RegisterFinaleSource(cosmicInflationController._travelerFinaleSource);
|
||||||
|
|
||||||
|
foreach (var controller in quantumCampsiteController._travelerControllers)
|
||||||
|
{
|
||||||
|
_eyeMusicController.RegisterLoopSource(controller._signal.GetOWAudioSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var eyeTraveler in _eyeTravelers.Values)
|
||||||
|
{
|
||||||
|
if (eyeTraveler.controller != null)
|
||||||
|
{
|
||||||
|
ArrayHelpers.Append(ref quantumCampsiteController._travelerControllers, eyeTraveler.controller);
|
||||||
|
eyeTraveler.controller.OnStartPlaying += quantumCampsiteController.OnTravelerStartPlaying;
|
||||||
|
|
||||||
|
ArrayHelpers.Append(ref cosmicInflationController._travelers, eyeTraveler.controller);
|
||||||
|
eyeTraveler.controller.OnStartPlaying += cosmicInflationController.OnTravelerStartPlaying;
|
||||||
|
|
||||||
|
ArrayHelpers.Append(ref cosmicInflationController._inflationObjects, eyeTraveler.controller.transform);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Missing Eye Traveler for ID \"{eyeTraveler.id}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eyeTraveler.loopAudioSource != null)
|
||||||
|
{
|
||||||
|
_eyeMusicController.RegisterLoopSource(eyeTraveler.loopAudioSource);
|
||||||
|
}
|
||||||
|
if (eyeTraveler.finaleAudioSource != null)
|
||||||
|
{
|
||||||
|
_eyeMusicController.RegisterFinaleSource(eyeTraveler.finaleAudioSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var quantumInstrument in eyeTraveler.quantumInstruments)
|
||||||
|
{
|
||||||
|
ArrayHelpers.Append(ref quantumInstrument._activateObjects, eyeTraveler.controller.gameObject);
|
||||||
|
ArrayHelpers.Append(ref quantumInstrument._deactivateObjects, eyeTraveler.instrumentZones.Select(z => z.gameObject));
|
||||||
|
|
||||||
|
var ancestorInstrumentZone = quantumInstrument.GetComponentInParent<InstrumentZone>();
|
||||||
|
if (ancestorInstrumentZone == null)
|
||||||
|
{
|
||||||
|
// Quantum instrument is not a child of an instrument zone, so treat it like its own zone
|
||||||
|
ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, quantumInstrument.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var instrumentZone in eyeTraveler.instrumentZones)
|
||||||
|
{
|
||||||
|
instrumentZone.gameObject.SetActive(false);
|
||||||
|
ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, instrumentZone.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateTravelerPositions()
|
||||||
|
{
|
||||||
|
//if (!GetCustomEyeTravelers().Any()) return;
|
||||||
|
|
||||||
|
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();
|
||||||
|
|
||||||
|
var travelers = new List<Transform>()
|
||||||
|
{
|
||||||
|
quantumCampsiteController._travelerControllers[0].transform, // Riebeck
|
||||||
|
quantumCampsiteController._travelerControllers[2].transform, // Chert
|
||||||
|
quantumCampsiteController._travelerControllers[6].transform, // Esker
|
||||||
|
quantumCampsiteController._travelerControllers[1].transform, // Felspar
|
||||||
|
quantumCampsiteController._travelerControllers[3].transform, // Gabbro
|
||||||
|
};
|
||||||
|
|
||||||
|
if (quantumCampsiteController._hasMetSolanum)
|
||||||
|
{
|
||||||
|
travelers.Add(quantumCampsiteController._travelerControllers[4].transform); // Solanum
|
||||||
|
}
|
||||||
|
if (quantumCampsiteController._hasMetPrisoner)
|
||||||
|
{
|
||||||
|
travelers.Add(quantumCampsiteController._travelerControllers[5].transform); // Prisoner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom travelers (starting at index 7)
|
||||||
|
for (int i = 7; i < quantumCampsiteController._travelerControllers.Length; i++)
|
||||||
|
{
|
||||||
|
travelers.Add(quantumCampsiteController._travelerControllers[i].transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
var radius = 2f + 0.2f * travelers.Count;
|
||||||
|
var angle = Mathf.PI * 2f / travelers.Count;
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
foreach (var traveler in travelers)
|
||||||
|
{
|
||||||
|
// Esker isn't at height 0 so we have to do all this
|
||||||
|
var initialY = traveler.transform.position.y;
|
||||||
|
var newPos = quantumCampsiteController.transform.TransformPoint(new Vector3(
|
||||||
|
Mathf.Cos(angle * index) * radius,
|
||||||
|
0f,
|
||||||
|
-Mathf.Sin(angle * index) * radius
|
||||||
|
));
|
||||||
|
newPos.y = initialY;
|
||||||
|
traveler.transform.position = newPos;
|
||||||
|
var lookTarget = quantumCampsiteController.transform.position;
|
||||||
|
lookTarget.y = newPos.y;
|
||||||
|
traveler.transform.LookAt(lookTarget, traveler.transform.up);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EyeTravelerData
|
||||||
|
{
|
||||||
|
public string id;
|
||||||
|
public EyeTravelerInfo info;
|
||||||
|
public TravelerEyeController controller;
|
||||||
|
public OWAudioSource loopAudioSource;
|
||||||
|
public OWAudioSource finaleAudioSource;
|
||||||
|
public List<QuantumInstrument> quantumInstruments = new();
|
||||||
|
public List<InstrumentZone> instrumentZones = new();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -691,6 +691,11 @@ namespace NewHorizons.Handlers
|
|||||||
atmosphere = AtmosphereBuilder.Make(go, sector, body.Config.Atmosphere, surfaceSize).GetComponentInChildren<LODGroup>();
|
atmosphere = AtmosphereBuilder.Make(go, sector, body.Config.Atmosphere, surfaceSize).GetComponentInChildren<LODGroup>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (body.Config.EyeOfTheUniverse != null)
|
||||||
|
{
|
||||||
|
EyeOfTheUniverseBuilder.Make(go, sector, body.Config.EyeOfTheUniverse, body);
|
||||||
|
}
|
||||||
|
|
||||||
if (body.Config.ParticleFields != null)
|
if (body.Config.ParticleFields != null)
|
||||||
{
|
{
|
||||||
EffectsBuilder.Make(go, sector, body.Config);
|
EffectsBuilder.Make(go, sector, body.Config);
|
||||||
|
|||||||
@ -436,7 +436,9 @@ namespace NewHorizons
|
|||||||
if (isEyeOfTheUniverse)
|
if (isEyeOfTheUniverse)
|
||||||
{
|
{
|
||||||
_playerAwake = true;
|
_playerAwake = true;
|
||||||
|
EyeSceneHandler.Init();
|
||||||
EyeSceneHandler.OnSceneLoad();
|
EyeSceneHandler.OnSceneLoad();
|
||||||
|
EyeSceneHandler.SetUpEyeCampfireSequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSolarSystem || isEyeOfTheUniverse)
|
if (isSolarSystem || isEyeOfTheUniverse)
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches.EyeScenePatches
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(CosmicInflationController))]
|
||||||
|
public static class CosmicInflationControllerPatches
|
||||||
|
{
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(nameof(CosmicInflationController.UpdateFormation))]
|
||||||
|
public static void CosmicInflationController_UpdateFormation(CosmicInflationController __instance)
|
||||||
|
{
|
||||||
|
if (__instance._waitForCrossfade)
|
||||||
|
{
|
||||||
|
NHLogger.Log($"Hijacking finale cross-fade, NH will handle it");
|
||||||
|
__instance._waitForCrossfade = false;
|
||||||
|
__instance._waitForMusicEnd = true;
|
||||||
|
EyeSceneHandler.GetMusicController().TransitionToFinale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches.EyeScenePatches
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(QuantumCampsiteController))]
|
||||||
|
public static class QuantumCampsiteControllerPatches
|
||||||
|
{
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(nameof(QuantumCampsiteController.Start))]
|
||||||
|
public static void QuantumCampsiteController_Start()
|
||||||
|
{
|
||||||
|
EyeSceneHandler.UpdateTravelerPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(nameof(QuantumCampsiteController.ActivateRemainingInstrumentZones))]
|
||||||
|
public static void QuantumCampsiteController_ActivateRemainingInstrumentZones(QuantumCampsiteController __instance)
|
||||||
|
{
|
||||||
|
// We modify this array when registering a custom instrument zone but the vanilla method only activates the first 6
|
||||||
|
for (int i = 6; i < __instance._instrumentZones.Length; i++)
|
||||||
|
{
|
||||||
|
__instance._instrumentZones[i].SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(QuantumCampsiteController.AreAllTravelersGathered))]
|
||||||
|
public static bool QuantumCampsiteController_AreAllTravelersGathered(QuantumCampsiteController __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
bool gatheredAllHearthianTravelers = __instance._travelerControllers.Take(4).All(t => t.gameObject.activeInHierarchy);
|
||||||
|
if (!gatheredAllHearthianTravelers)
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose("");
|
||||||
|
__result = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool needsSolanum = __instance._hasMetSolanum;
|
||||||
|
bool gatheredSolanum = __instance._travelerControllers[QuantumCampsiteController.SOLANUM_INDEX].gameObject.activeInHierarchy;
|
||||||
|
if (needsSolanum && !gatheredSolanum)
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool needsPrisoner = __instance._hasMetPrisoner && !__instance._hasErasedPrisoner;
|
||||||
|
bool gatheredPrisoner = __instance._travelerControllers[QuantumCampsiteController.PRISONER_INDEX].gameObject.activeInHierarchy;
|
||||||
|
if (needsPrisoner && !gatheredPrisoner)
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach (var traveler in EyeSceneHandler.GetCustomEyeTravelers())
|
||||||
|
{
|
||||||
|
bool needsTraveler = true;
|
||||||
|
if (!string.IsNullOrEmpty(traveler.info.participatingCondition))
|
||||||
|
{
|
||||||
|
needsTraveler = DialogueConditionManager.SharedInstance.GetConditionState(traveler.info.participatingCondition);
|
||||||
|
}
|
||||||
|
bool gatheredTraveler = traveler.controller.gameObject.activeInHierarchy;
|
||||||
|
if (needsTraveler && !gatheredTraveler)
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__result = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(QuantumCampsiteController.OnTravelerStartPlaying))]
|
||||||
|
public static void OnTravelerStartPlaying(QuantumCampsiteController __instance)
|
||||||
|
{
|
||||||
|
if (!__instance._hasJamSessionStarted)
|
||||||
|
{
|
||||||
|
NHLogger.Log($"NH is handling Eye sequence music");
|
||||||
|
// Jam session is starting, start our custom music handler
|
||||||
|
EyeSceneHandler.GetMusicController().StartPlaying();
|
||||||
|
}
|
||||||
|
// Letting the original method run in case mods have patched TravelerEyeController.OnStartCosmicJamSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches.EyeScenePatches
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(TravelerEyeController))]
|
||||||
|
public static class TravelerEyeControllerPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(TravelerEyeController.OnStartCosmicJamSession))]
|
||||||
|
public static bool TravelerEyeController_OnStartCosmicJamSession(TravelerEyeController __instance)
|
||||||
|
{
|
||||||
|
// Not starting the loop audio here; EyeMusicController will handle that
|
||||||
|
__instance._signal.GetOWAudioSource().SetLocalVolume(0f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user