Decompiled source of ValheimBoombox v0.2.8

BepInEx/plugins/Rizias-ValheimBoombox/ValheimBoombox.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SoftReferenceableAssets;
using TMPro;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.UI;
using UnityEngine.Playables;
using UnityEngine.Rendering;
using UnityEngine.UI;
using ValheimBoombox.Visuals;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.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 ValheimBoombox.Visuals
{
	internal sealed class BassShockwaveEdge
	{
		private const int Segments = 96;

		private const int Rows = 5;

		private readonly Mesh _mesh;

		private readonly MeshRenderer _renderer;

		private readonly Vector3[] _vertices = (Vector3[])(object)new Vector3[485];

		private readonly Color32[] _colors = (Color32[])(object)new Color32[485];

		private readonly float[] _rowPosition = new float[5] { 0f, 0.22f, 0.62f, 0.91f, 1f };

		private readonly float[] _rowHeight = new float[5] { 0f, 0.3f, 1f, 0.25f, 0f };

		private readonly float[] _rowAlpha = new float[5] { 0f, 0.32f, 1f, 0.6f, 0f };

		private readonly Material _material;

		public BassShockwaveEdge(Transform parent, Material source, Material fallback, Color tint, List<Object> owned)
		{
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Expected O, but got Unknown
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Expected O, but got Unknown
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Expected O, but got Unknown
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			Material val = source;
			if ((Object)(object)val == (Object)null || (Object)(object)val.shader == (Object)null || !val.shader.isSupported || ((Object)val.shader).name == "Hidden/InternalErrorShader")
			{
				val = fallback;
			}
			if ((Object)(object)val == (Object)null)
			{
				throw new InvalidOperationException("A supported transparent material is required for the bass front.");
			}
			Material val2 = (_material = new Material(val)
			{
				name = "Bass narrow textured edge",
				hideFlags = (HideFlags)52
			});
			owned.Add((Object)(object)val2);
			if ((Object)(object)source != (Object)null && source.HasProperty("_MainTex") && val2.HasProperty("_MainTex"))
			{
				val2.SetTexture("_MainTex", source.GetTexture("_MainTex"));
				Texture texture = source.GetTexture("_MainTex");
				val2.mainTextureScale = new Vector2(((Object)(object)texture != (Object)null && (int)texture.wrapMode == 0) ? 3f : 1f, 1f);
				val2.mainTextureOffset = Vector2.zero;
			}
			SetTint(tint);
			val2.renderQueue = 3010;
			Vector2[] array = (Vector2[])(object)new Vector2[_vertices.Length];
			Vector3[] array2 = (Vector3[])(object)new Vector3[_vertices.Length];
			int[] array3 = new int[2304];
			for (int i = 0; i < 5; i++)
			{
				for (int j = 0; j <= 96; j++)
				{
					int num = i * 97 + j;
					array[num] = new Vector2((float)j / 96f, 0.35f + (float)i / 4f * 0.65f);
					array2[num] = Vector3.up;
					if (i != 4 && j != 96)
					{
						int num2 = (i * 96 + j) * 6;
						array3[num2] = num;
						array3[num2 + 1] = num + 1;
						array3[num2 + 2] = num + 96 + 1;
						array3[num2 + 3] = num + 1;
						array3[num2 + 4] = num + 96 + 2;
						array3[num2 + 5] = num + 96 + 1;
					}
				}
			}
			_mesh = new Mesh
			{
				name = "Bass textured front edge",
				hideFlags = (HideFlags)52
			};
			_mesh.MarkDynamic();
			_mesh.vertices = _vertices;
			_mesh.normals = array2;
			_mesh.uv = array;
			_mesh.triangles = array3;
			owned.Add((Object)(object)_mesh);
			GameObject val3 = new GameObject("Narrow textured front")
			{
				hideFlags = (HideFlags)52
			};
			val3.transform.SetParent(parent, false);
			val3.AddComponent<MeshFilter>().sharedMesh = _mesh;
			_renderer = val3.AddComponent<MeshRenderer>();
			((Renderer)_renderer).sharedMaterial = val2;
			((Renderer)_renderer).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)_renderer).receiveShadows = false;
			((Renderer)_renderer).lightProbeUsage = (LightProbeUsage)0;
			((Renderer)_renderer).reflectionProbeUsage = (ReflectionProbeUsage)0;
		}

		public void Update(float radius, float maxRadius, float age)
		{
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Min(Mathf.Lerp(0.15f, 0.25f, Mathf.Clamp01(radius / maxRadius)), radius * 0.4f);
			float num2 = radius - num;
			float num3 = Mathf.Min(0.075f, num * 0.3f);
			float num4 = Mathf.SmoothStep(0f, 1f, age / 0.065f);
			float num5 = 1f - Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0.58f, 1f, age));
			for (int i = 0; i < 5; i++)
			{
				float num6 = Mathf.Lerp(num2, radius, _rowPosition[i]);
				for (int j = 0; j <= 96; j++)
				{
					float num7 = (float)j / 96f * (float)Math.PI * 2f;
					float num8 = 0.85f + 0.15f * Mathf.Sin(num7 * 9f + age * 5f);
					float num9 = num3 * _rowHeight[i] * num8;
					num9 = Mathf.Min(num9, Mathf.Sqrt(Mathf.Max(0f, radius * radius - num6 * num6)) * 0.8f);
					int num10 = i * 97 + j;
					_vertices[num10] = new Vector3(Mathf.Cos(num7) * num6, num9, Mathf.Sin(num7) * num6);
					_colors[num10] = Color32.op_Implicit(new Color(1f, 1f, 1f, _rowAlpha[i] * num4 * num5));
				}
			}
			_mesh.SetVertices(_vertices);
			_mesh.SetColors(_colors);
			_mesh.RecalculateBounds();
			((Renderer)_renderer).enabled = radius > 0.005f && num4 * num5 > 0.001f;
		}

		public void SetVisible(bool visible)
		{
			((Renderer)_renderer).enabled = visible && ((Renderer)_renderer).enabled;
		}

		public void SetTint(Color tint)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: 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)
			string text = (_material.HasProperty("_TintColor") ? "_TintColor" : (_material.HasProperty("_Color") ? "_Color" : null));
			if (text != null)
			{
				_material.SetColor(text, new Color(tint.r, tint.g, tint.b, 0.6f * tint.a));
			}
		}
	}
	public sealed class BassShockwaveVisual : MonoBehaviour
	{
		public const string DefaultResourceName = "ValheimBoombox.BassEffects.bundle";

		public float Intensity = 1f;

		public Quaternion FrontRotation = Quaternion.identity;

		private Color _color;

		private static AssetBundle _bundle;

		private static GameObject _magicPrefab;

		private readonly List<Object> _owned = new List<Object>(8);

		private Transform _root;

		private BassShockwaveEdge _front;

		private bool _initialized;

		public void SetColor(Color color)
		{
			//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)
			_color = color;
		}

		public static bool LoadEmbeddedAssets(Assembly assembly, string resourceName = "ValheimBoombox.BassEffects.bundle")
		{
			if ((Object)(object)_bundle != (Object)null)
			{
				return true;
			}
			if (assembly == null)
			{
				throw new ArgumentNullException("assembly");
			}
			using Stream stream = assembly.GetManifestResourceStream(resourceName);
			if (stream == null)
			{
				return false;
			}
			using MemoryStream memoryStream = new MemoryStream();
			stream.CopyTo(memoryStream);
			return LoadAssets(memoryStream.ToArray());
		}

		public static bool LoadAssets(byte[] bytes)
		{
			if ((Object)(object)_bundle != (Object)null)
			{
				return true;
			}
			if (bytes == null || bytes.Length == 0)
			{
				return false;
			}
			AssetBundle val = AssetBundle.LoadFromMemory(bytes);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			GameObject val2 = val.LoadAsset<GameObject>("bass-magicblast");
			if ((Object)(object)val2 == (Object)null)
			{
				val.Unload(true);
				return false;
			}
			_bundle = val;
			_magicPrefab = val2;
			return true;
		}

		public static void ReleaseAssets()
		{
			if ((Object)(object)_bundle != (Object)null)
			{
				_bundle.Unload(false);
			}
			_bundle = null;
			_magicPrefab = null;
		}

		public void Init(Color color, Material alphaMaterial)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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 (_initialized)
			{
				throw new InvalidOperationException("Init must be called once per bass wave.");
			}
			if ((Object)(object)_magicPrefab == (Object)null)
			{
				throw new InvalidOperationException("Load the BassEffects asset bundle before creating a bass wave.");
			}
			GameObject val = new GameObject("Bass native pressure front")
			{
				hideFlags = (HideFlags)52
			};
			_root = val.transform;
			_root.position = ((Component)this).transform.position;
			_color = color;
			Material sharedMaterial = ((Renderer)_magicPrefab.GetComponentInChildren<ParticleSystemRenderer>(true)).sharedMaterial;
			_front = new BassShockwaveEdge(_root, sharedMaterial, alphaMaterial, WithIntensity(Intensity), _owned);
			_initialized = true;
			((Component)_root).gameObject.SetActive(((Behaviour)this).isActiveAndEnabled);
		}

		public void UpdateRadius(float radius, float maxRadius, float normalizedAge)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if (_initialized && Finite(radius) && Finite(maxRadius) && Finite(normalizedAge))
			{
				radius = Mathf.Max(0f, radius);
				maxRadius = Mathf.Max(0.01f, maxRadius);
				float age = Mathf.Clamp01(normalizedAge);
				_root.position = ((Component)this).transform.position;
				_root.rotation = FrontRotation;
				_root.localScale = Vector3.one;
				_front.SetTint(WithIntensity(Intensity));
				_front.Update(radius, maxRadius, age);
				_front.SetVisible(Intensity > 0f);
			}
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private Color WithIntensity(float value)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			return new Color(_color.r, _color.g, _color.b, _color.a * Mathf.Max(0f, value));
		}

		private void OnEnable()
		{
			if ((Object)(object)_root != (Object)null)
			{
				((Component)_root).gameObject.SetActive(true);
			}
		}

		private void OnDisable()
		{
			if ((Object)(object)_root != (Object)null)
			{
				((Component)_root).gameObject.SetActive(false);
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_root).gameObject);
			}
			for (int i = 0; i < _owned.Count; i++)
			{
				if (_owned[i] != (Object)null)
				{
					Object.Destroy(_owned[i]);
				}
			}
			_owned.Clear();
		}
	}
}
namespace VikingBoombox
{
	public sealed class AudioDeck : MonoBehaviour
	{
		private const double EndToleranceSeconds = 0.025;

		private const float GlowGain = 3f;

		private const float GlowShare = 0.3f;

		public DeckState State;

		public AudioSource Source;

		public GameObject Visual;

		public BoomboxDisplay Display;

		public BoomboxMount Mount;

		public BoomboxAmplifier Amplifier;

		public BoomboxPlatformAnchor PlatformAnchor;

		public BoomboxTrophyVisuals TrophyVisuals;

		public BoomboxVisualPresenter VisualPresenter;

		public ItemData Item;

		public ItemDrop WorldDrop;

		public bool StoragePaused;

		private BoomboxMessage status = new BoomboxMessage("text.no_track_selected");

		public bool Loading;

		public int EmittedWaves;

		private int generation;

		private CancellationTokenSource preparation;

		private Task<MusicLiveSource> preparationTask;

		private Coroutine opening;

		private const double MembranePushSeconds = 0.15;

		private float bass;

		private float membraneLevel;

		private double membranePushUntil = double.NegativeInfinity;

		private float glowLevel;

		private readonly BoomboxMembraneMeter membranes = new BoomboxMembraneMeter();

		private bool simulationPaused;

		private bool started;

		private bool wasPlaying;

		private bool audioChanged;

		private BoomboxDesktopAudio desktopAudio;

		private long playedFrame;

		private double playedDsp;

		private string preparedTitle;

		private string preparedArtist;

		private int seekSettledFrame;

		private MusicLiveStream stream;

		private MusicLiveStream retiringStream;

		private bool buffering;

