Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DogHausAmbience v1.0.0
plugins/DogHausAmbience.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.Audio; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace DogHausAmbience; internal static class VLog { private static readonly HashSet<string> s_once = new HashSet<string>(); internal static void Info(string m) { if (DogHausAmbiencePlugin.Log != null) { DogHausAmbiencePlugin.Log.LogInfo((object)m); } else { Debug.Log((object)("[DogHausAmbience] " + m)); } } internal static void Warn(string m) { if (DogHausAmbiencePlugin.Log != null) { DogHausAmbiencePlugin.Log.LogWarning((object)m); } else { Debug.LogWarning((object)("[DogHausAmbience] " + m)); } } internal static void Once(string where, Exception e) { lock (s_once) { if (!s_once.Add(where)) { return; } } Warn(where + " failed (logged once): " + e.GetType().Name + ": " + e.Message); } } internal abstract class Effect { internal readonly string Key; internal readonly string Description; internal ConfigEntry<bool> Enabled; internal float Blend; internal bool Want; internal bool On { get { if (Effects.Running && Enabled != null) { return Enabled.Value; } return false; } } internal virtual bool Busy => false; internal virtual bool WantsGui => false; protected Effect(string key, string description) { Key = key; Description = description; } internal virtual void Bind(ConfigFile cfg) { Enabled = cfg.Bind<bool>("Effects", Key, true, Description); } internal abstract bool Evaluate(Player p); internal abstract void Tick(GameCamera gc, Player p, float now, float dt); internal abstract void TearDown(); internal virtual void OnGUI() { } } internal static class Effects { internal const float FadeSeconds = 1.5f; internal const float EvalInterval = 0.2f; internal static readonly List<Effect> All = new List<Effect>(); internal static ConfigEntry<bool> CfgAll; internal static bool Allowed; internal static string AllowedWhy = "no player yet"; internal static int TearDowns; private static Player s_evalFor; private static float s_nextEval; private static bool s_anyActive; private static bool s_dedicatedKnown; private static bool s_dedicated; private static int s_mainThread = -1; private static bool s_pendingAll; private static readonly List<Effect> s_pending = new List<Effect>(); internal static bool Running { get { if (Allowed && CfgAll != null) { return CfgAll.Value; } return false; } } internal static void Bind(ConfigFile cfg) { s_mainThread = Thread.CurrentThread.ManagedThreadId; CfgAll = cfg.Bind<bool>("General", "Enabled", true, "DogHaus Ambience on this PC. false = every sound effect off at once."); foreach (Effect item in All) { item.Bind(cfg); } Exclusion.Bind(cfg); CfgAll.SettingChanged += delegate { SwitchChanged(null); }; foreach (Effect item2 in All) { Effect tw = item2; if (tw.Enabled != null) { tw.Enabled.SettingChanged += delegate { SwitchChanged(tw); }; } } } private static void SwitchChanged(Effect t) { bool num; if (t != null) { if (t.Enabled == null) { return; } num = !t.Enabled.Value; } else { if (CfgAll == null) { return; } num = !CfgAll.Value; } if (!num) { return; } if (Thread.CurrentThread.ManagedThreadId != s_mainThread) { lock (s_pending) { if (t == null) { s_pendingAll = true; } else if (!s_pending.Contains(t)) { s_pending.Add(t); } return; } } try { if (t == null) { foreach (Effect item in All) { item.Want = false; } if (AnythingOurs()) { TearDown(); } } else { TearDownOne(t); } } catch (Exception e) { VLog.Once("Ambience: switch-off teardown", e); } } internal static void TearDownOne(Effect t) { t.Want = false; t.Blend = 0f; try { t.TearDown(); } catch (Exception e) { VLog.Once("Ambience: teardown " + t.Key, e); } TearDowns++; } private static void RunPending() { bool flag; Effect[] array; lock (s_pending) { if (!s_pendingAll && s_pending.Count == 0) { return; } flag = s_pendingAll; s_pendingAll = false; array = s_pending.ToArray(); s_pending.Clear(); } if (flag) { SwitchChanged(null); } Effect[] array2 = array; foreach (Effect t in array2) { SwitchChanged(t); } } internal static T Get<T>() where T : Effect { foreach (Effect item in All) { if (item is T result) { return result; } } return null; } internal static bool IsDedicated() { if (!s_dedicatedKnown && (Object)(object)ZNet.instance != (Object)null) { s_dedicatedKnown = true; s_dedicated = ZNet.instance.IsDedicated(); } return s_dedicated; } internal static void CameraTick(GameCamera gc) { if (IsDedicated()) { return; } RunPending(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)gc == (Object)null) { if (s_anyActive) { StopAll(); } return; } float time = Time.time; float num = Mathf.Min(Time.unscaledDeltaTime, 0.1f); if ((Object)(object)localPlayer != (Object)(object)s_evalFor || time >= s_nextEval || time < s_nextEval - 1f) { try { Evaluate(localPlayer, time); } catch (Exception e) { VLog.Once("Ambience: Evaluate", e); } } bool flag = false; bool gui = false; Ambience.BeginFrame(); for (int i = 0; i < All.Count; i++) { Effect effect = All[i]; effect.Blend = Mathf.MoveTowards(effect.Blend, effect.Want ? 1f : 0f, num / 1.5f); try { effect.Tick(gc, localPlayer, time, num); } catch (Exception e2) { VLog.Once("Ambience: " + effect.Key, e2); } if (effect.Blend > 0f || effect.Busy) { flag = true; } if (effect.WantsGui) { gui = true; } } try { Fx.UpdateLive(); } catch (Exception e3) { VLog.Once("Ambience: clip lookup", e3); } Fx.SetGui(gui); s_anyActive = flag || Ambience.AnyDuckOn; } private static void Evaluate(Player p, float now) { s_evalFor = p; s_nextEval = now + 0.2f; Allowed = Exclusion.LocalAllowed(p, out var why); if (why != AllowedWhy) { AllowedWhy = why; VLog.Info("DogHaus Ambience: " + (Allowed ? "on for this player" : "OFF for this player") + " (" + why + ")."); } if (!Running) { foreach (Effect item in All) { item.Want = false; } if (AnythingOurs()) { TearDown(); } return; } Cond.Evaluate(p); foreach (Effect item2 in All) { try { item2.Want = item2.On && item2.Evaluate(p); } catch (Exception e) { item2.Want = false; VLog.Once("Ambience: " + item2.Key + " evaluate", e); } } } internal static bool AnythingOurs() { foreach (Effect item in All) { if (item.Blend > 0f || item.Busy) { return true; } } if (!s_anyActive && !Ambience.AnyDuckOn) { return Fx.GuiOn; } return true; } internal static void TearDown() { foreach (Effect item in All) { item.Blend = 0f; item.Want = false; try { item.TearDown(); } catch (Exception e) { VLog.Once("Ambience: teardown " + item.Key, e); } } try { Ambience.ReleaseAll(); Fx.SetGui(on: false); } catch (Exception e2) { VLog.Once("Ambience: teardown", e2); } s_anyActive = false; TearDowns++; } internal static void StopAll() { TearDown(); s_evalFor = null; Allowed = false; } internal static string State() { StringBuilder stringBuilder = new StringBuilder(); foreach (Effect item in All) { stringBuilder.Append(item.Key).Append(' ').Append(item.Want ? "want " : "") .Append(item.Blend.ToString("0.00", CultureInfo.InvariantCulture)) .Append("; "); } stringBuilder.Append("running ").Append(Running).Append(" (") .Append(AllowedWhy) .Append("), ducks amb x") .Append(Ambience.AmbientDuck.LastRatio.ToString("0.00", CultureInfo.InvariantCulture)) .Append(" wind x") .Append(Ambience.WindDuck.LastRatio.ToString("0.00", CultureInfo.InvariantCulture)) .Append(" ocean x") .Append(Ambience.OceanDuck.LastRatio.ToString("0.00", CultureInfo.InvariantCulture)); return stringBuilder.ToString(); } } internal static class Cond { internal static bool UnderRoof; internal static bool Sheltered; internal static bool Interior; internal static bool InBase; internal static bool Wet; internal static bool Night; internal static bool Dead; internal static Biome Biome; internal static float Hp = 1f; internal static int TestShelter = -1; internal static int TestInterior = -1; internal static int TestInBase = -1; internal static int TestCombat = -1; internal static Biome TestBiome = (Biome)0; private static readonly FieldInfo s_underRoof = AccessTools.Field(typeof(Player), "m_underRoof"); internal static void ClearTestOverrides() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) TestShelter = (TestInterior = (TestInBase = (TestCombat = -1))); TestBiome = (Biome)0; Exclusion.TestLocalId = long.MinValue; Exclusion.TestIgnoreTweaks = false; } internal static void Evaluate(Player p) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) Dead = ((Character)p).IsDead(); bool flag = false; try { flag = s_underRoof != null && (bool)s_underRoof.GetValue(p); } catch (Exception e) { VLog.Once("Ambience: m_underRoof", e); } UnderRoof = ((TestShelter >= 0) ? (TestShelter == 1) : flag); Sheltered = ((TestShelter >= 0) ? (TestShelter == 1) : p.InShelter()); Interior = ((TestInterior >= 0) ? (TestInterior == 1) : ((Character)p).InInterior()); InBase = ((TestInBase >= 0) ? (TestInBase == 1) : ((Object)(object)EffectArea.IsPointInsideArea(((Component)p).transform.position, (Type)4, 0f) != (Object)null)); Wet = (Object)(object)EnvMan.instance != (Object)null && EnvMan.IsWet(); Night = (Object)(object)EnvMan.instance != (Object)null && EnvMan.IsNight(); Biome = (((int)TestBiome != 0) ? TestBiome : p.GetCurrentBiome()); Hp = ((Character)p).GetHealthPercentage(); } } internal static class Exclusion { internal const string DefaultList = "1723951585, -357758456"; internal static ConfigEntry<string> CfgExclude; internal static long TestLocalId = long.MinValue; internal static bool TestIgnoreTweaks; private static string s_parsedFrom; private static readonly HashSet<long> s_cfgIds = new HashSet<long>(); private static bool s_tried; private static PropertyInfo s_current; private static FieldInfo s_exclude; private static FieldInfo s_lookOnly; private static FieldInfo s_enabled; internal static string TweaksHow = "not looked up yet"; internal static bool TweaksActive { get { Lookup(); if (s_current != null) { return !TestIgnoreTweaks; } return false; } } internal static void Bind(ConfigFile cfg) { CfgExclude = cfg.Bind<string>("Players", "ExcludePlayers", "1723951585, -357758456", "Player IDs (comma-separated) whose own PC never plays these effects. Used only when DogHausTweaks is not loaded - with it, the server's [immersion] exclude_players and [seasons_look_only] players decide."); } internal static HashSet<long> ConfigIds() { string text = ((CfgExclude != null) ? (CfgExclude.Value ?? "") : "1723951585, -357758456"); if (text != s_parsedFrom) { s_parsedFrom = text; s_cfgIds.Clear(); string[] array = text.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { if (long.TryParse(text2.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result != 0) { s_cfgIds.Add(result); } else { VLog.Warn("DogHaus Ambience: ExcludePlayers entry '" + text2 + "' is not a player ID - skipped."); } } } return s_cfgIds; } private static void Lookup() { if (s_tried) { return; } s_tried = true; try { Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { type = assembly.GetType("DogHausBalance.Balance", throwOnError: false); if (type != null) { break; } } if (type == null) { TweaksHow = "DogHausTweaks not loaded"; return; } s_current = type.GetProperty("Current", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_exclude = Ids(type, "ImmersionExcludePlayers"); s_lookOnly = Ids(type, "SeasonsLookOnlyPlayers"); s_enabled = type.GetField("ImmersionEnabled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (s_enabled != null && s_enabled.FieldType != typeof(bool)) { s_enabled = null; } if (s_current == null || (s_exclude == null && s_lookOnly == null)) { s_current = null; TweaksHow = "DogHausTweaks loaded but no player list readable"; } else { TweaksHow = "DogHausTweaks balance (" + ((s_exclude != null) ? "ImmersionExcludePlayers" : "") + ((s_exclude != null && s_lookOnly != null) ? " + " : "") + ((s_lookOnly != null) ? "SeasonsLookOnlyPlayers" : "") + ((s_enabled != null) ? ", ImmersionEnabled" : "") + ")"; } } catch (Exception ex) { s_current = null; TweaksHow = "DogHausTweaks lookup failed: " + ex.Message; } VLog.Info("DogHaus Ambience: excluded players come from " + ((s_current != null) ? TweaksHow : ("this mod's config (" + TweaksHow + ")")) + "."); } private static FieldInfo Ids(Type b, string name) { FieldInfo field = b.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field != null) || !(field.FieldType == typeof(HashSet<long>))) { return null; } return field; } private static bool FromTweaks(long id, out bool excluded, out string why) { excluded = false; why = null; if (!TweaksActive) { return false; } try { object value = s_current.GetValue(null, null); if (value == null) { return false; } if (s_enabled != null && !(bool)s_enabled.GetValue(value)) { excluded = true; why = "the server's [immersion] enabled = false"; return true; } HashSet<long> hashSet = ((s_exclude != null) ? (s_exclude.GetValue(value) as HashSet<long>) : null); HashSet<long> hashSet2 = ((s_lookOnly != null) ? (s_lookOnly.GetValue(value) as HashSet<long>) : null); if (hashSet != null && hashSet.Contains(id)) { excluded = true; why = "player " + id + " is in DogHausTweaks' [immersion] exclude_players"; return true; } if (hashSet2 != null && hashSet2.Contains(id)) { excluded = true; why = "player " + id + " is a [seasons_look_only] player"; return true; } why = "player " + id + ", not excluded by DogHausTweaks' balance"; return true; } catch (Exception e) { VLog.Once("Ambience: DogHausTweaks balance read (using the config list from now on)", e); s_current = null; return false; } } internal static bool LocalAllowed(Player p, out string why) { long num = (((Object)(object)p == (Object)null) ? 0 : ((TestLocalId != long.MinValue) ? TestLocalId : p.GetPlayerID())); if (num == 0) { why = "player ID not known yet"; return false; } if (FromTweaks(num, out var excluded, out why)) { return !excluded; } if (ConfigIds().Contains(num)) { why = "player " + num + " is in ExcludePlayers (config)"; return false; } why = "player " + num + ", not in ExcludePlayers (config)"; return true; } internal static HashSet<long> TweaksIds() { if (!TweaksActive) { return null; } try { object value = s_current.GetValue(null, null); if (value == null) { return null; } HashSet<long> hashSet = new HashSet<long>(); HashSet<long> hashSet2 = ((s_exclude != null) ? (s_exclude.GetValue(value) as HashSet<long>) : null); HashSet<long> hashSet3 = ((s_lookOnly != null) ? (s_lookOnly.GetValue(value) as HashSet<long>) : null); if (hashSet2 != null) { hashSet.UnionWith(hashSet2); } if (hashSet3 != null) { hashSet.UnionWith(hashSet3); } return hashSet; } catch (Exception) { return null; } } } internal sealed class Duck { internal readonly string Name; internal AudioSource Src; internal float Game = -1f; internal float Written = -1f; internal float LastRatio = 1f; internal bool On; internal Duck(string name) { Name = name; } internal void Pre(AudioSource s) { if (On && (Object)(object)s != (Object)null && (Object)(object)s == (Object)(object)Src && s.volume == Written) { s.volume = Game; } } internal void Post(AudioSource s, float k) { if (!((Object)(object)s == (Object)null)) { if (k >= 0.9999f) { On = false; LastRatio = 1f; return; } Src = s; Game = s.volume; Written = Game * k; s.volume = Written; On = true; LastRatio = k; } } internal void Release() { if (On && (Object)(object)Src != (Object)null && Src.volume == Written) { Src.volume = Game; } On = false; LastRatio = 1f; } } internal static class Ambience { internal static readonly Duck AmbientDuck = new Duck("ambient loop"); internal static readonly Duck WindDuck = new Duck("wind"); internal static readonly Duck OceanDuck = new Duck("ocean"); internal static float AmbientFactor = 1f; internal static float WindFactor = 1f; internal static float OceanFactor = 1f; internal static AudioSource AmbientSource; internal static bool AnyDuckOn { get { if (!AmbientDuck.On && !WindDuck.On) { return OceanDuck.On; } return true; } } internal static void BeginFrame() { AmbientFactor = 1f; WindFactor = 1f; OceanFactor = 1f; } internal static void ReleaseAll() { BeginFrame(); AmbientDuck.Release(); WindDuck.Release(); OceanDuck.Release(); } } internal sealed class AmbienceHost : MonoBehaviour { private void OnGUI() { foreach (Effect item in Effects.All) { if (item.WantsGui) { try { item.OnGUI(); } catch (Exception e) { VLog.Once("Ambience: OnGUI " + item.Key, e); } } } } } internal static class Fx { private const int ScanPerFrame = 150; private static GameObject s_host; private static AmbienceHost s_gui; private static readonly HashSet<string> s_missingWarned = new HashSet<string>(); private static AudioMixerGroup s_sfxGroup; private static bool s_sfxGroupTried; private static readonly List<AudioClip> s_clipTmp = new List<AudioClip>(); private static readonly Dictionary<string, GameObject> s_found = new Dictionary<string, GameObject>(StringComparer.Ordinal); private static readonly HashSet<string> s_wanted = new HashSet<string>(StringComparer.Ordinal); private static readonly List<Transform> s_tmp = new List<Transform>(); private static int s_scanIndex; private static bool s_scanning; internal static bool GuiOn { get { if ((Object)(object)s_gui != (Object)null) { return ((Behaviour)s_gui).enabled; } return false; } } internal static bool Scanning => s_scanning; internal static GameObject Host() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)s_host == (Object)null) { s_host = new GameObject("DogHausAmbience"); Object.DontDestroyOnLoad((Object)(object)s_host); s_gui = s_host.AddComponent<AmbienceHost>(); ((Behaviour)s_gui).enabled = false; } return s_host; } internal static void SetGui(bool on) { if ((Object)(object)s_gui == (Object)null) { if (!on) { return; } Host(); } if (((Behaviour)s_gui).enabled != on) { ((Behaviour)s_gui).enabled = on; } } internal static AudioSource NewSource(string name, bool loop, float spatial) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown GameObject val = new GameObject(name); val.transform.SetParent(Host().transform, false); AudioSource val2 = val.AddComponent<AudioSource>(); val2.playOnAwake = false; val2.loop = loop; val2.spatialBlend = spatial; val2.dopplerLevel = 0f; val2.volume = 0f; return val2; } internal static AudioMixerGroup SfxGroup() { if ((Object)(object)s_sfxGroup != (Object)null || s_sfxGroupTried || (Object)(object)ZNetScene.instance == (Object)null) { return s_sfxGroup; } s_sfxGroupTried = true; string[] array = new string[4] { "sfx_wolf_haul", "sfx_troll_idle", "sfx_greydwarf_idle", "sfx_MeadBurp" }; string[] array2 = array; foreach (string text in array2) { GameObject prefab = ZNetScene.instance.GetPrefab(text); AudioSource val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponentInChildren<AudioSource>(true) : null); if ((Object)(object)val != (Object)null && (Object)(object)val.outputAudioMixerGroup != (Object)null) { s_sfxGroup = val.outputAudioMixerGroup; break; } } return s_sfxGroup; } internal static AudioClip ClipOf(string prefab) { GameObject val = Find(prefab); if ((Object)(object)val == (Object)null) { if (!s_scanning && s_missingWarned.Add(prefab)) { VLog.Warn("DogHaus Ambience: sound prefab '" + prefab + "' not found anywhere - skipped."); } return null; } s_clipTmp.Clear(); ZSFX[] componentsInChildren = val.GetComponentsInChildren<ZSFX>(true); foreach (ZSFX val2 in componentsInChildren) { if (val2.m_audioClips == null) { continue; } AudioClip[] audioClips = val2.m_audioClips; foreach (AudioClip val3 in audioClips) { if ((Object)(object)val3 != (Object)null) { s_clipTmp.Add(val3); } } } if (s_clipTmp.Count == 0) { AudioSource[] componentsInChildren2 = val.GetComponentsInChildren<AudioSource>(true); foreach (AudioSource val4 in componentsInChildren2) { if ((Object)(object)val4.clip != (Object)null) { s_clipTmp.Add(val4.clip); } } } if (s_clipTmp.Count == 0) { if (s_missingWarned.Add(prefab)) { VLog.Warn("DogHaus Ambience: sound prefab '" + prefab + "' has no clip (no ZSFX clip list, no AudioSource clip) - skipped."); } return null; } return s_clipTmp[Random.Range(0, s_clipTmp.Count)]; } internal static GameObject Find(string name) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return null; } GameObject value = instance.GetPrefab(name); if ((Object)(object)value != (Object)null) { return value; } if (s_found.TryGetValue(name, out value) && (Object)(object)value != (Object)null) { return value; } if (s_wanted.Add(name)) { ScanStatusEffects(); if (s_found.TryGetValue(name, out value) && (Object)(object)value != (Object)null) { return value; } s_scanIndex = 0; s_scanning = true; } return null; } private static void ScanStatusEffects() { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_StatusEffects == null) { return; } foreach (StatusEffect statusEffect in instance.m_StatusEffects) { if (!((Object)(object)statusEffect == (Object)null)) { Note(statusEffect.m_startEffects, "status effect " + ((Object)statusEffect).name + " (start)"); Note(statusEffect.m_stopEffects, "status effect " + ((Object)statusEffect).name + " (stop)"); } } } private static void Note(EffectList list, string where) { if (list == null || list.m_effectPrefabs == null) { return; } EffectData[] effectPrefabs = list.m_effectPrefabs; foreach (EffectData val in effectPrefabs) { if (val != null && (Object)(object)val.m_prefab != (Object)null && s_wanted.Contains(((Object)val.m_prefab).name) && !s_found.ContainsKey(((Object)val.m_prefab).name)) { s_found[((Object)val.m_prefab).name] = val.m_prefab; VLog.Info("DogHaus Ambience: sound '" + ((Object)val.m_prefab).name + "' found in " + where + "."); } } } internal static void UpdateLive() { if (!s_scanning) { return; } ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || instance.m_prefabs == null) { return; } int num = Mathf.Min(instance.m_prefabs.Count, s_scanIndex + 150); while (s_scanIndex < num) { GameObject val = instance.m_prefabs[s_scanIndex]; if (!((Object)(object)val == (Object)null)) { s_tmp.Clear(); val.GetComponentsInChildren<Transform>(true, s_tmp); for (int i = 0; i < s_tmp.Count; i++) { string name = ((Object)s_tmp[i]).name; if ((Object)(object)s_tmp[i] != (Object)(object)val.transform && s_wanted.Contains(name) && !s_found.ContainsKey(name)) { s_found[name] = ((Component)s_tmp[i]).gameObject; VLog.Info("DogHaus Ambience: sound '" + name + "' found as a child of the prefab '" + ((Object)val).name + "'."); } } } s_scanIndex++; } if (s_scanIndex < instance.m_prefabs.Count) { return; } s_scanning = false; foreach (string item in s_wanted) { if (!s_found.ContainsKey(item) && (Object)(object)instance.GetPrefab(item) == (Object)null && s_missingWarned.Add(item)) { VLog.Warn("DogHaus Ambience: sound '" + item + "' not found anywhere (ZNetScene, status effects, prefab children) - skipped."); } } } } [HarmonyPatch(typeof(AudioMan), "UpdateAmbientLoop")] internal static class AudioMan_UpdateAmbientLoop_Ambience { private static void Prefix(AudioSource ___m_ambientLoopSource) { try { Ambience.AmbientDuck.Pre(___m_ambientLoopSource); } catch (Exception e) { VLog.Once("Ambience: ambient prefix", e); } } private static void Postfix(AudioSource ___m_ambientLoopSource) { try { Ambience.AmbientSource = ___m_ambientLoopSource; Ambience.AmbientDuck.Post(___m_ambientLoopSource, Ambience.AmbientFactor); } catch (Exception e) { VLog.Once("Ambience: ambient postfix", e); } } } [HarmonyPatch(typeof(AudioMan), "UpdateWindAmbience")] internal static class AudioMan_UpdateWindAmbience_Ambience { private static void Prefix(AudioSource ___m_windLoopSource) { try { Ambience.WindDuck.Pre(___m_windLoopSource); } catch (Exception e) { VLog.Once("Ambience: wind prefix", e); } } private static void Postfix(AudioSource ___m_windLoopSource) { try { Ambience.WindDuck.Post(___m_windLoopSource, Ambience.WindFactor); } catch (Exception e) { VLog.Once("Ambience: wind postfix", e); } } } [HarmonyPatch(typeof(AudioMan), "UpdateOceanAmbiance")] internal static class AudioMan_UpdateOceanAmbiance_Ambience { private static void Prefix(AudioSource ___m_oceanAmbientSource) { try { Ambience.OceanDuck.Pre(___m_oceanAmbientSource); } catch (Exception e) { VLog.Once("Ambience: ocean prefix", e); } } private static void Postfix(AudioSource ___m_oceanAmbientSource) { try { Ambience.OceanDuck.Post(___m_oceanAmbientSource, Ambience.OceanFactor); } catch (Exception e) { VLog.Once("Ambience: ocean postfix", e); } } } [HarmonyPatch(typeof(GameCamera), "LateUpdate")] internal static class GameCamera_LateUpdate_Ambience { private static void Postfix(GameCamera __instance) { try { Effects.CameraTick(__instance); } catch (Exception e) { VLog.Once("Ambience: CameraTick", e); } } } [HarmonyPatch(typeof(GameCamera), "OnDestroy")] internal static class GameCamera_OnDestroy_Ambience { private static void Postfix() { try { Effects.StopAll(); } catch (Exception e) { VLog.Once("Ambience: GameCamera.OnDestroy", e); } } } [HarmonyPatch(typeof(Player), "OnDestroy")] internal static class Player_OnDestroy_Ambience { private static void Prefix(Player __instance) { try { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer) { Effects.StopAll(); } } catch (Exception e) { VLog.Once("Ambience: Player.OnDestroy", e); } } } [BepInPlugin("doghaus.ambience", "DogHaus Ambience", "1.0.0")] public class DogHausAmbiencePlugin : BaseUnityPlugin { public const string GUID = "doghaus.ambience"; public const string NAME = "DogHaus Ambience"; public const string VERSION = "1.0.0"; internal static ManualLogSource Log; internal static DogHausAmbiencePlugin Instance; private void Awake() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Instance = this; Effects.All.Add(new RainOnRoof()); Effects.All.Add(new IndoorQuiet()); Effects.All.Add(new DistantNightSounds()); Effects.All.Add(new LowHealthHeartbeat()); Effects.Bind(((BaseUnityPlugin)this).Config); Harmony val = new Harmony("doghaus.ambience"); Type[] array = new Type[6] { typeof(AudioMan_UpdateAmbientLoop_Ambience), typeof(AudioMan_UpdateWindAmbience_Ambience), typeof(AudioMan_UpdateOceanAmbiance_Ambience), typeof(GameCamera_LateUpdate_Ambience), typeof(GameCamera_OnDestroy_Ambience), typeof(Player_OnDestroy_Ambience) }; int num = 0; Type[] array2 = array; foreach (Type type in array2) { try { List<MethodInfo> list = val.CreateClassProcessor(type).Patch(); if (list == null || list.Count == 0) { Log.LogWarning((object)("DogHaus Ambience: patch " + type.Name + " attached to nothing - that part is off.")); } else { num++; } } catch (Exception ex) { Log.LogWarning((object)("DogHaus Ambience: patch " + type.Name + " failed (" + ex.Message + ") - that part is off.")); } } Log.LogInfo((object)("DogHaus Ambience 1.0.0 loaded: " + Effects.All.Count + " effects, " + num + "/" + array.Length + " patches.")); } private void Start() { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assembly.GetType("BeowulfVisuals.RainOnRoof", throwOnError: false) != null) { Log.LogWarning((object)"DogHaus Ambience: an older Beowulf Visuals with the same sound effects is loaded too - update it (or switch its RainOnRoof / IndoorQuiet / DistantNightSounds / LowHealthHeartbeat off), or every sound is changed twice."); break; } } } catch (Exception) { } } private void Update() { SelfTest.Poll((MonoBehaviour)(object)this); } } internal sealed class RainOnRoof : Effect { private const float Duck = 0.3f; private const float LoopVolume = 0.9f; private const float Cutoff = 700f; private AudioSource m_src; internal string Note = "not needed yet"; internal bool Playing { get { if ((Object)(object)m_src != (Object)null) { return m_src.isPlaying; } return false; } } internal float Volume { get { if (!((Object)(object)m_src != (Object)null)) { return 0f; } return m_src.volume; } } internal override bool Busy => Playing; internal RainOnRoof() : base("RainOnRoof", "Under a roof while it rains: the open-air rain is quieter and a muffled rain-on-the-roof plays.") { } internal override bool Evaluate(Player p) { if (!Cond.Dead && Cond.Wet && Cond.UnderRoof) { return !Cond.Interior; } return false; } internal override void Tick(GameCamera gc, Player p, float now, float dt) { Ambience.AmbientFactor *= Mathf.Lerp(1f, 0.3f, Blend); if (Blend <= 0f) { if (Playing) { m_src.Stop(); m_src.volume = 0f; } return; } EnvSetup val = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetCurrentEnvironment() : null); AudioClip val2 = ((val != null && (Object)(object)val.m_ambientLoop != (Object)null) ? val.m_ambientLoop : (((Object)(object)Ambience.AmbientSource != (Object)null) ? Ambience.AmbientSource.clip : null)); if ((Object)(object)val2 == (Object)null) { if (Note != "no rain clip") { Note = "no rain clip"; VLog.Info("DogHaus Ambience: rain on the roof - the current environment has no ambient rain loop, nothing to play."); } return; } if ((Object)(object)m_src == (Object)null) { m_src = Fx.NewSource("RoofRain", loop: true, 0f); AudioLowPassFilter val3 = ((Component)m_src).gameObject.AddComponent<AudioLowPassFilter>(); val3.cutoffFrequency = 700f; } if ((Object)(object)m_src.outputAudioMixerGroup == (Object)null && (Object)(object)Ambience.AmbientSource != (Object)null) { m_src.outputAudioMixerGroup = Ambience.AmbientSource.outputAudioMixerGroup; } if ((Object)(object)m_src.clip != (Object)(object)val2) { m_src.clip = val2; Note = "'" + ((Object)val2).name + "' (environment " + ((val != null) ? val.m_name : "?") + ") low-passed at " + 700f.ToString("0", CultureInfo.InvariantCulture) + " Hz"; VLog.Info("DogHaus Ambience: rain on the roof uses " + Note + "."); } float num = ((val != null && val.m_ambientVol > 0f) ? val.m_ambientVol : 0.5f); m_src.volume = Blend * 0.9f * num; if (!m_src.isPlaying) { m_src.Play(); } } internal override void TearDown() { if ((Object)(object)m_src != (Object)null) { m_src.Stop(); m_src.volume = 0f; } } } internal sealed class IndoorQuiet : Effect { private const float Duck = 0.4f; internal IndoorQuiet() : base("IndoorQuiet", "In a building or dungeon: wind, sea and outdoor ambience 60 % quieter (fires unchanged).") { } internal override bool Evaluate(Player p) { if (!Cond.Dead) { if (!Cond.Sheltered && !Cond.Interior) { if (Cond.InBase) { return Cond.UnderRoof; } return false; } return true; } return false; } internal override void Tick(GameCamera gc, Player p, float now, float dt) { float num = Mathf.Lerp(1f, 0.4f, Blend); Ambience.AmbientFactor *= num; Ambience.WindFactor *= num; Ambience.OceanFactor *= num; } internal override void TearDown() { } } internal sealed class DistantNightSounds : Effect { private const float MinGap = 90f; private const float MaxGap = 240f; private const float Retry = 15f; private const float Near = 60f; private const float Far = 120f; private const float Never = 50f; private const float Cutoff = 2200f; private const float CombatRange = 40f; private const float BossRange = 150f; private static readonly string[] s_meadows = new string[2] { "sfx_greydwarf_idle", "sfx_wolf_haul" }; private static readonly string[] s_forest = new string[4] { "sfx_troll_idle", "sfx_troll_footstep", "sfx_greydwarf_alerted", "sfx_greydwarf_idle" }; private static readonly string[] s_swamp = new string[3] { "sfx_draugr_idle", "sfx_draugr_alerted", "sfx_blob_idle" }; private static readonly string[] s_cold = new string[2] { "sfx_wolf_haul", "sfx_fenring_howl" }; private static readonly string[] s_plains = new string[2] { "sfx_wolf_haul", "sfx_troll_idle" }; private static readonly string[] s_mist = new string[2] { "sfx_gjall_idle_vocals", "sfx_wolf_haul" }; private AudioSource m_src; private float m_next = -1f; private bool m_now; internal int Plays; internal float LastDistance; internal string Note = "none yet"; internal bool HasSource => (Object)(object)m_src != (Object)null; internal bool Playing { get { if ((Object)(object)m_src != (Object)null) { return m_src.isPlaying; } return false; } } internal float Volume { get { if (!((Object)(object)m_src != (Object)null)) { return 0f; } return m_src.volume; } } internal Vector3 Position { get { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)m_src != (Object)null)) { return Vector3.zero; } return ((Component)m_src).transform.position; } } internal override bool Busy => Playing; internal DistantNightSounds() : base("DistantNightSounds", "At night outdoors: now and then a far-off howl, roar or groan (sound only).") { } internal void ForceNowForTest() { m_now = true; } internal string[] SoundsFor(Biome b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return For(b); } internal override bool Evaluate(Player p) { if (!Cond.Dead && Cond.Night && !Cond.UnderRoof && !Cond.Sheltered && !Cond.Interior) { return !Cond.InBase; } return false; } private static string[] For(Biome b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected I4, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 if ((int)b <= 8) { switch (b - 1) { default: if ((int)b != 8) { goto IL_005c; } return s_forest; case 0: return s_meadows; case 1: return s_swamp; case 3: break; case 2: goto IL_005c; } } else { if ((int)b == 16) { return s_plains; } if ((int)b != 64) { if ((int)b != 512) { goto IL_005c; } return s_mist; } } return s_cold; IL_005c: return null; } internal override void Tick(GameCamera gc, Player p, float now, float dt) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Unknown result type (might be due to invalid IL or missing references) if (Playing) { Vector3 val = ((Component)m_src).transform.position - ((Component)gc).transform.position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < 2500f && sqrMagnitude > 0.01f) { ((Component)m_src).transform.position = ((Component)gc).transform.position + ((Vector3)(ref val)).normalized * 50f; } else if (sqrMagnitude > 14400f) { ((Component)m_src).transform.position = ((Component)gc).transform.position + ((Vector3)(ref val)).normalized * 120f; } } bool now2 = m_now; m_now = false; if (!Want) { if (now2) { Note = "not now (day, indoors, in a base, dead or switched off)"; } return; } if (m_next < 0f || m_next > now + 240f + 1f) { m_next = now + Random.Range(90f, 240f); } if (!now2 && now < m_next) { return; } string text = Blocked(p); if (text != null) { m_next = now + 15f; Note = "held: " + text; return; } m_next = now + Random.Range(90f, 240f); string[] array = For(Cond.Biome); if (array == null) { Note = "no sound for " + Cond.Biome; return; } string text2 = null; AudioClip val2 = null; int num = Random.Range(0, array.Length); for (int i = 0; i < array.Length; i++) { if (!((Object)(object)val2 == (Object)null)) { break; } text2 = array[(num + i) % array.Length]; val2 = Fx.ClipOf(text2); } if ((Object)(object)val2 == (Object)null) { Note = string.Concat("no playable sound for ", Cond.Biome, " (", string.Join(", ", array), ")"); m_next = now + 15f; return; } if ((Object)(object)m_src == (Object)null) { m_src = Fx.NewSource("DistantNight", loop: false, 1f); m_src.rolloffMode = (AudioRolloffMode)1; m_src.minDistance = 5f; m_src.maxDistance = 300f; m_src.spread = 60f; AudioLowPassFilter val3 = ((Component)m_src).gameObject.AddComponent<AudioLowPassFilter>(); val3.cutoffFrequency = 2200f; } float num2 = Random.Range(0f, (float)Math.PI * 2f); float num3 = Random.Range(60f, 120f); float num4 = Random.Range(0f, 10f); float num5 = Mathf.Sqrt(num3 * num3 - num4 * num4); Vector3 position = ((Component)gc).transform.position; Vector3 val4 = position + new Vector3(Mathf.Cos(num2) * num5, num4, Mathf.Sin(num2) * num5); ((Component)m_src).transform.position = val4; if ((Object)(object)m_src.outputAudioMixerGroup == (Object)null) { m_src.outputAudioMixerGroup = Fx.SfxGroup(); } m_src.clip = val2; m_src.pitch = Random.Range(0.9f, 1.05f); m_src.volume = Random.Range(0.25f, 0.4f); m_src.Play(); Plays++; LastDistance = Vector3.Distance(position, val4); Note = text2 + " ('" + ((Object)val2).name + "') " + LastDistance.ToString("0", CultureInfo.InvariantCulture) + " m away, " + Cond.Biome; } private static string Blocked(Player p) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (Cond.TestCombat >= 0) { if (Cond.TestCombat != 1) { return null; } return "combat (test)"; } if ((Object)(object)RandEventSystem.instance != (Object)null && RandEventSystem.instance.GetActiveEvent() != null) { return "raid"; } Vector3 position = ((Component)p).transform.position; List<Character> allCharacters = Character.GetAllCharacters(); for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if ((Object)(object)val == (Object)null || val.IsPlayer() || val.IsDead()) { continue; } Vector3 val2 = ((Component)val).transform.position - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (val.IsBoss() && sqrMagnitude < 22500f) { return "boss"; } if (!(sqrMagnitude > 1600f) && !val.IsTamed()) { BaseAI baseAI = val.GetBaseAI(); if ((Object)(object)baseAI != (Object)null && baseAI.HaveTarget()) { return "combat"; } } } return null; } internal override void TearDown() { if ((Object)(object)m_src != (Object)null) { m_src.Stop(); } } } internal sealed class LowHealthHeartbeat : Effect { private const float OnBelow = 0.25f; private const float OffAbove = 0.3f; private const float BeatVolume = 0.6f; private const float VignetteMax = 0.6f; private const float MuffleCutoff = 2600f; private bool m_low; private AudioSource m_src; private AudioClip m_thump; private float m_nextBeat; private AudioLowPassFilter m_muffle; private AudioListener m_muffleOn; private Texture2D m_tex; internal float Strength; internal float VignetteAlpha; internal int Beats; internal string MuffleNote = "not needed yet"; internal bool MuffleOn => (Object)(object)m_muffle != (Object)null; internal float MuffleCutoffNow { get { if (!((Object)(object)m_muffle != (Object)null)) { return 22000f; } return m_muffle.cutoffFrequency; } } internal bool Playing { get { if ((Object)(object)m_src != (Object)null) { return m_src.isPlaying; } return false; } } internal override bool Busy { get { if (!MuffleOn && !Playing) { return VignetteAlpha > 0f; } return true; } } internal override bool WantsGui => VignetteAlpha > 0.001f; internal LowHealthHeartbeat() : base("LowHealthHeartbeat", "Below 25 % health: heartbeat, dark screen edges and muffled sound.") { } internal override bool Evaluate(Player p) { m_low = !Cond.Dead && (m_low ? (Cond.Hp <= 0.3f) : (Cond.Hp < 0.25f)); return m_low; } internal override void Tick(GameCamera gc, Player p, float now, float dt) { float num = Mathf.Clamp01(Mathf.InverseLerp(0.3f, 0.05f, Cond.Hp)); float num2 = (Strength = Blend * Mathf.Lerp(0.4f, 1f, num)); VignetteAlpha = num2 * 0.6f; if (num2 <= 0.0001f) { VignetteAlpha = 0f; RemoveMuffle(); return; } Muffle(num2); float unscaledTime = Time.unscaledTime; if (unscaledTime >= m_nextBeat) { m_nextBeat = unscaledTime + 60f / Mathf.Lerp(68f, 112f, num); if ((Object)(object)m_thump == (Object)null) { m_thump = MakeThump(); } if ((Object)(object)m_src == (Object)null) { m_src = Fx.NewSource("Heartbeat", loop: false, 0f); m_src.bypassListenerEffects = true; } if ((Object)(object)m_src.outputAudioMixerGroup == (Object)null) { m_src.outputAudioMixerGroup = Fx.SfxGroup(); } m_src.clip = m_thump; m_src.volume = 0.6f * num2; m_src.Play(); Beats++; } } private static AudioClip MakeThump() { int num = 11025; float[] array = new float[num]; for (int i = 0; i < num; i++) { float num2 = (float)i / 22050f; float num3 = ((num2 < 0.16f) ? (Mathf.Sin((float)Math.PI * 120f * num2) * Mathf.Exp((0f - num2) / 0.045f) * Mathf.Clamp01(num2 / 0.004f)) : 0f); float num4 = num2 - 0.2f; float num5 = ((num4 >= 0f && num4 < 0.15f) ? (0.7f * Mathf.Sin((float)Math.PI * 100f * num4) * Mathf.Exp((0f - num4) / 0.04f) * Mathf.Clamp01(num4 / 0.004f)) : 0f); array[i] = (num3 + num5) * 0.9f; } AudioClip val = AudioClip.Create("DogHausAmbience_Heartbeat", num, 1, 22050, false); val.SetData(array, 0); VLog.Info("DogHaus Ambience: low-health heartbeat uses a made-up low thump (the game has no heartbeat sound)."); return val; } private void Muffle(float k) { AudioListener val = (((Object)(object)AudioMan.instance != (Object)null) ? AudioMan.instance.GetActiveAudioListener() : null); if ((Object)(object)val == (Object)null) { RemoveMuffle(); return; } if ((Object)(object)m_muffle == (Object)null || (Object)(object)m_muffleOn != (Object)(object)val) { RemoveMuffle(); if ((Object)(object)((Component)val).GetComponent<AudioLowPassFilter>() != (Object)null) { if (MuffleNote != "busy") { MuffleNote = "busy"; VLog.Info("DogHaus Ambience: the listener already has a low-pass filter (another mod) - the heartbeat muffle leaves it alone."); } return; } m_muffle = ((Component)val).gameObject.AddComponent<AudioLowPassFilter>(); m_muffleOn = val; MuffleNote = "on " + ((Object)((Component)val).gameObject).name; } m_muffle.cutoffFrequency = Mathf.Lerp(22000f, 2600f, k); } private void RemoveMuffle() { if ((Object)(object)m_muffle != (Object)null) { Object.Destroy((Object)(object)m_muffle); } m_muffle = null; m_muffleOn = null; } internal override void OnGUI() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (!(VignetteAlpha <= 0.001f) && Event.current != null && (int)Event.current.type == 7) { if ((Object)(object)m_tex == (Object)null) { m_tex = MakeVignette(); } Color color = GUI.color; GUI.color = new Color(0.06f, 0f, 0f, VignetteAlpha); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)m_tex, (ScaleMode)0, true); GUI.color = color; } } private static Texture2D MakeVignette() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(128, 128, (TextureFormat)4, false); ((Texture)val).wrapMode = (TextureWrapMode)1; Color32[] array = (Color32[])(object)new Color32[16384]; for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { float num = ((float)j + 0.5f) / 128f * 2f - 1f; float num2 = ((float)i + 0.5f) / 128f * 2f - 1f; float num3 = Mathf.Sqrt(num * num + num2 * num2); float num4 = Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0.55f, 1.25f, num3)); ref Color32 reference = ref array[i * 128 + j]; reference = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num4 * 255f)); } } val.SetPixels32(array); val.Apply(false, true); return val; } internal override void TearDown() { m_low = false; Strength = 0f; VignetteAlpha = 0f; RemoveMuffle(); if ((Object)(object)m_src != (Object)null) { m_src.Stop(); } } } internal static class SelfTest { private sealed class Saved { internal bool All; internal readonly Dictionary<Effect, bool> On = new Dictionary<Effect, bool>(); internal string Exclude; internal string Env; internal bool Tod; internal float Time; internal float Health; internal ConfigEntry<bool> OldSwitch; internal bool OldWas; } private const float StartDelay = 20f; private const float Limit = 300f; private const long FakeAllowedId = 4242424242L; private const long RidId = 1723951585L; private const long GreyId = -357758456L; private static bool s_started; private static float s_nextCheck; private static float s_playerSince = -1f; private static int s_pass; private static int s_fail; private static readonly FieldInfo s_ambVol = AccessTools.Field(typeof(AudioMan), "m_ambientVol"); private static readonly FieldInfo s_ambSrc = AccessTools.Field(typeof(AudioMan), "m_ambientLoopSource"); private static readonly FieldInfo s_windSrc = AccessTools.Field(typeof(AudioMan), "m_windLoopSource"); private static string s_settleNote = ""; private static string ScriptPath => Path.Combine(Paths.ConfigPath, "DogHausAmbience.autotest.txt"); private static RainOnRoof Roof => Effects.Get<RainOnRoof>(); private static IndoorQuiet Indoor => Effects.Get<IndoorQuiet>(); private static DistantNightSounds Distant => Effects.Get<DistantNightSounds>(); private static LowHealthHeartbeat Heart => Effects.Get<LowHealthHeartbeat>(); internal static void Poll(MonoBehaviour host) { if (s_started || Time.unscaledTime < s_nextCheck) { return; } s_nextCheck = Time.unscaledTime + 2f; try { if (!File.Exists(ScriptPath)) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsTeleporting() || (Object)(object)GameCamera.instance == (Object)null || (Object)(object)EnvMan.instance == (Object)null || (Object)(object)AudioMan.instance == (Object)null) { s_playerSince = -1f; return; } if (s_playerSince < 0f) { s_playerSince = Time.unscaledTime; Log("player in the world - self-test starts in " + 20f.ToString("0", CultureInfo.InvariantCulture) + " s"); } if (!(Time.unscaledTime - s_playerSince < 20f)) { s_started = true; host.StartCoroutine(Wrap()); } } catch (Exception e) { VLog.Once("Ambience: self-test poll", e); } } private static void Log(string m) { VLog.Warn("[DogHausAmbience] " + m); } private static bool Check(string name, bool ok, string detail) { if (ok) { s_pass++; } else { s_fail++; } Log((ok ? "PASS " : "FAIL ") + name + ": " + detail); return ok; } private static WaitForSecondsRealtime Rt(float s) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown return new WaitForSecondsRealtime(s); } private static string N(float f) { return f.ToString("0.0000", CultureInfo.InvariantCulture); } private static IEnumerator Wrap() { float t0 = Time.realtimeSinceStartup; Log("selftest start (DogHaus Ambience 1.0.0, limit " + 300f.ToString("0", CultureInfo.InvariantCulture) + " s)"); IEnumerator e = Run(); while (true) { bool more; try { more = e.MoveNext(); } catch (Exception ex) { Check("selftest", ok: false, "exception: " + ex.GetType().Name + ": " + ex.Message); more = false; } if (!more) { break; } if (Time.realtimeSinceStartup - t0 > 300f) { Check("selftest", ok: false, "timed out after " + 300f.ToString("0", CultureInfo.InvariantCulture) + " s"); if (e is IDisposable disposable) { try { disposable.Dispose(); } catch (Exception) { } } break; } yield return e.Current; } Log("selftest done, " + s_fail + " FAIL, " + s_pass + " PASS (" + (Time.realtimeSinceStartup - t0).ToString("0", CultureInfo.InvariantCulture) + " s)"); try { string text = Path.ChangeExtension(ScriptPath, ".done"); if (File.Exists(text)) { text = Path.ChangeExtension(ScriptPath, ".done-" + DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)); } File.Move(ScriptPath, text); Log("renamed the trigger file to " + Path.GetFileName(text)); } catch (Exception ex3) { Log("could not rename the trigger file: " + ex3.Message); } } private static Saved Save(Player p, EnvMan em) { Saved saved = new Saved(); saved.All = Effects.CfgAll.Value; foreach (Effect item in Effects.All) { saved.On[item] = item.Enabled.Value; } saved.Exclude = Exclusion.CfgExclude.Value; saved.Env = em.m_debugEnv; saved.Tod = em.m_debugTimeOfDay; saved.Time = em.m_debugTime; saved.Health = ((Character)p).GetHealth(); try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (!(assembly.GetType("BeowulfVisuals.RainOnRoof", throwOnError: false) == null)) { Type type = assembly.GetType("BeowulfVisuals.Visuals", throwOnError: false); FieldInfo fieldInfo = ((type != null) ? type.GetField("CfgAll", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : null); ConfigEntry<bool> val = ((fieldInfo != null) ? (fieldInfo.GetValue(null) as ConfigEntry<bool>) : null); if (val != null) { saved.OldSwitch = val; saved.OldWas = val.Value; val.Value = false; Log("an older Beowulf Visuals with the same effects is installed too - switched off for this test (restored after; was " + saved.OldWas + ")"); break; } } } } catch (Exception ex) { Log("could not look for an older Beowulf Visuals: " + ex.Message); } return saved; } private static void Restore(Saved s) { Cond.ClearTestOverrides(); Effects.CfgAll.Value = s.All; foreach (KeyValuePair<Effect, bool> item in s.On) { item.Key.Enabled.Value = item.Value; } Exclusion.CfgExclude.Value = s.Exclude; EnvMan instance = EnvMan.instance; if ((Object)(object)instance != (Object)null) { instance.m_debugEnv = s.Env; instance.m_debugTimeOfDay = s.Tod; instance.m_debugTime = s.Time; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead()) { ((Character)localPlayer).SetHealth(Mathf.Max(s.Health, 1f)); } if (s.OldSwitch != null) { s.OldSwitch.Value = s.OldWas; } } private static void AllOn() { Effects.CfgAll.Value = true; foreach (Effect item in Effects.All) { item.Enabled.Value = true; } } private static bool Quiet0() { foreach (Effect item in Effects.All) { if (item.Blend > 0f) { return false; } } if (!Heart.MuffleOn && Heart.VignetteAlpha == 0f && !Fx.GuiOn && !Ambience.AnyDuckOn && !Roof.Playing && !Heart.Playing) { return !Distant.Playing; } return false; } private static string EnvName() { EnvSetup val = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetCurrentEnvironment() : null); if (val == null) { return "?"; } return val.m_name; } private static IEnumerator WaitFor(Func<bool> cond, float max) { float t = 0f; while (!cond() && t < max) { t += 0.5f; yield return Rt(0.5f); } } private static IEnumerator Settle(bool wet, float max) { float t = 0f; float still = 0f; while (t < max) { AudioSource a = (AudioSource)s_ambSrc.GetValue(AudioMan.instance); float target = (float)s_ambVol.GetValue(AudioMan.instance); still = ((((Object)(object)a == (Object)null || !a.isPlaying || a.volume == target) && EnvMan.IsWet() == wet) ? (still + 0.5f) : 0f); if (still >= 1.5f) { break; } t += 0.5f; yield return Rt(0.5f); } s_settleNote = "settled after " + t.ToString("0.0", CultureInfo.InvariantCulture) + " s (" + EnvName() + ", wet " + EnvMan.IsWet() + ")"; } private static string ListenerFilterState() { AudioListener val = (((Object)(object)AudioMan.instance != (Object)null) ? AudioMan.instance.GetActiveAudioListener() : null); AudioLowPassFilter val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<AudioLowPassFilter>() : null); if ((Object)(object)val2 == (Object)null) { return "none"; } return "#" + ((Object)val2).GetInstanceID() + (((Behaviour)val2).enabled ? " on " : " off ") + val2.cutoffFrequency.ToString("0.###", CultureInfo.InvariantCulture) + " Hz"; } private static IEnumerator Run() { Player p = Player.m_localPlayer; EnvMan em = EnvMan.instance; if ((Object)(object)p == (Object)null || (Object)(object)em == (Object)null || (Object)(object)GameCamera.instance == (Object)null || (Object)(object)AudioMan.instance == (Object)null || Effects.CfgAll == null) { Check("selftest", ok: false, "no local player / EnvMan / camera / AudioMan / config"); yield break; } bool allTrue = (bool)((ConfigEntryBase)Effects.CfgAll).DefaultValue; foreach (Effect item in Effects.All) { if (!(bool)((ConfigEntryBase)item.Enabled).DefaultValue) { allTrue = false; } } HashSet<long> defIds = new HashSet<long>(); string[] array = ((string)((ConfigEntryBase)Exclusion.CfgExclude).DefaultValue).Split(','); foreach (string text in array) { if (long.TryParse(text.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { defIds.Add(result); } } Check("config defaults: master + all 4 effects on, ExcludePlayers = Rid and Grey", allTrue && Effects.All.Count == 4 && defIds.Count == 2 && defIds.Contains(1723951585L) && defIds.Contains(-357758456L), string.Concat("every switch true ", allTrue, ", ", Effects.All.Count, " effects, ExcludePlayers '", ((ConfigEntryBase)Exclusion.CfgExclude).DefaultValue, "'")); string realWhy; bool realAllowed = Exclusion.LocalAllowed(p, out realWhy); Check("exclusion (informational): this PC's real player", ok: true, (realAllowed ? "allowed" : "EXCLUDED") + " - " + realWhy + "; list source: " + (Exclusion.TweaksActive ? Exclusion.TweaksHow : ("config (" + Exclusion.TweaksHow + ")"))); Saved saved = Save(p, em); try { Cond.ClearTestOverrides(); Cond.TestShelter = 0; Cond.TestInterior = 0; Cond.TestInBase = 0; Cond.TestCombat = 0; Cond.TestBiome = (Biome)1; Exclusion.TestLocalId = 4242424242L; AllOn(); ((Character)p).SetHealth(((Character)p).GetMaxHealth()); em.m_debugEnv = "Clear"; em.m_debugTimeOfDay = true; em.m_debugTime = 0.5f; yield return Rt(5f); string preFilter = ListenerFilterState(); Check("baseline: midday, Clear, nothing forced, a player on no list - running, every effect off", Effects.Running && Quiet0(), "listener filter " + preFilter + "; " + Effects.State()); em.m_debugEnv = "Rain"; IEnumerator w1 = WaitFor(() => EnvMan.IsWet(), 20f); while (w1.MoveNext()) { yield return w1.Current; } yield return Rt(3f); AudioSource amb = (AudioSource)s_ambSrc.GetValue(AudioMan.instance); if (Check("roof: the Rain environment is wet", EnvMan.IsWet(), "environment " + EnvName() + ", ambient loop " + (((Object)(object)amb != (Object)null && (Object)(object)amb.clip != (Object)null) ? ((Object)amb.clip).name : "none"))) { Check("roof: control - raining, open sky: no roof loop, no duck", !Roof.Playing && !Ambience.AmbientDuck.On, Effects.State()); Indoor.Enabled.Value = false; Cond.TestShelter = 1; yield return Rt(2.5f); float ratio = Ambience.AmbientDuck.LastRatio; float measured = (((Object)(object)amb != (Object)null && Ambience.AmbientDuck.Game > 0f) ? (amb.volume / Ambience.AmbientDuck.Game) : (-1f)); Check("roof: muffled rain loop playing, open-air rain ducked to x0.3", Roof.Playing && Roof.Volume > 0.05f && Mathf.Abs(ratio - 0.3f) < 0.02f && Mathf.Abs(measured - 0.3f) < 0.02f, "loop " + Roof.Note + " at volume " + N(Roof.Volume) + "; ambient loop " + N(((Object)(object)amb != (Object)null) ? amb.volume : (-1f)) + " of the game's " + N(Ambience.AmbientDuck.Game) + " (x" + N(measured) + ")"); Roof.Enabled.Value = false; Check("roof: RainOnRoof = false while playing - the loop stops at once (same frame)", !Roof.Playing && Roof.Blend == 0f, "playing " + Roof.Playing + ", blend " + N(Roof.Blend)); Roof.Enabled.Value = true; yield return Rt(2.5f); Cond.TestShelter = 0; yield return Rt(3f); float target = (float)s_ambVol.GetValue(AudioMan.instance); Check("roof: off again, the game's own rain volume back exactly", !Roof.Playing && !Ambience.AmbientDuck.On && (Object)(object)amb != (Object)null && amb.volume == target, "ambient loop " + N(((Object)(object)amb != (Object)null) ? amb.volume : (-1f)) + " vs the game's target m_ambientVol " + N(target)); Indoor.Enabled.Value = true; } em.m_debugEnv = "Clear"; IEnumerator s1 = Settle(wet: false, 30f); while (s1.MoveNext()) { yield return s1.Current; } Log("indoor: weather " + s_settleNote); Roof.Enabled.Value = false; AudioSource wind = (AudioSource)s_windSrc.GetValue(AudioMan.instance); Check("indoor: control - outdoors: wind and ambience untouched", !Ambience.WindDuck.On && !Ambience.AmbientDuck.On, Effects.State()); Cond.TestShelter = 1; yield return Rt(2.5f); float wr = (((Object)(object)wind != (Object)null && Ambience.WindDuck.Game > 0f) ? (wind.volume / Ambience.WindDuck.Game) : (-1f)); Check("indoor: wind and ambience ducked 60 %", Mathf.Abs(Ambience.WindDuck.LastRatio - 0.4f) < 0.02f && Mathf.Abs(Ambience.AmbientDuck.LastRatio - 0.4f) < 0.02f && ((Object)(object)wind == (Object)null || Ambience.WindDuck.Game <= 0f || Mathf.Abs(wr - 0.4f) < 0.02f), "wind x" + N(Ambience.WindDuck.LastRatio) + " (measured " + N(wr) + "), ambience x" + N(Ambience.AmbientDuck.LastRatio) + ", ocean x" + N(Ambience.OceanDuck.LastRatio)); Cond.TestShelter = 0; yield return Rt(3f); amb = (AudioSource)s_ambSrc.GetValue(AudioMan.instance); float target2 = (float)s_ambVol.GetValue(AudioMan.instance); Check("indoor: off again, every duck handed back", !Ambience.AnyDuckOn && Ambience.WindDuck.LastRatio == 1f && Ambience.AmbientDuck.LastRatio == 1f && ((Object)(object)amb == (Object)null || !amb.isPlaying || amb.volume == target2), "ambient loop '" + (((Object)(object)amb != (Object)null && (Object)(object)amb.clip != (Object)null) ? ((Object)amb.clip).name : "none") + "' " + N(((Object)(object)amb != (Object)null) ? amb.volume : (-1f)) + " vs target " + N(target2) + ", ducks on: ambient " + Ambience.AmbientDuck.On + " wind " + Ambience.WindDuck.On + " ocean " + Ambience.OceanDuck.On); Roof.Enabled.Value = true; em.m_debugTime = 0.95f; Cond.TestBiome = (Biome)8; yield return Rt(2f); int d0 = Distant.Plays; Cond.TestCombat = 1; Distant.ForceNowForTest(); yield return Rt(1f); Check("distant: held during combat (fail-first)", Distant.Plays == d0 && Distant.Note.StartsWith("held", StringComparison.Ordinal), Distant.Note); IEnumerator wl = WaitFor(() => !Fx.Scanning, 10f); List<string> noSound = new List<string>(); Biome[] array2 = (Biome[])(object)new Biome[6] { (Biome)1, (Biome)8, (Biome)2, (Biome)4, (Biome)16, (Biome)512 }; foreach (Biome b in array2) { string[] array3 = Distant.SoundsFor(b); foreach (string prefab in array3) { Fx.ClipOf(prefab); } } while (wl.MoveNext()) { yield return wl.Current; } Biome[] array4 = (Biome[])(object)new Biome[6] { (Biome)1, (Biome)8, (Biome)2, (Biome)4, (Biome)16, (Biome)512 }; foreach (Biome val in array4) { bool flag = false; string[] array5 = Distant.SoundsFor(val); foreach (string prefab2 in array5) { if ((Object)(object)Fx.ClipOf(prefab2) != (Object)null) { flag = true; } } if (!flag) { noSound.Add(((object)val).ToString()); } } Check("distant: every biome has a playable far-off sound", noSound.Count == 0, (noSound.Count == 0) ? "all six" : ("none for " + string.Join(", ", noSound.ToArray()))); Cond.TestCombat = 0; Distant.ForceNowForTest(); yield return Rt(0.3f); bool src = Distant.HasSource; float camDist = (src ? Vector3.Distance(Distant.Position, ((Component)GameCamera.instance).transform.position) : (-1f)); Check("distant: one far-off sound, 60-120 m away, quiet", Distant.Plays == d0 + 1 && src && Distant.Playing && Distant.Volume > 0f && Distant.Volume <= 0.4f && Distant.LastDistance >= 60f && Distant.LastDistance <= 120f && camDist >= 50f && camDist <= 120.5f, Distant.Note + ", volume " + N(Distant.Volume) + ", " + (src ? ("now " + N(camDist) + " m from the camera") : "no source made yet (nothing played)")); em.m_debugTime = 0.5f; yield return Rt(1.5f); int d1 = Distant.Plays; Distant.ForceNowForTest(); yield return Rt(1f); Check("distant: nothing by day", Distant.Plays == d1, Distant.Note); float maxH = ((Character)p).GetMaxHealth(); int b2 = Heart.Beats; Check("heartbeat: control - full health: off", !Heart.Want && !Heart.MuffleOn, "health " + N(((Character)p).GetHealthPercentage())); ((Character)p).SetHealth(maxH * 0.15f); yield return Rt(2.5f); bool muffleOk = ((!(preFilter == "none")) ? (!Heart.MuffleOn && ListenerFilterState() == preFilter) : (Heart.MuffleOn && Heart.MuffleCutoffNow < 20000f)); Check("heartbeat: on at 15 % - beat, dark edges, muffled world (another mod's listener filter left alone)", Heart.Beats > b2 && Heart.VignetteAlpha > 0.1f && Fx.GuiOn && muffleOk, Heart.Beats - b2 + " beats, vignette " + N(Heart.VignetteAlpha) + ", our low-pass " + N(Heart.MuffleCutoffNow) + " Hz (" + Heart.MuffleNote + "), listener filter before " + preFilter + ", now " + ListenerFilterState()); ((Character)p).SetHealth(maxH * 0.28f); yield return Rt(1f); Check("heartbeat: still on at 28 % (off only above 30 %)", Heart.Want, "health " + N(((Character)p).GetHealthPercentage())); Heart.Enabled.Value = false; Check("heartbeat: LowHealthHeartbeat = false while beating - muffle and dark edges gone at once (same frame)", !Heart.MuffleOn && Heart.VignetteAlpha == 0f && Heart.Blend == 0f && !Heart.Playing, "muffle " + Heart.MuffleOn + ", vignette " + N(Heart.VignetteAlpha) + ", beat playing " + Heart.Playing); yield return Rt(0.3f); Check("heartbeat: switched off - the listener's filter is as it was before", ListenerFilterState() == preFilter, "listener filter " + ListenerFilterState() + " (before " + preFilter + ")"); Heart.Enabled.Value = true; yield return Rt(2.5f); ((Character)p).SetHealth(maxH); yield return Rt(3f); Check("heartbeat: off above 30 %, everything handed back", !Heart.Want && Heart.Blend == 0f && Heart.VignetteAlpha == 0f && !Fx.GuiOn && !Heart.MuffleOn && ListenerFilterState() == preFilter, "listener filter " + ListenerFilterState() + " (before " + preFilter + "); " + Effects.State()); ((Character)p).SetHealth(maxH * 0.15f); Cond.TestShelter = 1; yield return Rt(2.5f); Check("exclusion: control - a player on no list: heartbeat and indoor quiet on", Effects.Allowed && Heart.Want && Heart.VignetteAlpha > 0.1f && Ambience.WindDuck.On, Effects.State()); Exclusion.TestIgnoreTweaks = true; try { long[] array6 = new long[2] { 1723951585L, -357758456L }; foreach (long id in array6) { int td = Effects.TearDowns; Exclusion.TestLocalId = id; yield return Rt(0.5f); Check("exclusion: config default list - player " + id + " gets everything off within 0.5 s (no fade)", !Effects.Allowed && Quiet0() && Effects.TearDowns > td && ListenerFilterState() == preFilter, Effects.AllowedWhy + "; " + Effects.State()); Exclusion.TestLocalId = 4242424242L; yield return Rt(2.5f); Check("exclusion: back to a player on no list - on again", Effects.Allowed && Heart.Want && Heart.VignetteAlpha > 0.1f, Effects.State()); } } finally { } Exclusion.CfgExclude.Value = 4242424242L.ToString(CultureInfo.InvariantCulture) + ", 77"; yield return Rt(0.5f); Check("exclusion: ExcludePlayers edited live to this ID - off within 0.5 s", !Effects.Allowed && Quiet0(), Effects.AllowedWhy); Exclusion.CfgExclude.Value = saved.Exclude; yield return Rt(2.5f); Exclusion.TestIgnoreTweaks = false; if (Exclusion.TweaksActive) { HashSet<long> tw = Exclusion.TweaksIds(); long pick = 0L; if (tw != null) { if (tw.Contains(1723951585L)) { pick = 1723951585L; } else { using HashSet<long>.Enumerator enumerator2 = tw.GetEnumerator(); if (enumerator2.MoveNext()) { long current2 = enumerator2.Current; pick = current2; } } } if (pick != 0) { Exclusion.TestLocalId = pick; yield return Rt(0.5f); Check("exclusion: DogHausTweaks' live list - player " + pick + " off within 0.5 s", !Effects.Allowed && Quiet0(), Effects.AllowedWhy + " (" + Exclusion.TweaksHow + ", " + tw.Count + " IDs)"); Exclusion.TestLocalId = 4242424242L; yield return Rt(2.5f); } else { Check("exclusion: DogHausTweaks loaded, its lists are empty (informational)", ok: true, Exclusion.TweaksHow); } Check("exclusion: DogHausTweaks loaded - a player on none of its lists is allowed", Effects.Allowed, Effects.AllowedWhy); } else { Check("exclusion: DogHausTweaks not loaded - the config list decides (informational)", ok: true, Exclusion.TweaksHow); } Exclusion.TestLocalId = 0L; yield return Rt(0.5f); Check("exclusion: unknown player ID (0) - everything off", !Effects.Allowed && Quiet0(), Effects.AllowedWhy); Exclusion.TestLocalId = 4242424242L; yield return Rt(2.5f); Cond.TestShelter = 0; em.m_debugTime = 0.95f; Cond.TestBiome = (Biome)2; ((Character)p).SetHealth(maxH * 0.15f); yield return Rt(2.5f); IEnumerator wh = WaitFor(delegate { if (((Character)p).GetHealthPercentage() > 0.2f) { ((Character)p).SetHealth(maxH * 0.15f); } return Heart.Want && Heart.VignetteAlpha > 0.1f; }, 10f); while (wh.MoveNext()) { yield return wh.Current; } if (((Character)p).GetHealthPercentage() > 0.2f) { ((Character)p).SetHealth(maxH * 0.15f); } Distant.ForceNowForTest(); yield return Rt(0.3f); Check("teardown: before the flip - a distant sound playing and the heartbeat on", Distant.Playing && Heart.Want, Distant.Note + "; health " + N(((Character)p).GetHealthPercentage()) + "; " + Effects.State()); int tdm = Effects.TearDowns; Effects.CfgAll.Value = false; Check("teardown: General.Enabled = false - torn down in the same frame (no 5 Hz wait)", Effects.TearDowns > tdm && !Distant.Playing && !Heart.MuffleOn && Heart.VignetteAlpha == 0f, "teardowns " + (Effects.TearDowns - tdm) + ", distant playing " + Distant.Playing + ", muffle " + Heart.MuffleOn); yield return Rt(0.5f); Check("teardown: General.Enabled = false - everything gone within 0.5 s (no fade, no sound, all handed back)", Quiet0() && ListenerFilterState() == preFilter, Effects.State()); int dp2 = Distant.Plays; Distant.ForceNowForTest(); yield return Rt(3f); Check("teardown: stays off while switched off - no new distant sounds", Quiet0() && Distant.Plays == dp2, Distant.Plays - dp2 + " distant"); Effects.CfgAll.Value = true; ((Character)p).SetHealth(maxH); Cond.ClearTestOverrides(); Cond.TestShelter = 0; Cond.TestInterior = 0; Cond.TestInBase = 0; Cond.TestCombat = 0; Cond.TestBiome = (Biome)1; Exclusion.TestLocalId = 4242424242L; em.m_debugEnv = "Clear"; em.m_debugTime = 0.5f; yield return Rt(5f); Check("end: every effect off", Quiet0(), Effects.State()); } finally { Restore(saved); } } }