Decompiled source of Earendil Ship v2.4.0

EarendilShip.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Profiling;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace EarendilShip;

[BepInPlugin("goyboys.earendilship", "Earendil Ship", "2.4.0")]
public class EarendilPlugin : BaseUnityPlugin
{
	private class SavedMaterial
	{
		public Material mat;

		public Dictionary<string, Color> colors = new Dictionary<string, Color>();

		public Dictionary<string, Color> colorTargets = new Dictionary<string, Color>();

		public Dictionary<string, float> floats = new Dictionary<string, float>();

		public Dictionary<string, float> floatTargets = new Dictionary<string, float>();
	}

	private class ShipEffects
	{
		public List<SavedMaterial> materials = new List<SavedMaterial>();

		public GameObject lightObj;

		public Light light;

		public GameObject shieldObj;

		public ZNetView shieldView;

		public ShieldGenerator shield;

		public GameObject demisterObj;

		public ParticleSystemForceField mistField;

		public AudioSource audio;

		public bool fading = true;

		public bool audioFading = true;
	}

	private class Chord
	{
		public ConfigEntry<KeyboardShortcut> entry;

		public KeyCode main;

		public bool[] wanted;

		public Chord(ConfigEntry<KeyboardShortcut> e)
		{
			entry = e;
			wanted = new bool[ModifierKeys.Length];
			Refresh();
		}