		private float? pendingSeek;

		private readonly BoomboxSyntheticBeatClock synthetic = new BoomboxSyntheticBeatClock();

		private readonly MusicEffectFeed feed = new MusicEffectFeed();

		private MusicEffectRunner effect;

		public string Status
		{
			get
			{
				return status.ToString();
			}
			set
			{
				status = BoomboxMessage.Raw(value);
			}
		}

		public double LastBeatTime { get; private set; }

		public float LastBeatStrength { get; private set; }

		public string TrackArtist
		{
			get
			{
				if (!string.IsNullOrEmpty(preparedArtist))
				{
					return preparedArtist;
				}
				return BoomboxPlugin.Instance.FindTrack(State.TrackId)?.artist;
			}
		}

		public bool IsActivePlayback
		{
			get
			{
				if (State.insertedCassette != null && !StoragePaused && (Object)(object)Source != (Object)null && Source.isPlaying && State.playing && !Loading && !simulationPaused && Time.timeScale > 0f && !AudioListener.pause && (Object)(object)BoomboxPlugin.Instance != (Object)null)
				{
					return BoomboxPlugin.Instance.AudioWorldActive;
				}
				return false;
			}
		}

		public bool IsSyntheticActive
		{
			get
			{
				if (BoomboxSyntheticTrackId.IsSynthetic(State.TrackId) && State.insertedCassette != null && !StoragePaused && State.playing && !Loading && !simulationPaused && Time.timeScale > 0f && !AudioListener.pause && (Object)(object)BoomboxPlugin.Instance != (Object)null)
				{
					return BoomboxPlugin.Instance.AudioWorldActive;
				}
				return false;
			}
		}

		public string TrackTitle
		{
			get
			{
				if (!string.IsNullOrEmpty(preparedTitle))
				{
					return preparedTitle;
				}
				return BoomboxPlugin.Instance.FindTrack(State.TrackId)?.title;
			}
		}

		public string TrackLabel
		{
			get
			{
				if (!string.IsNullOrEmpty(preparedTitle))
				{
					if (!string.IsNullOrEmpty(preparedArtist))
					{
						return preparedArtist + " — " + preparedTitle;
					}
					return preparedTitle;
				}
				return BoomboxPlugin.Instance.FindTrack(State.TrackId)?.Label;
			}
		}

		public long AudioBufferBytes => stream?.BufferBytes ?? 0;

		public MusicLiveStream Stream => stream;

		public float VisualStrength => membraneLevel;

		public float GlowStrength => glowLevel;

		public float TrackSeconds
		{
			get
			{
				if (stream == null)
				{
					return 0f;
				}
				return (float)stream.Seconds;
			}
		}

		public MusicEffectRunner Effect => effect;

		public MusicMoment Moment => feed.Moment;

		private static double Threshold => BoomboxPlugin.Instance.Settings.Effect.Current.Threshold.Value;

		public void SetStatus(string key, params object[] arguments)
		{
			status = new BoomboxMessage(key, arguments);
		}

		public void Initialize(DeckState state)
		{
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Expected O, but got Unknown
			State = state;
			Visual = VikingBoomboxVisual.Create(((Component)this).transform);
			Display = ((Component)this).gameObject.AddComponent<BoomboxDisplay>();
			Display.Initialize();
			Mount = ((Component)this).gameObject.AddComponent<BoomboxMount>();
			Mount.Initialize();
			Source = ((Component)this).gameObject.AddComponent<AudioSource>();
			Source.playOnAwake = false;
			Source.spatialBlend = (state.onBack ? 0f : 1f);
			Source.reverbZoneMix = 0f;
			Source.bypassReverbZones = true;
			Source.dopplerLevel = 0f;
			Source.rolloffMode = (AudioRolloffMode)1;
			Source.minDistance = 2f;
			Source.maxDistance = BoomboxPlugin.Instance.SoundDistance.Value;
			Source.spread = 35f;
			Source.priority = 80;
			Source.volume = (state.muted ? 0f : state.volume);
			desktopAudio = ((Component)this).gameObject.AddComponent<BoomboxDesktopAudio>();
			ConnectMixer();
			Amplifier = ((Component)this).gameObject.AddComponent<BoomboxAmplifier>();
			Amplifier.Initialize(this);
			PlatformAnchor = ((Component)this).gameObject.AddComponent<BoomboxPlatformAnchor>();
			PlatformAnchor.Initialize(this);
			VisualPresenter = ((Component)this).gameObject.AddComponent<BoomboxVisualPresenter>();
			VisualPresenter.Initialize(State, Visual);
			TrophyVisuals = VisualPresenter.Decorations;
			((Component)this).gameObject.AddComponent<BoomboxEnchantment>().Initialize(this);
			((Component)this).gameObject.AddComponent<BoomboxBoatHelp>().Initialize(this);
			effect = ((Component)this).gameObject.AddComponent<MusicEffectRunner>();
			effect.Initialize(this);
			AudioSettings.OnAudioConfigurationChanged += new AudioConfigurationChangeHandler(OnAudioChanged);
			if (state.TrackId != null)
			{
				LoadTrack();
			}
		}

		public void TogglePlayback()
		{
			if (!State.playing && State.insertedCassette == null)
			{
				SetStatus("text.insert_a_cassette_2");
				BoomboxPlugin.Instance.Notice = Status;
				return;
			}
			Capture();
			State.playing = !State.playing;
			if ((Object)(object)Source.clip != (Object)null)
			{
				if (State.playing)
				{
					PlayAtSavedPosition();
				}
				else
				{
					Source.Pause();
				}
			}
			else if (State.playing && !Loading)
			{
				LoadTrack();
			}
			BoomboxPlugin.Instance.Save();
		}

		public void SelectTrack(int index)
		{
			if (State.tracks.Count != 0)
			{
				State.trackIndex = (index % State.tracks.Count + State.tracks.Count) % State.tracks.Count;
				State.seconds = 0f;
				LoadTrack();
				BoomboxPlugin.Instance.Save();
			}
		}

		public bool Seek(float seconds)
		{
			if ((Object)(object)Source == (Object)null || (Object)(object)Source.clip == (Object)null || stream == null || Loading || float.IsNaN(seconds) || float.IsInfinity(seconds))
			{
				return false;
			}
			float num = Mathf.Clamp(seconds, 0f, Mathf.Max(0f, TrackSeconds - 0.02f));
			Source.Pause();
			buffering = true;
			pendingSeek = num;
			stream.Seek(FrameAt(num));
			State.seconds = num;
			seekSettledFrame = Time.frameCount + 2;
			bass = 0f;
			ForgetMembranes();
			feed.Reset();
			LastBeatTime = -1.0;
			if (!State.playing || StoragePaused || simulationPaused || Time.timeScale <= 0f || AudioListener.pause)
			{
				Source.Pause();
			}
			BoomboxPlugin.Instance.Save();
			return true;
		}

		private void PlayAtSavedPosition()
		{
			if (Loading || (Object)(object)Source.clip == (Object)null)
			{
				return;
			}
			if (State.insertedCassette == null || StoragePaused || Time.timeScale <= 0f || AudioListener.pause)
			{
				simulationPaused = true;
				return;
			}
			if (!stream.Ready)
			{
				buffering = true;
				Source.Pause();
				return;
			}
			float seconds = Resumable(State.seconds);
			if (started)
			{
				Source.UnPause();
			}
			if (!Source.isPlaying)
			{
				Source.timeSamples = stream.Clip(FrameAt(seconds));
				Source.Play();
			}
			playedFrame = Played();
			ForgetMembranes();
			feed.Reset();
			State.seconds = seconds;
			started = true;
			wasPlaying = true;
			playedDsp = AudioSettings.dspTime;
		}

		private void ForgetMembranes()
		{
			membraneLevel = (glowLevel = 0f);
			membranePushUntil = double.NegativeInfinity;
			membranes.Forget();
		}

		public void Capture()
		{
			if (Time.frameCount > seekSettledFrame && (Object)(object)Source != (Object)null && (Object)(object)Source.clip != (Object)null && !Loading && Source.isPlaying)
			{
				State.seconds = Mathf.Max(State.seconds, (float)Played() / 48000f);
			}
		}

		private void Update()
		{
			if (State == null || (Object)(object)BoomboxPlugin.Instance == (Object)null)
			{
				return;
			}
			if (!BoomboxPlugin.Instance.AudioWorldActive)
			{
				desktopAudio?.Poll(wanted: false);
				membraneLevel = (glowLevel = 0f);
				membranes.Rest();
				if ((Object)(object)Visual != (Object)null)
				{
					Visual.GetComponent<VikingBoomboxVisualState>().ApplyMembranes(0f);
				}
				return;
			}
			if (audioChanged)
			{
				Revive();
			}
			ConnectMixer();
			Source.volume = (State.muted ? 0f : State.volume);
			Source.spatialBlend = (State.onBack ? 0f : 1f);
			Source.maxDistance = BoomboxPlugin.Instance.SoundDistance.Value;
			bool flag = State.TrackId == "system:computer-audio";
			BoomboxMusicTap desktopTap = desktopAudio.Poll(flag && IsSyntheticActive);
			if (flag)
			{
				Status = desktopAudio.Status;
			}
			bool paused = SynchronizePlaybackPause();
			UpdateStreamReadiness(paused);
			AnalyzePlayback(flag, desktopTap, paused);
			PresentPlayback();
		}

		private bool SynchronizePlaybackPause()
		{
			bool flag = State.insertedCassette == null || StoragePaused || Time.timeScale <= 0f || AudioListener.pause;
			if (flag && !simulationPaused)
			{
				Capture();
				Source.Pause();
				simulationPaused = true;
				wasPlaying = false;
			}
			else if (!flag && simulationPaused)
			{
				simulationPaused = false;
				if (State.playing && (Object)(object)Source.clip != (Object)null && !Loading)
				{
					PlayAtSavedPosition();
				}
			}
			return flag;
		}

		private void UpdateStreamReadiness(bool paused)
		{
			if (stream != null && stream.Error != null)
			{
				string error = stream.Error;
				Source.Stop();
				if ((Object)(object)Source.clip != (Object)null)
				{
					Object.Destroy((Object)(object)Source.clip);
				}
				Source.clip = null;
				ReleaseStream();
				buffering = false;
				Fail(error);
			}
			if (stream == null || !buffering || !stream.Ready)
			{
				return;
			}
			if (Loading)
			{
				Loading = false;
				SetStatus("text.ready");
			}
			if (pendingSeek.HasValue)
			{
				int num = stream.Clip(FrameAt(pendingSeek.Value));
				if (Source.timeSamples == num)
				{
					Source.timeSamples = (num + 1) % stream.Frames;
				}
				Source.timeSamples = num;
				pendingSeek = null;
			}
			buffering = false;
			if (State.playing && !paused)
			{
				PlayAtSavedPosition();
			}
		}

		private void AnalyzePlayback(bool computerAudio, BoomboxMusicTap desktopTap, bool paused)
		{
			bass = Mathf.MoveTowards(bass, 0f, Time.deltaTime * 3.5f);
			feed.Rest();
			if (IsActivePlayback && (Object)(object)Source.clip != (Object)null && Time.frameCount > seekSettledFrame)
			{
				Capture();
				wasPlaying = true;
				playedFrame = Played();
				playedDsp = AudioSettings.dspTime;
				membranes.Measure(stream, playedFrame, Source.clip.channels, Source.clip.frequency, stream.Length);
				feed.Track(stream, playedFrame, Threshold);
				membranes.MeasureWave(stream, (long)Math.Floor(feed.Moment.Time * 48000.0));
				if (stream.EofFrame >= 0 && playedFrame >= stream.EofFrame)
				{
					Advance();
				}
				else if (Source.isVirtual && stream.EofFrame < 0 && playedFrame >= MusicLiveStream.FramesOf(stream.Seconds))
				{
					Advance();
				}
			}
			else if (IsSyntheticActive)
			{
				if (computerAudio)
				{
					if ((Object)(object)desktopTap != (Object)null)
					{
						feed.Tap(desktopTap, Threshold);
						membranes.MeasureDesktop(desktopTap, Time.unscaledTime, 1f);
					}
					else
					{
						feed.Rest();
						membranes.Rest();
					}
				}
				else
				{
					synthetic.Advance(feed, State.TrackId, Threshold);
					if (BoomboxSyntheticTrackId.IsGameMusic(State.TrackId))
					{
						membranes.MeasureGameMusic(Time.unscaledTime, AudioSettings.outputSampleRate);
					}
				}
			}
			else if (started && wasPlaying && State.playing && !Loading && !buffering && (Object)(object)Source.clip != (Object)null && !paused && !Source.isPlaying)
			{
				wasPlaying = false;
				if (ReachedEnd())
				{
					Advance();
				}
			}
		}

