Decompiled source of DogHausAmbience v1.0.0

plugins/DogHausAmbience.dll

Decompiled a day ago
using 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);
		}
	}
}