// Distant Lands 2024 // COZY: Stylized Weather 3 // All code included in this file is protected under the Unity Asset Store Eula using System.Collections; using DistantLands.Cozy.Data; using System.Collections.Generic; using System.Linq; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace DistantLands.Cozy { [ExecuteAlways] public class CozyAmbienceModule : CozyBiomeModuleBase { public AmbienceProfile[] ambienceProfiles = new AmbienceProfile[0]; [System.Serializable] public class WeightedAmbience { public AmbienceProfile ambienceProfile; [Range(0, 1)] public float weight; public bool transitioning; public IEnumerator Transition(float value, float time) { transitioning = true; float t = 0; float start = weight; while (t < time) { float div = (t / time); yield return new WaitForEndOfFrame(); weight = Mathf.Lerp(start, value, div); t += Time.deltaTime; } weight = value; ambienceProfile.SetWeight(weight); transitioning = false; } } public List weightedAmbience = new List(); public AmbienceProfile currentAmbienceProfile; public AmbienceProfile ambienceChangeCheck; public float timeToChangeProfiles = 7; private float m_AmbienceTimer; void Start() { if (!enabled) return; if (isBiomeModule) return; foreach (AmbienceProfile profile in ambienceProfiles) { foreach (FXProfile fx in profile.FX) fx?.InitializeEffect(weatherSphere); } if (Application.isPlaying) { SetNextAmbience(); weightedAmbience = new List() { new WeightedAmbience() { weight = 1, ambienceProfile = currentAmbienceProfile } }; } } public void FindAllAmbiences() { List profiles = new List(); foreach (AmbienceProfile i in EditorUtilities.GetAllInstances()) if (i.name != "Default Ambience") profiles.Add(i); foreach (AmbienceProfile profile in ambienceProfiles) { foreach (FXProfile fx in profile.FX) fx?.InitializeEffect(weatherSphere); } ambienceProfiles = profiles.ToArray(); } // Update is called once per frame public override void UpdateWeatherWeights() { if (Application.isPlaying) { if (ambienceChangeCheck != currentAmbienceProfile) { SetAmbience(currentAmbienceProfile); } if (weatherSphere.timeModule) m_AmbienceTimer -= Time.deltaTime * weatherSphere.timeModule.modifiedTimeSpeed; else m_AmbienceTimer -= Time.deltaTime * 0.005f; if (m_AmbienceTimer <= 0) { SetNextAmbience(); } foreach (WeightedAmbience i in weightedAmbience) { i.weight = i.weight * weight; } weightedAmbience.RemoveAll(x => x.weight == 0 && x.transitioning == false); } ComputeBiomeWeights(); // ManageBiomeWeights(); } public override void UpdateFXWeights() { foreach (WeightedAmbience weather in weightedAmbience) { weather.ambienceProfile.SetWeight(weather.weight); } } public override void UpdateBiomeModule() { currentAmbienceProfile.SetWeight(weight); } public void SetNextAmbience() { currentAmbienceProfile = WeightedRandom(ambienceProfiles.ToArray()); } public void SetAmbience(AmbienceProfile profile) { currentAmbienceProfile = profile; ambienceChangeCheck = currentAmbienceProfile; if (weightedAmbience.Find(x => x.ambienceProfile == profile) == null) weightedAmbience.Add(new WeightedAmbience() { weight = 0, ambienceProfile = profile, transitioning = true }); foreach (WeightedAmbience j in weightedAmbience) { if (j.ambienceProfile == profile) StartCoroutine(j.Transition(1, timeToChangeProfiles)); else StartCoroutine(j.Transition(0, timeToChangeProfiles)); } m_AmbienceTimer += Random.Range(currentAmbienceProfile.minTime, currentAmbienceProfile.maxTime); } public void SetAmbience(AmbienceProfile profile, float timeToChange) { currentAmbienceProfile = profile; ambienceChangeCheck = currentAmbienceProfile; if (weightedAmbience.Find(x => x.ambienceProfile == profile) == null) weightedAmbience.Add(new WeightedAmbience() { weight = 0, ambienceProfile = profile, transitioning = true }); foreach (WeightedAmbience j in weightedAmbience) { if (j.ambienceProfile == profile) StartCoroutine(j.Transition(1, timeToChange)); else StartCoroutine(j.Transition(0, timeToChange)); } m_AmbienceTimer += Random.Range(currentAmbienceProfile.minTime, currentAmbienceProfile.maxTime); } public void SkipTime(float timeToSkip) => m_AmbienceTimer -= timeToSkip; public AmbienceProfile WeightedRandom(AmbienceProfile[] profiles) { AmbienceProfile i = null; List floats = new List(); float totalChance = 0; foreach (AmbienceProfile k in profiles) { float chance; if (weatherSphere.weatherModule) if (k.dontPlayDuring.Contains(weatherSphere.weatherModule.ecosystem.currentWeather)) chance = 0; else chance = k.GetChance(weatherSphere); else chance = k.GetChance(weatherSphere); floats.Add(chance); totalChance += chance; } if (totalChance == 0) { i = (AmbienceProfile)Resources.Load("Default Ambience"); Debug.LogWarning("Could not find a suitable ambience given the current selected profiles and chance effectors. Defaulting to an empty ambience."); return i; } float selection = Random.Range(0, totalChance); int m = 0; float l = 0; while (l <= selection) { if (selection >= l && selection < l + floats[m]) { i = profiles[m]; break; } l += floats[m]; m++; } if (!i) { i = profiles[0]; } return i; } public float GetTimeTillNextAmbience() => m_AmbienceTimer; } #if UNITY_EDITOR [CustomEditor(typeof(CozyAmbienceModule))] [CanEditMultipleObjects] public class E_AmbienceManager : E_CozyModule, E_BiomeModule, IControlPanel { protected static bool profileSettings; protected static bool currentInfo; CozyAmbienceModule ambienceManager; public override GUIContent GetGUIContent() { return new GUIContent(" Ambience", (Texture)Resources.Load("Ambience Profile"), "Controls a secondary weather system that runs parallel to the main system allowing for ambient noises and FX."); } void OnEnable() { if (target) ambienceManager = (CozyAmbienceModule)target; } public override void GetReportsInformation() { EditorGUILayout.LabelField(GetGUIContent(), EditorStyles.toolbar); EditorGUILayout.HelpBox("Current Ambiences", MessageType.None); foreach (CozyAmbienceModule.WeightedAmbience w in ambienceManager.weightedAmbience) EditorGUILayout.HelpBox($"{w.ambienceProfile.name} - Weight: {w.weight}", MessageType.None); } public void GetControlPanel() { EditorGUILayout.PropertyField(serializedObject.FindProperty("currentAmbienceProfile")); } public override void OpenDocumentationURL() { Application.OpenURL("https://distant-lands.gitbook.io/cozy-stylized-weather-documentation/how-it-works/modules/ambience-module"); } public override void DisplayInCozyWindow() { serializedObject.Update(); EditorGUI.indentLevel = 0; if (ambienceManager == null) if (target) ambienceManager = (CozyAmbienceModule)target; else return; profileSettings = EditorGUILayout.BeginFoldoutHeaderGroup(profileSettings, " Forecast Settings", EditorUtilities.FoldoutStyle); EditorGUI.EndFoldoutHeaderGroup(); if (profileSettings) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serializedObject.FindProperty("ambienceProfiles")); serializedObject.ApplyModifiedProperties(); EditorGUILayout.Space(); if (GUILayout.Button("Add all ambience profiles")) ambienceManager.FindAllAmbiences(); EditorGUI.indentLevel--; } currentInfo = EditorGUILayout.BeginFoldoutHeaderGroup(currentInfo, " Current Information", EditorUtilities.FoldoutStyle); EditorGUI.EndFoldoutHeaderGroup(); if (currentInfo) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serializedObject.FindProperty("currentAmbienceProfile")); if (Application.isPlaying) EditorGUILayout.HelpBox(ambienceManager.currentAmbienceProfile.name + " will be playing for the next " + ((MeridiemTime)ambienceManager.GetTimeTillNextAmbience()).ToString() + " of in-game time.", MessageType.None, true); EditorGUI.indentLevel--; } serializedObject.ApplyModifiedProperties(); } public void DrawBiomeReports() { EditorGUILayout.HelpBox($"Current ambience is {ambienceManager.currentAmbienceProfile.name}", MessageType.None, true); } public void DrawInlineBiomeUI() { if (target == null) return; serializedObject.Update(); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(serializedObject.FindProperty("currentAmbienceProfile")); EditorGUI.indentLevel--; serializedObject.ApplyModifiedProperties(); } } #endif }