		private void PresentPlayback()
		{
			bool num = IsActivePlayback || IsSyntheticActive;
			Player localPlayer = Player.m_localPlayer;
			bool flag = num && (Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && !((Character)localPlayer).IsTeleporting();
			PresentMusic(flag);
			membraneLevel = ((!flag) ? 0f : ((feed.Moment.Time < membranePushUntil) ? 1f : membranes.Travel));
			glowLevel = (flag ? BoomboxAudioEnvelope.Follow(glowLevel, Mathf.Max(Mathf.Min(1f, membranes.Level * 3f) * 0.3f, bass), Time.unscaledDeltaTime) : 0f);
			if (!flag)
			{
				membranes.Rest();
			}
			VisualPresenter.PresentFrame(flag, membraneLevel, glowLevel);
			Display.Show(this, glowLevel);
		}

		private bool ReachedEnd()
		{
			double num = (double)playedFrame + Math.Max(0.0, AudioSettings.dspTime - playedDsp) * (double)Source.clip.frequency;
			long num2 = ((stream.EofFrame >= 0) ? stream.EofFrame : stream.Length);
			return num + 0.025 * (double)Source.clip.frequency >= (double)num2;
		}

		private void Advance()
		{
			State.seconds = 0f;
			if (!State.repeat && State.trackIndex >= State.tracks.Count - 1)
			{
				State.playing = false;
			}
			SelectTrack(State.trackIndex + 1);
		}

		private void ConnectMixer()
		{
			if ((Object)(object)Source.outputAudioMixerGroup == (Object)null && (Object)(object)MusicMan.instance != (Object)null)
			{
				Source.outputAudioMixerGroup = MusicMan.instance.m_musicMixer;
			}
		}

		private void OnDestroy()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			AudioSettings.OnAudioConfigurationChanged -= new AudioConfigurationChangeHandler(OnAudioChanged);
			generation++;
			CancelPreparation();
			if ((Object)(object)Source != (Object)null)
			{
				Source.Stop();
			}
			ReleaseStream();
			if ((Object)(object)Source != (Object)null && (Object)(object)Source.clip != (Object)null)
			{
				Object.Destroy((Object)(object)Source.clip);
				Source.clip = null;
			}
			if (preparationTask == null)
			{
				return;
			}
			try
			{
				if (!preparationTask.Wait(2250))
				{
					BoomboxPlugin.Instance?.LogError("Audio track preparation shutdown timed out");
				}
			}
			catch (AggregateException)
			{
			}
		}

		private void PresentMusic(bool active)
		{
			if (active)
			{
				foreach (MusicStrike item in feed.Moment.Fresh)
				{
					OnBeat(item.Strength, item.Time);
				}
			}
			effect.Step(feed.Moment, active);
			BoomboxRemoteNetwork.Observe(this, active);
		}

