using System; using System.Collections.Generic; using FMOD.Studio; using FMODUnity; using NitroxClient.GameLogic.FMOD; using NitroxClient.Unity.Helper; using NitroxModel.GameLogic.FMOD; using UnityEngine; namespace NitroxClient.MonoBehaviours; [DisallowMultipleComponent] public class FMODEmitterController : MonoBehaviour { private readonly Dictionary customEmitters = new(); private readonly Dictionary> loopingEmitters = new(); // Tuple private readonly Dictionary studioEmitters = new(); private readonly Dictionary eventInstances = new(); // 2D Sounds /// /// When s are copied their Start()/Awake() functions don't get called again. /// So the FMOD Start patch that tried to locate a won't find one and will error later. /// This function can late register missed FMOD MonoBehaviours /// public void LateRegisterEmitter() { foreach (MonoBehaviour behaviour in gameObject.GetComponentsInChildren(true)) { switch (behaviour) { case FMOD_CustomEmitter customEmitter when this.Resolve().IsWhitelisted(customEmitter.asset.path, out float maxDistance): AddEmitter(customEmitter.asset.path, customEmitter, maxDistance); break; case FMOD_StudioEventEmitter studioEmitter when this.Resolve().IsWhitelisted(studioEmitter.asset.path, out float maxDistance): AddEmitter(studioEmitter.asset.path, studioEmitter, maxDistance); break; } } } public void AddEmitter(string path, FMOD_CustomEmitter customEmitter, float maxDistance) { if (customEmitters.ContainsKey(path)) { return; } customEmitter.CacheEventInstance(); EventInstance evt = customEmitter.GetEventInstance(); evt.getDescription(out EventDescription description); description.is3D(out bool is3D); if (is3D) { evt.setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); evt.setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, maxDistance); customEmitters.Add(path, customEmitter); if (customEmitter is FMOD_CustomLoopingEmitter loopingEmitter) { if (loopingEmitter.assetStart && this.Resolve().IsWhitelisted(loopingEmitter.assetStart.path, out float radiusStart)) { AddEmitter(loopingEmitter.assetStart.path, loopingEmitter, radiusStart); } if (loopingEmitter.assetStop && this.Resolve().IsWhitelisted(loopingEmitter.assetStop.path, out float radiusStop)) { AddEmitter(loopingEmitter.assetStop.path, loopingEmitter, radiusStop); } } } else { AddEventInstance(customEmitter.asset.path, evt); } } private void AddEmitter(string path, FMOD_CustomLoopingEmitter loopingEmitter, float radius) { if (!loopingEmitters.ContainsKey(path)) { loopingEmitter.CacheEventInstance(); loopingEmitter.evt.getDescription(out EventDescription description); description.is3D(out bool is3D); loopingEmitters.Add(path, new Tuple(loopingEmitter, is3D, radius)); } } public void AddEmitter(string path, FMOD_StudioEventEmitter studioEmitter, float maxDistance) { if (!customEmitters.ContainsKey(path)) { studioEmitter.CacheEventInstance(); studioEmitters.Add(path, studioEmitter); } } private void AddEventInstance(string path, EventInstance eventInstance) { if (!eventInstances.ContainsKey(path)) { eventInstances.Add(path, eventInstance); } } public void PlayCustomEmitter(string path) => customEmitters[path].AliveOrNull()?.Play(); public void SetParameterCustomEmitter(string path, string paramString, float value) => customEmitters[path].AliveOrNull()?.SetParameterValue(paramString, value); public void StopCustomEmitter(string path) => customEmitters[path].AliveOrNull()?.Stop(); public void PlayStudioEmitter(string path) => studioEmitters[path].AliveOrNull()?.PlayUI(); public void StopStudioEmitter(string path, bool allowFadeout) => studioEmitters[path].AliveOrNull()?.Stop(allowFadeout); public void PlayCustomLoopingEmitter(string path) { (FMOD_CustomLoopingEmitter loopingEmitter, bool is3D, float radius) = loopingEmitters[path]; EventInstance eventInstance = FMODUWE.GetEventImpl(path); if (is3D) { eventInstance.set3DAttributes(loopingEmitter.transform.To3DAttributes()); eventInstance.setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); eventInstance.setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radius); } else { eventInstance.setVolume(FMODSystem.CalculateVolume(loopingEmitter.transform.position, Player.main.transform.position, radius, 1f)); } eventInstance.start(); eventInstance.release(); loopingEmitter.timeLastStopSound = Time.time; } public void PlayEventInstance(string path, float volume) { EventInstance eventInstance = eventInstances[path]; eventInstance.setVolume(volume); eventInstance.start(); } public void StopEventInstance(string path) { EventInstance eventInstance = eventInstances[path]; eventInstance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE); } public static void PlayEventOneShot(FMODAsset asset, float radius, Vector3 origin, float volume = 1f) => PlayEventOneShot(asset.path, radius, origin, volume); public static void PlayEventOneShot(string path, float radius, Vector3 origin, float volume = 1f) { EventInstance evt = FMODUWE.GetEventImpl(path); evt.getDescription(out EventDescription description); description.is3D(out bool is3D); if (is3D) { evt.setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); evt.setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radius); evt.setVolume(volume); } else { evt.setVolume(FMODSystem.CalculateVolume(origin, Player.main.transform.position, radius, volume)); } evt.set3DAttributes(origin.To3DAttributes()); evt.start(); evt.release(); } }