Decompiled source of CartFM v1.2.0

CartRadio.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using Photon.Voice;
using Photon.Voice.Unity;
using Strobotnik.Klattersynth;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CartRadio")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CartRadio")]
[assembly: AssemblyTitle("CartRadio")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CartRadio
{
	internal class RadioPusher : IAudioPusher<float>, IAudioDesc, IDisposable
	{
		public const int Rate = 24000;

		private Action<float[]> callback;

		private ObjectFactory<float[], int> bufferFactory;

		public int SamplingRate => 24000;

		public int Channels => 1;

		public string Error => null;

		public void SetCallback(Action<float[]> callback, ObjectFactory<float[], int> bufferFactory)
		{
			this.callback = callback;
			this.bufferFactory = bufferFactory;
		}

		public void Push(float[] samples, int count)
		{
			if (callback != null && count > 0)
			{
				float[] array = bufferFactory.New(count);
				Array.Copy(samples, array, count);
				callback(array);
			}
		}

		public void Dispose()
		{
			callback = null;
		}
	}
	internal class RadioMixer
	{
		private class Track
		{
			public float[] data;

			public int channels;

			public int frequency;

			public double position;

			public float gain;
		}

		private readonly Dictionary<AudioClip, float[]> cache = new Dictionary<AudioClip, float[]>();

		private readonly List<Track> shots = new List<Track>();

		private Track music;

		private float[] mix = new float[24000];

		public float Volume = 0.35f;

		public const float MusicGain = 0.55f;

		private Track MakeTrack(AudioClip clip, float gain)
		{
			if (!cache.TryGetValue(clip, out var value))
			{
				value = new float[clip.samples * clip.channels];
				clip.GetData(value, 0);
				cache[clip] = value;
			}
			return new Track
			{
				data = value,
				channels = clip.channels,
				frequency = clip.frequency,
				gain = gain
			};
		}

		public void PlayMusic(AudioClip clip, float offset)
		{
			music = MakeTrack(clip, 0.55f);
			music.position = offset * (float)clip.frequency;
		}

		public void StopMusic()
		{
			music = null;
		}

		public void PlayShot(AudioClip clip, float gain)
		{
			shots.Add(MakeTrack(clip, gain));
		}

		public void Clear()
		{
			music = null;
			shots.Clear();
		}

		public float[] Render(int count)
		{
			if (mix.Length < count)
			{
				mix = new float[count];
			}
			Array.Clear(mix, 0, count);
			if (music != null && !AddTrack(music, count))
			{
				music = null;
			}
			for (int num = shots.Count - 1; num >= 0; num--)
			{
				if (!AddTrack(shots[num], count))
				{
					shots.RemoveAt(num);
				}
			}
			for (int i = 0; i < count; i++)
			{
				mix[i] = Mathf.Clamp(mix[i] * Volume, -1f, 1f);
			}
			return mix;
		}

		private bool AddTrack(Track track, int count)
		{
			int num = track.data.Length / track.channels;
			double num2 = (double)track.frequency / 24000.0;
			for (int i = 0; i < count; i++)
			{
				int num3 = (int)track.position;
				if (num3 >= num - 1)
				{
					return false;
				}
				float num4 = (float)(track.position - (double)num3);
				float num5 = track.data[num3 * track.channels];
				float num6 = track.data[(num3 + 1) * track.channels];
				mix[i] += (num5 + (num6 - num5) * num4) * track.gain;
				track.position += num2;
			}
			return true;
		}
	}
	[BepInPlugin("CartFM.Radio", "Cart FM", "1.2.0")]
	public class Plugin : BaseUnityPlugin
	{
		private class Radio
		{
			public AudioSource music;

			public AudioSource djAudio;

			public Speech dj;

			public int djFrequency;
		}

		private const byte SyncEvent = 177;

		private const float HostTimeout = 15f;

		private const float HeartbeatInterval = 4f;

		internal static ManualLogSource Log;

		private static ConfigEntry<bool> radioOn;

		private static ConfigEntry<float> volume;

		private static ConfigEntry<float> hearingDistance;

		private static ConfigEntry<KeyCode> toggleKey;

		private static ConfigEntry<bool> djOn;

		private static ConfigEntry<int> songsBetweenJokes;

		private static ConfigEntry<int> djPitch;

		private static ConfigEntry<float> botPitch;

		private static ConfigEntry<bool> episodesInOrder;

		private static ConfigEntry<KeyCode> onAirKey;

		private static ConfigEntry<float> onAirVolume;

		private static ConfigEntry<KeyCode> onAirLouderKey;

		private static ConfigEntry<KeyCode> onAirQuieterKey;

		private static ConfigEntry<KeyCode> echoTestKey;

		private static Plugin instance;

		private readonly RadioMixer mixer = new RadioMixer();

		private RadioPusher pusher;

		private bool onAir;

		private Recorder onAirRecorder;

		private double lastDspTime;

		private float onAirMessageUntil;

		private float volumeMessageUntil;

		private static ConfigEntry<KeyCode> volumeDownKey;

		private static ConfigEntry<KeyCode> volumeUpKey;

		private static ConfigEntry<float> djVolume;

		private static ConfigEntry<float> sfxVolume;

		private static readonly FieldRef<PlayerVoiceChat, Recorder> VoiceRecorder = AccessTools.FieldRefAccess<PlayerVoiceChat, Recorder>("recorder");

		private readonly List<AudioClip> playlist = new List<AudioClip>();

		private readonly List<List<string>> bits = new List<List<string>>();

		private readonly List<List<string>> bitDeck = new List<List<string>>();

		private readonly Queue<string> bitLines = new Queue<string>();

		private float lineWaitUntil;

		private bool lineSpeaking;

		private const float PauseAfterLine = 0.7f;

		private readonly Dictionary<string, AudioClip> sfx = new Dictionary<string, AudioClip>();

		private readonly Dictionary<string, AudioClip> voiceLines = new Dictionary<string, AudioClip>();

		private readonly SortedDictionary<int, List<string>> episodes = new SortedDictionary<int, List<string>>();

		private bool stationOpened;

		private int songsUntilBit;

		private readonly Dictionary<PhysGrabCart, Radio> radios = new Dictionary<PhysGrabCart, Radio>();

		private int song;

		private int songsSinceJoke;

		private float songStart;

		private AudioClip currentClip;

		private float lastHostMessage = -999f;

		private float heartbeatTimer;

		private bool djTalking;

		private float djStart;

		private float scanTimer;

		private static readonly string[] SpeakerNames = new string[3] { "DJ", "BOT", "PANIC" };

		private GUIStyle onAirStyle;

		private bool FollowingHost
		{
			get
			{
				if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient)
				{
					return Time.time - lastHostMessage < 15f;
				}
				return false;
			}
		}

		private static bool Leading
		{
			get
			{
				if (PhotonNetwork.InRoom)
				{
					return PhotonNetwork.IsMasterClient;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Expected O, but got Unknown
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Expected O, but got Unknown
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Expected O, but got Unknown
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Expected O, but got Unknown
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			radioOn = ((BaseUnityPlugin)this).Config.Bind<bool>("Radio", "On", true, "Carts play music.");
			volume = ((BaseUnityPlugin)this).Config.Bind<float>("Radio", "Volume", 0.3f, new ConfigDescription("Radio volume (also scaled by the game's Music volume).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			hearingDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Radio", "HearingDistance", 12f, new ConfigDescription("How far away (m) you can still hear a cart's radio.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 40f), Array.Empty<object>()));
			toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Radio", "ToggleKey", (KeyCode)121, "Turns the radio on or off.");
			volumeDownKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Radio", "VolumeDownKey", (KeyCode)45, "Turns your radio down (music, DJ and sound effects together).");
			volumeUpKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Radio", "VolumeUpKey", (KeyCode)61, "Turns your radio up (music, DJ and sound effects together).");
			djVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Radio", "DJVolume", 1f, new ConfigDescription("DJ voice loudness compared to the music.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			sfxVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Radio", "EffectsVolume", 0.5f, new ConfigDescription("Sound effect loudness (air horns, crashes...) compared to the music. Lower it if they make you jump.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			djOn = ((BaseUnityPlugin)this).Config.Bind<bool>("DJ", "On", true, "A robot DJ reads a joke from jokes.txt between songs.");
			songsBetweenJokes = ((BaseUnityPlugin)this).Config.Bind<int>("DJ", "SongsBetweenJokes", 1, new ConfigDescription("How many songs play before each joke.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			episodesInOrder = ((BaseUnityPlugin)this).Config.Bind<bool>("DJ", "EpisodesInOrder", false, "Play the W-A-S-T-E FM episodes first, in order (1, 2, 3...). Off = everything is shuffled.");
			djPitch = ((BaseUnityPlugin)this).Config.Bind<int>("DJ", "Pitch", 0, new ConfigDescription("DJ voice pitch in Hz (0 = same as the players' robot voice).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 400), Array.Empty<object>()));
			botPitch = ((BaseUnityPlugin)this).Config.Bind<float>("DJ", "BotVoicePitch", 0.55f, new ConfigDescription("Pitch of the deep repo-bot voice (\"BOT:\" lines), relative to the DJ.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 2f), Array.Empty<object>()));
			instance = this;
			string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			Directory.CreateDirectory(Path.Combine(directoryName, "music"));
			Directory.CreateDirectory(Path.Combine(directoryName, "sfx"));
			LoadJokes(Path.Combine(directoryName, "jokes.txt"));
			((MonoBehaviour)this).StartCoroutine(LoadSongs(Path.Combine(directoryName, "music")));
		}

		private void Start()
		{
			PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
		}

		private void OnDestroy()
		{
			PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
		}

		private static void Send(string kind, string text = "", float number = 0f)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			if (Leading)
			{
				PhotonNetwork.RaiseEvent((byte)177, (object)new object[3] { kind, text, number }, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable);
			}
		}

		private void OnEvent(EventData e)
		{
			if (e.Code != 177 || PhotonNetwork.MasterClient == null || e.Sender != PhotonNetwork.MasterClient.ActorNumber || !(e.CustomData is object[] array) || array.Length < 3)
			{
				return;
			}
			if (!FollowingHost)
			{
				bitLines.Clear();
				djTalking = false;
				foreach (Radio value in radios.Values)
				{
					Speech dj = value.dj;
					if (dj != null)
					{
						dj.stop(true);
					}
				}
			}
			lastHostMessage = Time.time;
			string text = (array[1] as string) ?? "";
			float num = ((array[2] is float num2) ? num2 : 0f);
			switch (array[0] as string)
			{
			case "song":
			{
				AudioClip val = ((IEnumerable<AudioClip>)playlist).FirstOrDefault((Func<AudioClip, bool>)((AudioClip c) => ((Object)c).name == text));
				if ((Object)(object)val == (Object)null)
				{
					StopMusicLocal();
				}
				else if ((Object)(object)val != (Object)(object)currentClip || Mathf.Abs(Time.time - songStart - num) > 1.5f)
				{
					PlaySongLocal(val, num);
				}
				break;
			}
			case "stop":
				StopMusicLocal();
				break;
			case "say":
				SayLocal(text, Mathf.RoundToInt(num));
				break;
			case "sfx":
				SfxLocal(text);
				break;
			}
		}

		private void LoadJokes(string file)
		{
			if (!File.Exists(file))
			{
				Log.LogWarning((object)"No jokes.txt found; the DJ has nothing to say.");
				return;
			}
			List<string> list = new List<string>();
			foreach (string item in File.ReadAllLines(file).Append(""))
			{
				string text = item.Trim();
				if (text.StartsWith("#"))
				{
					continue;
				}
				if (text.Length > 0)
				{
					list.Add(text);
					if (IsTag(text, "episode", out var argument))
					{
						episodes[(int)Number(argument, 1f, 0f, 999f)] = list;
					}
				}
				else if (list.Count > 0)
				{
					bits.Add(list);
					list = new List<string>();
				}
			}
			if (!episodesInOrder.Value)
			{
				episodes.Clear();
			}
			Log.LogInfo((object)$"Cart Radio DJ loaded {bits.Count} joke(s) and bit(s).");
		}

		private static bool IsTag(string line, string tag, out string argument)
		{
			argument = "";
			if (!line.StartsWith("(" + tag, StringComparison.OrdinalIgnoreCase) || !line.EndsWith(")"))
			{
				return false;
			}
			string text = line.Substring(tag.Length + 1, line.Length - tag.Length - 2);
			if (text.Length > 0 && text[0] != ' ')
			{
				return false;
			}
			argument = text.Trim();
			return true;
		}

		private static float Number(string text, float fallback, float min, float max)
		{
			if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return Mathf.Clamp(result, min, max);
			}
			return fallback;
		}

		private static IEnumerator LoadClips(string folder, Action<AudioClip> loaded)
		{
			string[] array = (from f in Directory.GetFiles(folder)
				where (int)AudioTypeOf(f) > 0
				orderby f
				select f).ToArray();
			string[] array2 = array;
			foreach (string file in array2)
			{
				UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(file).AbsoluteUri, AudioTypeOf(file));
				try
				{
					yield return request.SendWebRequest();
					if ((int)request.result != 1)
					{
						Log.LogWarning((object)("Couldn't load " + Path.GetFileName(file) + ": " + request.error));
						continue;
					}
					AudioClip content = DownloadHandlerAudioClip.GetContent(request);
					((Object)content).name = Path.GetFileNameWithoutExtension(file);
					loaded(content);
				}
				finally
				{
					((IDisposable)request)?.Dispose();
				}
			}
		}

		private IEnumerator LoadSongs(string folder)
		{
			yield return LoadClips(Path.Combine(Path.GetDirectoryName(folder), "sfx"), delegate(AudioClip c)
			{
				sfx[((Object)c).name.ToLowerInvariant()] = c;
			});
			if (sfx.Count > 0)
			{
				Log.LogInfo((object)$"Cart Radio loaded {sfx.Count} sound effect(s).");
			}
			string text = Path.Combine(Path.GetDirectoryName(folder), "voice");
			if (Directory.Exists(text))
			{
				yield return LoadClips(text, delegate(AudioClip c)
				{
					voiceLines[((Object)c).name.ToLowerInvariant()] = c;
				});
				Log.LogInfo((object)$"Cart Radio loaded {voiceLines.Count} recorded DJ line(s).");
			}
			yield return LoadClips(folder, playlist.Add);
			if (playlist.Count == 0)
			{
				Log.LogWarning((object)("No songs found. Put .wav, .ogg or .mp3 files in " + folder));
				yield break;
			}
			Log.LogInfo((object)string.Format("Cart Radio loaded {0} song(s): {1}. Press {2} to turn it on or off.", playlist.Count, string.Join(", ", playlist.Select((AudioClip c) => ((Object)c).name)), toggleKey.Value));
			song = Random.Range(0, playlist.Count);
			PlaySongLocal(playlist[song], 0f);
		}

		private static AudioType AudioTypeOf(string file)
		{
			return (AudioType)(Path.GetExtension(file).ToLowerInvariant() switch
			{
				".wav" => 20, 
				".ogg" => 14, 
				".mp3" => 13, 
				_ => 0, 
			});
		}

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (Input.GetKeyDown(toggleKey.Value))
			{
				radioOn.Value = !radioOn.Value;
				Log.LogInfo((object)("Cart Radio " + (radioOn.Value ? "on" : "off") + "."));
			}
			if (Input.GetKeyDown(volumeUpKey.Value) || Input.GetKeyDown(volumeDownKey.Value))
			{
				float num = (Input.GetKeyDown(volumeUpKey.Value) ? 0.05f : (-0.05f));
				volume.Value = Mathf.Clamp(Mathf.Round((volume.Value + num) * 20f) / 20f, 0f, 1f);
				volumeMessageUntil = Time.time + 2f;
			}
			if (playlist.Count == 0)
			{
				return;
			}
			if ((scanTimer -= Time.deltaTime) <= 0f)
			{
				scanTimer = 1f;
				AddRadios();
			}
			if (!FollowingHost)
			{
				AdvanceTimeline();
				if (Leading && (heartbeatTimer -= Time.deltaTime) <= 0f)
				{
					heartbeatTimer = 4f;
					if ((Object)(object)currentClip != (Object)null)
					{
						Send("song", ((Object)currentClip).name, Time.time - songStart);
					}
					else
					{
						Send("hb");
					}
				}
			}
			bool flag = true;
			foreach (Radio value2 in radios.Values)
			{
				if (!((Object)(object)value2.music == (Object)null))
				{
					value2.music.volume = volume.Value;
					value2.djAudio.volume = Mathf.Min(1f, volume.Value * 1.6f * djVolume.Value);
					AudioSource music = value2.music;
					float maxDistance = (value2.djAudio.maxDistance = hearingDistance.Value);
					music.maxDistance = maxDistance;
					AudioSource music2 = value2.music;
					maxDistance = (value2.djAudio.spatialBlend = (onAir ? 0f : 1f));
					music2.spatialBlend = maxDistance;
					AudioSource music3 = value2.music;
					bool mute = (value2.djAudio.mute = !radioOn.Value || (onAir && !flag));
					music3.mute = mute;
					flag = false;
				}
			}
		}

		private void AdvanceTimeline()
		{
			if (djTalking)
			{
				if (lineSpeaking)
				{
					if ((radios.Values.Any((Radio r) => (Object)(object)r.dj != (Object)null && r.dj.isTalking()) || Time.time - djStart < 0.4f) && Time.time - djStart < 30f)
					{
						return;
					}
					lineSpeaking = false;
					lineWaitUntil = Time.time + 0.7f;
				}
				if (!(Time.time < lineWaitUntil))
				{
					if (bitLines.Count > 0)
					{
						SayNextLine();
						return;
					}
					djTalking = false;
					StartSong(NextSong());
				}
			}
			else if ((Object)(object)currentClip == (Object)null)
			{
				StartSong(NextSong());
			}
			else if (!(Time.time - songStart < currentClip.length))
			{
				int num = ((songsUntilBit > 0) ? songsUntilBit : songsBetweenJokes.Value);
				if (djOn.Value && bits.Count > 0 && ++songsSinceJoke >= num && radios.Count > 0)
				{
					songsSinceJoke = 0;
					songsUntilBit = 0;
					TellJoke();
				}
				else
				{
					StartSong(NextSong());
				}
			}
		}

		private int NextSong()
		{
			if (playlist.Count < 2)
			{
				return 0;
			}
			int num = Random.Range(0, playlist.Count - 1);
			if (num < song)
			{
				return num;
			}
			return num + 1;
		}

		private void StartSong(int index)
		{
			song = index;
			PlaySongLocal(playlist[song], 0f);
			Send("song", ((Object)playlist[song]).name);
		}

		private void PlaySongLocal(AudioClip clip, float offset)
		{
			currentClip = clip;
			offset = Mathf.Clamp(offset, 0f, clip.length - 0.1f);
			songStart = Time.time - offset;
			mixer.PlayMusic(clip, offset);
			foreach (Radio value in radios.Values)
			{
				if ((Object)(object)value.music != (Object)null)
				{
					value.music.clip = clip;
					value.music.Play();
					value.music.time = offset;
				}
			}
		}

		private void StopMusicLocal()
		{
			currentClip = null;
			mixer.StopMusic();
			foreach (Radio value in radios.Values)
			{
				AudioSource music = value.music;
				if (music != null)
				{
					music.Stop();
				}
			}
		}

		private float SayLocal(string line, int speaker)
		{
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			speaker = Mathf.Clamp(speaker, 0, SpeakerNames.Length - 1);
			if (voiceLines.TryGetValue(VoiceKey(speaker, line), out var value))
			{
				foreach (Radio value2 in radios.Values)
				{
					AudioSource djAudio = value2.djAudio;
					if (djAudio != null)
					{
						djAudio.PlayOneShot(value);
					}
				}
				mixer.PlayShot(value, djVolume.Value);
				return value.length;
			}
			float num = speaker switch
			{
				2 => 1.6f, 
				1 => botPitch.Value, 
				_ => 1f, 
			};
			foreach (Radio value3 in radios.Values)
			{
				if ((Object)(object)value3.dj != (Object)null)
				{
					int num2 = Mathf.Clamp(Mathf.RoundToInt((float)value3.djFrequency * num), 40, 600);
					value3.dj.speak(num2, value3.dj.voicingSource, line.ToLowerInvariant(), false, (string)null);
				}
			}
			return 0f;
		}

		private float SfxLocal(string name)
		{
			if (!sfx.TryGetValue(name.ToLowerInvariant(), out var value))
			{
				Log.LogWarning((object)("Sound effect '" + name + "' not found in the sfx folder."));
				return 0f;
			}
			foreach (Radio value2 in radios.Values)
			{
				AudioSource music = value2.music;
				if (music != null)
				{
					music.PlayOneShot(value, sfxVolume.Value);
				}
			}
			mixer.PlayShot(value, sfxVolume.Value);
			return value.length;
		}

		private void TellJoke()
		{
			if (bitDeck.Count == 0)
			{
				bitDeck.AddRange(bits.OrderBy((List<string> _) => Random.value));
			}
			List<string> list = bitDeck[0];
			if (episodes.Count > 0)
			{
				int key = episodes.Keys.First();
				list = episodes[key];
				episodes.Remove(key);
			}
			stationOpened = true;
			bitDeck.Remove(list);
			bitLines.Clear();
			foreach (string item in list)
			{
				bitLines.Enqueue(item);
			}
			djTalking = true;
			lineSpeaking = false;
			lineWaitUntil = Time.time + 0.5f;
			StopMusicLocal();
			Send("stop");
		}

		private void SayNextLine()
		{
			string line = bitLines.Dequeue();
			string argument2;
			if (IsTag(line, "pause", out var argument))
			{
				lineWaitUntil = Time.time + Number(argument, 1.5f, 0.1f, 10f);
			}
			else if (IsTag(line, "songs", out argument2))
			{
				songsUntilBit = (int)Number(argument2, 1f, 1f, 20f);
			}
			else
			{
				if (IsTag(line, "episode", out var _))
				{
					return;
				}
				if (IsTag(line, "sfx", out var argument4))
				{
					lineWaitUntil = Time.time + SfxLocal(argument4);
					Send("sfx", argument4);
					return;
				}
				int num = 0;
				if (TrimSpeaker(ref line, "BOT:"))
				{
					num = 1;
				}
				else if (TrimSpeaker(ref line, "PANIC:"))
				{
					num = 2;
				}
				else
				{
					TrimSpeaker(ref line, "DJ:");
				}
				float num2 = SayLocal(line, num);
				Send("say", line, num);
				if (num2 > 0f)
				{
					lineWaitUntil = Time.time + num2 + 0.7f;
					return;
				}
				lineSpeaking = true;
				djStart = Time.time;
			}
		}

		private static string VoiceKey(int speaker, string line)
		{
			using SHA1 sHA = SHA1.Create();
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(SpeakerNames[speaker] + "|" + line.Trim().ToLowerInvariant())), 0, 8).Replace("-", "").ToLowerInvariant();
		}

		private static bool TrimSpeaker(ref string line, string speaker)
		{
			if (!line.StartsWith(speaker, StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			line = line.Substring(speaker.Length).Trim();
			return true;
		}

		private void OnAirKeys()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: 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)
			if (Input.GetKeyDown(onAirKey.Value))
			{
				onAir = !onAir;
				if (!onAir)
				{
					StopOnAir();
				}
				Log.LogInfo((object)("Cart FM on air: " + (onAir ? "yes" : "no") + "."));
				onAirMessageUntil = Time.time + 3f;
			}
			if (Input.GetKeyDown(echoTestKey.Value) && (Object)(object)onAirRecorder != (Object)null)
			{
				onAirRecorder.DebugEchoMode = !onAirRecorder.DebugEchoMode;
				Log.LogInfo((object)("Cart FM echo test: " + (onAirRecorder.DebugEchoMode ? "on" : "off") + "."));
				onAirMessageUntil = Time.time + 3f;
			}
			float num = 0f;
			if (Input.GetKeyDown(onAirLouderKey.Value))
			{
				num = 0.05f;
			}
			else if (Input.GetKeyDown(onAirQuieterKey.Value))
			{
				num = -0.05f;
			}
			if (num != 0f)
			{
				onAirVolume.Value = Mathf.Clamp(Mathf.Round((onAirVolume.Value + num) * 20f) / 20f, 0.05f, 1f);
				onAirMessageUntil = Time.time + 3f;
			}
			mixer.Volume = onAirVolume.Value;
		}

		private void OnAirTick()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			if (!onAir)
			{
				return;
			}
			PlayerVoiceChat val = PlayerVoiceChat.instance;
			Recorder val2 = (((Object)(object)val != (Object)null) ? VoiceRecorder.Invoke(val) : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return;
			}
			if ((Object)(object)val2 != (Object)(object)onAirRecorder || (int)val2.SourceType != 2)
			{
				if (pusher == null)
				{
					pusher = new RadioPusher();
				}
				val2.InputFactory = () => (IAudioDesc)(object)pusher;
				val2.SourceType = (InputSourceType)2;
				val2.RecordingEnabled = true;
				val2.RestartRecording();
				onAirRecorder = val2;
				lastDspTime = AudioSettings.dspTime;
			}
			double dspTime = AudioSettings.dspTime;
			int num = Mathf.Min((int)((dspTime - lastDspTime) * 24000.0), 12000);
			if (num > 0)
			{
				lastDspTime += (double)num / 24000.0;
				if (dspTime - lastDspTime > 0.5)
				{
					lastDspTime = dspTime;
				}
				pusher.Push(mixer.Render(num), num);
			}
		}

		private void StopOnAir()
		{
			if ((Object)(object)onAirRecorder != (Object)null)
			{
				onAirRecorder.DebugEchoMode = false;
				onAirRecorder.SourceType = (InputSourceType)0;
				onAirRecorder.InputFactory = null;
				onAirRecorder.RestartRecording();
			}
			onAirRecorder = null;
		}

		private static void KeepTransmitting(PlayerVoiceChat __instance)
		{
			if ((Object)(object)instance != (Object)null && instance.onAir && (Object)(object)__instance == (Object)(object)PlayerVoiceChat.instance && (Object)(object)instance.onAirRecorder != (Object)null && (Object)(object)VoiceRecorder.Invoke(__instance) == (Object)(object)instance.onAirRecorder)
			{
				instance.onAirRecorder.TransmitEnabled = true;
			}
		}

		private void OnGUI()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_004d: 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_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: 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_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			if (onAirStyle == null)
			{
				onAirStyle = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					fontStyle = (FontStyle)1
				};
			}
			if (Time.time < volumeMessageUntil)
			{
				GUI.Label(new Rect(16f, 40f, 500f, 30f), $"Cart FM volume (just for you): {Mathf.RoundToInt(volume.Value * 100f)}%   [{volumeDownKey.Value}] [{volumeUpKey.Value}]", onAirStyle);
			}
			if (onAir || !(Time.time > onAirMessageUntil))
			{
				string text = (onAir ? $"● CART FM ON AIR  {Mathf.RoundToInt(onAirVolume.Value * 100f)}%   [{onAirQuieterKey.Value}] [{onAirLouderKey.Value}] volume, [{onAirKey.Value}] off" : "Cart FM off air (mic back on)");
				if (onAir && (Object)(object)onAirRecorder != (Object)null)
				{
					text += (onAirRecorder.DebugEchoMode ? $"   ECHO TEST ON: you hear what others hear [{echoTestKey.Value}]" : $"   [{echoTestKey.Value}] echo test");
				}
				else if (onAir)
				{
					text += "   (waiting for voice chat: needs a multiplayer lobby)";
				}
				GUI.color = (Color)(onAir ? new Color(1f, 0.35f, 0.35f) : Color.white);
				GUI.Label(new Rect(16f, 12f, 700f, 30f), text, onAirStyle);
				GUI.color = Color.white;
			}
		}

		private void AddRadios()
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			foreach (PhysGrabCart item in radios.Keys.Where((PhysGrabCart c) => (Object)(object)c == (Object)null).ToList())
			{
				radios.Remove(item);
			}
			PhysGrabCart[] array = Object.FindObjectsOfType<PhysGrabCart>();
			foreach (PhysGrabCart val in array)
			{
				if (!radios.ContainsKey(val))
				{
					Radio radio = new Radio();
					GameObject val2 = new GameObject("Cart Radio");
					val2.transform.SetParent(((Component)val).transform, false);
					radio.music = MakeSource(val2);
					if ((Object)(object)currentClip != (Object)null)
					{
						radio.music.clip = currentClip;
						radio.music.Play();
						radio.music.time = Mathf.Clamp(Time.time - songStart, 0f, currentClip.length - 0.1f);
					}
					radio.dj = MakeDJ(((Component)val).transform, out radio.djAudio);
					radio.djFrequency = radio.dj.voiceBaseFrequency;
					radios[val] = radio;
				}
			}
			if (!stationOpened && !FollowingHost && bits.Count > 0 && djOn.Value && radios.Count > 0 && !djTalking)
			{
				TellJoke();
			}
		}

		private static AudioSource MakeSource(GameObject holder)
		{
			AudioSource val = holder.AddComponent<AudioSource>();
			val.playOnAwake = false;
			val.spatialBlend = 1f;
			val.rolloffMode = (AudioRolloffMode)1;
			val.minDistance = 1.5f;
			val.maxDistance = hearingDistance.Value;
			val.dopplerLevel = 0f;
			val.volume = volume.Value;
			val.mute = !radioOn.Value;
			if ((Object)(object)AudioManager.instance != (Object)null)
			{
				val.outputAudioMixerGroup = AudioManager.instance.MusicMasterGroup;
			}
			return val;
		}

		private static Speech MakeDJ(Transform cart, out AudioSource audio)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0071: 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)
			GameObject val = new GameObject("Cart Radio DJ");
			val.SetActive(false);
			val.transform.SetParent(cart, false);
			audio = MakeSource(val);
			Speech val2 = val.AddComponent<Speech>();
			TTSVoice val3 = Object.FindObjectOfType<TTSVoice>();
			Speech val4 = (((Object)(object)val3 != (Object)null && val3.voices != null && val3.voices.Length != 0) ? val3.voices[0] : null);
			if ((Object)(object)val4 != (Object)null)
			{
				val2.voiceBaseFrequency = val4.voiceBaseFrequency;
				val2.voicingSource = val4.voicingSource;
				val2.msPerSpeechFrame = val4.msPerSpeechFrame;
				val2.flutter = val4.flutter;
				val2.flutterSpeed = val4.flutterSpeed;
			}
			if (djPitch.Value > 0)
			{
				val2.voiceBaseFrequency = djPitch.Value;
			}
			val.SetActive(true);
			return val2;
		}
	}
}