		internal void OnBeat(float strength, double timestamp)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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)
			bass = Mathf.Max(bass, strength);
			membranePushUntil = Math.Max(membranePushUntil, timestamp + 0.15);
			LastBeatTime = timestamp;
			LastBeatStrength = strength;
			Vector3 origin = ((Component)this).transform.position + ((Component)this).transform.up * 0.2f;
			EmittedWaves++;
			BassWave.Emit(origin, State.upgradeTier, strength);
			VisualPresenter?.OnMusicEvent(strength);
			try
			{
				BoomboxSpeakerPulse.Emit(Visual.GetComponent<VikingBoomboxVisualState>(), strength);
			}
			catch (Exception ex)
			{
				BoomboxPlugin.Instance.LogError(BoomboxText.Get("text.speaker_effect_unavailable_the_music_pulse_was") + ex.Message);
			}
		}

		private void OnAudioChanged(bool deviceWasChanged)
		{
			audioChanged = true;
		}

		public void LoadTrack()
		{
			CancelPreparation();
			preparation = new CancellationTokenSource();
			int marker = ++generation;
			Source.Stop();
			bool num = ReleaseStream();
			buffering = false;
			pendingSeek = null;
			if ((Object)(object)Source.clip != (Object)null)
			{
				Object.Destroy((Object)(object)Source.clip);
			}
			Source.clip = null;
			started = false;
			wasPlaying = false;
			preparedTitle = null;
			preparedArtist = null;
			ForgetMembranes();
			feed.Reset();
			synthetic.Reset();
			if (!num)
			{
				Fail(BoomboxText.Get("error.stream_shutdown_timeout"));
			}
			else if (State.insertedCassette == null)
			{
				State.playing = false;
				SetStatus("text.the_boombox_is_not_ready_for_playback_yet");
				Loading = false;
			}
			else if (State.TrackId == null)
			{
				SetStatus("text.no_track_selected");
				Loading = false;
			}
			else if (!State.playing)
			{
				SetStatus("text.press_play_to_prepare_the_track");
				Loading = false;
			}
			else if (BoomboxSyntheticTrackId.IsSynthetic(State.TrackId))
			{
				SetStatus("text.ready");
				Loading = false;
			}
			else
			{
				opening = ((MonoBehaviour)this).StartCoroutine(Open(marker, State.TrackId, preparation.Token));
			}
		}

		private IEnumerator Open(int marker, string trackId, CancellationToken cancellation)
		{
			Loading = true;
			SetStatus("text.preparing_track");
			BoomboxPlugin plugin = BoomboxPlugin.Instance;
			Stopwatch retiring = Stopwatch.StartNew();
			while (preparationTask != null && !preparationTask.IsCompleted)
			{
				if (marker != generation || cancellation.IsCancellationRequested)
				{
					yield break;
				}
				if (retiring.ElapsedMilliseconds >= 2000)
				{
					Fail(BoomboxText.Get("error.previous_track_cleanup_pending"));
					yield break;
				}
				yield return null;
			}
			if (marker != generation || cancellation.IsCancellationRequested)
			{
				yield break;
			}
			Task<MusicLiveSource> task = (preparationTask = Task.Run(() => MusicPreparation.Locate(plugin, trackId, cancellation), cancellation));
			MusicProcess.Observe(task);
			while (!task.IsCompleted)
			{
				yield return null;
			}
			if (marker != generation)
			{
				yield break;
			}
			if (task.IsCanceled)
			{
				Loading = false;
				yield break;
			}
			if (task.IsFaulted)
			{
				Fail(task.Exception.GetBaseException().Message);
				yield break;
			}
			MusicLiveSource result = task.Result;
			preparedTitle = result.Title;
			preparedArtist = result.Artist;
			plugin.UpdateTrackMetadata(trackId, result.Title, result.Artist);
			long frame;
			try
			{
				int num = default(int);
				int num2 = default(int);
				AudioSettings.GetDSPBufferSize(ref num, ref num2);
				long num3 = MusicLiveStream.FramesOf(result.Duration);
				frame = Math.Min(Math.Max(0L, FrameAt(State.seconds)), Math.Max(0L, num3 - 960));
				stream = new MusicLiveStream(result, plugin.Settings.AudioBufferSeconds.Value, checked(num * num2));
			}
			catch (Exception ex)
			{
				Fail(ex.Message);
				yield break;
			}
			yield return Attach(marker, trackId, frame);
		}

		private IEnumerator Attach(int marker, string trackId, long frame)
		{
			if (marker != generation || stream == null)
			{
				yield break;
			}
			State.seconds = (float)frame / 48000f;
			frame = FrameAt(State.seconds);
			int clipFrame = stream.Clip(frame);
			if (clipFrame == 0)
			{
				stream.Begin(frame);
				while (marker == generation && stream != null && stream.Error == null && !stream.Ready)
				{
					yield return null;
				}
				if (marker != generation || stream == null || stream.Error != null)
				{
					yield break;
				}
			}
			try
			{
				Source.clip = AudioClip.Create(trackId, stream.Frames, 2, 48000, true, new PCMReaderCallback(stream.Read), new PCMSetPositionCallback(stream.SetPosition));
				((Object)Source.clip).name = trackId;
				Source.loop = stream.Looping;
				if (clipFrame > 0)
				{
					stream.Begin(frame);
					Source.timeSamples = clipFrame;
				}
			}
			catch (Exception ex)
			{
				ReleaseStream();
				if ((Object)(object)Source.clip != (Object)null)
				{
					Object.Destroy((Object)(object)Source.clip);
					Source.clip = null;
				}
				Fail(ex.Message);
				yield break;
			}
			buffering = true;
		}

		private void Revive()
		{
			audioChanged = false;
			desktopAudio?.ResetDevice();
			if (!((Object)(object)Source.clip == (Object)null))
			{
				long frame = FrameAt(Resumable(State.seconds));
				string name = ((Object)Source.clip).name;
				Source.Stop();
				Object.Destroy((Object)(object)Source.clip);
				Source.clip = null;
				stream.Hold();
				feed.Reset();
				started = (wasPlaying = false);
				if (opening != null)
				{
					((MonoBehaviour)this).StopCoroutine(opening);
				}
				opening = ((MonoBehaviour)this).StartCoroutine(Attach(generation, name, frame));
			}
		}

		private void CancelPreparation()
		{
			if (opening != null)
			{
				((MonoBehaviour)this).StopCoroutine(opening);
				opening = null;
			}
			CancellationTokenSource previous = preparation;
			preparation = null;
			if (previous == null)
			{
				return;
			}
			previous.Cancel();
			if (preparationTask == null || preparationTask.IsCompleted)
			{
				previous.Dispose();
				return;
			}
			preparationTask.ContinueWith(delegate(Task<MusicLiveSource> done)
			{
				_ = done.Exception;
				previous.Dispose();
			}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
		}

		private bool ReleaseStream()
		{
			if (retiringStream != null)
			{
				if (!retiringStream.Stopped)
				{
					return false;
				}
				retiringStream = null;
			}
			MusicLiveStream musicLiveStream = stream;
			stream = null;
			if (musicLiveStream == null)
			{
				return true;
			}
			musicLiveStream.Dispose();
			if (musicLiveStream.CleanupError != null)
			{
				BoomboxPlugin.Instance?.LogError(musicLiveStream.CleanupError);
			}
			if (musicLiveStream.Stopped)
			{
				return true;
			}
			retiringStream = musicLiveStream;
			return false;
		}

		private float Resumable(float seconds)
		{
			return Mathf.Max(0f, stream.EndKnown ? Mathf.Min(seconds, TrackSeconds - 0.02f) : seconds);
		}

		private static long FrameAt(float seconds)
		{
			return (long)Math.Round((double)seconds * 48000.0);
		}

		private long Played()
		{
			return stream.Track(Source.timeSamples);
		}

		private void Fail(string error)
		{
			Loading = false;
			State.playing = false;
			SetStatus("status.playback_error", error);
			BoomboxPlugin.Instance.LogError(Status);
		}
	}
	internal sealed class BackSurfaceSampler : IDisposable
	{
		private readonly Renderer renderer;

		private readonly SkinnedMeshRenderer skinned;

		private readonly MeshFilter filter;

		private readonly List<Vector3> vertices = new List<Vector3>();

		private readonly List<Vector3> projected = new List<Vector3>();

		private readonly Vector3[] clipA = (Vector3[])(object)new Vector3[12];

		private readonly Vector3[] clipB = (Vector3[])(object)new Vector3[12];

		private Mesh baked;

		private Mesh original;

		private int[] triangles = new int[0];

		public bool Active
		{
			get
			{
				if ((Object)(object)renderer != (Object)null && renderer.enabled)
				{
					return ((Component)renderer).gameObject.activeInHierarchy;
				}
				return false;
			}
		}

		public BackSurfaceSampler(Renderer source)
		{
			renderer = source;
			skinned = (SkinnedMeshRenderer)(object)((source is SkinnedMeshRenderer) ? source : null);
			filter = ((Component)source).GetComponent<MeshFilter>();
		}

		public static bool Supports(Renderer renderer)
		{
			if (!(renderer is SkinnedMeshRenderer))
			{
				if (renderer is MeshRenderer)
				{
					return (Object)(object)((Component)renderer).GetComponent<MeshFilter>() != (Object)null;
				}
				return false;
			}
			return true;
		}

		public bool Prepare()
		{
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			if ((Object)(object)renderer == (Object)null || !renderer.enabled || !((Component)renderer).gameObject.activeInHierarchy)
			{
				vertices.Clear();
				return false;
			}
			Mesh val = (((Object)(object)skinned != (Object)null) ? skinned.sharedMesh : (((Object)(object)filter != (Object)null) ? filter.sharedMesh : null));
			if ((Object)(object)val == (Object)null)
			{
				vertices.Clear();
				return false;
			}
			bool flag = (Object)(object)val != (Object)(object)original;
			original = val;
			if ((Object)(object)skinned != (Object)null)
			{
				if ((Object)(object)baked == (Object)null)
				{
					baked = new Mesh
					{
						name = "Boombox surface snapshot"
					};
					baked.MarkDynamic();
				}
				skinned.BakeMesh(baked, true);
				baked.GetVertices(vertices);
				if (flag || triangles.Length == 0)
				{
					triangles = baked.triangles;
				}
			}
			else if (val.isReadable)
			{
				val.GetVertices(vertices);
				if (flag || triangles.Length == 0)
				{
					triangles = val.triangles;
				}
			}
			else
			{
				Mesh val2 = BoomboxHeadPlacement.CollisionMesh(filter);
				val2.GetVertices(vertices);
				if (flag || triangles.Length == 0)
				{
					triangles = val2.triangles;
				}
			}
			return true;
		}

		public bool Sample(Matrix4x4 worldToFrame, Rect area, out float depth, bool prepared = false)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			depth = float.NegativeInfinity;
			if ((!prepared && !Prepare()) || vertices.Count == 0 || (Object)(object)renderer == (Object)null)
			{
				return false;
			}
			Project(worldToFrame);
			bool result = false;
			for (int i = 0; i + 2 < triangles.Length; i += 3)
			{
				int num = ClipTriangle(i, area);
				for (int j = 0; j < num; j++)
				{
					if (!float.IsNaN(clipA[j].z) && !float.IsInfinity(clipA[j].z))
					{
						depth = Mathf.Max(depth, clipA[j].z);
						result = true;
					}
				}
			}
			return result;
		}

		public void BlockedOffsets(Matrix4x4 worldToFrame, Rect area, float rear, float front, float clearance, List<Vector2> result)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)renderer == (Object)null || vertices.Count == 0)
			{
				return;
			}
			Project(worldToFrame);
			for (int i = 0; i + 2 < triangles.Length; i += 3)
			{
				int num = ClipTriangle(i, area);
				if (num != 0)
				{
					float num2 = float.PositiveInfinity;
					float num3 = float.NegativeInfinity;
					for (int j = 0; j < num; j++)
					{
						num2 = Mathf.Min(num2, clipA[j].z);
						num3 = Mathf.Max(num3, clipA[j].z);
					}
					if (!float.IsInfinity(num2) && !float.IsInfinity(num3) && !float.IsNaN(num2) && !float.IsNaN(num3))
					{
						result.Add(new Vector2(num2 - front - clearance, num3 - rear + clearance));
					}
				}
			}
		}

		private void Project(Matrix4x4 worldToFrame)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			Matrix4x4 val = worldToFrame * ((Component)renderer).transform.localToWorldMatrix;
			projected.Clear();
			foreach (Vector3 vertex in vertices)
			{
				projected.Add(((Matrix4x4)(ref val)).MultiplyPoint3x4(vertex));
			}
		}

		private int ClipTriangle(int triangle, Rect area)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			int num = triangles[triangle];
			int num2 = triangles[triangle + 1];
			int num3 = triangles[triangle + 2];
			if (num >= projected.Count || num2 >= projected.Count || num3 >= projected.Count)
			{
				return 0;
			}
			Vector3 val = projected[num];
			Vector3 val2 = projected[num2];
			Vector3 val3 = projected[num3];
			if (Mathf.Max(val.x, Mathf.Max(val2.x, val3.x)) < ((Rect)(ref area)).xMin || Mathf.Min(val.x, Mathf.Min(val2.x, val3.x)) > ((Rect)(ref area)).xMax || Mathf.Max(val.y, Mathf.Max(val2.y, val3.y)) < ((Rect)(ref area)).yMin || Mathf.Min(val.y, Mathf.Min(val2.y, val3.y)) > ((Rect)(ref area)).yMax)
			{
				return 0;
			}
			clipA[0] = val;
			clipA[1] = val2;
			clipA[2] = val3;
			int count = Clip(clipA, 3, clipB, 0, ((Rect)(ref area)).xMin, greater: true);
			count = Clip(clipB, count, clipA, 0, ((Rect)(ref area)).xMax, greater: false);
			count = Clip(clipA, count, clipB, 1, ((Rect)(ref area)).yMin, greater: true);
			return Clip(clipB, count, clipA, 1, ((Rect)(ref area)).yMax, greater: false);
		}

		private static int Clip(Vector3[] input, int count, Vector3[] output, int axis, float boundary, bool greater)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			if (count == 0)
			{
				return 0;
			}
			int result = 0;
			Vector3 val = input[count - 1];
			float num = (((Vector3)(ref val))[axis] - boundary) * (greater ? 1f : (-1f));
			for (int i = 0; i < count; i++)
			{
				Vector3 val2 = input[i];
				float num2 = (((Vector3)(ref val2))[axis] - boundary) * (greater ? 1f : (-1f));
				if (num2 >= 0f != num >= 0f)
				{
					output[result++] = Vector3.LerpUnclamped(val, val2, num / (num - num2));
				}
				if (num2 >= 0f)
				{
					output[result++] = val2;
				}
				val = val2;
				num = num2;
			}
			return result;
		}

		public void Dispose()
		{
			if ((Object)(object)baked != (Object)null)
			{
				Object.Destroy((Object)(object)baked);
			}
		}
	}
	public sealed class BassWave : MonoBehaviour
	{
		public BoomboxWorldImpulse EnvironmentImpulse;

		private float speed;

		public float Radius { get; private set; }

		public float MaximumRadius { get; private set; }

		public static BassWave Emit(Vector3 origin, int tierLevel, float strength)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Boombox musical impulse front");
			val.transform.position = origin;
			BassWave bassWave = val.AddComponent<BassWave>();
			BoomboxSettings settings = BoomboxPlugin.Instance.Settings;
			bassWave.MaximumRadius = BoomboxAuraNetwork.WaveRadius(tierLevel);
			bassWave.speed = BoomboxSettings.PositiveOr(settings.WaveSpeed.Value, 7f);
			bassWave.EnvironmentImpulse = val.AddComponent<BoomboxWorldImpulse>();
			bassWave.EnvironmentImpulse.Initialize(origin, bassWave.MaximumRadius, Mathf.Clamp01(strength));
			return bassWave;
		}

		private void Update()
		{
			if (!(Time.timeScale <= 0f) && !AudioListener.pause)
			{
				float radius = Radius;
				Radius = Mathf.Min(MaximumRadius, Radius + speed * Time.deltaTime);
				if (BoomboxPlugin.Instance.Settings.LocalGameplayEnabled)
				{
					EnvironmentImpulse.ApplyFront(radius, Radius);
				}
				if (Radius >= MaximumRadius)
				{
					Object.Destroy((Object)(object)((Component)this).gameObject);
				}
			}
		}
	}
	public sealed class BoomboxAmplifier : MonoBehaviour
	{
		private AudioDeck deck;

		private BoomboxAmplifierVisual appearance;

		public string UpgradeMessage { get; private set; } = "";

		public UpgradeTier Tier => BoomboxProgression.GetTier(deck.State.upgradeTier);

		public void Initialize(AudioDeck ownerDeck)
		{
			deck = ownerDeck;
			appearance = ((Component)this).gameObject.AddComponent<BoomboxAmplifierVisual>();
			appearance.Initialize(deck);
		}

		public UpgradePlan GetUpgradePlan()
		{
			return PlanFor(Tier.Level, Player.m_localPlayer);
		}

		public static UpgradePlan PlanFor(int currentTier, Player player)
		{
			Dictionary<string, int> inventory = CountInventory(player);
			if (currentTier >= BoomboxProgression.EnabledMaxTier)
			{
				return BoomboxProgression.PlanNextUpgrade(currentTier, null, 0, inventory);
			}
			UpgradeTier tier = BoomboxProgression.GetTier(currentTier + 1);
			CraftingStation val = FindStation(player, tier.Recipe);
			return BoomboxProgression.PlanNextUpgrade(currentTier, ((Object)(object)val != (Object)null) ? tier.Recipe.StationPrefab : null, ((Object)(object)val != (Object)null) ? val.GetLevel(true) : 0, inventory);
		}

		public bool TryUpgrade()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !BoomboxPlugin.Instance.CanControl(deck))
			{
				UpgradeMessage = BoomboxText.Get("text.move_closer_to_your_boombox");
				return false;
			}
			UpgradePlan upgradePlan = GetUpgradePlan();
			if (!upgradePlan.Allowed)
			{
				UpgradeMessage = Describe(upgradePlan);
				return false;
			}
			if (!Spend(localPlayer, upgradePlan.TargetTier.Recipe))
			{
				UpgradeMessage = BoomboxText.Get("text.not_enough_materials");
				return false;
			}
			deck.State.upgradeTier = upgradePlan.TargetTier.Level;
			appearance.Refresh();
			BoomboxPlugin.Instance.Save();
			if ((Object)(object)Game.instance != (Object)null)
			{
				Game.instance.SavePlayerProfile(false, false);
			}
			UpgradeMessage = BoomboxText.Get("text.amplifier_upgraded_to_level") + Tier.Level + ".";
			((Character)localPlayer).Message((MessageType)2, UpgradeMessage, 0, (Sprite)null, false);
			return true;
		}

		public static bool Spend(Player player, UpgradeRecipe recipe)
		{
			return SpendExcept(player, recipe);
		}

		public static bool SpendExcept(Player player, UpgradeRecipe recipe, params ItemData[] excluded)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			Dictionary<string, int> dictionary = recipe.Ingredients.ToDictionary<UpgradeIngredient, string, int>((UpgradeIngredient i) => i.Prefab, (UpgradeIngredient i) => i.Amount, StringComparer.Ordinal);
			List<KeyValuePair<ItemData, int>> list = new List<KeyValuePair<ItemData, int>>();
			ItemData[] array = inventory.GetAllItems().ToArray();
			foreach (ItemData val in array)
			{
				if (!excluded.Contains(val) && !((Object)(object)val.m_dropPrefab == (Object)null) && dictionary.TryGetValue(((Object)val.m_dropPrefab).name, out var value) && value > 0)
				{
					int num2 = Math.Min(val.m_stack, value);
					list.Add(new KeyValuePair<ItemData, int>(val, num2));
					dictionary[((Object)val.m_dropPrefab).name] -= num2;
				}
			}
			if (dictionary.Values.Any((int v) => v > 0))
			{
				return false;
			}
			foreach (KeyValuePair<ItemData, int> item in list)
			{
				inventory.RemoveItem(item.Key, item.Value);
			}
			return true;
		}

		internal static CraftingStation FindStation(Player player, UpgradeRecipe recipe)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return null;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(recipe.StationPrefab);
			CraftingStation val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<CraftingStation>() : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			List<CraftingStation> list = new List<CraftingStation>();
			CraftingStation.FindStationsInRange(val.m_name, ((Component)player).transform.position, float.MaxValue, list);
			return (from s in list
				where (Object)(object)s != (Object)null && s.InUseDistance((Humanoid)(object)player) && s.CheckUsable(player, false)
				orderby s.GetLevel(true) descending
				select s).FirstOrDefault();
		}

		public static Dictionary<string, int> CountInventory(Player player)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			if ((Object)(object)player == (Object)null)
			{
				return dictionary;
			}
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				if (!((Object)(object)allItem.m_dropPrefab == (Object)null))
				{
					string name = ((Object)allItem.m_dropPrefab).name;
					dictionary[name] = (dictionary.TryGetValue(name, out var value) ? value : 0) + allItem.m_stack;
				}
			}
			return dictionary;
		}

		public static string ItemName(string prefab)
		{
			GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefab) : null);
			ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val2 != (Object)null))
			{
				return prefab;
			}
			return Localization.instance.Localize(val2.m_itemData.m_shared.m_name);
		}

		public static string StationName(string prefab)
		{
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefab) : null);
			CraftingStation val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<CraftingStation>() : null);
			if (!((Object)(object)val2 != (Object)null))
			{
				return prefab;
			}
			return Localization.instance.Localize(val2.m_name);
		}

		public static string Describe(UpgradePlan plan)
		{
			return plan.Status switch
			{
				UpgradeStatus.Ready => BoomboxText.Get("text.all_requirements_met"), 
				UpgradeStatus.MaximumTier => BoomboxText.Get("text.maximum_amplifier_level"), 
				UpgradeStatus.WrongStation => BoomboxText.Get("text.requires_an_accessible") + StationName(plan.TargetTier.Recipe.StationPrefab) + BoomboxText.Get("text.of_level") + plan.TargetTier.Recipe.StationLevel + ".", 
				UpgradeStatus.StationLevelTooLow => BoomboxText.Get("text.station_level_too_low_required") + plan.TargetTier.Recipe.StationLevel + ".", 
				UpgradeStatus.MissingMaterials => BoomboxText.Get("text.missing_materials") + string.Join(", ", plan.MissingIngredients.Select((UpgradeIngredient i) => ItemName(i.Prefab) + " × " + i.Amount)) + ".", 
				_ => BoomboxText.Get("text.invalid_amplifier_level"), 
			};
		}

		private void Update()
		{
			if ((Object)(object)appearance != (Object)null)
			{
				appearance.Refresh();
			}
		}
	}
	public sealed class BoomboxAmplifierVisual : MonoBehaviour
	{
		private AudioDeck deck;

		private bool refreshed;

		private ItemDrop currentWorldDrop;

		public void Initialize(AudioDeck ownerDeck)
		{
			deck = ownerDeck;
			Refresh();
		}

		public void Refresh()
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			if (!refreshed || !((Object)(object)deck.WorldDrop == (Object)(object)currentWorldDrop))
			{
				refreshed = true;
				currentWorldDrop = deck.WorldDrop;
				VikingBoomboxVisualState component = deck.Visual.GetComponent<VikingBoomboxVisualState>();
				if ((Object)(object)deck.Mount != (Object)null)
				{
					deck.Mount.RefreshModelBounds();
				}
				BoxCollider component2 = ((Component)deck).GetComponent<BoxCollider>();
				Bounds modelBounds;
				if ((Object)(object)component2 != (Object)null)
				{
					modelBounds = component.ModelBounds;
					component2.center = ((Bounds)(ref modelBounds)).center;
					modelBounds = component.ModelBounds;
					component2.size = ((Bounds)(ref modelBounds)).size;
				}
				BoxCollider val = (((Object)(object)deck.WorldDrop != (Object)null) ? ((Component)deck.WorldDrop).GetComponent<BoxCollider>() : null);
				if ((Object)(object)val != (Object)null)
				{
					modelBounds = component.ModelBounds;
					val.center = ((Bounds)(ref modelBounds)).center;
					modelBounds = component.ModelBounds;
					val.size = ((Bounds)(ref modelBounds)).size;
				}
			}
		}
	}
	internal static class BoomboxAudioEnvelope
	{
		public const double BassHz = 180.0;

		public static float BassRms(float[] samples, int channels, int sampleRate)
		{
			if (samples == null || channels <= 0 || sampleRate <= 0 || samples.Length < channels)
			{
				return 0f;
			}
			int num = samples.Length / channels;
			double num2 = 1.0 - Math.Exp(Math.PI * -360.0 / (double)sampleRate);
			double num3 = 0.0;
			for (int i = 0; i < channels; i++)
			{
				double num4 = 0.0;
				for (int j = 0; j < num; j++)
				{
					num4 += num2 * ((double)samples[j * channels + i] - num4);
					num3 += num4 * num4;
				}
			}
			double num5 = Math.Sqrt(num3 / (double)(num * channels));
			if (!double.IsNaN(num5) && !double.IsInfinity(num5))
			{
				return (float)num5;
			}
			return 0f;
		}

		public static float Follow(float current, float target, float deltaSeconds)
		{
			double num = ((target > current) ? 0.015 : 0.065);
			return current + (target - current) * (float)(1.0 - Math.Exp((double)(0f - Math.Max(0f, deltaSeconds)) / num));
		}
	}
	internal static class BoomboxAuraCodec
	{
		internal static void Header(ZPackage package, string session, long revision)
		{
			package.Write(session);
			package.Write(revision);
		}

		internal static string ReadHeader(ZPackage package, out long revision)
		{
			if (package.Size() > 1048576)
			{
				throw new InvalidDataException("Boombox state packet too large");
			}
			string text = Text(package, 64);
			if (!Guid.TryParseExact(text, "N", out var _))
			{
				throw new InvalidDataException("Invalid Boombox session");
			}
			revision = package.ReadLong();
			if (revision < 1)
			{
				throw new InvalidDataException("Invalid Boombox revision");
			}
			return text;
		}

		internal static void Sources(ZPackage package, ICollection<BoomboxAuraSource> sources)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			package.Write(sources.Count);
			foreach (BoomboxAuraSource source in sources)
			{
				package.Write(source.Id);
				package.Write(source.Owner);
				package.Write(source.OwnerName ?? "");
				package.Write(source.Peer);
				package.Write(source.WorldItem);
				package.Write(source.Worn);
				package.Write(source.Active);
				package.Write(source.Position);
				int[] ranks = source.Ranks;
				foreach (int num in ranks)
				{
					package.Write(num);
				}
				package.Write(source.Decorations.Count);
				foreach (BoomboxAuraDecoration decoration in source.Decorations)
				{
					package.Write(decoration.Slot);
					package.Write(decoration.Prefab);
					package.Write(decoration.Effect);
				}
			}
		}

		internal static List<BoomboxAuraSource> ReadSources(ZPackage package)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			int num = Count(package, package.Size() / 8);
			List<BoomboxAuraSource> list = new List<BoomboxAuraSource>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < num; i++)
			{
				BoomboxAuraSource boomboxAuraSource = new BoomboxAuraSource
				{
					Id = Text(package, 64),
					Owner = package.ReadLong(),
					OwnerName = Text(package, 128),
					Peer = package.ReadLong(),
					WorldItem = package.ReadZDOID(),
					Worn = package.ReadBool(),
					Active = package.ReadBool(),
					Position = package.ReadVector3()
				};
				if (!Guid.TryParseExact(boomboxAuraSource.Id, "N", out var _) || !hashSet.Add(boomboxAuraSource.Id) || !BoomboxAuraMath.Finite(boomboxAuraSource.Position.x) || !BoomboxAuraMath.Finite(boomboxAuraSource.Position.y) || !BoomboxAuraMath.Finite(boomboxAuraSource.Position.z))
				{
					throw new InvalidDataException("Invalid Boombox identity or position");
				}
				for (int j = 0; j < 5; j++)
				{
					boomboxAuraSource.Ranks[j] = package.ReadInt();
					if (boomboxAuraSource.Ranks[j] < 0)
					{
						throw new InvalidDataException("Invalid cassette rank");
					}
				}
				int num2 = Count(package, BoomboxDecorationCatalog.Slots.Length);
				HashSet<int> hashSet2 = new HashSet<int>();
				for (int k = 0; k < num2; k++)
				{
					BoomboxAuraDecoration boomboxAuraDecoration = new BoomboxAuraDecoration
					{
						Slot = package.ReadInt(),
						Prefab = Text(package, 128),
						Effect = package.ReadInt()
					};
					if (!hashSet2.Add(boomboxAuraDecoration.Slot) || boomboxAuraDecoration.Slot < 0 || boomboxAuraDecoration.Slot >= BoomboxDecorationCatalog.Slots.Length || boomboxAuraDecoration.Effect < 0 || boomboxAuraDecoration.Effect >= 5)
					{
						throw new InvalidDataException("Invalid Boombox decoration");
					}
					boomboxAuraSource.Decorations.Add(boomboxAuraDecoration);
				}
				list.Add(boomboxAuraSource);
			}
			return list;
		}

		internal static void Balance(ZPackage package, BoomboxAuraBalance balance)
		{
			package.Write(balance.Radius);
			package.Write(balance.MusicOnly);
			package.Write(balance.Fallback ?? "Health");
			float[][] array = new float[6][] { balance.Strength, balance.Cassette, balance.Decoration, balance.Initial, balance.PerRank, balance.Period };
			float[] array2;
			for (int i = 0; i < array.Length; i++)
			{
				array2 = array[i];
				foreach (float num in array2)
				{
					package.Write(num);
				}
			}
			array2 = balance.WaveRadius;
			foreach (float num2 in array2)
			{
				package.Write(num2);
			}
		}

		internal static BoomboxAuraBalance ReadBalance(ZPackage package)
		{
			BoomboxAuraBalance boomboxAuraBalance = new BoomboxAuraBalance
			{
				Radius = Number(package),
				MusicOnly = package.ReadBool(),
				Fallback = Text(package, 32)
			};
			float[][] array = new float[6][] { boomboxAuraBalance.Strength, boomboxAuraBalance.Cassette, boomboxAuraBalance.Decoration, boomboxAuraBalance.Initial, boomboxAuraBalance.PerRank, boomboxAuraBalance.Period };
			foreach (float[] array2 in array)
			{
				for (int j = 0; j < array2.Length; j++)
				{
					array2[j] = Number(package, array2 == boomboxAuraBalance.Initial || array2 == boomboxAuraBalance.PerRank);
				}
			}
			float[] period = boomboxAuraBalance.Period;
			for (int i = 0; i < period.Length; i++)
			{
				if (period[i] <= 0f)
				{
					throw new InvalidDataException("Invalid recovery period");
				}
			}
			for (int k = 0; k < boomboxAuraBalance.WaveRadius.Length; k++)
			{
				boomboxAuraBalance.WaveRadius[k] = Number(package);
			}
			return boomboxAuraBalance;
		}

		private static float Number(ZPackage package, bool signed = false)
		{
			float num = package.ReadSingle();
			if (!BoomboxAuraMath.Finite(num) || (!signed && num < 0f))
			{
				throw new InvalidDataException("Invalid shared balance");
			}
			return num;
		}

		private static int Count(ZPackage package, int maximum)
		{
			int num = package.ReadInt();
			if (num < 0 || num > maximum)
			{
				throw new InvalidDataException("Invalid Boombox collection");
			}
			return num;
		}

		private static string Text(ZPackage package, int maximum)
		{
			string text = package.ReadString();
			if (text.Length > maximum)
			{
				throw new InvalidDataException("Invalid Boombox text");
			}
			return text;
		}
	}
	public sealed class BoomboxAuraSource
	{
		public string Id;

		public long Owner;

		public string OwnerName;

		public long Peer;

		public ZDOID WorldItem;

		public bool Worn;

		public bool Active;

		public Vector3 Position;

		public readonly int[] Ranks = new int[5];

		public readonly List<BoomboxAuraDecoration> Decorations = new List<BoomboxAuraDecoration>();
	}
	public sealed class BoomboxAuraDecoration
	{
		public int Slot;

		public string Prefab;

		public int Effect;
	}
	public sealed class BoomboxAuraBalance
	{
		public float Radius = 20f;

		public bool MusicOnly;

		public readonly float[] Strength = new float[5] { 1f, 1f, 1f, 1f, 1f };

		public readonly float[] Cassette = new float[5] { 0.05f, 1f, 1f, 1f, 0.05f };

		public readonly float[] Decoration = new float[5] { 0.0025f, 0.1f, 0.1f, 0.1f, 0.0025f };

		public readonly float[] Initial = new float[3] { 1f, 1f, 1f };

		public readonly float[] PerRank = new float[3] { 1f, 1f, 1f };

		public readonly float[] Period = new float[3] { 5f, 5f, 5f };

		public readonly float[] WaveRadius = new float[9];

		public string Fallback = "Health";
	}
	public sealed class BoomboxAuraContribution
	{
		public string Id;

		public string OwnerName;

		public long Owner;

		public readonly float[] Values = new float[5];
	}
	public sealed class BoomboxAuraAggregate
	{
		public readonly float[] Values = new float[5];

		public readonly List<BoomboxAuraContribution> Contributions = new List<BoomboxAuraContribution>();
	}
	public static class BoomboxAuraMath
	{
		public const int BonusCount = 5;

		public const int RecoveryCount = 3;

		public static string CassetteProperty(BoomboxDecorationBonus bonus)
		{
			return bonus switch
			{
				BoomboxDecorationBonus.Damage => "Red", 
				BoomboxDecorationBonus.Health => "Green", 
				BoomboxDecorationBonus.Stamina => "Yellow", 
				BoomboxDecorationBonus.Eitr => "Blue", 
				_ => "Purple", 
			};
		}

		public static int RecoveryIndex(BoomboxDecorationBonus bonus)
		{
			return (int)(bonus - 1);
		}

		public static BoomboxDecorationBonus RecoveryBonus(int index)
		{
			return (BoomboxDecorationBonus)(1 + index);
		}

		public static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		public static float CassetteAmount(int index, int rank, BoomboxAuraBalance balance)
		{
			if (index == 0 || index == 4)
			{
				return (float)rank * balance.Cassette[index];
			}
			if (rank <= 0)
			{
				return 0f;
			}
			int num = RecoveryIndex((BoomboxDecorationBonus)index);
			float val = balance.Initial[num] + (float)(rank - 1) * balance.PerRank[num];
			return Math.Max(0f, val) * balance.Cassette[index];
		}

		public static float DecorationAmount(int index, int count, BoomboxAuraBalance balance)
		{
			return (float)count * balance.Decoration[index];
		}

		public static BoomboxAuraContribution Contribution(BoomboxAuraSource source, BoomboxAuraBalance balance)
		{
			BoomboxAuraContribution boomboxAuraContribution = new BoomboxAuraContribution
			{
				Id = source.Id,
				Owner = source.Owner,
				OwnerName = source.OwnerName
			};
			int[] array = new int[5];
			foreach (BoomboxAuraDecoration decoration in source.Decorations)
			{
				array[decoration.Effect]++;
			}
			for (int i = 0; i < 5; i++)
			{
				float num = CassetteAmount(i, source.Ranks[i], balance);
				boomboxAuraContribution.Values[i] = (num + DecorationAmount(i, array[i], balance)) * balance.Strength[i];
			}
			return boomboxAuraContribution;
		}

		public static BoomboxAuraAggregate Aggregate(IEnumerable<BoomboxAuraSource> sources, Vector3 recipient, BoomboxAuraBalance balance)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			BoomboxAuraAggregate boomboxAuraAggregate = new BoomboxAuraAggregate();
			if (balance == null || balance.MusicOnly)
			{
				return boomboxAuraAggregate;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (BoomboxAuraSource source in sources)
			{
				if (source.Active && !(Vector3.Distance(source.Position, recipient) > balance.Radius) && hashSet.Add(source.Id))
				{
					BoomboxAuraContribution boomboxAuraContribution = Contribution(source, balance);
					boomboxAuraAggregate.Contributions.Add(boomboxAuraContribution);
					for (int i = 0; i < 5; i++)
					{
						boomboxAuraAggregate.Values[i] += boomboxAuraContribution.Values[i];
					}
				}
			}
			return boomboxAuraAggregate;
		}
	}
	public sealed class BoomboxAuraNetwork : MonoBehaviour
	{
		private readonly BoomboxAuraRegistry registry = new BoomboxAuraRegistry();

		private readonly string session = Guid.NewGuid().ToString("N");

		private readonly HashSet<ZRpc> registered = new HashSet<ZRpc>();

		private readonly Dictionary<ZRpc, string> acceptedSessions = new Dictionary<ZRpc, string>();

		private ZNet network;

		private long revision;

		private long receivedRevision;

		private string receivedSession;

		private float next;

		private float lastSnapshot;

		private List<BoomboxAuraSource> snapshot = new List<BoomboxAuraSource>();

		private BoomboxAuraBalance balance;

		public static BoomboxAuraNetwork Current { get; private set; }

		public static bool CanEditBalance
		{
			get
			{
				if (!((Object)(object)ZNet.instance == (Object)null))
				{
					return ZNet.instance.IsServer();
				}
				return true;
			}
		}

		public static BoomboxAuraBalance Balance
		{
			get
			{
				if (!((Object)(object)Current != (Object)null) || Current.balance == null)
				{
					if (CanEditBalance)
					{
						if (BoomboxPlugin.Instance?.Settings == null)
						{
							return null;
						}
						return BoomboxPartySettings.Capture(BoomboxPlugin.Instance.Settings);
					}
					return null;
				}
				return Current.balance;
			}
		}

		public static IReadOnlyList<BoomboxAuraSource> Sources
		{
			get
			{
				if (!((Object)(object)Current == (Object)null))
				{
					if (!Current.network.IsServer())
					{
						if (!(Time.realtimeSinceStartup - Current.lastSnapshot <= 1.25f))
						{
							return Array.Empty<BoomboxAuraSource>();
						}
						return Current.snapshot;
					}
					return Current.registry.Sources;
				}
				return Array.Empty<BoomboxAuraSource>();
			}
		}

		internal static float WaveRadius(int tier)
		{
			if (Balance == null)
			{
				if (!CanEditBalance)
				{
					return 0f;
				}
				return BoomboxPlugin.Instance.Settings.WaveRadius(tier);
			}
			return Balance.WaveRadius[Mathf.Clamp(tier, 0, 8)];
		}

		internal static BoomboxAuraNetwork Attach(ZNet owner)
		{
			BoomboxAuraNetwork obj = ((Component)owner).GetComponent<BoomboxAuraNetwork>() ?? ((Component)owner).gameObject.AddComponent<BoomboxAuraNetwork>();
			obj.network = owner;
			Current = obj;
			return obj;
		}

		internal void Connected(ZNetPeer peer)
		{
			if (registered.Add(peer.m_rpc))
			{
				peer.m_rpc.Register<ZPackage>("BoomboxAuraSources", (Action<ZRpc, ZPackage>)ReceiveSources);
				peer.m_rpc.Register<ZPackage>("BoomboxAuraState", (Action<ZRpc, ZPackage>)ReceiveState);
			}
		}

		internal static void PublishImmediate()
		{
			BoomboxAuraNetwork current = Current;
			if (!((Object)(object)current == (Object)null) && !((Object)(object)current.network == (Object)null))
			{
				current.revision++;
				if (current.network.IsServer())
				{
					current.TickServer(current.network.GetPeers(), Time.realtimeSinceStartup);
				}
				else
				{
					current.TickClient(current.network.GetPeers());
				}
			}
		}

		public static BoomboxAuraAggregate For(Player player)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			BoomboxPlugin instance = BoomboxPlugin.Instance;
			if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || ((Character)player).IsTeleporting() || (Object)(object)instance == (Object)null || !instance.AudioWorldActive)
			{
				return new BoomboxAuraAggregate();
			}
			return BoomboxAuraMath.Aggregate(Sources, ((Component)player).transform.position, Balance);
		}

		private void ReceiveSources(ZRpc rpc, ZPackage package)
		{
			if (!network.IsServer() || !BoomboxNetworkHandshake.Attach(network).Accepts(rpc))
			{
				return;
			}
			ZNetPeer peer = ((IEnumerable<ZNetPeer>)network.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc));
			if (!BoomboxAuraSources.Identity(peer, out var character, out var player, out var name))
			{
				return;
			}
			try
			{
				long num;
				string text = BoomboxAuraCodec.ReadHeader(package, out num);
				if (!acceptedSessions.TryGetValue(rpc, out var value) || !(value != text))
				{
					List<BoomboxAuraSource> list = BoomboxAuraCodec.ReadSources(package);
					if (!list.Any((BoomboxAuraSource source) => !BoomboxAuraSources.ValidateAndNormalize(source, peer.m_uid, player, name, character)) && registry.Replace(peer.m_uid, text, num, list, Time.realtimeSinceStartup))
					{
						acceptedSessions[rpc] = text;
						BoomboxMusicNetwork.SourcesReceived(peer.m_uid, list);
					}
				}
			}
			catch (Exception ex)
			{
				BoomboxPlugin.Instance?.LogError("Boombox aura source rejected: " + ex.Message);
			}
		}

		private void ReceiveState(ZRpc rpc, ZPackage package)
		{
			if (network.IsServer() || !BoomboxNetworkHandshake.Attach(network).Accepts(rpc) || ((IEnumerable<ZNetPeer>)network.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc && p.m_server && p.IsReady())) == null)
			{
				return;
			}
			try
			{
				long num;
				string text = BoomboxAuraCodec.ReadHeader(package, out num);
				if (receivedSession == null || (!(receivedSession != text) && num > receivedRevision))
				{
					BoomboxAuraBalance boomboxAuraBalance = BoomboxAuraCodec.ReadBalance(package);
					List<BoomboxAuraSource> sources = BoomboxAuraCodec.ReadSources(package);
					balance = boomboxAuraBalance;
					snapshot = sources;
					receivedSession = text;
					receivedRevision = num;
					lastSnapshot = Time.realtimeSinceStartup;
					BoomboxMusicNetwork.AuraStateReceived(sources);
				}
			}
			catch (Exception ex)
			{
				BoomboxPlugin.Instance?.LogError("Boombox aura state rejected: " + ex.Message);
			}
		}

		private void Update()
		{
			if ((Object)(object)network == (Object)null || BoomboxPlugin.Instance?.Settings == null)
			{
				return;
			}
			List<ZNetPeer> peers = network.GetPeers();
			foreach (ZNetPeer item in peers)
			{
				Connected(item);
			}
			HashSet<ZRpc> rpcs = new HashSet<ZRpc>(peers.Select((ZNetPeer p) => p.m_rpc));
			registered.RemoveWhere((ZRpc rpc) => !rpcs.Contains(rpc));
			ZRpc[] array = acceptedSessions.Keys.Where((ZRpc r) => !rpcs.Contains(r)).ToArray();
			foreach (ZRpc key in array)
			{
				acceptedSessions.Remove(key);
			}
			if (!network.IsServer() && !peers.Any((ZNetPeer p) => p.m_server))
			{
				snapshot.Clear();
				balance = null;
				receivedSession = null;
				receivedRevision = 0L;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!(realtimeSinceStartup < next))
			{
				next = realtimeSinceStartup + 0.25f;
				revision++;
				if (network.IsServer())
				{
					TickServer(peers, realtimeSinceStartup);
				}
				else
				{
					TickClient(peers);
				}
			}
		}

		private void TickClient(List<ZNetPeer> peers)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			ZNetPeer val = ((IEnumerable<ZNetPeer>)peers).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_server && p.IsReady() && BoomboxNetworkHandshake.Attach(network).Accepts(p.m_rpc)));
			if (val != null && !((Object)(object)Player.m_localPlayer == (Object)null) && balance != null)
			{
				ZPackage val2 = new ZPackage();
				BoomboxAuraCodec.Header(val2, session, revision);
				BoomboxAuraCodec.Sources(val2, BoomboxAuraSources.Local());
				val.m_rpc.Invoke("BoomboxAuraSources", new object[1] { val2 });
			}
		}

		private void TickServer(List<ZNetPeer> peers, float now)
		{
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			HashSet<long> hashSet = new HashSet<long>(from p in peers
				where p.IsReady() && BoomboxNetworkHandshake.Attach(network).Accepts(p.m_rpc)
				select p.m_uid);
			Player local = Player.m_localPlayer;
			if ((Object)(object)local != (Object)null)
			{
				long uid = ZNet.GetUID();
				hashSet.Add(uid);
				ZNetView component = ((Component)local).GetComponent<ZNetView>();
				ZDO character = ((component != null) ? component.GetZDO() : null);
				List<BoomboxAuraSource> list = BoomboxAuraSources.Local();
				if (character != null && list.All((BoomboxAuraSource s) => BoomboxAuraSources.ValidateAndNormalize(s, uid, local.GetPlayerID(), local.GetPlayerName(), character)) && registry.Replace(uid, session, revision, list, now))
				{
					BoomboxMusicNetwork.SourcesReceived(uid, list);
				}
			}
			registry.Prune(hashSet, now);
			balance = BoomboxPartySettings.Capture(BoomboxPlugin.Instance.Settings);
			ZPackage val = new ZPackage();
			BoomboxAuraCodec.Header(val, session, revision);
			BoomboxAuraCodec.Balance(val, balance);
			BoomboxAuraCodec.Sources(val, registry.Sources.ToList());
			foreach (ZNetPeer peer in peers)
			{
				if (hashSet.Contains(peer.m_uid))
				{
					peer.m_rpc.Invoke("BoomboxAuraState", new object[1] { val });
				}
			}
		}

		private void OnDestroy()
		{
			foreach (ZRpc item in registered)
			{
				item.Unregister("BoomboxAuraSources");
				item.Unregister("BoomboxAuraState");
			}
			registered.Clear();
			acceptedSessions.Clear();
			snapshot.Clear();
			balance = null;
			if ((Object)(object)Current == (Object)(object)this)
			{
				Current = null;
				BoomboxAuraRecipient.Clear();
			}
		}
	}
	public static class BoomboxAuraRecipient
	{
		private static Player recipient;

		private static int appliedFrame = -1;

		private static readonly double[] elapsed = new double[3];

		private static readonly double[] ticks = new double[3];

		public static double HealthTicks => ticks[0];

		public static double StaminaTicks => ticks[1];

		public static double EitrTicks => ticks[2];

		internal static void Apply(Player player)
		{
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer || Time.frameCount == appliedFrame)
			{
				return;
			}
			if ((Object)(object)recipient != (Object)(object)player)
			{
				Clear();
				recipient = player;
			}
			appliedFrame = Time.frameCount;
			BoomboxAuraBalance balance = BoomboxAuraNetwork.Balance;
			BoomboxAuraAggregate boomboxAuraAggregate = BoomboxAuraNetwork.For(player);
			if (((Character)player).IsDead() || ((Character)player).IsTeleporting() || balance == null || Time.timeScale <= 0f || AudioListener.pause)
			{
				Array.Clear(elapsed, 0, elapsed.Length);
				BoomboxAuraHud.Remove(player);
				return;
			}
			BoomboxAuraHud.Present(player, boomboxAuraAggregate, balance);
			for (int i = 0; i < 3; i++)
			{
				float num = boomboxAuraAggregate.Values[(int)BoomboxAuraMath.RecoveryBonus(i)];
				if (num <= 0f)
				{
					elapsed[i] = 0.0;
					continue;
				}
				elapsed[i] += Time.unscaledDeltaTime;
				double num2 = Math.Floor(elapsed[i] / (double)balance.Period[i]);
				elapsed[i] -= num2 * (double)balance.Period[i];
				if (num2 <= 0.0)
				{
					continue;
				}
				ticks[i] += num2;
				switch (i)
				{
				case 0:
					((Character)player).Heal(Grant(num, num2, ((Character)player).GetMaxHealth() - ((Character)player).GetHealth()), false);
					continue;
				case 1:
					((Character)player).AddStamina(Grant(num, num2, ((Character)player).GetMaxStamina() - player.GetStamina()));
					continue;
				}
				if (((Character)player).GetMaxEitr() > 0f)
				{
					((Character)player).AddEitr(Grant(num, num2, ((Character)player).GetMaxEitr() - player.GetEitr()));
				}
			}
		}

		private static float Grant(float amount, double count, float missing)
		{
			return (float)Math.Min(Math.Max(0f, missing), (double)amount * count);
		}

		public static void Clear()
		{
			BoomboxAuraHud.Clear(recipient);
			recipient = null;
			appliedFrame = -1;
			Array.Clear(elapsed, 0, elapsed.Length);
			Array.Clear(ticks, 0, ticks.Length);
		}
	}
	internal sealed class BoomboxAuraRegistry
	{
		private sealed class Batch
		{
			internal string Session;

			internal long Revision;
		}

		private sealed class Entry
		{
			internal BoomboxAuraSource Source;

			internal float Heard;
		}

		private readonly Dictionary<long, Batch> batches = new Dictionary<long, Batch>();

		private readonly Dictionary<string, Entry> sources = new Dictionary<string, Entry>(StringComparer.Ordinal);

		internal IReadOnlyList<BoomboxAuraSource> Sources => sources.Values.Select((Entry e) => e.Source).ToArray();

		internal bool Replace(long peer, string session, long revision, List<BoomboxAuraSource> incoming, float now)
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			if (batches.TryGetValue(peer, out var value) && (value.Session != session || revision <= value.Revision))
			{
				return false;
			}
			foreach (BoomboxAuraSource item in incoming)
			{
				if (sources.TryGetValue(item.Id, out var value2) && value2.Source.Peer != peer)
				{
					object obj;
					if (!((ZDOID)(ref item.WorldItem)).IsNone())
					{
						ZDOMan instance = ZDOMan.instance;
						obj = ((instance != null) ? instance.GetZDO(item.WorldItem) : null);
					}
					else
					{
						obj = null;
					}
					ZDO val = (ZDO)obj;
					bool num = val != null && val.GetOwner() == peer && value2.Source.WorldItem == item.WorldItem;
					int num2;
					if (item.Worn && !((ZDOID)(ref value2.Source.WorldItem)).IsNone())
					{
						ZDOMan instance2 = ZDOMan.instance;
						num2 = ((((instance2 != null) ? instance2.GetZDO(value2.Source.WorldItem) : null) == null) ? 1 : 0);
					}
					else
					{
						num2 = 0;
					}
					bool flag = (byte)num2 != 0;
					if (!num && !flag)
					{
						return false;
					}
				}
			}
			HashSet<string> ids = new HashSet<string>(incoming.Select((BoomboxAuraSource s) => s.Id), StringComparer.Ordinal);
			string[] array = (from p in sources
				where p.Value.Source.Peer == peer && !ids.Contains(p.Key)
				select p.Key).ToArray();
			foreach (string key in array)
			{
				sources.Remove(key);
			}
			foreach (BoomboxAuraSource item2 in incoming)
			{
				sources[item2.Id] = new Entry
				{
					Source = item2,
					Heard = now
				};
			}
			batches[peer] = new Batch
			{
				Session = session,
				Revision = revision
			};
			return true;
		}

		internal void Prune(HashSet<long> connected, float now)
		{
			string[] array = (from p in sources
				where !connected.Contains(p.Value.Source.Peer) || now - p.Value.Heard > 1.25f
				select p.Key).ToArray();
			foreach (string key in array)
			{
				sources.Remove(key);
			}
			long[] array2 = batches.Keys.Where((long p) => !connected.Contains(p)).ToArray();
			foreach (long key2 in array2)
			{
				batches.Remove(key2);
			}
		}
	}
	internal static class BoomboxAuraSources
	{
		internal static readonly string[] Properties = (from index in Enumerable.Range(0, 5)
			select BoomboxAuraMath.CassetteProperty((BoomboxDecorationBonus)index)).ToArray();

		internal static List<BoomboxAuraSource> Local()
		{
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			List<BoomboxAuraSource> list = new List<BoomboxAuraSource>();
			BoomboxPlugin instance = BoomboxPlugin.Instance;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)instance == (Object)null || (Object)(object)localPlayer == (Object)null || !instance.AudioWorldActive)
			{
				return list;
			}
			foreach (AudioDeck value in instance.Decks.Values)
			{
				if ((Object)(object)value == (Object)null || value.State.owner != localPlayer.GetPlayerID() || value.Item == null)
				{
					continue;
				}
				DeckState state = value.State;
				BoomboxAuraSource obj = new BoomboxAuraSource
				{
					Id = state.id,
					Owner = state.owner,
					OwnerName = localPlayer.GetPlayerName(),
					Worn = state.onBack,
					Active = (!((Character)localPlayer).IsDead() && !((Character)localPlayer).IsTeleporting() && !value.StoragePaused && (value.IsActivePlayback || value.IsSyntheticActive)),
					Position = ((Component)value).transform.position
				};
				ItemDrop worldDrop = value.WorldDrop;
				ZDOID? obj2;
				if (worldDrop == null)
				{
					obj2 = null;
				}
				else
				{
					ZNetView component = ((Component)worldDrop).GetComponent<ZNetView>();
					obj2 = ((component == null) ? ((ZDOID?)null) : component.GetZDO()?.m_uid);
				}
				obj.WorldItem = (ZDOID)(((??)obj2) ?? ZDOID.None);
				BoomboxAuraSource boomboxAuraSource = obj;
				boomboxAuraSource.Active &= instance.Settings.LocalGameplayEnabled;
				for (int i = 0; i < 5; i++)
				{
					boomboxAuraSource.Ranks[i] = state.insertedCassette?.Rank(Properties[i]) ?? 0;
				}
				HashSet<int> hashSet = new HashSet<int>();
				foreach (BoomboxDecorationPlacement decorationPlacement in state.decorationPlacements)
				{
					if (decorationPlacement != null && hashSet.Add(decorationPlacement.slot) && state.unlockedDecorationSlots.Contains(decorationPlacement.slot) && state.unlockedDecorationPrefabs.Contains(decorationPlacement.prefab) && BoomboxDecorationCatalog.DataEligible(decorationPlacement.prefab, decorationPlacement.slot))
					{
						boomboxAuraSource.Decorations.Add(new BoomboxAuraDecoration
						{
							Slot = decorationPlacement.slot,
							Prefab = decorationPlacement.prefab,
							Effect = (int)BoomboxDecorationCatalog.DataEffect(decorationPlacement.prefab, decorationPlacement.effect, BoomboxAuraNetwork.Balance?.Fallback ?? instance.Settings.DecorationModFallback.Value)
						});
					}
				}
				list.Add(boomboxAuraSource);
			}
			return list;
		}

		internal static bool Identity(ZNetPeer peer, out ZDO character, out long player, out string name)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			object obj;
			if (peer == null || !peer.IsReady())
			{
				obj = null;
			}
			else
			{
				ZDOMan instance = ZDOMan.instance;
				obj = ((instance != null) ? instance.GetZDO(peer.m_characterID) : null);
			}
			character = (ZDO)obj;
			ZDO obj2 = character;
			player = ((obj2 != null) ? obj2.GetLong(ZDOVars.s_playerID, 0L) : 0);
			ZDO obj3 = character;
			name = ((obj3 != null) ? obj3.GetString(ZDOVars.s_playerName, "") : null) ?? "";
			if (player != 0L && character != null)
			{
				return character.GetOwner() == peer.m_uid;
			}
			return false;
		}

		internal static bool ValidateAndNormalize(BoomboxAuraSource source, long peer, long player, string name, ZDO character)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Expected O, but got Unknown
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			if (source.Owner != player || source.Id == null || character == null || source.Decorations.Any((BoomboxAuraDecoration d) => !BoomboxDecorationCatalog.DataEligible(d.Prefab, d.Slot)))
			{
				return false;
			}
			source.Peer = peer;
			source.OwnerName = ((name.Length > 128) ? name.Substring(0, 128) : name);
			source.Active &= !character.GetBool(ZDOVars.s_dead, false);
			if (source.Worn)
			{
				if (!((ZDOID)(ref source.WorldItem)).IsNone())
				{
					return false;
				}
				return Vector3.Distance(source.Position, character.GetPosition()) <= 5f;
			}
			if (((ZDOID)(ref source.WorldItem)).IsNone())
			{
				return !source.Active;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(source.WorldItem);
			if (zDO == null || zDO.GetPrefab() != StringExtensionMethods.GetStableHashCode("ValheimBoombox_Item"))
			{
				return false;
			}
			ItemData val = new ItemData();
			ZNetScene instance = ZNetScene.instance;
			val.m_dropPrefab = ((instance != null) ? instance.GetPrefab("ValheimBoombox_Item") : null);
			ItemData val2 = val;
			ItemDrop.LoadFromZDO(val2, zDO, -1);
			DeckState saved = BoomboxItemData.Read(val2).deck;
			if (saved.id != source.Id || (saved.owner != player && zDO.GetOwner() != peer))
			{
				return false;
			}
			source.Position = zDO.GetPosition();
			for (int num = 0; num < 5; num++)
			{
				if (source.Ranks[num] != (saved.insertedCassette?.Rank(Properties[num]) ?? 0))
				{
					return false;
				}
			}
			BoomboxDecorationPlacement[] placements = saved.decorationPlacements.Where((BoomboxDecorationPlacement p) => p != null && saved.unlockedDecorationSlots.Contains(p.slot) && saved.unlockedDecorationPrefabs.Contains(p.prefab) && BoomboxDecorationCatalog.DataEligible(p.prefab, p.slot)).ToArray();
			if (placements.Length == source.Decorations.Count)
			{
				return source.Decorations.All((BoomboxAuraDecoration d) => placements.Any((BoomboxDecorationPlacement p) => p.slot == d.Slot && p.prefab == d.Prefab && BoomboxDecorationCatalog.DataEffect(p.prefab, p.effect, BoomboxAuraNetwork.Balance.Fallback) == (BoomboxDecorationBonus)d.Effect));
			}
			return false;
		}
	}
	public sealed class BoomboxAuraStatus : SE_Stats
	{
		public int BonusIndex;

		public float BonusValue;

		public float BonusPeriod;

		public int BonusSources;

		public override string GetTooltipString()
		{
			return ((StatusEffect)this).m_tooltip;
		}
	}
	internal static class BoomboxAuraHud
	{
		private static readonly BoomboxAuraStatus[] templates = new BoomboxAuraStatus[5];

		private static readonly string[] colors = new string[5] { "Red", "Green", "Yellow", "Blue", "Purple" };

		internal static string Name(int index)
		{
			return BoomboxText.Get((new string[5] { "text.wave_damage", "text.health", "text.stamina", "text.eitr", "text.speed" })[index]);
		}

		internal static string Number(float value, int index)
		{
			return (value * ((index == 0 || index == 4) ? 100f : 1f)).ToString("0.##", BoomboxText.Culture) + ((index == 0 || index == 4) ? " %" : "");
		}

		internal static void Present(Player player, BoomboxAuraAggregate aggregate, BoomboxAuraBalance balance)
		{
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan == null)
			{
				return;
			}
			int index;
			for (index = 0; index < 5; index++)
			{
				int stableHashCode = StringExtensionMethods.GetStableHashCode(Key(index));
				if (aggregate.Values[index] <= 0f)
				{
					sEMan.RemoveStatusEffect(stableHashCode, true);
					continue;
				}
				BoomboxAuraStatus boomboxAuraStatus = sEMan.GetStatusEffect(stableHashCode) as BoomboxAuraStatus;
				if ((Object)(object)boomboxAuraStatus == (Object)null)
				{
					if ((Object)(object)templates[index] == (Object)null)
					{
						templates[index] = ScriptableObject.CreateInstance<BoomboxAuraStatus>();
						((Object)templates[index]).name = Key(index);
					}
					ObjectDB instance = ObjectDB.instance;
					GameObject val = ((instance != null) ? instance.GetItemPrefab("ValheimBoombox_Cassette" + colors[index]) : null);
					BoomboxAuraStatus obj = templates[index];
					object icon;
					if (!VikingBoomboxVisual.CanRender)
					{
						if (val == null)
						{
							icon = null;
						}
						else
						{
							ItemDrop component = val.GetComponent<ItemDrop>();
							icon = ((component != null) ? component.m_itemData.GetIcon() : null);
						}
					}
					else
					{
						icon = BoomboxBonusLook.Icon(index);
					}
					((StatusEffect)obj).m_icon = (Sprite)icon;
					boomboxAuraStatus = sEMan.AddStatusEffect((StatusEffect)(object)templates[index], false, 0, 0f, (short)0) as BoomboxAuraStatus;
				}
				if ((Object)(object)boomboxAuraStatus == (Object)null)
				{
					continue;
				}
				boomboxAuraStatus.BonusIndex = index;
				boomboxAuraStatus.BonusValue = aggregate.Values[index];
				boomboxAuraStatus.BonusSources = aggregate.Contributions.Count((BoomboxAuraContribution c) => c.Values[index] > 0f);
				boomboxAuraStatus.BonusPeriod = ((index > 0 && index < 4) ? balance.Period[index - 1] : 0f);
				((StatusEffect)boomboxAuraStatus).m_name = Name(index);
				string text = ((index > 0 && index < 4) ? (BoomboxText.Get("text.every_2") + balance.Period[index - 1].ToString("0.##", BoomboxText.Culture) + BoomboxText.Get("text.s")) : "");
				((StatusEffect)boomboxAuraStatus).m_tooltip = Name(index) + ": +" + Number(aggregate.Values[index], index) + text;
				BoomboxAuraStatus boomboxAuraStatus2 = boomboxAuraStatus;
				((StatusEffect)boomboxAuraStatus2).m_tooltip = ((StatusEffect)boomboxAuraStatus2).m_tooltip + BoomboxText.Get("text.source_boomboxes") + boomboxAuraStatus.BonusSources;
				foreach (IGrouping<long, BoomboxAuraContribution> item in from c in aggregate.Contributions
					where c.Values[index] > 0f
					group c by c.Owner)
				{
					BoomboxAuraStatus boomboxAuraStatus3 = boomboxAuraStatus;
					((StatusEffect)boomboxAuraStatus3).m_tooltip = ((StatusEffect)boomboxAuraStatus3).m_tooltip + "\n" + Clean(item.First().OwnerName) + ": +" + Number(item.Sum((BoomboxAuraContribution c) => c.Values[index]), index);
				}
				((SE_Stats)boomboxAuraStatus).m_speedModifier = ((index == 4) ? aggregate.Values[index] : 0f);
			}
		}

		internal static void Remove(Player player)
		{
			if (!((Object)(object)player != (Object)null))
			{
				return;
			}
			for (int i = 0; i < 5; i++)
			{
				SEMan sEMan = ((Character)player).GetSEMan();
				if (sEMan != null)
				{
					sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode(Key(i)), true);
				}
			}
		}

		internal static void Clear(Player player)
		{
			Remove(player);
			for (int i = 0; i < 5; i++)
			{
				if ((Object)(object)templates[i] != (Object)null)
				{
					Object.Destroy((Object)(object)templates[i]);
				}
				templates[i] = null;
			}
		}

		private static string Key(int index)
		{
			return "ValheimBoombox_Shared_" + colors[index];
		}

		private static string Clean(string text)
		{
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text.Replace("<", "").Replace(">", "").Replace("\n", " ")
					.Replace("\r", " ");
			}
			return BoomboxText.Get("text.player");
		}
	}
	internal static class BoomboxBoatDrag
	{
		private static readonly FieldRef<Ship, float> LastDepth = AccessTools.FieldRefAccess<Ship, float>("m_lastDepth");

		private static readonly float[] losses = new float[25];

		private static Ship ship;

		private static Rigidbody body;

		private static int count;

		private static int next;

		internal static void Step(Ship current)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)current != (Object)(object)ship)
			{
				ship = current;
				body = (((Object)(object)current == (Object)null) ? null : ((Component)current).GetComponent<Rigidbody>());
				count = 0;
				next = 0;
			}
			if (!((Object)(object)ship == (Object)null) && !((Object)(object)body == (Object)null))
			{
				float num = Vector3.Dot(body.linearVelocity, ((Component)ship).transform.forward);
				float num2 = Mathf.Clamp01(Mathf.Abs(LastDepth.Invoke(ship)) / ship.m_forceDistance);
				losses[next] = ship.m_dampingForward * num * num * num2;
				next = (next + 1) % losses.Length;
				count = Mathf.Min(count + 1, losses.Length);
			}
		}

		internal static float Take(Ship current)
		{
			if ((Object)(object)current != (Object)(object)ship)
			{
				return 0f;
			}
			float num = 0f;
			for (int i = 1; i <= count; i++)
			{
				num += losses[(next - i + losses.Length) % losses.Length];
			}
			count = 0;
			return num;
		}
	}
	[DefaultExecutionOrder(10001)]
	public sealed class BoomboxBoatHelp : MonoBehaviour
	{
		internal const float DragShare = 0.203f;

		internal const int MaximumSteps = 25;

		internal const float MovingSpeed = 0.5f;

		private AudioDeck deck;

		private BoomboxBoatWave wave;

		private bool waveFailed;

		private int beats;

		internal void Initialize(AudioDeck owner)
		{
			deck = owner;
			beats = owner.EmittedWaves;
		}

		internal static bool TryPush(Ship ship)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_0088: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ship == (Object)null || (int)ship.GetSpeedSetting() == 0)
			{
				return false;
			}
			ZNetView component = ((Component)ship).GetComponent<ZNetView>();
			Rigidbody component2 = ((Component)ship).GetComponent<Rigidbody>();
			if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner() || (Object)(object)component2 == (Object)null)
			{
				return false;
			}
			Vector3 linearVelocity = component2.linearVelocity;
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(linearVelocity.x, 0f, linearVelocity.z);
			if (((Vector3)(ref val)).magnitude < 0.5f)
			{
				return false;
			}
			component2.AddForce(((Vector3)(ref val)).normalized * (0.203f * BoomboxBoatDrag.Take(ship)), (ForceMode)2);
			return true;
		}

		private void LateUpdate()
		{
			if ((Object)(object)deck == (Object)null || deck.State == null)
			{
				return;
			}
			bool flag = deck.EmittedWaves != beats;
			beats = deck.EmittedWaves;
			bool worn;
			Ship val = HelpedShip(out worn);
			if ((Object)(object)val == (Object)null)
			{
				wave?.Clear();
				return;
			}
			bool pushed = flag && TryPush(val);
			if (!BoomboxPlugin.Instance.Settings.ShowVisualEffects.Value)
			{
				wave?.Clear();
			}
			else
			{
				if (waveFailed || (Object)(object)deck.Visual == (Object)null)
				{
					return;
				}
				try
				{
					if (wave == null)
					{
						wave = new BoomboxBoatWave();
					}
					wave.Present(deck, val, pushed);
				}
				catch (Exception ex)
				{
					waveFailed = true;
					wave?.Destroy();
					wave = null;
					BoomboxPlugin.Instance?.LogError(BoomboxText.Get("text.boombox_sail_wave_is_not_shown_boat_impulses_still_work") + ex.Message);
				}
			}
		}

		private void FixedUpdate()
		{
			bool worn;
			Ship current = HelpedShip(out worn);
			if (worn)
			{
				BoomboxBoatDrag.Step(current);
			}
		}

		private Ship HelpedShip(out bool worn)
		{
			Player localPlayer = Player.m_localPlayer;
			BoomboxSettings boomboxSettings = BoomboxPlugin.Instance?.Settings;
			worn = (Object)(object)deck != (Object)null && deck.State != null && (Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && deck.Item != null && deck.State.onBack && deck.Item == BoomboxEquipment.