		public void Refresh()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between I4 and Unknown
			KeyboardShortcut value = entry.Value;
			main = ((KeyboardShortcut)(ref value)).MainKey;
			for (int i = 0; i < wanted.Length; i++)
			{
				wanted[i] = false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref value)).Modifiers)
			{
				for (int j = 0; j < ModifierKeys.Length; j++)
				{
					if ((int)ModifierKeys[j] == (int)modifier)
					{
						wanted[j] = true;
					}
				}
			}
		}
	}

	private class PerfCounter
	{
		public string name;

		public long ticks;

		public long calls;

		public long worst;

		public PerfCounter(string n)
		{
			name = n;
		}
	}

	public const string GUID = "goyboys.earendilship";

	public const string NAME = "Earendil Ship";

	public const string VERSION = "2.4.0";

	private const string KeyActive = "earendil_active";

	private const string KeyStartMs = "earendil_start_ms";

	private const string KeyMuted = "earendil_muted";

	private const string KeyShield = "earendil_shield";

	private const bool ShieldDefault = false;

	private const bool MistDefault = true;

	private const string KeyMist = "earendil_mist";

	private const string SpecProp = "_SpecColor";

	private const float CarrySaneSpeed = 200f;

	internal static ManualLogSource Log;

	internal static ConfigEntry<KeyboardShortcut> ToggleKey;

	internal static ConfigEntry<KeyboardShortcut> MuteKey;

	internal static ConfigEntry<KeyCode> AscendKey;

	internal static ConfigEntry<KeyCode> DescendKey;

	internal static ConfigEntry<KeyboardShortcut> ShieldKey;

	internal static ConfigEntry<KeyboardShortcut> MistKey;

	internal static ConfigEntry<float> FlySpeed;

	internal static ConfigEntry<float> ClimbSpeed;

	internal static ConfigEntry<float> TurnSpeed;

	internal static ConfigEntry<float> Acceleration;

	internal static ConfigEntry<float> GlowIntensity;

	internal static ConfigEntry<float> LightIntensity;

	internal static ConfigEntry<float> LightRange;

	internal static ConfigEntry<float> Shine;

	internal static ConfigEntry<float> FadeSeconds;

	internal static ConfigEntry<bool> PlayAudio;

	internal static ConfigEntry<string> AudioFile;

	internal static ConfigEntry<float> Volume;

	internal static ConfigEntry<float> MusicFadeSeconds;

	internal static ConfigEntry<bool> LoopAudio;

	internal static ConfigEntry<bool> AudioFromShip;

	internal static ConfigEntry<bool> AllowPassengerJump;

	internal static ConfigEntry<bool> EnableShield;

	internal static ConfigEntry<float> ShieldRadius;

	internal static ConfigEntry<float> ShieldMargin;

	internal static ConfigEntry<string> ShieldPrefab;

	internal static ConfigEntry<float> ShieldHeight;

	internal static ConfigEntry<bool> EnableMist;

	internal static ConfigEntry<float> MistRadius;

	internal static ConfigEntry<bool> LogPerformance;

	internal static ConfigEntry<float> PerformanceInterval;

	private static EarendilPlugin instance;

	private static Action<Ship, float> updateSailSize;

	private static AudioClip clip;

	private static bool clipLoading;

	private static readonly string[] DefaultAudioNames = new string[3] { "earendil.ogg", "earendil.wav", "earendil.mp3" };

	private static readonly string[] TintProps = new string[2] { "_Color", "_TintColor" };

	private static readonly string[] EmissionProps = new string[2] { "_EmissionColor", "_EmissiveColor" };

	private static readonly string[] GlossProps = new string[2] { "_Glossiness", "_GlossMapScale" };

	private static Dictionary<Ship, ShipEffects> effects = new Dictionary<Ship, ShipEffects>();

	private static HashSet<Ship> gravityOff = new HashSet<Ship>();

	private static Ship carryShip;

	private static Vector3 carryAnchorLocal;

	private static Vector3 carryAnchorWorld;

	private static Vector3 carryLastVel;

	private static readonly string[] ShieldRpcs = new string[4] { "RPC_AddFuel", "RPC_SetFuel", "RPC_Attack", "RPC_HitNow" };

	private static GameObject shieldPrefab;

	private static bool shieldPrefabMissing;

	private static readonly HashSet<ShieldGenerator> ourShields = new HashSet<ShieldGenerator>();

	private static readonly KeyCode[] ModifierKeys = (KeyCode[])(object)new KeyCode[8]
	{
		(KeyCode)304,
		(KeyCode)303,
		(KeyCode)306,
		(KeyCode)305,
		(KeyCode)308,
		(KeyCode)307,
		(KeyCode)310,
		(KeyCode)309
	};

	private static Chord toggleChord;

	private static Chord muteChord;

	private static Chord shieldChord;

	private static Chord mistChord;

	private static bool perfEnabled;

	private static readonly PerfCounter perfShipTick = new PerfCounter("Ship tick postfix");

	private static readonly PerfCounter perfSail = new PerfCounter("Sail prefix");

	private static readonly PerfCounter perfControls = new PerfCounter("Controls prefix");

	private static readonly PerfCounter perfCarry = new PerfCounter("Character tick postfix");

	private static readonly PerfCounter perfUpdate = new PerfCounter("Plugin Update");

	private static float perfWindowStart;

	private static long perfHeapStart;

	private static int perfGen0Start;

	private void Awake()
	{
		//IL_0029: 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_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0501: Unknown result type (might be due to invalid IL or missing references)
		//IL_0507: Expected O, but got Unknown
		//IL_0554: Unknown result type (might be due to invalid IL or missing references)
		//IL_0561: Expected O, but got Unknown
		//IL_05ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_05c8: Expected O, but got Unknown
		//IL_07c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_07d3: Expected O, but got Unknown
		//IL_0622: Unknown result type (might be due to invalid IL or missing references)
		//IL_062f: Expected O, but got Unknown
		//IL_068c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0699: Expected O, but got Unknown
		//IL_06f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0703: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		instance = this;
		ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("1. Keys", "Toggle", new KeyboardShortcut((KeyCode)106, (KeyCode[])(object)new KeyCode[0]), "Toggles flight on the ship whose helm you hold.");
		MuteKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("1. Keys", "Mute music", new KeyboardShortcut((KeyCode)106, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Mutes or unmutes the ship's music for everyone, from the helm.");
		ShieldKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("1. Keys", "Shield", new KeyboardShortcut((KeyCode)106, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Raises or drops the shield dome on the ship whose helm you hold.");
		MistKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("1. Keys", "Mist clearing", new KeyboardShortcut((KeyCode)106, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Turns the ship's mist clearing on or off, from the helm. Note this shares a modifier with the default descend key, so descending while pressing the base key toggles it.");
		AscendKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("1. Keys", "Ascend", (KeyCode)32, "Hold at the helm to climb. Jump is suppressed at the helm of a flying ship so this does not release it.");
		DescendKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("1. Keys", "Descend", (KeyCode)306, "Hold at the helm to descend. Crouch is suppressed at the helm of a flying ship.");
		FlySpeed = ((BaseUnityPlugin)this).Config.Bind<float>("2. Flight", "Flight speed", 25f, "Metres per second at Full sail.");
		ClimbSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("2. Flight", "Climb speed", 6f, "Metres per second while holding ascend or descend.");
		TurnSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("2. Flight", "Turn speed", 45f, "Degrees per second at full rudder.");
		Acceleration = ((BaseUnityPlugin)this).Config.Bind<float>("2. Flight", "Acceleration seconds", 6f, "Seconds to build from standstill to full flight speed, and to slow back down. 0 = instant.");
		GlowIntensity = ((BaseUnityPlugin)this).Config.Bind<float>("3. Light", "Glow intensity", 0.7f, "Emission strength on the hull and sail.");
		LightIntensity = ((BaseUnityPlugin)this).Config.Bind<float>("3. Light", "Light intensity", 1.2f, "Brightness of the white point light carried by the ship.");
		LightRange = ((BaseUnityPlugin)this).Config.Bind<float>("3. Light", "Light range", 40f, "Radius the ship lights up around itself.");
		Shine = ((BaseUnityPlugin)this).Config.Bind<float>("3. Light", "Shine", 0f, "Surface smoothness while flying, 0 to 1. 0 leaves materials untouched. Higher values look glossier but cause sparkly, spotty highlights on detailed textures.");
		FadeSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("3. Light", "Fade-in seconds", 2.5f, "How long the glow and light take to reach full strength when flight starts. 0 = instant.");
		PlayAudio = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Audio", "Play audio", true, "Play an audio file while a ship is flying.");
		AudioFile = ((BaseUnityPlugin)this).Config.Bind<string>("4. Audio", "Audio file", "", "Audio file to play. Absolute path, or a filename next to this plugin. Leave empty to auto-detect earendil.ogg / .wav / .mp3 in BepInEx/EarendilShip, then in the plugin folder. OGG is the most reliable format.");
		Volume = ((BaseUnityPlugin)this).Config.Bind<float>("4. Audio", "Volume", 0.4f, "Playback volume, 0 to 1.");
		MusicFadeSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("4. Audio", "Music fade-in seconds", 3f, "How long the music takes to reach full volume when flight starts. 0 = instant.");
		LoopAudio = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Audio", "Loop", true, "Repeat until flight is switched off.");
		AudioFromShip = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Audio", "Play from ship", true, "Positional audio emitted by the ship. Disable for flat non-positional playback.");
		AllowPassengerJump = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Flight", "Passengers may jump", true, "Let everyone aboard a flying ship jump. The ship's velocity is carried under them while they are in the air, so a jump does not leave the deck behind. The captain never jumps: at the helm that key ascends and would drop the rudder.");
		EnableShield = ((BaseUnityPlugin)this).Config.Bind<bool>("6. Shield", "Shield", true, "Allow a shield generator dome over a flying ship, held at full power without fuel. The dome starts down and is raised from the helm with the shield key.");
		ShieldRadius = ((BaseUnityPlugin)this).Config.Bind<float>("6. Shield", "Shield radius", 0f, "Dome radius in metres. 0 fits it to the hull: the ship's own half-length plus the margin below, so it covers the deck and no more.");
		ShieldMargin = ((BaseUnityPlugin)this).Config.Bind<float>("6. Shield", "Shield margin", 4f, "Metres of dome beyond the hull when the radius is fitted automatically.");
		ShieldPrefab = ((BaseUnityPlugin)this).Config.Bind<string>("6. Shield", "Shield prefab", "", "Name of the prefab to take the dome from. Empty picks one automatically. Every candidate found is listed in the log at startup.");
		ShieldHeight = ((BaseUnityPlugin)this).Config.Bind<float>("6. Shield", "Shield height", 2f, "Height of the dome's centre above the ship, in metres.");
		EnableMist = ((BaseUnityPlugin)this).Config.Bind<bool>("7. Mist", "Clear mist", true, "Clear Mistlands mist around a flying ship.");
		MistRadius = ((BaseUnityPlugin)this).Config.Bind<float>("7. Mist", "Mist clearing radius", 70f, "Radius cleared, in metres. A wisplight clears 20, so this is three and a half of them.");
		LogPerformance = ((BaseUnityPlugin)this).Config.Bind<bool>("5. Diagnostics", "Log performance", false, "Periodically log the CPU time and memory used by this mod.");
		PerformanceInterval = ((BaseUnityPlugin)this).Config.Bind<float>("5. Diagnostics", "Performance log interval", 10f, "Seconds between performance log lines.");
		toggleChord = new Chord(ToggleKey);
		muteChord = new Chord(MuteKey);
		shieldChord = new Chord(ShieldKey);
		mistChord = new Chord(MistKey);
		((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged;
		perfEnabled = LogPerformance.Value;
		ResetPerfWindow();
		try
		{
			Harmony val = new Harmony("goyboys.earendilship");
			MethodInfo methodInfo = AccessTools.Method(typeof(Ship), "CustomFixedUpdate", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Log.LogError((object)"Ship.CustomFixedUpdate not found. The mod is inactive.");
				return;
			}
			val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(EarendilPlugin), "ShipUpdatePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log.LogInfo((object)"Patched Ship.CustomFixedUpdate for flight and effects.");
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "SetControls", (Type[])null, (Type[])null);
			if (methodInfo2 == null)
			{
				Log.LogWarning((object)"Player.SetControls not found. Ascend will also release the helm.");
			}
			else
			{
				val.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.Method(typeof(EarendilPlugin), "PlayerControlsPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Patched Player.SetControls to free ascend/descend keys.");
			}
			MethodInfo methodInfo3 = AccessTools.Method(typeof(Character), "CustomFixedUpdate", (Type[])null, (Type[])null);
			if (methodInfo3 == null)
			{
				Log.LogWarning((object)"Character.CustomFixedUpdate not found. Jumping aboard will throw you off.");
			}
			else
			{
				val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(EarendilPlugin), "CharacterTickPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Patched Character.CustomFixedUpdate to carry jumping passengers.");
			}
			MethodInfo methodInfo4 = AccessTools.Method(typeof(Character), "ApplyGroundForce", (Type[])null, (Type[])null);
			if (methodInfo4 == null)
			{
				Log.LogWarning((object)"Character.ApplyGroundForce not found. Steering a jump aboard will drag you off.");
			}
			else
			{
				val.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(EarendilPlugin), "GroundForcePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Patched Character.ApplyGroundForce to steer jumps in the ship's frame.");
			}
			MethodInfo methodInfo5 = AccessTools.Method(typeof(ShieldGenerator), "Update", (Type[])null, (Type[])null);
			if (methodInfo5 == null)
			{
				Log.LogWarning((object)"ShieldGenerator.Update not found. The dome will not follow the ship.");
			}
			else
			{
				val.Patch((MethodBase)methodInfo5, new HarmonyMethod(AccessTools.Method(typeof(EarendilPlugin), "ShieldUpdatePrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Patched ShieldGenerator.Update to keep the dome with the ship.");
			}
			MethodInfo methodInfo6 = AccessTools.Method(typeof(Ship), "UpdateSailSize", (Type[])null, (Type[])null);
			if (methodInfo6 != null)
			{
				try
				{
					updateSailSize = (Action<Ship, float>)Delegate.CreateDelegate(typeof(Action<Ship, float>), null, methodInfo6);
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("Could not bind Ship.UpdateSailSize: " + ex.Message));
				}
			}
			MethodInfo methodInfo7 = AccessTools.Method(typeof(Ship), "UpdateSail", (Type[])null, (Type[])null);
			if (methodInfo7 == null || updateSailSize == null)
			{
				Log.LogWarning((object)"Ship sail internals not found. Sails will follow the helm while flying.");
				return;
			}
			val.Patch((MethodBase)methodInfo7, new HarmonyMethod(AccessTools.Method(typeof(EarendilPlugin), "UpdateSailPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log.LogInfo((object)"Patched Ship.UpdateSail for full, straight sails.");
		}
		catch (Exception ex2)
		{
			Log.LogError((object)("Harmony patch failed: " + ex2));
		}
	}

	private static void OnSettingChanged(object sender, SettingChangedEventArgs args)
	{
		toggleChord.Refresh();
		muteChord.Refresh();
		shieldChord.Refresh();
		mistChord.Refresh();
	}

	private void Update()
	{
		perfEnabled = LogPerformance.Value;
		long start = PerfBegin();
		try
		{
			PruneUnloadedShips();
			if (ChordDown(toggleChord))
			{
				Toggle();
			}
			if (ChordDown(muteChord))
			{
				ToggleMute();
			}
			if (ChordDown(shieldChord))
			{
				ToggleFlag("earendil_shield", fallback: false, "The shield closes over her.", "The shield opens.");
			}
			if (ChordDown(mistChord))
			{
				ToggleFlag("earendil_mist", fallback: true, "The mist draws back.", "The mist returns.");
			}
		}
		finally
		{
			PerfEnd(perfUpdate, start);
		}
		if (perfEnabled && Time.realtimeSinceStartup - perfWindowStart >= PerformanceInterval.Value)
		{
			ReportPerformance();
		}
	}

	private static ZNetView GetNetView(Ship ship)
	{
		ZNetView component = ((Component)ship).GetComponent<ZNetView>();
		if ((Object)(object)component == (Object)null || !component.IsValid())
		{
			return null;
		}
		return component;
	}

	private static bool IsFlying(Ship ship)
	{
		if ((Object)(object)ship == (Object)null)
		{
			return false;
		}
		ZNetView netView = GetNetView(ship);
		if ((Object)(object)netView != (Object)null)
		{
			return netView.GetZDO().GetBool("earendil_active", false);
		}
		return false;
	}

	private static ZDO TakeCommand()
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return null;
		}
		Ship controlledShip = localPlayer.GetControlledShip();
		if ((Object)(object)controlledShip == (Object)null)
		{
			return null;
		}
		ZNetView netView = GetNetView(controlledShip);
		if ((Object)(object)netView == (Object)null)
		{
			return null;
		}
		netView.ClaimOwnership();
		return netView.GetZDO();
	}

	private static void ToggleMute()
	{
		ZDO val = TakeCommand();
		if (val != null)
		{
			bool flag = !val.GetBool("earendil_muted", false);
			val.Set("earendil_muted", flag);
			Message(flag ? "The music is silenced." : "The music returns.");
		}
	}

	private static void Toggle()
	{
		ZDO val = TakeCommand();
		if (val != null)
		{
			bool flag = !val.GetBool("earendil_active", false);
			val.Set("earendil_active", flag);
			if (flag)
			{
				val.Set("earendil_start_ms", (long)(ZNet.instance.GetTimeSeconds() * 1000.0));
			}
			Message(flag ? "Earendil rises." : "The light fades.");
		}
	}

	private static bool ChordDown(Chord chord)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		if (chord == null)
		{
			return false;
		}
		if ((int)chord.main == 0 || !Input.GetKeyDown(chord.main))
		{
			return false;
		}
		for (int i = 0; i < ModifierKeys.Length; i++)
		{
			if (chord.wanted[i] != Input.GetKey(ModifierKeys[i]))
			{
				return false;
			}
		}
		return true;
	}

	private static void ToggleFlag(string key, bool fallback, string onMessage, string offMessage)
	{
		ZDO val = TakeCommand();
		if (val != null)
		{
			bool flag = !val.GetBool(key, fallback);
			val.Set(key, flag);
			Message(flag ? onMessage : offMessage);
		}
	}

	public static void CharacterTickPostfix(Character __instance, float dt)
	{
		if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
		{
			return;
		}
		long start = PerfBegin();
		try
		{
			CarryTick(dt);
		}
		finally
		{
			PerfEnd(perfCarry, start);
		}
	}

	private static void CarryTick(float dt)
	{
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: 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)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: 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_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_0127: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0160: 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_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)
		//IL_0171: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_017d: Unknown result type (might be due to invalid IL or missing references)
		Player localPlayer = Player.m_localPlayer;
		Ship standingOnShip = ((Character)localPlayer).GetStandingOnShip();
		bool flag = (Object)(object)standingOnShip != (Object)null && IsFlying(standingOnShip);
		if (flag && (Object)(object)carryShip != (Object)(object)standingOnShip)
		{
			carryShip = standingOnShip;
			carryAnchorLocal = ((Component)standingOnShip).transform.InverseTransformPoint(((Component)localPlayer).transform.position);
			carryAnchorWorld = ((Component)standingOnShip).transform.TransformPoint(carryAnchorLocal);
			carryLastVel = Vector3.zero;
		}
		else
		{
			if ((Object)(object)carryShip == (Object)null)
			{
				return;
			}
			if (!flag && (((Character)localPlayer).IsOnGround() || ((Character)localPlayer).IsAttached() || ((Character)localPlayer).InNumShipVolumes <= 0 || !IsFlying(carryShip)))
			{
				carryShip = null;
				carryLastVel = Vector3.zero;
				return;
			}
			Transform transform = ((Component)carryShip).transform;
			Vector3 val = transform.TransformPoint(carryAnchorLocal);
			Vector3 val2 = ((dt > 0f) ? ((val - carryAnchorWorld) / dt) : Vector3.zero);
			carryAnchorWorld = val;
			if (((Vector3)(ref val2)).magnitude > 200f)
			{
				carryLastVel = Vector3.zero;
				return;
			}
			if (flag)
			{
				carryAnchorLocal = transform.InverseTransformPoint(((Component)localPlayer).transform.position);
				carryAnchorWorld = transform.TransformPoint(carryAnchorLocal);
				carryLastVel = val2;
				return;
			}
			Rigidbody component = ((Component)localPlayer).GetComponent<Rigidbody>();
			if ((Object)(object)component == (Object)null)
			{
				carryShip = null;
				return;
			}
			component.linearVelocity += val2 - carryLastVel;
			carryLastVel = val2;
		}
	}

	public static void GroundForcePostfix(Character __instance, ref Vector3 vel)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_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)
		if (!((Object)(object)carryShip == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !__instance.IsOnGround())
		{
			vel += carryLastVel;
		}
	}

	private static void UpdateSubsystems(Ship ship, ShipEffects fx)
	{
		ZNetView netView = GetNetView(ship);
		ZDO val = (((Object)(object)netView != (Object)null) ? netView.GetZDO() : null);
		bool flag = EnableShield.Value && (val == null || val.GetBool("earendil_shield", false));
		if (flag && (Object)(object)fx.shieldObj == (Object)null)
		{
			CreateShield(ship, fx);
		}
		else if (!flag && (Object)(object)fx.shieldObj != (Object)null)
		{
			DestroyShield(fx);
		}
		bool flag2 = EnableMist.Value && (val == null || val.GetBool("earendil_mist", true));
		if (flag2 && (Object)(object)fx.demisterObj == (Object)null)
		{
			CreateDemister(ship, fx);
		}
		else if (!flag2 && (Object)(object)fx.demisterObj != (Object)null)
		{
			Object.Destroy((Object)(object)fx.demisterObj);
			fx.demisterObj = null;
			fx.mistField = null;
		}
		if ((Object)(object)fx.mistField != (Object)null && fx.mistField.endRange != MistRadius.Value)
		{
			fx.mistField.endRange = MistRadius.Value;
		}
	}

	private static bool IsUsableShield(ShieldGenerator shield)
	{
		if ((Object)(object)shield.m_shieldDome == (Object)null || (Object)(object)shield.m_energyParticlesFlare == (Object)null)
		{
			return false;
		}
		if (shield.m_energyParticles == null || shield.m_coloredLights == null)
		{
			return false;
		}
		for (int i = 0; i < shield.m_energyParticles.Length; i++)
		{
			if ((Object)(object)shield.m_energyParticles[i] == (Object)null)
			{
				return false;
			}
		}
		for (int j = 0; j < shield.m_coloredLights.Length; j++)
		{
			if ((Object)(object)shield.m_coloredLights[j] == (Object)null)
			{
				return false;
			}
		}
		return true;
	}

	private static GameObject FindShieldPrefab()
	{
		if ((Object)(object)shieldPrefab != (Object)null || shieldPrefabMissing)
		{
			return shieldPrefab;
		}
		ZNetScene val = ZNetScene.instance;
		if ((Object)(object)val == (Object)null || val.m_prefabs == null)
		{
			return null;
		}
		string value = ShieldPrefab.Value;
		GameObject val2 = null;
		List<string> list = new List<string>();
		for (int i = 0; i < val.m_prefabs.Count; i++)
		{
			GameObject val3 = val.m_prefabs[i];
			if ((Object)(object)val3 == (Object)null)
			{
				continue;
			}
			ShieldGenerator component = val3.GetComponent<ShieldGenerator>();
			if ((Object)(object)component == (Object)null)
			{
				continue;
			}
			bool flag = IsUsableShield(component);
			list.Add(((Object)val3).name + (flag ? "" : " (incomplete)"));
			if (!string.IsNullOrEmpty(value))
			{
				if (((Object)val3).name == value)
				{
					shieldPrefab = val3;
				}
			}
			else if (flag && (Object)(object)shieldPrefab == (Object)null)
			{
				shieldPrefab = val3;
			}
			else if ((Object)(object)val2 == (Object)null)
			{
				val2 = val3;
			}
		}
		if (list.Count == 0)
		{
			shieldPrefabMissing = true;
			Log.LogWarning((object)"No ShieldGenerator prefab found. The ship carries no shield.");
			return null;
		}
		Log.LogInfo((object)("Shield generator prefabs: " + string.Join(", ", list.ToArray()) + "."));
		if ((Object)(object)shieldPrefab == (Object)null)
		{
			if (!string.IsNullOrEmpty(value))
			{
				Log.LogWarning((object)("Configured shield prefab '" + value + "' was not found."));
			}
			shieldPrefab = val2;
		}
		if ((Object)(object)shieldPrefab == (Object)null)
		{
			shieldPrefabMissing = true;
			Log.LogWarning((object)"No usable ShieldGenerator prefab. The ship carries no shield.");
			return null;
		}
		Log.LogInfo((object)("Shield generator prefab: " + ((Object)shieldPrefab).name + "."));
		return shieldPrefab;
	}

	private static void CreateShield(Ship ship, ShipEffects fx)
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = FindShieldPrefab();
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		bool activeSelf = val.activeSelf;
		val.SetActive(false);
		GameObject val2;
		try
		{
			val2 = Object.Instantiate<GameObject>(val, ((Component)ship).transform, false);
		}
		finally
		{
			val.SetActive(activeSelf);
		}
		((Object)val2).name = "EarendilShield";
		val2.transform.localPosition = new Vector3(0f, ShieldHeight.Value, 0f);
		val2.transform.localRotation = Quaternion.identity;
		StripShieldClone(val2);
		ShieldGenerator component = val2.GetComponent<ShieldGenerator>();
		if ((Object)(object)component == (Object)null)
		{
			Object.Destroy((Object)(object)val2);
			return;
		}
		component.m_defaultFuel = component.m_maxFuel;
		component.m_fuelPerDamage = 0f;
		component.m_offWhenNoFuel = false;
		float num = ((ShieldRadius.Value > 0f) ? ShieldRadius.Value : FitRadius(ship));
		if (num > 0f)
		{
			component.m_maxShieldRadius = num;
			component.m_minShieldRadius = Mathf.Min(component.m_minShieldRadius, num);
		}
		HideShieldModel(val2, component);
		val2.SetActive(true);
		fx.shieldObj = val2;
		fx.shieldView = GetNetView(ship);
		fx.shield = component;
		ourShields.Add(component);
		Log.LogInfo((object)("Shield raised on " + ((Object)ship).name + " at radius " + component.m_maxShieldRadius + "."));
	}

	private static float FitRadius(Ship ship)
	{
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: 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_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		Transform transform = ((Component)ship).transform;
		float num = 0f;
		bool flag = false;
		Collider[] componentsInChildren = ((Component)ship).GetComponentsInChildren<Collider>(true);
		Vector3 val2 = default(Vector3);
		foreach (Collider val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null) && !val.isTrigger && val.enabled)
			{
				Bounds bounds = val.bounds;
				Vector3 center = ((Bounds)(ref bounds)).center;
				Vector3 extents = ((Bounds)(ref bounds)).extents;
				for (int j = 0; j < 8; j++)
				{
					((Vector3)(ref val2))..ctor(center.x + (((j & 1) == 0) ? (0f - extents.x) : extents.x), center.y + (((j & 2) == 0) ? (0f - extents.y) : extents.y), center.z + (((j & 4) == 0) ? (0f - extents.z) : extents.z));
					Vector3 val3 = transform.InverseTransformPoint(val2);
					num = Mathf.Max(num, Mathf.Abs(val3.x));
					num = Mathf.Max(num, Mathf.Abs(val3.z));
					flag = true;
				}
			}
		}
		if (!flag)
		{
			return 0f;
		}
		return num + ShieldMargin.Value;
	}

	private static void HideShieldModel(GameObject clone, ShieldGenerator shield)
	{
		Transform val = (((Object)(object)shield.m_shieldDome != (Object)null) ? shield.m_shieldDome.transform : null);
		if ((Object)(object)val != (Object)null && !val.IsChildOf(clone.transform))
		{
			val = null;
		}
		Renderer[] componentsInChildren = clone.GetComponentsInChildren<Renderer>(true);
		for (int i = 0; i < componentsInChildren.Length; i++)
		{
			if ((Object)(object)val == (Object)null || !((Component)componentsInChildren[i]).transform.IsChildOf(val))
			{
				componentsInChildren[i].enabled = false;
			}
		}
		Light[] componentsInChildren2 = clone.GetComponentsInChildren<Light>(true);
		for (int j = 0; j < componentsInChildren2.Length; j++)
		{
			if ((Object)(object)val == (Object)null || !((Component)componentsInChildren2[j]).transform.IsChildOf(val))
			{
				((Behaviour)componentsInChildren2[j]).enabled = false;
			}
		}
		AudioSource[] componentsInChildren3 = clone.GetComponentsInChildren<AudioSource>(true);
		for (int k = 0; k < componentsInChildren3.Length; k++)
		{
			if ((Object)(object)val == (Object)null || !((Component)componentsInChildren3[k]).transform.IsChildOf(val))
			{
				((Behaviour)componentsInChildren3[k]).enabled = false;
			}
		}
		Log.LogInfo((object)("Shield model hidden" + (((Object)(object)val != (Object)null) ? ", dome kept." : "; no dome of its own.")));
	}

	public static void ShieldUpdatePrefix(ShieldGenerator __instance, ref float ___m_radiusSent)
	{
		if (ourShields.Count > 0 && ourShields.Contains(__instance))
		{
			___m_radiusSent = float.MinValue;
		}
	}

	private static void DestroyShield(ShipEffects fx)
	{
		if ((Object)(object)fx.shieldObj != (Object)null)
		{
			Object.Destroy((Object)(object)fx.shieldObj);
			fx.shieldObj = null;
		}
		if ((Object)(object)fx.shield != (Object)null)
		{
			ourShields.Remove(fx.shield);
			fx.shield = null;
		}
		if ((Object)(object)fx.shieldView != (Object)null && fx.shieldView.IsValid())
		{
			for (int i = 0; i < ShieldRpcs.Length; i++)
			{
				fx.shieldView.Unregister(ShieldRpcs[i]);
			}
		}
		fx.shieldView = null;
	}

	private static void StripShieldClone(GameObject clone)
	{
		Collider[] componentsInChildren = clone.GetComponentsInChildren<Collider>(true);
		for (int i = 0; i < componentsInChildren.Length; i++)
		{
			componentsInChildren[i].enabled = false;
		}
		StripComponents((Component[])(object)clone.GetComponentsInChildren<ZNetView>(true));
		StripComponents((Component[])(object)clone.GetComponentsInChildren<ZSyncTransform>(true));
		StripComponents((Component[])(object)clone.GetComponentsInChildren<Piece>(true));
		StripComponents((Component[])(object)clone.GetComponentsInChildren<WearNTear>(true));
		StripComponents((Component[])(object)clone.GetComponentsInChildren<Destructible>(true));
		StripComponents((Component[])(object)clone.GetComponentsInChildren<StaticPhysics>(true));
	}

	private static void StripComponents(Component[] components)
	{
		for (int i = 0; i < components.Length; i++)
		{
			if ((Object)(object)components[i] != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)components[i]);
			}
		}
	}

	private static void CreateDemister(Ship ship, ShipEffects fx)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject("EarendilDemister");
		val.transform.SetParent(((Component)ship).transform, false);
		val.transform.localPosition = new Vector3(0f, 2f, 0f);
		ParticleSystemForceField val2 = val.AddComponent<ParticleSystemForceField>();
		val2.shape = (ParticleSystemForceFieldShape)0;
		val2.startRange = 0f;
		val2.endRange = MistRadius.Value;
		val2.gravity = new MinMaxCurve(-1f);
		val.AddComponent<Demister>();
		fx.demisterObj = val;
		fx.mistField = val2;
		Log.LogInfo((object)("Mist clearing on " + ((Object)ship).name + " at " + MistRadius.Value + " metres."));
	}

	public static void ShipUpdatePostfix(Ship __instance)
	{
		long start = PerfBegin();
		try
		{
			FlyTick(__instance);
		}
		finally
		{
			PerfEnd(perfShipTick, start);
		}
	}

	private static void FlyTick(Ship ship)
	{
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: 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_0105: Expected I4, but got Unknown
		//IL_00a5: 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_012f: 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_0158: 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)
		//IL_016b: Unknown result type (might be due to invalid IL or missing references)
		//IL_016d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_020e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0213: Unknown result type (might be due to invalid IL or missing references)
		//IL_0226: Unknown result type (might be due to invalid IL or missing references)
		//IL_022b: Unknown result type (might be due to invalid IL or missing references)
		//IL_022f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0234: Unknown result type (might be due to invalid IL or missing references)
		//IL_0241: Unknown result type (might be due to invalid IL or missing references)
		bool flag = IsFlying(ship);
		SyncEffects(ship, flag);
		Rigidbody component = ((Component)ship).GetComponent<Rigidbody>();
		ZNetView netView = GetNetView(ship);
		if ((Object)(object)component == (Object)null || (Object)(object)netView == (Object)null)
		{
			return;
		}
		if (!flag)
		{
			if (gravityOff.Remove(ship))
			{
				component.useGravity = true;
			}
			return;
		}
		Player localPlayer = Player.m_localPlayer;
		bool flag2 = (Object)(object)localPlayer != (Object)null && (Object)(object)localPlayer.GetControlledShip() == (Object)(object)ship;
		if (flag2 && !netView.IsOwner())
		{
			netView.ClaimOwnership();
		}
		if (!netView.IsOwner())
		{
			return;
		}
		component.useGravity = false;
		gravityOff.Add(ship);
		float y = 0f;
		if (flag2)
		{
			if (Input.GetKey(AscendKey.Value))
			{
				y = ClimbSpeed.Value;
			}
			else if (Input.GetKey(DescendKey.Value))
			{
				y = 0f - ClimbSpeed.Value;
			}
		}
		float num = 0f;
		Speed speedSetting = ship.GetSpeedSetting();
		switch (speedSetting - 1)
		{
		case 3:
			num = 1f;
			break;
		case 2:
			num = 0.6f;
			break;
		case 1:
			num = 0.3f;
			break;
		case 0:
			num = -0.3f;
			break;
		}
		Vector3 forward = ((Component)ship).transform.forward;
		forward.y = 0f;
		if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f)
		{
			((Vector3)(ref forward)).Normalize();
		}
		Vector3 linearVelocity = component.linearVelocity;
		linearVelocity.y = 0f;
		float num2 = Vector3.Dot(linearVelocity, forward);
		float num3 = num * FlySpeed.Value;
		float value = Acceleration.Value;
		float num4 = ((value <= 0f) ? num3 : Mathf.MoveTowards(num2, num3, FlySpeed.Value / value * Time.fixedDeltaTime));
		Vector3 linearVelocity2 = forward * num4;
		linearVelocity2.y = y;
		component.linearVelocity = linearVelocity2;
		float rudderValue = ship.GetRudderValue();
		component.angularVelocity = new Vector3(0f, rudderValue * TurnSpeed.Value * ((float)Math.PI / 180f), 0f);
		Quaternion val = Quaternion.Euler(0f, ((Component)ship).transform.eulerAngles.y, 0f);
		component.MoveRotation(Quaternion.Slerp(component.rotation, val, Time.fixedDeltaTime * 4f));
	}

	public static bool UpdateSailPrefix(Ship __instance, float __0, ref Speed ___m_speed)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Expected I4, but got Unknown
		//IL_0015: 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)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: 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_0075: Unknown result type (might be due to invalid IL or missing references)
		long start = PerfBegin();
		try
		{
			if (!IsFlying(__instance))
			{
				return true;
			}
			Speed val = ___m_speed;
			___m_speed = (Speed)4;
			try
			{
				updateSailSize(__instance, __0);
			}
			finally
			{
				___m_speed = (Speed)(int)val;
			}
			GameObject mastObject = __instance.m_mastObject;
			if ((Object)(object)mastObject != (Object)null)
			{
				Quaternion val2 = Quaternion.LookRotation(-((Component)__instance).transform.forward, ((Component)__instance).transform.up);
				mastObject.transform.rotation = Quaternion.RotateTowards(mastObject.transform.rotation, val2, 90f * __0);
			}
			return false;
		}
		finally
		{
			PerfEnd(perfSail, start);
		}
	}

	public static void PlayerControlsPrefix(Player __instance, ref bool jump, ref bool crouch)
	{
		long start = PerfBegin();
		try
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				if (IsFlying(__instance.GetControlledShip()))
				{
					jump = false;
					crouch = false;
				}
				else if (!AllowPassengerJump.Value && IsFlying(((Character)__instance).GetStandingOnShip()))
				{
					jump = false;
					crouch = false;
				}
			}
		}
		finally
		{
			PerfEnd(perfControls, start);
		}
	}

	private static void SyncEffects(Ship ship, bool flying)
	{
		bool flag = effects.TryGetValue(ship, out var value);
		if (flying && !flag)
		{
			value = ApplyEffects(ship);
			effects[ship] = value;
			flag = true;
		}
		else if (!flying && flag)
		{
			RemoveEffects(value);
			effects.Remove(ship);
			return;
		}
		if (!flying)
		{
			return;
		}
		UpdateSubsystems(ship, value);
		if (value.fading)
		{
			UpdateFade(ship, value);
		}
		if ((Object)(object)value.audio != (Object)null)
		{
			ZNetView netView = GetNetView(ship);
			value.audio.mute = (Object)(object)netView != (Object)null && netView.GetZDO().GetBool("earendil_muted", false);
			if (value.audioFading)
			{
				UpdateMusicFade(ship, value);
			}
		}
	}

	private static void UpdateMusicFade(Ship ship, ShipEffects fx)
	{
		float num = FadeProgress(ship, MusicFadeSeconds.Value);
		fx.audio.volume = Volume.Value * Ease(num);
		if (num >= 1f)
		{
			fx.audioFading = false;
		}
	}

	private static float Ease(float t)
	{
		return t * t * (3f - 2f * t);
	}

	private static float FadeProgress(Ship ship, float duration)
	{
		if (duration <= 0f || (Object)(object)ZNet.instance == (Object)null)
		{
			return 1f;
		}
		ZNetView netView = GetNetView(ship);
		if ((Object)(object)netView == (Object)null)
		{
			return 1f;
		}
		long num = netView.GetZDO().GetLong("earendil_start_ms", 0L);
		if (num <= 0)
		{
			return 1f;
		}
		double num2 = ZNet.instance.GetTimeSeconds() - (double)num / 1000.0;
		return Mathf.Clamp01((float)(num2 / (double)duration));
	}

	private static void UpdateFade(Ship ship, ShipEffects fx)
	{
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		float num = FadeProgress(ship, FadeSeconds.Value);
		float num2 = Ease(num);
		for (int i = 0; i < fx.materials.Count; i++)
		{
			SavedMaterial savedMaterial = fx.materials[i];
			if ((Object)(object)savedMaterial.mat == (Object)null)
			{
				continue;
			}
			foreach (KeyValuePair<string, Color> color in savedMaterial.colors)
			{
				savedMaterial.mat.SetColor(color.Key, Color.Lerp(color.Value, savedMaterial.colorTargets[color.Key], num2));
			}
			foreach (KeyValuePair<string, float> @float in savedMaterial.floats)
			{
				savedMaterial.mat.SetFloat(@float.Key, Mathf.Lerp(@float.Value, savedMaterial.floatTargets[@float.Key], num2));
			}
		}
		if ((Object)(object)fx.light != (Object)null)
		{
			fx.light.intensity = LightIntensity.Value * num2;
		}
		if (num >= 1f)
		{
			fx.fading = false;
		}
	}

	private static ShipEffects ApplyEffects(Ship ship)
	{
		//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bd: Expected O, but got Unknown
		//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_021b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: 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_00df: 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_00ed: 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_00fe: Unknown result type (might be due to invalid IL or missing references)
		ShipEffects shipEffects = new ShipEffects();
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		Renderer[] componentsInChildren = ((Component)ship).GetComponentsInChildren<Renderer>(true);
		for (int i = 0; i < componentsInChildren.Length; i++)
		{
			Material[] materials = componentsInChildren[i].materials;
			foreach (Material val in materials)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				SavedMaterial savedMaterial = new SavedMaterial();
				savedMaterial.mat = val;
				for (int k = 0; k < TintProps.Length; k++)
				{
					CaptureColor(savedMaterial, TintProps[k], Color.white, dictionary);
				}
				for (int l = 0; l < EmissionProps.Length; l++)
				{
					CaptureColor(savedMaterial, EmissionProps[l], Color.white * GlowIntensity.Value, dictionary);
				}
				if (Shine.Value > 0f)
				{
					if (val.HasProperty("_SpecColor"))
					{
						Color color = val.GetColor("_SpecColor");
						CaptureColor(savedMaterial, "_SpecColor", Color.Lerp(color, Color.white, Shine.Value), dictionary);
					}
					for (int m = 0; m < GlossProps.Length; m++)
					{
						string text = GlossProps[m];
						if (val.HasProperty(text))
						{
							float num = val.GetFloat(text);
							savedMaterial.floats[text] = num;
							savedMaterial.floatTargets[text] = Mathf.Max(num, Shine.Value);
							Count(dictionary, text);
						}
					}
				}
				val.EnableKeyword("_EMISSION");
				shipEffects.materials.Add(savedMaterial);
			}
		}
		shipEffects.lightObj = new GameObject("EarendilLight");
		shipEffects.lightObj.transform.SetParent(((Component)ship).transform, false);
		shipEffects.lightObj.transform.localPosition = new Vector3(0f, 3f, 0f);
		shipEffects.light = shipEffects.lightObj.AddComponent<Light>();
		shipEffects.light.type = (LightType)2;
		shipEffects.light.color = Color.white;
		shipEffects.light.intensity = 0f;
		shipEffects.light.range = LightRange.Value;
		shipEffects.light.shadows = (LightShadows)0;
		UpdateFade(ship, shipEffects);
		StartAudio(ship, shipEffects);
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, int> item in dictionary)
		{
			list.Add(item.Key + " x" + item.Value);
		}
		Log.LogInfo((object)("Flight started on " + ((Object)ship).name + ": " + shipEffects.materials.Count + " materials (" + ((list.Count > 0) ? string.Join(", ", list.ToArray()) : "no matching properties") + ")."));
		return shipEffects;
	}

	private static void CaptureColor(SavedMaterial entry, string prop, Color target, Dictionary<string, int> matched)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		if (entry.mat.HasProperty(prop))
		{
			entry.colors[prop] = entry.mat.GetColor(prop);
			entry.colorTargets[prop] = target;
			Count(matched, prop);
		}
	}

	private static void Count(Dictionary<string, int> counts, string key)
	{
		counts.TryGetValue(key, out var value);
		counts[key] = value + 1;
	}

	private static void RemoveEffects(ShipEffects fx)
	{
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		for (int i = 0; i < fx.materials.Count; i++)
		{
			SavedMaterial savedMaterial = fx.materials[i];
			if ((Object)(object)savedMaterial.mat == (Object)null)
			{
				continue;
			}
			foreach (KeyValuePair<string, Color> color in savedMaterial.colors)
			{
				savedMaterial.mat.SetColor(color.Key, color.Value);
			}
			foreach (KeyValuePair<string, float> @float in savedMaterial.floats)
			{
				savedMaterial.mat.SetFloat(@float.Key, @float.Value);
			}
		}
		fx.materials.Clear();
		if ((Object)(object)fx.lightObj != (Object)null)
		{
			Object.Destroy((Object)(object)fx.lightObj);
			fx.lightObj = null;
			fx.light = null;
		}
		DestroyShield(fx);
		if ((Object)(object)fx.demisterObj != (Object)null)
		{
			Object.Destroy((Object)(object)fx.demisterObj);
			fx.demisterObj = null;
			fx.mistField = null;
		}
		StopAudio(fx);
	}

	private static void PruneUnloadedShips()
	{
		if (effects.Count == 0 && gravityOff.Count == 0)
		{
			return;
		}
		List<Ship> list = null;
		foreach (KeyValuePair<Ship, ShipEffects> effect in effects)
		{
			if ((Object)(object)effect.Key == (Object)null)
			{
				if (list == null)
				{
					list = new List<Ship>();
				}
				list.Add(effect.Key);
			}
		}
		if (list != null)
		{
			for (int i = 0; i < list.Count; i++)
			{
				StopAudio(effects[list[i]]);
				effects.Remove(list[i]);
			}
		}
		gravityOff.RemoveWhere((Ship s) => (Object)(object)s == (Object)null);
		ourShields.RemoveWhere((ShieldGenerator s) => (Object)(object)s == (Object)null);
	}

	private static string ResolveAudioPath()
	{
		string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
		string value = AudioFile.Value;
		if (!string.IsNullOrEmpty(value))
		{
			string text = (Path.IsPathRooted(value) ? value : Path.Combine(directoryName, value));
			if (File.Exists(text))
			{
				return text;
			}
			Log.LogWarning((object)("Configured audio file not found: " + text));
			return null;
		}
		string[] array = new string[2]
		{
			Path.Combine(Paths.BepInExRootPath, "EarendilShip"),
			directoryName
		};
		for (int i = 0; i < array.Length; i++)
		{
			for (int j = 0; j < DefaultAudioNames.Length; j++)
			{
				string text2 = Path.Combine(array[i], DefaultAudioNames[j]);
				if (File.Exists(text2))
				{
					return text2;
				}
			}
		}
		return null;
	}

	private static AudioType AudioTypeFor(string path)
	{
		string text = Path.GetExtension(path).ToLowerInvariant();
		if (!(text == ".wav"))
		{
			if (!(text == ".mp3"))
			{
				return (AudioType)14;
			}
			return (AudioType)13;
		}
		return (AudioType)20;
	}

	private static void StartAudio(Ship ship, ShipEffects fx)
	{
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Expected O, but got Unknown
		if (!PlayAudio.Value || (Object)(object)fx.audio != (Object)null)
		{
			return;
		}
		if ((Object)(object)clip == (Object)null)
		{
			if (!clipLoading && (Object)(object)instance != (Object)null)
			{
				string text = ResolveAudioPath();
				if (text == null)
				{
					Log.LogInfo((object)"No audio file found. Put earendil.ogg in BepInEx/EarendilShip, or set 'Audio file' in the config.");
				}
				else
				{
					((MonoBehaviour)instance).StartCoroutine(instance.LoadClip(text));
				}
			}
			return;
		}
		float time = 0f;
		ZNetView netView = GetNetView(ship);
		if ((Object)(object)netView != (Object)null && (Object)(object)ZNet.instance != (Object)null)
		{
			long num = netView.GetZDO().GetLong("earendil_start_ms", 0L);
			double num2 = ZNet.instance.GetTimeSeconds() - (double)num / 1000.0;
			if (num > 0 && num2 > 0.0)
			{
				if (LoopAudio.Value)
				{
					time = (float)(num2 % (double)clip.length);
				}
				else
				{
					if (num2 >= (double)clip.length)
					{
						return;
					}
					time = (float)num2;
				}
			}
		}
		GameObject val = new GameObject("EarendilAudio");
		bool value = AudioFromShip.Value;
		if (value)
		{
			val.transform.SetParent(((Component)ship).transform, false);
		}
		fx.audio = val.AddComponent<AudioSource>();
		fx.audio.clip = clip;
		fx.audio.volume = Volume.Value * Ease(FadeProgress(ship, MusicFadeSeconds.Value));
		fx.audio.loop = LoopAudio.Value;
		fx.audio.spatialBlend = (value ? 1f : 0f);
		fx.audio.rolloffMode = (AudioRolloffMode)1;
		fx.audio.minDistance = 8f;
		fx.audio.maxDistance = 160f;
		fx.audio.time = time;
		fx.audio.mute = (Object)(object)netView != (Object)null && netView.GetZDO().GetBool("earendil_muted", false);
		fx.audio.Play();
	}

	private static void StopAudio(ShipEffects fx)
	{
		if (!((Object)(object)fx.audio == (Object)null))
		{
			fx.audio.Stop();
			Object.Destroy((Object)(object)((Component)fx.audio).gameObject);
			fx.audio = null;
		}
	}

	private IEnumerator LoadClip(string path)
	{
		clipLoading = true;
		string url = "file:///" + path.Replace("\\", "/");
		UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(url, AudioTypeFor(path));
		yield return request.SendWebRequest();
		clipLoading = false;
		if ((int)request.result != 1)
		{
			Log.LogError((object)("Failed to load audio from " + path + ": " + request.error));
			yield break;
		}
		clip = DownloadHandlerAudioClip.GetContent(request);
		Log.LogInfo((object)("Loaded audio: " + Path.GetFileName(path)));
		foreach (KeyValuePair<Ship, ShipEffects> effect in effects)
		{
			if ((Object)(object)effect.Key != (Object)null)
			{
				StartAudio(effect.Key, effect.Value);
			}
		}
	}

	private static long PerfBegin()
	{
		if (!perfEnabled)
		{
			return 0L;
		}
		return Stopwatch.GetTimestamp();
	}

	private static void PerfEnd(PerfCounter counter, long start)
	{
		if (start != 0)
		{
			long num = Stopwatch.GetTimestamp() - start;
			counter.ticks += num;
			counter.calls++;
			if (num > counter.worst)
			{
				counter.worst = num;
			}
		}
	}

	private static void ResetPerfWindow()
	{
		perfWindowStart = Time.realtimeSinceStartup;
		perfHeapStart = GC.GetTotalMemory(forceFullCollection: false);
		perfGen0Start = GC.CollectionCount(0);
		PerfCounter[] array = new PerfCounter[5] { perfShipTick, perfSail, perfControls, perfCarry, perfUpdate };
		for (int i = 0; i < array.Length; i++)
		{
			array[i].ticks = 0L;
			array[i].calls = 0L;
			array[i].worst = 0L;
		}
	}

	private static void ReportPerformance()
	{
		float num = Time.realtimeSinceStartup - perfWindowStart;
		if (num <= 0f)
		{
			return;
		}
		double num2 = 1000.0 / (double)Stopwatch.Frequency;
		PerfCounter[] array = new PerfCounter[5] { perfShipTick, perfSail, perfControls, perfCarry, perfUpdate };
		double num3 = 0.0;
		List<string> list = new List<string>();
		foreach (PerfCounter perfCounter in array)
		{
			double num4 = (double)perfCounter.ticks * num2 / (double)num;
			num3 += num4;
			double num5 = ((perfCounter.calls > 0) ? ((double)perfCounter.ticks * num2 * 1000.0 / (double)perfCounter.calls) : 0.0);
			list.Add($"{perfCounter.name}: {(float)perfCounter.calls / num:F0}/s, {num4:F3} ms/s, avg {num5:F1} us, worst {(double)perfCounter.worst * num2:F2} ms");
		}
		int num6 = 0;
		int num7 = 0;
		foreach (KeyValuePair<Ship, ShipEffects> effect in effects)
		{
			num6 += effect.Value.materials.Count;
			if ((Object)(object)effect.Value.audio != (Object)null)
			{
				num7++;
			}
		}
		long totalMemory = GC.GetTotalMemory(forceFullCollection: false);
		string text = (((Object)(object)clip == (Object)null) ? "not loaded" : $"{(double)Profiler.GetRuntimeMemorySizeLong((Object)(object)clip) / 1048576.0:F1} MB runtime, {(double)((long)clip.samples * (long)clip.channels * 4) / 1048576.0:F1} MB PCM estimate");
		Log.LogInfo((object)string.Format("[perf] window {0:F1}s | mod CPU {1:F3} ms/s ({2:F3}% of one core) | {3}", num, num3, num3 / 10.0, string.Join(" | ", list.ToArray())));
		Log.LogInfo((object)$"[perf] flying ships {effects.Count}, owned-flying {gravityOff.Count}, instanced materials {num6}, audio sources {num7} | audio clip {text} | managed heap {(double)totalMemory / 1048576.0:F1} MB (delta {(double)(totalMemory - perfHeapStart) / 1048576.0:+0.0;-0.0} MB), gen0 GCs {GC.CollectionCount(0) - perfGen0Start} | Unity allocated {(double)Profiler.GetTotalAllocatedMemoryLong() / 1048576.0:F1} MB, mono used {(double)Profiler.GetMonoUsedSizeLong() / 1048576.0:F1} MB");
		ResetPerfWindow();
	}

	private static void Message(string text)
	{
		Log.LogInfo((object)text);
		if ((Object)(object)Player.m_localPlayer != (Object)null)
		{
			((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
		}
	}
}