mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Merge branch 'dev' into profiler
This commit is contained in:
commit
1f02e7fe88
@ -1,6 +1,69 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/main/NewHorizons/Schemas/translation_schema.json",
|
"$schema": "https://raw.githubusercontent.com/outer-wilds-new-horizons/new-horizons/main/NewHorizons/Schemas/translation_schema.json",
|
||||||
|
"DialogueDictionary": {
|
||||||
|
"NEW_HORIZONS_WARP_DRIVE_DIALOGUE_1": "Sua nave agora está equipada com uma unidade de translocação!",
|
||||||
|
"NEW_HORIZONS_WARP_DRIVE_DIALOGUE_2": "Você pode usar a nova aba \"Modo Interestelar\" no diário de bordo de sua nave para ajustar o piloto automático rumo a outro sistema solar.",
|
||||||
|
"NEW_HORIZONS_WARP_DRIVE_DIALOGUE_3": "Então é só acionar o piloto automático e relaxar!"
|
||||||
|
},
|
||||||
"UIDictionary": {
|
"UIDictionary": {
|
||||||
"Vessel": "Hospedeiro"
|
"INTERSTELLAR_MODE": "Modo Interestelar",
|
||||||
|
"FREQ_STATUE": "Estátua Nomai",
|
||||||
|
"FREQ_WARP_CORE": "Fluxo Anti-Gravitacional",
|
||||||
|
"FREQ_UNKNOWN": "???",
|
||||||
|
"ENGAGE_WARP_PROMPT": "Acionar translocação a {0}",
|
||||||
|
"WARP_LOCKED": "PILOTO AUTOMÁTICO AJUSTADO PARA:\n{0}",
|
||||||
|
"LOCK_AUTOPILOT_WARP": "Ajustar Piloto Automático para sistema solar",
|
||||||
|
"RICH_PRESENCE_EXPLORING": "Explorando {0}.",
|
||||||
|
"RICH_PRESENCE_WARPING": "Translocando para {0}.",
|
||||||
|
"OUTDATED_VERSION_WARNING": "AVISO\n\nA mod New Horizons funciona apenas na versão {0} ou superior. Você está usando a versão {1}.\n\nAtualize Outer Wilds ou desinstale NH.",
|
||||||
|
"JSON_FAILED_TO_LOAD": "Arquivo(s) inválido(s): {0}",
|
||||||
|
"DEBUG_RAYCAST": "Raycast",
|
||||||
|
"DEBUG_PLACE": "Posicionar objeto",
|
||||||
|
"DEBUG_PLACE_TEXT": "Colocar texto Nomai",
|
||||||
|
"DEBUG_UNDO": "Desfazer",
|
||||||
|
"DEBUG_REDO": "Refazer",
|
||||||
|
"Vessel": "Hospedeiro",
|
||||||
|
"DLC_REQUIRED": "AVISO\n\nVocê tem addons (ex. {0}) instalados que requerem a DLC, mas a mesma não está habilitada.\n\nSuas mods podem não funcionar como esperado."
|
||||||
|
},
|
||||||
|
"OtherDictionary": {
|
||||||
|
"NOMAI_SHUTTLE_COMPUTER": "A <![CDATA[<color=orange>exploradora</color>]]> está repousando por hora em: <![CDATA[<color=lightblue>{0}</color>]]>."
|
||||||
|
},
|
||||||
|
"AchievementTranslations": {
|
||||||
|
"NH_EATEN_OUTSIDE_BRAMBLE": {
|
||||||
|
"Name": "Fenda de Contenção",
|
||||||
|
"Description": "Seja devorado fora dos limites de Abrolho Sombrio"
|
||||||
|
},
|
||||||
|
"NH_MULTIPLE_SYSTEM": {
|
||||||
|
"Name": "Viajante",
|
||||||
|
"Description": "Visite 5 sistemas solares seguidos."
|
||||||
|
},
|
||||||
|
"NH_NEW_FREQ": {
|
||||||
|
"Name": "Frequências Anômalas",
|
||||||
|
"Description": "Descubra uma nova frequência."
|
||||||
|
},
|
||||||
|
"NH_PROBE_LOST": {
|
||||||
|
"Name": "Conexão Perdida",
|
||||||
|
"Description": "Perca seu pequeno batedor."
|
||||||
|
},
|
||||||
|
"NH_WARP_DRIVE": {
|
||||||
|
"Name": "História Mal Contada",
|
||||||
|
"Description": "Use a unidade de translocação da nave."
|
||||||
|
},
|
||||||
|
"NH_VESSEL_WARP": {
|
||||||
|
"Name": "História Acertada",
|
||||||
|
"Description": "Transloque para um sistema estelar usando o Hospedeiro."
|
||||||
|
},
|
||||||
|
"NH_RAFTING": {
|
||||||
|
"Name": "A Jangada e os Furiosos",
|
||||||
|
"Description": "Dê um rolé de jangada."
|
||||||
|
},
|
||||||
|
"NH_SUCKED_INTO_LAVA_BY_TORNADO": {
|
||||||
|
"Name": "Dieclone",
|
||||||
|
"Description": "Seja sugado na lava por um tornado."
|
||||||
|
},
|
||||||
|
"NH_TALK_TO_FIVE_CHARACTERS": {
|
||||||
|
"Name": "Social",
|
||||||
|
"Description": "Converse com 5 personagens."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,9 +27,9 @@ namespace NewHorizons.Builder.General
|
|||||||
gravityGO.layer = Layer.BasicEffectVolume;
|
gravityGO.layer = Layer.BasicEffectVolume;
|
||||||
gravityGO.SetActive(false);
|
gravityGO.SetActive(false);
|
||||||
|
|
||||||
var SC = gravityGO.AddComponent<SphereCollider>();
|
var sphereCollider = gravityGO.AddComponent<SphereCollider>();
|
||||||
SC.isTrigger = true;
|
sphereCollider.isTrigger = true;
|
||||||
SC.radius = gravityRadius;
|
sphereCollider.radius = gravityRadius;
|
||||||
|
|
||||||
var owCollider = gravityGO.AddComponent<OWCollider>();
|
var owCollider = gravityGO.AddComponent<OWCollider>();
|
||||||
owCollider.SetLODActivationMask(DynamicOccupant.Player);
|
owCollider.SetLODActivationMask(DynamicOccupant.Player);
|
||||||
@ -49,7 +49,8 @@ namespace NewHorizons.Builder.General
|
|||||||
if (config.Base.surfaceGravity == 0) alignmentRadius = 0;
|
if (config.Base.surfaceGravity == 0) alignmentRadius = 0;
|
||||||
|
|
||||||
gravityVolume._alignmentRadius = config.Base.gravityAlignmentRadiusOverride ?? alignmentRadius;
|
gravityVolume._alignmentRadius = config.Base.gravityAlignmentRadiusOverride ?? alignmentRadius;
|
||||||
gravityVolume._upperSurfaceRadius = config.FocalPoint != null ? 0 : config.Base.surfaceSize;
|
// Nobody write any FocalPoint overriding here, those work as intended gravitationally so deal with it!
|
||||||
|
gravityVolume._upperSurfaceRadius = config.Base.surfaceSize;
|
||||||
gravityVolume._lowerSurfaceRadius = 0;
|
gravityVolume._lowerSurfaceRadius = 0;
|
||||||
gravityVolume._layer = 3;
|
gravityVolume._layer = 3;
|
||||||
gravityVolume._priority = config.Base.gravityVolumePriority;
|
gravityVolume._priority = config.Base.gravityVolumePriority;
|
||||||
@ -59,6 +60,21 @@ namespace NewHorizons.Builder.General
|
|||||||
gravityVolume._isPlanetGravityVolume = true;
|
gravityVolume._isPlanetGravityVolume = true;
|
||||||
gravityVolume._cutoffRadius = 0f;
|
gravityVolume._cutoffRadius = 0f;
|
||||||
|
|
||||||
|
// If it's a focal point dont add collision stuff
|
||||||
|
// This is overkill
|
||||||
|
if (config.FocalPoint != null)
|
||||||
|
{
|
||||||
|
owCollider.enabled = false;
|
||||||
|
owTriggerVolume.enabled = false;
|
||||||
|
sphereCollider.radius = 0;
|
||||||
|
sphereCollider.enabled = false;
|
||||||
|
sphereCollider.isTrigger = false;
|
||||||
|
// This should ensure that even if the player enters the volume, it counts them as being inside the zero-gee cave equivalent
|
||||||
|
gravityVolume._cutoffRadius = gravityVolume._upperSurfaceRadius;
|
||||||
|
gravityVolume._lowerSurfaceRadius = gravityVolume._upperSurfaceRadius;
|
||||||
|
gravityVolume._cutoffAcceleration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
gravityGO.SetActive(true);
|
gravityGO.SetActive(true);
|
||||||
|
|
||||||
ao._gravityVolume = gravityVolume;
|
ao._gravityVolume = gravityVolume;
|
||||||
|
|||||||
@ -496,7 +496,10 @@ namespace NewHorizons.Builder.Props
|
|||||||
// Disable the angler anim controller because we don't want Update or LateUpdate to run, just need it to set the initial Animator state
|
// Disable the angler anim controller because we don't want Update or LateUpdate to run, just need it to set the initial Animator state
|
||||||
angler.enabled = false;
|
angler.enabled = false;
|
||||||
angler.OnChangeAnglerState(AnglerfishController.AnglerState.Lurking);
|
angler.OnChangeAnglerState(AnglerfishController.AnglerState.Lurking);
|
||||||
|
|
||||||
|
angler._animator.SetFloat("MoveSpeed", angler._moveCurrent);
|
||||||
|
angler._animator.SetFloat("Jaw", angler._jawCurrent);
|
||||||
|
|
||||||
Destroy(this);
|
Destroy(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
258
NewHorizons/Builder/Props/EyeOfTheUniverseBuilder.cs
Normal file
258
NewHorizons/Builder/Props/EyeOfTheUniverseBuilder.cs
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
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 travelerData = EyeSceneHandler.CreateEyeTravelerData(info.id);
|
||||||
|
travelerData.info = info;
|
||||||
|
travelerData.requirementsMet = true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.requiredFact) && !ShipLogHandler.KnowsFact(info.requiredFact))
|
||||||
|
{
|
||||||
|
travelerData.requirementsMet = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.requiredPersistentCondition) && !DialogueConditionManager.SharedInstance.GetConditionState(info.requiredPersistentCondition))
|
||||||
|
{
|
||||||
|
travelerData.requirementsMet = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!travelerData.requirementsMet)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (info.dialogue.position == null && info.dialogue.parentPath == null)
|
||||||
|
{
|
||||||
|
info.dialogue.isRelativeToParent = true;
|
||||||
|
}
|
||||||
|
GeneralPropBuilder.MakeFromExisting(dialogueTree.gameObject, planetGO, sector, info.dialogue, defaultParent: go.transform);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
travelerController._dialogueTree = go.GetComponentInChildren<CharacterDialogueTree>();
|
||||||
|
if (travelerController._dialogueTree == null)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Eye Traveler with ID \"{info.id}\" does not have any dialogue set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
travelerData.controller = travelerController;
|
||||||
|
|
||||||
|
OWAudioSource loopAudioSource = null;
|
||||||
|
|
||||||
|
if (info.signal != null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(info.signal.name))
|
||||||
|
{
|
||||||
|
info.signal.name = go.name;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(info.signal.frequency))
|
||||||
|
{
|
||||||
|
info.signal.frequency = "Traveler";
|
||||||
|
}
|
||||||
|
var signalGO = SignalBuilder.Make(planetGO, sector, info.signal, nhBody.Mod);
|
||||||
|
if (info.signal.position == null && info.signal.parentPath == null)
|
||||||
|
{
|
||||||
|
info.signal.isRelativeToParent = true;
|
||||||
|
}
|
||||||
|
GeneralPropBuilder.MakeFromExisting(signalGO, planetGO, sector, info.signal, defaultParent: go.transform);
|
||||||
|
|
||||||
|
var signal = signalGO.GetComponent<AudioSignal>();
|
||||||
|
travelerController._signal = signal;
|
||||||
|
signal.SetSignalActivation(false);
|
||||||
|
loopAudioSource = signal.GetOWAudioSource();
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (travelerController._signal == null)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Eye Traveler with ID \"{info.id}\" does not have any loop audio set");
|
||||||
|
}
|
||||||
|
|
||||||
|
travelerData.loopAudioSource = loopAudioSource;
|
||||||
|
|
||||||
|
OWAudioSource finaleAudioSource = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.finaleAudio))
|
||||||
|
{
|
||||||
|
var finaleAudioInfo = new AudioSourceInfo()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
travelerData.finaleAudioSource = finaleAudioSource;
|
||||||
|
|
||||||
|
return travelerController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QuantumInstrument MakeQuantumInstrument(GameObject planetGO, Sector sector, QuantumInstrumentInfo info, NewHorizonsBody nhBody)
|
||||||
|
{
|
||||||
|
var travelerData = EyeSceneHandler.GetEyeTravelerData(info.id);
|
||||||
|
|
||||||
|
if (travelerData != null && !travelerData.requirementsMet)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
ArrayHelpers.Append(ref quantumInstrument._deactivateObjects, go);
|
||||||
|
|
||||||
|
var trigger = go.AddComponent<QuantumInstrumentTrigger>();
|
||||||
|
trigger.gatherCondition = info.gatherCondition;
|
||||||
|
|
||||||
|
if (travelerData != null)
|
||||||
|
{
|
||||||
|
travelerData.quantumInstruments.Add(quantumInstrument);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Quantum instrument with ID \"{info.id}\" has no matching eye traveler");
|
||||||
|
}
|
||||||
|
|
||||||
|
info.signal ??= new SignalInfo();
|
||||||
|
|
||||||
|
if (travelerData?.info != null && travelerData.info.signal != null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(info.signal.name))
|
||||||
|
{
|
||||||
|
info.signal.name = travelerData.info.signal.name;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(info.signal.audio))
|
||||||
|
{
|
||||||
|
info.signal.audio = travelerData.info.signal.audio;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(info.signal.frequency))
|
||||||
|
{
|
||||||
|
info.signal.frequency = travelerData.info.signal.frequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.signal.audio))
|
||||||
|
{
|
||||||
|
var signalGO = SignalBuilder.Make(planetGO, sector, info.signal, nhBody.Mod);
|
||||||
|
if (info.signal.position == null && info.signal.parentPath == null)
|
||||||
|
{
|
||||||
|
info.signal.isRelativeToParent = true;
|
||||||
|
}
|
||||||
|
GeneralPropBuilder.MakeFromExisting(signalGO, planetGO, sector, info.signal, defaultParent: go.transform);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Eye Traveler with ID \"{info.id}\" does not have any loop audio set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return quantumInstrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InstrumentZone MakeInstrumentZone(GameObject planetGO, Sector sector, InstrumentZoneInfo info, NewHorizonsBody nhBody)
|
||||||
|
{
|
||||||
|
var travelerData = EyeSceneHandler.GetEyeTravelerData(info.id);
|
||||||
|
|
||||||
|
if (travelerData != null && !travelerData.requirementsMet)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var go = DetailBuilder.Make(planetGO, sector, nhBody.Mod, info);
|
||||||
|
|
||||||
|
var instrumentZone = go.AddComponent<InstrumentZone>();
|
||||||
|
|
||||||
|
if (travelerData != null)
|
||||||
|
{
|
||||||
|
travelerData.instrumentZones.Add(instrumentZone);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Instrument zone with ID \"{info.id}\" has no matching eye traveler");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
147
NewHorizons/Components/EyeOfTheUniverse/EyeMusicController.cs
Normal file
147
NewHorizons/Components/EyeOfTheUniverse/EyeMusicController.cs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
using NewHorizons.Utility;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NewHorizons.Components.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
public class EyeMusicController : MonoBehaviour
|
||||||
|
{
|
||||||
|
// 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 List<OWAudioSource> _loopSources = new();
|
||||||
|
private List<OWAudioSource> _finaleSources = new();
|
||||||
|
private bool _transitionToFinale;
|
||||||
|
private bool _isPlaying;
|
||||||
|
private double _segmentEndAudioTime;
|
||||||
|
private float _segmentEndGameTime;
|
||||||
|
public CosmicInflationController CosmicInflationController { get; private set; }
|
||||||
|
|
||||||
|
public void RegisterLoopSource(OWAudioSource src)
|
||||||
|
{
|
||||||
|
src.loop = false;
|
||||||
|
src.SetLocalVolume(1f);
|
||||||
|
src.Stop();
|
||||||
|
src.playOnAwake = false;
|
||||||
|
_loopSources.Add(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFinaleSource(OWAudioSource src)
|
||||||
|
{
|
||||||
|
src.loop = false;
|
||||||
|
src.SetLocalVolume(1f);
|
||||||
|
src.Stop();
|
||||||
|
src.playOnAwake = false;
|
||||||
|
_finaleSources.Add(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartPlaying()
|
||||||
|
{
|
||||||
|
if (_isPlaying) return;
|
||||||
|
_isPlaying = true;
|
||||||
|
StartCoroutine(DoLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TransitionToFinale()
|
||||||
|
{
|
||||||
|
_transitionToFinale = true;
|
||||||
|
|
||||||
|
// Schedule finale for as soon as the current segment loop ends
|
||||||
|
double finaleAudioTime = _segmentEndAudioTime;
|
||||||
|
float finaleGameTime = _segmentEndGameTime;
|
||||||
|
|
||||||
|
// Cancel loop audio
|
||||||
|
foreach (var loopSrc in _loopSources)
|
||||||
|
{
|
||||||
|
loopSrc._audioSource.SetScheduledEndTime(finaleAudioTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(finaleAudioTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Awake()
|
||||||
|
{
|
||||||
|
// EOTP makes 2 new CosmicInflationControllers for no reason
|
||||||
|
CosmicInflationController = SearchUtilities.Find("EyeOfTheUniverse_Body/Sector_EyeOfTheUniverse/Sector_Campfire/InflationController").GetComponent<CosmicInflationController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the bubble has finished expanding
|
||||||
|
while (Time.time < CosmicInflationController._finishFormationTime)
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable audio signals
|
||||||
|
foreach (var loopSrc in _loopSources)
|
||||||
|
{
|
||||||
|
var signal = loopSrc.GetComponent<AudioSignal>();
|
||||||
|
if (signal != null)
|
||||||
|
{
|
||||||
|
signal.SetSignalActivation(false, 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
NewHorizons/Components/EyeOfTheUniverse/InstrumentZone.cs
Normal file
12
NewHorizons/Components/EyeOfTheUniverse/InstrumentZone.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NewHorizons.Components.EyeOfTheUniverse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class does nothing but is used with GetComponent to find instrument zones in the hierarchy
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
NewHorizons/External/Modules/Props/EyeOfTheUniverse/EyeTravelerInfo.cs
vendored
Normal file
50
NewHorizons/External/Modules/Props/EyeOfTheUniverse/EyeTravelerInfo.cs
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using NewHorizons.External.Modules.Props.Audio;
|
||||||
|
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>
|
||||||
|
/// If set, the player must know this ship log fact for this traveler (and their instrument zones and quantum instruments) to appear. The fact does not need to exist in the current star system; the player's save data will be checked directly.
|
||||||
|
/// </summary>
|
||||||
|
public string requiredFact;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set, the player must have this persistent dialogue condition set for this traveler (and their instrument zones and quantum instruments) to appear.
|
||||||
|
/// </summary>
|
||||||
|
public string requiredPersistentCondition;
|
||||||
|
|
||||||
|
/// <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 signal to use for the traveler while playing around the campfire (and also for their paired quantum instrument if another is not specified). The audio clip should be 16 measures at 92 BPM (approximately 42 seconds long).
|
||||||
|
/// </summary>
|
||||||
|
public SignalInfo signal;
|
||||||
|
|
||||||
|
/// <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 dialogue to use for this traveler. If omitted, the first CharacterDialogueTree in the object will be used.
|
||||||
|
/// </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;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
NewHorizons/External/Modules/Props/EyeOfTheUniverse/QuantumInstrumentInfo.cs
vendored
Normal file
40
NewHorizons/External/Modules/Props/EyeOfTheUniverse/QuantumInstrumentInfo.cs
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using NewHorizons.External.Modules.Props.Audio;
|
||||||
|
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 audio signal emitted by this quantum instrument. The fields `name`, `audio`, and `frequency` will be copied from the corresponding Eye Traveler's signal if not specified here.
|
||||||
|
/// </summary>
|
||||||
|
public SignalInfo signal;
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
|||||||
@ -1,14 +1,66 @@
|
|||||||
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 GetEyeTravelerData(string id)
|
||||||
|
{
|
||||||
|
if (_eyeTravelers.TryGetValue(id, out EyeTravelerData traveler))
|
||||||
|
{
|
||||||
|
return traveler;
|
||||||
|
}
|
||||||
|
return traveler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EyeTravelerData CreateEyeTravelerData(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> GetActiveCustomEyeTravelers()
|
||||||
|
{
|
||||||
|
return _eyeTravelers.Values.Where(t => t.requirementsMet).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 +186,142 @@ namespace NewHorizons.Handlers
|
|||||||
SunLightEffectsController.AddStar(starController);
|
SunLightEffectsController.AddStar(starController);
|
||||||
SunLightEffectsController.AddStarLight(sunLight);
|
SunLightEffectsController.AddStarLight(sunLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetUpEyeCampfireSequence()
|
||||||
|
{
|
||||||
|
if (!GetActiveCustomEyeTravelers().Any()) return;
|
||||||
|
|
||||||
|
_eyeMusicController = new GameObject("EyeMusicController").AddComponent<EyeMusicController>();
|
||||||
|
|
||||||
|
var quantumCampsiteController = Object.FindObjectOfType<QuantumCampsiteController>();
|
||||||
|
var cosmicInflationController = _eyeMusicController.CosmicInflationController;
|
||||||
|
|
||||||
|
_eyeMusicController.RegisterFinaleSource(cosmicInflationController._travelerFinaleSource);
|
||||||
|
|
||||||
|
foreach (var controller in quantumCampsiteController._travelerControllers)
|
||||||
|
{
|
||||||
|
_eyeMusicController.RegisterLoopSource(controller._signal.GetOWAudioSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var eyeTraveler in GetActiveCustomEyeTravelers())
|
||||||
|
{
|
||||||
|
if (eyeTraveler.controller != null)
|
||||||
|
{
|
||||||
|
eyeTraveler.controller.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
eyeTraveler.loopAudioSource.GetComponent<AudioSignal>().SetSignalActivation(false);
|
||||||
|
_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
|
||||||
|
quantumInstrument.gameObject.SetActive(false);
|
||||||
|
ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, quantumInstrument.gameObject);
|
||||||
|
|
||||||
|
ArrayHelpers.Append(ref cosmicInflationController._inflationObjects, quantumInstrument.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var instrumentZone in eyeTraveler.instrumentZones)
|
||||||
|
{
|
||||||
|
instrumentZone.gameObject.SetActive(false);
|
||||||
|
ArrayHelpers.Append(ref quantumCampsiteController._instrumentZones, instrumentZone.gameObject);
|
||||||
|
|
||||||
|
ArrayHelpers.Append(ref cosmicInflationController._inflationObjects, instrumentZone.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTravelerPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateTravelerPositions()
|
||||||
|
{
|
||||||
|
if (!GetActiveCustomEyeTravelers().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();
|
||||||
|
public bool requirementsMet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,44 @@
|
|||||||
using NewHorizons.Utility.OWML;
|
using NewHorizons.Utility.OWML;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
namespace NewHorizons.Handlers
|
namespace NewHorizons.Handlers
|
||||||
{
|
{
|
||||||
internal class InvulnerabilityHandler
|
internal class InvulnerabilityHandler
|
||||||
{
|
{
|
||||||
private static float _defaultImpactDeathSpeed = -1f;
|
/// <summary>
|
||||||
|
/// Used in patches
|
||||||
|
/// </summary>
|
||||||
|
public static bool Invincible { get; private set; }
|
||||||
|
|
||||||
public static void MakeInvulnerable(bool invulnerable)
|
public static void MakeInvulnerable(bool invulnerable)
|
||||||
{
|
{
|
||||||
NHLogger.Log($"Toggling immortality: {invulnerable}");
|
NHLogger.Log($"Toggling immortality: {invulnerable}");
|
||||||
|
|
||||||
|
Invincible = invulnerable;
|
||||||
var deathManager = GetDeathManager();
|
var deathManager = GetDeathManager();
|
||||||
var resources = GetPlayerResouces();
|
var resources = GetPlayerResouces();
|
||||||
|
|
||||||
if (invulnerable)
|
if (invulnerable)
|
||||||
{
|
{
|
||||||
if (_defaultImpactDeathSpeed == -1f)
|
|
||||||
_defaultImpactDeathSpeed = deathManager._impactDeathSpeed;
|
|
||||||
|
|
||||||
deathManager._impactDeathSpeed = Mathf.Infinity;
|
|
||||||
deathManager._invincible = true;
|
deathManager._invincible = true;
|
||||||
|
resources._invincible = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
deathManager._impactDeathSpeed = _defaultImpactDeathSpeed;
|
|
||||||
resources._currentHealth = 100f;
|
resources._currentHealth = 100f;
|
||||||
deathManager._invincible = false;
|
deathManager._invincible = false;
|
||||||
|
resources._invincible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DeathManager GetDeathManager() => GameObject.FindObjectOfType<DeathManager>();
|
private static DeathManager GetDeathManager() => GameObject.FindObjectOfType<DeathManager>();
|
||||||
private static PlayerResources GetPlayerResouces() => GameObject.FindObjectOfType<PlayerResources>();
|
private static PlayerResources GetPlayerResouces() => GameObject.FindObjectOfType<PlayerResources>();
|
||||||
|
|
||||||
|
static InvulnerabilityHandler()
|
||||||
|
{
|
||||||
|
// If the scene unloads when Invincible is on it might not get turned off
|
||||||
|
SceneManager.sceneUnloaded += (_) => Invincible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -514,7 +514,11 @@ namespace NewHorizons.Handlers
|
|||||||
|
|
||||||
if (body.Config.Orbit.showOrbitLine && !body.Config.Orbit.isStatic)
|
if (body.Config.Orbit.showOrbitLine && !body.Config.Orbit.isStatic)
|
||||||
{
|
{
|
||||||
OrbitlineBuilder.Make(body.Object, body.Config.Orbit.isMoon, body.Config);
|
// No map mode at eye
|
||||||
|
if (LoadManager.GetCurrentScene() != OWScene.EyeOfTheUniverse)
|
||||||
|
{
|
||||||
|
OrbitlineBuilder.Make(body.Object, body.Config.Orbit.isMoon, body.Config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DetectorBuilder.Make(go, owRigidBody, primaryBody, ao, body.Config);
|
DetectorBuilder.Make(go, owRigidBody, primaryBody, ao, body.Config);
|
||||||
@ -691,6 +695,18 @@ 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)
|
||||||
|
{
|
||||||
|
if (Main.Instance.CurrentStarSystem == "EyeOfTheUniverse")
|
||||||
|
{
|
||||||
|
EyeOfTheUniverseBuilder.Make(go, sector, body.Config.EyeOfTheUniverse, body);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogWarning($"A mod creator (you?) has defined Eye of the Universe specific settings on a body [{body.Config.name}] that is not in the eye of the universe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (body.Config.ParticleFields != null)
|
if (body.Config.ParticleFields != null)
|
||||||
{
|
{
|
||||||
EffectsBuilder.Make(go, sector, body.Config);
|
EffectsBuilder.Make(go, sector, body.Config);
|
||||||
@ -843,7 +859,11 @@ namespace NewHorizons.Handlers
|
|||||||
var isMoon = newAO.GetAstroObjectType() is AstroObject.Type.Moon or AstroObject.Type.Satellite or AstroObject.Type.SpaceStation;
|
var isMoon = newAO.GetAstroObjectType() is AstroObject.Type.Moon or AstroObject.Type.Satellite or AstroObject.Type.SpaceStation;
|
||||||
if (body.Config.Orbit.showOrbitLine)
|
if (body.Config.Orbit.showOrbitLine)
|
||||||
{
|
{
|
||||||
OrbitlineBuilder.Make(go, isMoon, body.Config);
|
// No map mode at eye
|
||||||
|
if (LoadManager.GetCurrentScene() != OWScene.EyeOfTheUniverse)
|
||||||
|
{
|
||||||
|
OrbitlineBuilder.Make(go, isMoon, body.Config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DetectorBuilder.SetDetector(primary, newAO, go.GetComponentInChildren<ConstantForceDetector>());
|
DetectorBuilder.SetDetector(primary, newAO, go.GetComponentInChildren<ConstantForceDetector>());
|
||||||
|
|||||||
@ -70,6 +70,24 @@ namespace NewHorizons.Handlers
|
|||||||
|
|
||||||
// Spawn ship
|
// Spawn ship
|
||||||
Delay.FireInNUpdates(SpawnShip, 30);
|
Delay.FireInNUpdates(SpawnShip, 30);
|
||||||
|
|
||||||
|
// Have had bug reports (#1034, #975) where sometimes after spawning via vessel warp or ship warp you die from impact velocity after being flung
|
||||||
|
// Something weird must be happening with velocity.
|
||||||
|
// Try to correct it here after the ship is done spawning
|
||||||
|
Delay.FireInNUpdates(() => FixVelocity(shouldWarpInFromVessel, shouldWarpInFromShip), 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FixVelocity(bool shouldWarpInFromVessel, bool shouldWarpInFromShip)
|
||||||
|
{
|
||||||
|
var spawnOWRigidBody = GetDefaultSpawn().GetAttachedOWRigidbody();
|
||||||
|
if (shouldWarpInFromVessel) spawnOWRigidBody = VesselWarpHandler.VesselSpawnPoint.GetAttachedOWRigidbody();
|
||||||
|
if (shouldWarpInFromShip) spawnOWRigidBody = Locator.GetShipBody();
|
||||||
|
|
||||||
|
var spawnVelocity = spawnOWRigidBody.GetVelocity();
|
||||||
|
var spawnAngularVelocity = spawnOWRigidBody.GetPointTangentialVelocity(Locator.GetPlayerBody().GetPosition());
|
||||||
|
var velocity = spawnVelocity + spawnAngularVelocity;
|
||||||
|
|
||||||
|
Locator.GetPlayerBody().SetVelocity(velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SpawnShip()
|
public static void SpawnShip()
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using NewHorizons.Utility;
|
|||||||
using NewHorizons.Utility.OuterWilds;
|
using NewHorizons.Utility.OuterWilds;
|
||||||
using NewHorizons.Utility.OWML;
|
using NewHorizons.Utility.OWML;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using System.Collections;
|
||||||
using static NewHorizons.Main;
|
using static NewHorizons.Main;
|
||||||
using static NewHorizons.Utility.Files.AssetBundleUtilities;
|
using static NewHorizons.Utility.Files.AssetBundleUtilities;
|
||||||
|
|
||||||
@ -56,7 +57,9 @@ namespace NewHorizons.Handlers
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsVesselPresentAndActive())
|
if (IsVesselPresentAndActive())
|
||||||
|
{
|
||||||
_vesselSpawnPoint = Instance.CurrentStarSystem == "SolarSystem" ? UpdateVessel() : CreateVessel();
|
_vesselSpawnPoint = Instance.CurrentStarSystem == "SolarSystem" ? UpdateVessel() : CreateVessel();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var vesselDimension = SearchUtilities.Find("DB_VesselDimension_Body/Sector_VesselDimension");
|
var vesselDimension = SearchUtilities.Find("DB_VesselDimension_Body/Sector_VesselDimension");
|
||||||
@ -84,7 +87,12 @@ namespace NewHorizons.Handlers
|
|||||||
if (_vesselSpawnPoint is VesselSpawnPoint vesselSpawnPoint)
|
if (_vesselSpawnPoint is VesselSpawnPoint vesselSpawnPoint)
|
||||||
{
|
{
|
||||||
NHLogger.LogVerbose("Relative warping into vessel");
|
NHLogger.LogVerbose("Relative warping into vessel");
|
||||||
vesselSpawnPoint.WarpPlayer();//Delay.FireOnNextUpdate(vesselSpawnPoint.WarpPlayer);
|
vesselSpawnPoint.WarpPlayer();
|
||||||
|
|
||||||
|
// #1034 Vessel warp sometimes has the player get flung away into space and die
|
||||||
|
// We do what we do with regular spawns where we keep resetting their position to the right one while invincible until we're relatively certain
|
||||||
|
// that the spawning sequence is done
|
||||||
|
Delay.StartCoroutine(FixPlayerSpawning(25, vesselSpawnPoint));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -96,6 +104,24 @@ namespace NewHorizons.Handlers
|
|||||||
LoadDB();
|
LoadDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerator FixPlayerSpawning(int frameInterval, VesselSpawnPoint vesselSpawn)
|
||||||
|
{
|
||||||
|
InvulnerabilityHandler.MakeInvulnerable(true);
|
||||||
|
|
||||||
|
var frameCount = 0;
|
||||||
|
while (frameCount <= frameInterval)
|
||||||
|
{
|
||||||
|
vesselSpawn.WarpPlayer();
|
||||||
|
frameCount++;
|
||||||
|
yield return null; // Wait for the next frame
|
||||||
|
}
|
||||||
|
|
||||||
|
InvulnerabilityHandler.MakeInvulnerable(false);
|
||||||
|
var playerBody = SearchUtilities.Find("Player_Body").GetAttachedOWRigidbody();
|
||||||
|
var resources = playerBody.GetComponent<PlayerResources>();
|
||||||
|
resources._currentHealth = 100f;
|
||||||
|
}
|
||||||
|
|
||||||
public static void LoadDB()
|
public static void LoadDB()
|
||||||
{
|
{
|
||||||
if (Instance.CurrentStarSystem == "SolarSystem")
|
if (Instance.CurrentStarSystem == "SolarSystem")
|
||||||
|
|||||||
@ -436,6 +436,7 @@ namespace NewHorizons
|
|||||||
if (isEyeOfTheUniverse)
|
if (isEyeOfTheUniverse)
|
||||||
{
|
{
|
||||||
_playerAwake = true;
|
_playerAwake = true;
|
||||||
|
EyeSceneHandler.Init();
|
||||||
EyeSceneHandler.OnSceneLoad();
|
EyeSceneHandler.OnSceneLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,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
|
||||||
@ -598,8 +601,8 @@ namespace NewHorizons
|
|||||||
{
|
{
|
||||||
IsSystemReady = true;
|
IsSystemReady = true;
|
||||||
|
|
||||||
// ShipWarpController will handle the invulnerability otherwise
|
// ShipWarpController or VesselWarpHandler will handle the invulnerability otherwise
|
||||||
if (!shouldWarpInFromShip)
|
if (!shouldWarpInFromShip && !shouldWarpInFromVessel)
|
||||||
{
|
{
|
||||||
Delay.FireOnNextUpdate(() => InvulnerabilityHandler.MakeInvulnerable(false));
|
Delay.FireOnNextUpdate(() => InvulnerabilityHandler.MakeInvulnerable(false));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
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 && EyeSceneHandler.GetActiveCustomEyeTravelers().Any())
|
||||||
|
{
|
||||||
|
NHLogger.Log($"Hijacking finale cross-fade, NH will handle it");
|
||||||
|
__instance._waitForCrossfade = false;
|
||||||
|
__instance._waitForMusicEnd = true;
|
||||||
|
EyeSceneHandler.GetMusicController().TransitionToFinale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
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)
|
||||||
|
{
|
||||||
|
if (!EyeSceneHandler.GetActiveCustomEyeTravelers().Any())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool gatheredAllHearthianTravelers = __instance._travelerControllers.Take(4).All(t => t.gameObject.activeInHierarchy);
|
||||||
|
if (!gatheredAllHearthianTravelers)
|
||||||
|
{
|
||||||
|
__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.GetActiveCustomEyeTravelers())
|
||||||
|
{
|
||||||
|
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 && EyeSceneHandler.GetActiveCustomEyeTravelers().Any())
|
||||||
|
{
|
||||||
|
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,26 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches.EyeScenePatches
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(TravelerEyeController))]
|
||||||
|
public static class TravelerEyeControllerPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(TravelerEyeController.OnStartCosmicJamSession))]
|
||||||
|
public static bool TravelerEyeController_OnStartCosmicJamSession(TravelerEyeController __instance)
|
||||||
|
{
|
||||||
|
if (!EyeSceneHandler.GetActiveCustomEyeTravelers().Any())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Not starting the loop audio here; EyeMusicController will handle that
|
||||||
|
if (__instance._signal != null)
|
||||||
|
{
|
||||||
|
__instance._signal.GetOWAudioSource().SetLocalVolume(0f);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
NewHorizons/Patches/PlayerImpactAudioPatches.cs
Normal file
16
NewHorizons/Patches/PlayerImpactAudioPatches.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches;
|
||||||
|
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class PlayerImpactAudioPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(PlayerImpactAudio), nameof(PlayerImpactAudio.OnImpact))]
|
||||||
|
public static bool PlayerImpactAudio_OnImpact()
|
||||||
|
{
|
||||||
|
// DeathManager and PlayerResources _invincible stops player dying but you still hear the impact sounds which is annoying so we disable them
|
||||||
|
return !InvulnerabilityHandler.Invincible;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
|||||||
"author": "xen, Bwc9876, JohnCorby, MegaPiggy, Trifid, and friends",
|
"author": "xen, Bwc9876, JohnCorby, MegaPiggy, Trifid, and friends",
|
||||||
"name": "New Horizons",
|
"name": "New Horizons",
|
||||||
"uniqueName": "xen.NewHorizons",
|
"uniqueName": "xen.NewHorizons",
|
||||||
"version": "1.25.2",
|
"version": "1.26.0",
|
||||||
"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" ],
|
||||||
|
|||||||
@ -87,6 +87,7 @@ Translation credits:
|
|||||||
- Spanish: Ciborgm9, Ink, GayCoffee
|
- Spanish: Ciborgm9, Ink, GayCoffee
|
||||||
- French: xen
|
- French: xen
|
||||||
- Japanese: TRSasasusu
|
- Japanese: TRSasasusu
|
||||||
|
- Portuguese: avengerx, loco-choco
|
||||||
|
|
||||||
New Horizons was based off [Marshmallow](https://github.com/misternebula/Marshmallow) was made by:
|
New Horizons was based off [Marshmallow](https://github.com/misternebula/Marshmallow) was made by:
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
342
docs/src/content/docs/guides/eye-of-the-universe.mdx
Normal file
342
docs/src/content/docs/guides/eye-of-the-universe.mdx
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
---
|
||||||
|
title: Eye of the Universe
|
||||||
|
description: A guide to adding additional content to the Eye of the Universe with New Horizons
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
|
This guide covers some 'gotchas' and features unique to the Eye of the Universe.
|
||||||
|
|
||||||
|
## Extending the Eye of the Universe
|
||||||
|
|
||||||
|
### Star System
|
||||||
|
|
||||||
|
To define a Star System config for the Eye of the Universe, name your star system config file `EyeOfTheUniverse.json` or specify the `"name"` as `"EyeOfTheUniverse"`. Note that many of the star system features have no effect at the Eye compared to a regular custom star system.
|
||||||
|
|
||||||
|
```json title="systems/EyeOfTheUniverse.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/star_system_schema.json",
|
||||||
|
"name": "EyeOfTheUniverse",
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Existing Planet
|
||||||
|
|
||||||
|
The existing areas of the Eye of the Universe, such as the "sixth planet" and funnel, the museum/observatory, the forest of galaxies, and the campfire, are all contained within one static "planet" (despite visually being distinct locations). To add to these areas, you'll need to specify a planet config file with a `"name"` of `"EyeOfTheUniverse"` and *also* a `"starSystem"` of `"EyeOfTheUniverse"`, as the star system and "planet" share the same name.
|
||||||
|
|
||||||
|
```json title="planets/EyeOfTheUniverse.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
|
||||||
|
"name": "EyeOfTheUniverse",
|
||||||
|
"starSystem": "EyeOfTheUniverse",
|
||||||
|
"Props": {
|
||||||
|
"details": [
|
||||||
|
// etc.
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### The Vessel
|
||||||
|
|
||||||
|
You can also add props to the Vessel at the Eye:
|
||||||
|
|
||||||
|
```json title="planets/Vessel.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
|
||||||
|
"name": "Vessel",
|
||||||
|
"starSystem": "EyeOfTheUniverse",
|
||||||
|
"Props": {
|
||||||
|
"details": [
|
||||||
|
// etc.
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Eye Travelers
|
||||||
|
|
||||||
|
Eye Travelers are a special kind of detail prop (see [Detailing](/guides/details/)) that get added in as additional characters around the campfire at the end of the Eye sequence, similar to Solanum and the Prisoner in the vanilla game.
|
||||||
|
|
||||||
|
At minimum, you will need a character object to act as the traveler, an audio file for the looping part of the new instrument track, an audio file to layer over the finale, a dialogue XML file with certain dialogue conditions set up, and a [quantum instrument](#quantum-instruments).
|
||||||
|
|
||||||
|
The traveler will only appear once their quantum instrument is gathered. After that, they will appear in the circle around the campfire, and they can be interacted with through dialogue to start playing their instrument. The instrument audio is handled via a signal on the traveler that only becomes audible after talking to them.
|
||||||
|
|
||||||
|
Custom travelers will automatically be included in the inflation animation that pushes everyone away from the campfire at the end of the sequence.
|
||||||
|
|
||||||
|
[Eye Travelers](#eye-travelers), [Quantum Instruments](#quantum-instruments), and [Instrument Zones](#instrument-zones) are all linked by their `"id"` properties. Ensure that your ID matches between those details and is unique enough to not conflict with other mods.
|
||||||
|
|
||||||
|
Here's an example config:
|
||||||
|
```json title="planets/EyeOfTheUniverse.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
|
||||||
|
"name": "EyeOfTheUniverse",
|
||||||
|
"starSystem": "EyeOfTheUniverse",
|
||||||
|
"EyeOfTheUniverse": {
|
||||||
|
"eyeTravelers": [
|
||||||
|
{
|
||||||
|
"id": "Slate",
|
||||||
|
"signal": {
|
||||||
|
"name": "Slate",
|
||||||
|
"audio": "planets/MetronomeLoop.wav",
|
||||||
|
"detectionRadius": 0,
|
||||||
|
"identificationRadius": 10,
|
||||||
|
"onlyAudibleToScope": false,
|
||||||
|
"position": {"x": 0, "y": 0.75, "z": 0},
|
||||||
|
"isRelativeToParent": true
|
||||||
|
},
|
||||||
|
"finaleAudio": "planets/MetronomeFinale.wav",
|
||||||
|
"startPlayingCondition": "EyeSlatePlaying",
|
||||||
|
"participatingCondition": "EyeSlateParticipating",
|
||||||
|
"dialogue": {
|
||||||
|
"xmlFile": "planets/EyeSlate.xml",
|
||||||
|
"position": {"x": 0.0, "y": 1.5, "z": 0.0},
|
||||||
|
"isRelativeToParent": true
|
||||||
|
},
|
||||||
|
"path": "TimberHearth_Body/Sector_TH/Sector_Village/Sector_StartingCamp/Characters_StartingCamp/Villager_HEA_Slate"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the full list of eye traveler properties and descriptions of what each property does, check [the EyeTravelerInfo schema](/schemas/body-schema/defs/eyetravelerinfo/).
|
||||||
|
|
||||||
|
<Aside title="On Compatibility">
|
||||||
|
New Horizons changes the campfire sequence in minor ways to make it easier for mods which add additional travelers to be compatible with each other. The audio handling is changed so that instrument audio will be synchronized and layered with each other automatically. All travelers will be automatically repositioned in a circle around the campfire based on the total number of travelers, both vanilla and modded. As long as other details (such as quantum instruments and instrument zones) are not placed in the same locations as other existing mods, the campfire sequence changes will be compatible.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
### Audio
|
||||||
|
|
||||||
|
The looping audio clip is used for the signals emitted by the traveler and their quantum instrument, and is the audio that gets played when they play their instrument. It should be a WAV file with 16 measures of music at 92 BPM (exactly 2,003,478 samples at 48,000 Hz, or approximately 42 seconds long). It is highly recommended that you use Audacity or another audio editor to trim your audio clip to exactly the same length as one of the vanilla traveler audio clips, or else it will fall gradually out of sync with the other instrument loops.
|
||||||
|
|
||||||
|
The finale audio clip is only played once, after all travelers have started playing. 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). Unlike the looping audio clip, it does not need to precisely match the length of the vanilla finale clip; it can end early or continue playing after the other ends.
|
||||||
|
|
||||||
|
The game plays all of the looping audio clips (including your custom one) simultaneously once you tell the first traveler to start playing, and then fades them in one by one as you talk to the others. After all travelers are playing, the game selects a finale audio clip that contains all Hearthian and Nomai/Owlk instruments mixed into one file, and then your custom finale audio clip will be layered over whichever vanilla clip plays. Only include your own instrument in the clip, and ensure it sounds okay alongside Solanum, the Prisoner, and both/neither.
|
||||||
|
|
||||||
|
### Dialogue
|
||||||
|
|
||||||
|
The dialogue XML for your traveler works like other dialogue (see the [Dialogue](/guides/dialogue/) guide) but there are specially named conditions you will need to use for the functionality to work as expected:
|
||||||
|
- Use `<EntryCondition>AnyTravelersGathered</EntryCondition>` to check if any traveler has been gathered yet. This includes Riebeck and Esker, so it should always be true, unless you forcibly enable your traveler to be enabled early.
|
||||||
|
- Use `<EntryCondition>AllTravelersGathered</EntryCondition>` to check if all of the travelers have been gathered and are ready to start playing.
|
||||||
|
- Use `<EntryCondition>JamSessionIsOver</EntryCondition>` to check if the travelers have stopped playing the song and the sphere of possibilities has appeared.
|
||||||
|
- Use a `<SetCondition></SetCondition>` with the condition defined in your eye traveler config's `"startPlayingCondition"` on the node or dialogue option that should make your traveler start playing their instrument. This condition name must be unique and not conflict with other mods.
|
||||||
|
- If you want your traveler to be present but have an option to not participate in the campfire song (like the Prisoner), use a `<SetCondition></SetCondition>` with the condition defined in your eye traveler config's `"participatingCondition"` on the node or dialogue option where your traveler agrees to join in. This condition name must be unique and not conflict with other mods.
|
||||||
|
|
||||||
|
```xml title="planets/dialogue/SlateEyeTraveler.xml"
|
||||||
|
<DialogueTree xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/dialogue_schema.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<NameField>Slate, Probably</NameField>
|
||||||
|
<DialogueNode>
|
||||||
|
<Name>WAITING_FOR_OTHERS</Name>
|
||||||
|
<EntryCondition>DEFAULT</EntryCondition>
|
||||||
|
<Dialogue>
|
||||||
|
<Page>It's me, definitely Slate and not a dreamstalker in disguise.</Page>
|
||||||
|
</Dialogue>
|
||||||
|
</DialogueNode>
|
||||||
|
<DialogueNode>
|
||||||
|
<Name>ANY_GATHERED</Name>
|
||||||
|
<EntryCondition>AnyTravelersGathered</EntryCondition>
|
||||||
|
<Dialogue>
|
||||||
|
<Page>You still have other travelers to gather.</Page>
|
||||||
|
</Dialogue>
|
||||||
|
<DialogueOptionsList>
|
||||||
|
<DialogueOption>
|
||||||
|
<Text>You're going to join in, right?</Text>
|
||||||
|
<DialogueTarget>PARTICIPATING</DialogueTarget>
|
||||||
|
</DialogueOption>
|
||||||
|
<DialogueOption>
|
||||||
|
<Text>Okay then...</Text>
|
||||||
|
</DialogueOption>
|
||||||
|
</DialogueOptionsList>
|
||||||
|
</DialogueNode>
|
||||||
|
<DialogueNode>
|
||||||
|
<Name>PARTICIPATING</Name>
|
||||||
|
<Dialogue>
|
||||||
|
<Page>Sure.</Page>
|
||||||
|
</Dialogue>
|
||||||
|
<SetCondition>EyeSlateParticipating</SetCondition>
|
||||||
|
</DialogueNode>
|
||||||
|
<DialogueNode>
|
||||||
|
<Name>READY_TO_PLAY</Name>
|
||||||
|
<EntryCondition>AllTravelersGathered</EntryCondition>
|
||||||
|
<Dialogue>
|
||||||
|
<Page>We're all here. Time to start the music.</Page>
|
||||||
|
</Dialogue>
|
||||||
|
<DialogueOptionsList>
|
||||||
|
<DialogueOption>
|
||||||
|
<Text>Ready to go.</Text>
|
||||||
|
<DialogueTarget>START_PLAYING</DialogueTarget>
|
||||||
|
</DialogueOption>
|
||||||
|
<DialogueOption>
|
||||||
|
<Text>Not yet.</Text>
|
||||||
|
<DialogueTarget>NOT_YET</DialogueTarget>
|
||||||
|
</DialogueOption>
|
||||||
|
</DialogueOptionsList>
|
||||||
|
</DialogueNode>
|
||||||
|
<DialogueNode>
|
||||||
|
<Name>START_PLAYING</Name>
|
||||||
|
<Dialogue>
|
||||||
|
<Page>Let's begin.</Page>
|
||||||
|
</Dialogue>
|
||||||
|
<SetCondition>EyeSlatePlaying</SetCondition>
|
||||||
|
</DialogueNode>
|
||||||
|
<DialogueNode>
|
||||||
|
<Name>NOT_YET</Name>
|
||||||
|
<Dialogue>
|
||||||
|
<Page>Whenever you're ready.</Page>
|
||||||
|
</Dialogue>
|
||||||
|
</DialogueNode>
|
||||||
|
<DialogueNode>
|
||||||
|
<Name>FAREWELL</Name>
|
||||||
|
<EntryCondition>JamSessionIsOver</EntryCondition>
|
||||||
|
<Dialogue>
|
||||||
|
<Page>It's rewind time.</Page>
|
||||||
|
</Dialogue>
|
||||||
|
</DialogueNode>
|
||||||
|
</DialogueTree>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Animation
|
||||||
|
|
||||||
|
To add custom animations to your Eye Traveler, there is some setup work that has to be done in Unity. You will need to set up your character in Unity and load them via asset bundle, like you would any other detail:
|
||||||
|
|
||||||
|
```json title="planets/EyeOfTheUniverse.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
|
||||||
|
"name": "EyeOfTheUniverse",
|
||||||
|
"starSystem": "EyeOfTheUniverse",
|
||||||
|
"EyeOfTheUniverse": {
|
||||||
|
"eyeTravelers": [
|
||||||
|
{
|
||||||
|
"id": "MyCoolEyeGuy",
|
||||||
|
"assetBundle": "planets/bundles/eyeoftheuniverse",
|
||||||
|
"path": "Assets/EyeOfTheUniverse/Traveler/MyCoolEyeGuy.prefab"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, create an Animator Controller asset in Unity with at least two states, named "Idle" and "PlayingInstrument". You can assign whatever animation clip you like to these states, but the names of the states must match exactly. The default Idle state will play when the traveler is first spawned in, and will transition to the PlayingInstrument state when the right conditions are met to start playing the instrument. Ensure that both animation clips are set to loop in their import settings.
|
||||||
|
|
||||||
|
Add a boolean Parameter in the left panel named "Playing". This will be set to true when the traveler starts playing their instrument.
|
||||||
|
|
||||||
|
Add a transition in both directions between the Idle and PlayingInstrument states. Uncheck "Has Exit Time" for both transitions and adjust the other timing settings as desired.
|
||||||
|
|
||||||
|
Add a Condition on the `Idle -> PlayingInstrument` transition to check for `Playing` = `true`, and the inverse for `PlayingInstrument -> Idle`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In your character object, find the `Animator` component and set its `Controller` property to the Animator Controller asset you created. If you have a `TravelerEyeController` component in your object, set its `_animator` property to your Animator component.
|
||||||
|
|
||||||
|
If everything was set up correctly, your character should play their animations in-game.
|
||||||
|
|
||||||
|
## Quantum Instruments
|
||||||
|
|
||||||
|
Quantum instruments are the interactible instruments, typically hidden by a short 'puzzle', that cause their corresponding traveler to appear around the campfire. They are just like any other detail prop (see [Detailing](/guides/details/)) but they have additional handling to only activate after gathering and speaking to Riebeck, like the other instrument 'puzzles' in the Eye sequence.
|
||||||
|
|
||||||
|
If not specified, the quantum instrument will inherit some of the properties for its `"signal"` from the corresponding eye traveler config.
|
||||||
|
|
||||||
|
If you want other objects besides the traveler to appear or disappear in response to gathering the instrument, specify a custom dialogue condition name for `"gatherCondition"` and use that same condition as the `"activationCondition"` or `"deactivationCondition"` for the details you want to toggle.
|
||||||
|
|
||||||
|
Quantum instruments will automatically be included in the inflation animation that pushes everyone away from the campfire at the end of the sequence.
|
||||||
|
|
||||||
|
[Eye Travelers](#eye-travelers), [Quantum Instruments](#quantum-instruments), and [Instrument Zones](#instrument-zones) are all linked by their `"id"` properties. Ensure that your ID matches between those details and is unique enough to not conflict with other mods.
|
||||||
|
|
||||||
|
```json title="planets/EyeOfTheUniverse.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
|
||||||
|
"name": "EyeOfTheUniverse",
|
||||||
|
"starSystem": "EyeOfTheUniverse",
|
||||||
|
"EyeOfTheUniverse": {
|
||||||
|
"eyeTravelers": [
|
||||||
|
{
|
||||||
|
"id": "Slate",
|
||||||
|
"signal": {
|
||||||
|
"name": "Slate",
|
||||||
|
"audio": "planets/MetronomeLoop.wav"
|
||||||
|
// etc.
|
||||||
|
},
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quantumInstruments": [
|
||||||
|
{
|
||||||
|
"id": "Slate",
|
||||||
|
"gatherWithScope": false,
|
||||||
|
"gatherCondition": "EyeSlateGather",
|
||||||
|
"path": "TimberHearth_Body/Sector_TH/Sector_Village/Sector_StartingCamp/Props_StartingCamp/OtherComponentsGroup/Props_HEA_CampsiteLogAssets/Props_HEA_MarshmallowCanOpened",
|
||||||
|
"position": {"x": -43.94369, "y": 0, "z": 7506.436},
|
||||||
|
"signal": {
|
||||||
|
"detectionRadius": 0,
|
||||||
|
"identificationRadius": 10,
|
||||||
|
"position": {"x": 0, "y": 0.1, "z": 0},
|
||||||
|
"isRelativeToParent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the full list of quantum instrument properties and descriptions of what each property does, check [the QuantumInstrumentInfo schema](/schemas/body-schema/defs/quantuminstrumentinfo/).
|
||||||
|
|
||||||
|
## Instrument Zones
|
||||||
|
|
||||||
|
Instrument zones are just like any other detail prop (see [Detailing](/guides/details/)) but they have additional handling to only activate after gathering and speaking to Riebeck, like the other instrument 'puzzles' in the Eye sequence.
|
||||||
|
|
||||||
|
Custom instrument zones will automatically be included in the inflation animation that pushes everyone away from the campfire at the end of the sequence.
|
||||||
|
|
||||||
|
[Eye Travelers](#eye-travelers), [Quantum Instruments](#quantum-instruments), and [Instrument Zones](#instrument-zones) are all linked by their `"id"` properties. Ensure that your ID matches between those details and is unique enough to not conflict with other mods.
|
||||||
|
|
||||||
|
```json title="planets/EyeOfTheUniverse.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
|
||||||
|
"name": "EyeOfTheUniverse",
|
||||||
|
"starSystem": "EyeOfTheUniverse",
|
||||||
|
"EyeOfTheUniverse": {
|
||||||
|
"eyeTravelers": [
|
||||||
|
{
|
||||||
|
"id": "Slate",
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"instrumentZones": [
|
||||||
|
{
|
||||||
|
"id": "Slate",
|
||||||
|
"deactivationCondition": "EyeSlateGather",
|
||||||
|
"path": "TimberHearth_Body/Sector_TH/Sector_Village/Sector_StartingCamp/Props_StartingCamp/OtherComponentsGroup/Props_HEA_CampsiteLogAssets",
|
||||||
|
"removeChildren": [
|
||||||
|
"Props_HEA_MarshmallowCanOpened"
|
||||||
|
],
|
||||||
|
"position": {"x": -43.30302, "y": 0, "z": 7507.822}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the full list of instrument zone properties and descriptions of what each property does, check [the InstrumentZoneInfo schema](/schemas/body-schema/defs/instrumentzoneinfo/).
|
||||||
|
|
||||||
|
## Eye-Specific Considerations
|
||||||
|
|
||||||
|
#### Cross-System Details
|
||||||
|
|
||||||
|
Specifying details with a `"path"` pointing to an object in the regular solar system normally wouldn't work, as the Eye of the Universe lives in a completely separate scene from the rest of the game and those objects don't exist at the Eye. New Horizons works around this by force-loading the regular solar system, grabbing any objects referenced in Eye of the Universe config files, and then attempting to preserve these objects when loading the Eye of the Universe scene. This can cause issues with many different kinds of props, especially interactive ones that depend on some other part of the solar system existing.
|
||||||
|
|
||||||
|
Because the objects are not available outside of this workaround, objects from the regular solar system cannot be spawned in via the New Horizons API.
|
||||||
|
|
||||||
|
#### Custom Planets
|
||||||
|
|
||||||
|
While you *can* define completely custom planets the same as you would in a regular custom solar system, they may exhibit weird orbital behaviors or pass through the existing static Eye of the Universe objects. Prefer adding onto the existing bodies or setting a `"staticPosition"` on your planet configs to lock them in place.
|
||||||
|
|
||||||
|
#### The Player Ship and Ship Logs
|
||||||
|
|
||||||
|
The player's ship does not exist in the Eye of the Universe scene. In addition to the obvious issues this causes (no access to the ship's warp functionality, ship spawn points being non-functional, etc.), the ship log computer not existing causes some methods of checking and learning ship log facts to not function at all while at the Eye. If you need to track whether the player has met certain conditions elsewhere in the game (for example, if they've previously met a character that you now want to appear at the campfire), consider using a Persistent Dialogue Condition, which does not have these issues.
|
||||||
|
|
||||||
|
#### Mod Compatibility
|
||||||
|
|
||||||
|
Other existing and future story mods will want to add additional content to the Eye of the Universe, and unlike entirely custom planets and star systems, there is a high probability that objects placed at the Eye may overlap with those placed by other mods. When testing, try installing as many of these other mods as possible and seeing if the objects they add overlap with yours. If so, consider moving your objects to a different position. When possible, use New Horizons features that preserve compatibility between mods.
|
||||||
@ -90,6 +90,9 @@ This makes the second planet a quantum state of the first, anything you specify
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Keep in mind that if you redefine `Orbit` on all configs (even with the same parameters each time), **the planet will change its position within its orbit when changing states.**
|
||||||
|
*If you want your Quantum Planet's position to* ***NOT*** *change,* ***only define `Orbit` on the main state***.
|
||||||
|
|
||||||
## Barycenters (Focal Points)
|
## Barycenters (Focal Points)
|
||||||
|
|
||||||
To create a binary system of planets (like ash twin and ember twin), first create a config with `FocalPoint` set
|
To create a binary system of planets (like ash twin and ember twin), first create a config with `FocalPoint` set
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user