Decompiled source of Skidbladnir v1.0.0

Skidbladnir.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Splatform;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[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 Skidbladnir
{
	internal static class AdminCheats
	{
		[HarmonyPatch(typeof(Terminal), "IsCheatsEnabled")]
		private static class CheatsPatch
		{
			private static readonly FieldInfo Cheat = AccessTools.Field(typeof(Terminal), "m_cheat");

			private static void Postfix(ref bool __result)
			{
				if (!(!SkidbladnirPlugin.AdminCheats.Value | __result) && (bool)Cheat.GetValue(null) && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer())
				{
					__result = ZNet.instance.LocalPlayerIsAdminOrHost();
				}
			}
		}

		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		private static class HostOnlyPatch
		{
			private static void Postfix()
			{
				if (!SkidbladnirPlugin.AdminCheats.Value)
				{
					return;
				}
				Dictionary<string, ConsoleCommand> dictionary = AccessTools.StaticFieldRefAccess<Terminal, Dictionary<string, ConsoleCommand>>("commands");
				List<string> list = new List<string>();
				string[] hostOnly = HostOnly;
				foreach (string text in hostOnly)
				{
					if (dictionary.TryGetValue(text, out var value) && value.OnlyServer)
					{
						value.OnlyServer = false;
						list.Add(text);
					}
				}
				if (list.Count > 0)
				{
					SkidbladnirPlugin.Log.LogInfo((object)("Admins on a server may use cheats, including " + string.Join(", ", list)));
				}
			}
		}

		private static readonly string[] HostOnly = new string[3] { "debugmode", "fly", "nocost" };
	}
	internal class AutoTest : MonoBehaviour
	{
		[HarmonyPatch(typeof(GameCamera), "LateUpdate")]
		private static class CameraOverride
		{
			private static void Postfix(GameCamera __instance)
			{
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				if (CamPos.HasValue)
				{
					((Component)__instance).transform.SetPositionAndRotation(CamPos.Value, CamRot);
				}
			}
		}

		[HarmonyPatch(typeof(FejdStartup), "Start")]
		private static class MenuStart
		{
			private static void Postfix(FejdStartup __instance)
			{
				if (Enabled && !_started)
				{
					((MonoBehaviour)__instance).StartCoroutine(StartWorld(__instance));
				}
			}
		}

		[HarmonyPatch(typeof(Player), "SetControls")]
		private static class WalkPatch
		{
			private static void Prefix(Player __instance, ref Vector3 movedir, ref bool run, ref bool block, ref bool blockHold, ref bool dodge)
			{
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
				{
					if (WalkDir.HasValue)
					{
						movedir = WalkDir.Value;
						run = false;
					}
					if (BlockHold)
					{
						block = true;
						blockHold = true;
					}
					if (DodgeOnce)
					{
						dodge = true;
						DodgeOnce = false;
					}
				}
			}
		}

		[HarmonyPatch(typeof(MonsterAI), "UpdateAI")]
		private static class HoldIvaldi
		{
			private static bool Prefix(MonsterAI __instance, ref bool __result)
			{
				//IL_0023: 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)
				if (!IvaldiPose.HasValue || (Object)(object)((Component)__instance).GetComponent<IvaldiNpc>() == (Object)null)
				{
					return true;
				}
				Character component = ((Component)__instance).GetComponent<Character>();
				component.SetMoveDir(Vector3.zero);
				component.SetLookDir(IvaldiPose.Value, 0f);
				__result = false;
				return false;
			}
		}

		private class Cam
		{
			public Vector3 Pos;

			public Vector3 Look;

			public float Fov;

			public bool Collide;

			public Cam(Vector3 pos, Vector3 look, float fov = 50f, bool collide = true)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000e: 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)
				Pos = pos;
				Look = look;
				Fov = fov;
				Collide = collide;
			}
		}

		private class TShot
		{
			public string Name;

			public float From;

			public float To;

			public Func<IEnumerator> Setup;

			public Func<float, float, Cam> Camera;

			public Action<float, float> Each;

			public Func<float, float> Slow;
		}

		[HarmonyPatch(typeof(GameCamera), "LateUpdate")]
		private static class TrailerFov
		{
			[HarmonyPriority(0)]
			private static void Postfix(GameCamera __instance)
			{
				if (CamFov.HasValue)
				{
					Camera component = ((Component)__instance).GetComponent<Camera>();
					if ((Object)(object)component != (Object)null)
					{
						component.fieldOfView = CamFov.Value;
					}
				}
			}
		}

		[HarmonyPatch(typeof(MessageHud), "ShowMessage")]
		private static class TrailerQuiet
		{
			private static bool Prefix()
			{
				return TrailerFolder == null;
			}
		}

		[HarmonyPatch(typeof(Player), "SetControls")]
		private static class CrouchPatch
		{
			private static void Prefix(Player __instance, ref bool crouch)
			{
				if (CrouchHold && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					crouch = true;
				}
			}
		}

		public const string TriggerFile = "skidbladnir_autotest.txt";

		public const string ReportFile = "SkidbladnirTest.txt";

		private const string WorldName = "SkidTest";

		private const string WorldSeed = "SkidTest1";

		private const string ProfileName = "skidtest";

		private const float TimeLimit = 900f;

		internal static bool Enabled;

		internal static int SiteIndex;

		internal static bool NoBlueprint;

		internal static string ShotFolder;

		internal static string TourFolder;

		internal static bool TourCatalog;

		internal static bool TourMist;

		internal static Vector3? CamPos;

		internal static Quaternion CamRot;

		internal static float? LiftOverride;

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

		private int _pass;

		private int _fail;

		private string _shotDir;

		private bool _finished;

		private static bool _started;

		internal static Vector3? WalkDir;

		internal static string LastWalkState = "";

		internal static bool BlockHold;

		internal static bool DodgeOnce;

		internal static string IvaldiFolder;

		internal static bool IvaldiHands;

		internal static bool IvaldiSweep;

		internal static bool IvaldiPoseRef;

		private static readonly (float, float, Vector2)[] SweepCases = new(float, float, Vector2)[6]
		{
			(2f, 20f, new Vector2(2f, 0f)),
			(2f, 20f, new Vector2(3f, 0f)),
			(2f, 20f, new Vector2(2f, 1.5f)),
			(2f, 20f, new Vector2(2f, -1.5f)),
			(2f, 28f, new Vector2(2f, 0f)),
			(1.8f, 20f, new Vector2(2f, 0f))
		};

		internal static Vector3? IvaldiPose;

		private static readonly FieldInfo VisRightInstance = AccessTools.Field(typeof(VisEquipment), "m_rightItemInstance");

		private static readonly FieldInfo LookYaw = AccessTools.Field(typeof(Player), "m_lookYaw");

		private static readonly string[] PropWords = new string[23]
		{
			"hammer", "sledge", "pickaxe", "lantern", "torch", "dverg", "helmet", "cape", "demister", "tankard",
			"hoe", "cultivator", "tool", "key", "scroll", "spyglass", "goggle", "chisel", "tong", "apron",
			"wrench", "mace", "hood"
		};

		internal static string RefFolder;

		private static readonly string[] PhotoNames = new string[10] { "Mistlands_GuardTower1_new", "Mistlands_GuardTower2_new", "Mistlands_GuardTower3_new", "Mistlands_Lighthouse1_new", "Mistlands_Harbour1", "Mistlands_Excavation1", "Mistlands_Excavation2", "Mistlands_RoadPost1", "Mistlands_DvergrTownEntrance1", "Mistlands_Viaduct1" };

		internal static string TrailerFolder;

		internal static float? CamFov;

		private const int Fps = 30;

		private static readonly string[] Solid = new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" };

		private string _frameDir;

		private TowerAnchor _ta;

		private Vector3 _site;

		private Light _flash;

		private Character _elder;

		private int _framesSaved;

		private float _az1;

		private float _az2;

		private float _az5;

		private Vector3 _walkDir;

		private float _walkScore;

		private readonly List<Character> _extras = new List<Character>();

		private Transform _focus;

		private float _nextSwing;

		private string _gear = "";

		private Quaternion _ivaldiFrom;

		private Vector3 _mineDoor;

		internal static bool CrouchHold;

		private GameObject _sea;

		private GameObject _flyover;

		private GameObject _aimer;

		private Vector3 _ash;

		private Vector3 _seaStart;

		private Vector3 _seaDir;

		private ZeppelinFlight _zepFly;

		private Character _troll;

		private Vector3 _flyDir = Vector3.forward;

		private static int SolidMask => LayerMask.GetMask(Solid);

		private TowerAnchor A
		{
			get
			{
				if (!((Object)(object)_ta != (Object)null))
				{
					return _ta = TowerAnchor.Instances.FirstOrDefault();
				}
				return _ta;
			}
		}

		internal static void CheckTrigger(GameObject host)
		{
			try
			{
				string path = Path.Combine(Paths.BepInExRootPath, "skidbladnir_autotest.txt");
				if (!File.Exists(path))
				{
					return;
				}
				string[] array = File.ReadAllText(path).Trim().Split(new char[3] { ' ', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length != 0 && array[0].ToLowerInvariant() == "tour")
				{
					TourFolder = ((array.Length > 1) ? array[1] : "tour");
					TourCatalog = array.Any((string w) => w.ToLowerInvariant() == "catalog");
					TourMist = array.Any((string w) => w.ToLowerInvariant() == "mist");
				}
				if (array.Length != 0 && array[0].ToLowerInvariant() == "refs")
				{
					RefFolder = ((array.Length > 1) ? array[1] : "refs");
				}
				if (array.Length != 0 && array[0].ToLowerInvariant() == "ivaldi")
				{
					IvaldiFolder = ((array.Length > 1) ? array[1] : "ivaldi");
					IvaldiHands = array.Any((string w) => w.ToLowerInvariant() == "hands");
					IvaldiSweep = array.Any((string w) => w.ToLowerInvariant() == "sweep");
					IvaldiPoseRef = array.Any((string w) => w.ToLowerInvariant() == "pose");
					if (IvaldiPoseRef)
					{
						IvaldiHands = true;
					}
				}
				if (array.Length > 1 && array[0].ToLowerInvariant() == "site" && int.TryParse(array[1], out var result))
				{
					SiteIndex = result;
					ShotFolder = ((array.Length > 2) ? array[2] : null);
				}
				NoBlueprint = array.Any((string w) => w.ToLowerInvariant() == "noblueprint");
				if (array.Length != 0 && array[0].ToLowerInvariant() == "trailer")
				{
					TrailerFolder = ((array.Length > 1) ? array[1] : "v1");
				}
				File.Delete(path);
				Enabled = true;
				host.AddComponent<AutoTest>();
				SkidbladnirPlugin.Log.LogInfo((object)"AUTOTEST: trigger found; the game will start the test world by itself");
			}
			catch (Exception arg)
			{
				SkidbladnirPlugin.Log.LogError((object)$"AUTOTEST trigger check failed: {arg}");
			}
		}

		private static IEnumerator StartWorld(FejdStartup menu)
		{
			_started = true;
			yield return (object)new WaitForSeconds(2f);
			try
			{
				PlayerProfile val = new PlayerProfile("skidtest", (FileSource)2);
				if (!val.Load())
				{
					val.SetName("Skidtest");
				}
				val.m_firstSpawn = false;
				val.Save();
				Game.SetProfile("skidtest", (FileSource)2);
				World.RemoveWorld("SkidTest", (FileSource)2);
				SaveSystem.InvalidateCache((SaveDataType)0);
				World val2 = new World("SkidTest", "SkidTest1")
				{
					m_fileSource = (FileSource)2,
					m_needsDB = false
				};
				SaveSystem.SetSaveNumber(0u);
				val2.SaveWorldFWLData(DateTime.Now);
				SaveSystem.InvalidateCache((SaveDataType)0);
				ZNet.SetServer(true, false, false, val2.m_name, "", val2);
				ZNet.ResetServerHost();
				AccessTools.Field(typeof(FejdStartup), "m_startingWorld").SetValue(menu, true);
				SkidbladnirPlugin.Log.LogInfo((object)"AUTOTEST: starting world SkidTest (seed SkidTest1) as skidtest");
				AccessTools.Method(typeof(FejdStartup), "TransitionToMainScene", (Type[])null, (Type[])null).Invoke(menu, null);
			}
			catch (Exception arg)
			{
				SkidbladnirPlugin.Log.LogError((object)$"AUTOTEST could not start the world: {arg}");
			}
		}

		private void Start()
		{
			_shotDir = Path.Combine(Path.Combine(Paths.GameRootPath, "Skidbladnir"), "test");
			if (TourFolder != null)
			{
				_shotDir = Path.Combine(_shotDir, TourFolder.Replace('/', Path.DirectorySeparatorChar));
			}
			if (RefFolder != null)
			{
				_shotDir = Path.Combine(_shotDir, RefFolder.Replace('/', Path.DirectorySeparatorChar));
			}
			if (IvaldiFolder != null)
			{
				_shotDir = Path.Combine(_shotDir, IvaldiFolder.Replace('/', Path.DirectorySeparatorChar));
			}
			if (ShotFolder != null)
			{
				_shotDir = Path.Combine(_shotDir, ShotFolder.Replace('/', Path.DirectorySeparatorChar));
			}
			try
			{
				Directory.CreateDirectory(_shotDir);
				string[] files = Directory.GetFiles(_shotDir, "*.png");
				for (int i = 0; i < files.Length; i++)
				{
					File.Delete(files[i]);
				}
			}
			catch
			{
			}
			((MonoBehaviour)this).StartCoroutine(Watchdog());
			((MonoBehaviour)this).StartCoroutine(Run());
		}

		private IEnumerator Watchdog()
		{
			yield return (object)new WaitForSecondsRealtime((RefFolder != null || TrailerFolder != null) ? 2700f : 900f);
			if (!_finished)
			{
				Check("finished within the time limit", ok: false, $"{900f:F0} s");
				Finish();
			}
		}

		private void Check(string what, bool ok, string detail = "")
		{
			if (ok)
			{
				_pass++;
			}
			else
			{
				_fail++;
			}
			string text = (ok ? "PASS" : "FAIL") + "  " + what + ((detail.Length > 0) ? ("  (" + detail + ")") : "");
			_lines.Add(text);
			SkidbladnirPlugin.Log.LogInfo((object)("AUTOTEST " + text));
			WriteReport(done: false);
		}

		private void Note(string text)
		{
			_lines.Add("      " + text);
			SkidbladnirPlugin.Log.LogInfo((object)("AUTOTEST " + text));
		}

		private void WriteReport(bool done)
		{
			try
			{
				List<string> first = new List<string>
				{
					string.Format("Skidbladnir autotest {0}, {1:yyyy-MM-dd HH:mm:ss}", "1.0.0", DateTime.Now),
					done ? string.Format("RESULT: {0}  ({1} passed, {2} failed)", (_fail == 0) ? "PASS" : "FAIL", _pass, _fail) : $"RUNNING  ({_pass} passed, {_fail} failed so far)",
					""
				};
				File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "SkidbladnirTest.txt"), first.Concat(_lines).ToArray());
			}
			catch (Exception ex)
			{
				SkidbladnirPlugin.Log.LogWarning((object)("AUTOTEST report write failed: " + ex.Message));
			}
		}

		private void Finish()
		{
			if (!_finished)
			{
				_finished = true;
				LiftOverride = null;
				WriteReport(done: true);
				SkidbladnirPlugin.Log.LogInfo((object)$"AUTOTEST finished: {_pass} passed, {_fail} failed; quitting");
				((MonoBehaviour)this).StartCoroutine(Quit());
			}
		}

		private IEnumerator Quit()
		{
			yield return (object)new WaitForSecondsRealtime(2f);
			Application.Quit();
		}

		private IEnumerator Shot(string name)
		{
			yield return (object)new WaitForEndOfFrame();
			try
			{
				Texture2D val = new Texture2D(Screen.width, Screen.height, (TextureFormat)3, false);
				val.ReadPixels(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), 0, 0);
				val.Apply();
				File.WriteAllBytes(Path.Combine(_shotDir, name + ".png"), ImageConversion.EncodeToPNG(val));
				Object.Destroy((Object)(object)val);
				Note("screenshot " + name + ".png");
			}
			catch (Exception ex)
			{
				Note("screenshot " + name + " failed: " + ex.Message);
			}
		}

		private static IEnumerator WaitFor(Func<bool> cond, float timeout)
		{
			float t = 0f;
			while (!cond() && t < timeout)
			{
				t += Time.deltaTime;
				yield return null;
			}
		}

		private static IEnumerator Tp(Player p, Vector3 pos, bool distant = false)
		{
			//IL_000e: 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)
			float t = 0f;
			while (!((Character)p).TeleportTo(pos, ((Component)p).transform.rotation, distant) && t < 10f)
			{
				t += 0.25f;
				yield return (object)new WaitForSeconds(0.25f);
			}
			yield return (object)new WaitForSeconds(0.2f);
			yield return WaitFor(() => !((Character)p).IsTeleporting(), 120f);
			yield return (object)new WaitForSeconds(1.5f);
		}

		private static void Look(Player p, Vector3 at)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: 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_001e: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = at - ((Character)p).GetEyePoint();
			if (((Vector3)(ref val)).sqrMagnitude > 0.01f)
			{
				((Character)p).SetLookDir(((Vector3)(ref val)).normalized, 0f);
			}
		}

		private IEnumerator Run()
		{
			yield return WaitFor(() => (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)ZNetScene.instance != (Object)null && !((Character)Player.m_localPlayer).IsTeleporting(), 600f);
			Player player = Player.m_localPlayer;
			if ((Object)(object)player == (Object)null)
			{
				Check("world loaded with a player", ok: false);
				Finish();
				yield break;
			}
			yield return (object)new WaitForSeconds(5f);
			player.SetGodMode(true);
			Check("world loaded with a player", ok: true, string.Format("{0}, at {1}", "SkidTest", ((Component)player).transform.position));
			IEnumerator body = null;
			try
			{
				body = ((TrailerFolder != null) ? Trailer(player) : ((IvaldiFolder != null) ? IvaldiTour(player) : ((RefFolder != null) ? Refs(player) : ((TourFolder != null) ? Tour(player) : Steps(player)))));
			}
			catch (Exception ex)
			{
				Check("test steps", ok: false, ex.ToString());
			}
			if (body != null)
			{
				while (true)
				{
					object current;
					try
					{
						if (!body.MoveNext())
						{
							break;
						}
						current = body.Current;
						goto IL_01bb;
					}
					catch (Exception ex2)
					{
						Check("no exceptions during the test", ok: false, ex2.ToString());
					}
					break;
					IL_01bb:
					yield return current;
				}
			}
			Finish();
		}

		private IEnumerator Tour(Player player)
		{
			yield return WaitFor(() => SkidWorld.SitesReady, 300f);
			if (SkidWorld.Sites.Count == 0)
			{
				Check("tower sites", ok: false);
				yield break;
			}
			Vector3 val = SkidWorld.Sites.OrderByDescending(MistShare).First();
			Note($"tour site {SkidWorld.Sites.IndexOf(val)} at {val}: {MistShare(val):P0} Mistlands within 200 m");
			yield return Tp(player, val + new Vector3(0f, 5f, -40f), distant: true);
			yield return WaitFor(() => TowerAnchor.Instances.Count > 0 && TowerAnchor.Instances[0].Built, 90f);
			TowerAnchor anchor = TowerAnchor.Instances.FirstOrDefault();
			Check("tower raised and built", (Object)(object)anchor != (Object)null && anchor.Built, $"{TowerPrefab.PieceCount} pieces");
			if ((Object)(object)anchor == (Object)null)
			{
				yield break;
			}
			EnvMan env = EnvMan.instance;
			env.m_debugEnv = "Clear";
			env.m_debugTimeOfDay = true;
			env.m_debugTime = 0.5f;
			if ((Object)(object)Hud.instance != (Object)null)
			{
				Hud.instance.m_userHidden = true;
			}
			GameObject value = Traverse.Create((object)player).Field("m_visual").GetValue<GameObject>();
			if ((Object)(object)value != (Object)null)
			{
				value.SetActive(false);
			}
			if (TourCatalog)
			{
				float deck = 40.5f;
				GameObject cat = TowerPrefab.Catalog(anchor.World(new Vector3(-7f, deck, -17f)), ((Component)anchor).transform.rotation * Quaternion.Euler(0f, -90f, 0f), flat: true);
				yield return Tp(player, anchor.World(new Vector3(0f, deck + 0.5f, 0f)));
				(string, float)[] array = new(string, float)[2]
				{
					("0_catalog_a", -9f),
					("0_catalog_b", 9f)
				};
				for (int num = 0; num < array.Length; num++)
				{
					(string, float) tuple = array[num];
					string name = tuple.Item1;
					float item = tuple.Item2;
					CamPos = anchor.World(new Vector3(5f, deck + 3f, item));
					CamRot = Quaternion.LookRotation(anchor.World(new Vector3(-7f, deck + 1f, item)) - CamPos.Value);
					yield return (object)new WaitForSeconds(4f);
					yield return Shot(name);
				}
				CamPos = null;
				Object.Destroy((Object)(object)cat);
			}
			anchor.SetStage(3);
			yield return WaitFor(() => (Object)(object)anchor.Zeppelin != (Object)null, 15f);
			Check("ship moored", (Object)(object)anchor.Zeppelin != (Object)null);
			Vector3 turretCentre = TowerPrefab.TurretCentre;
			List<(string, Vector3, Vector3, float, bool)> list = new List<(string, Vector3, Vector3, float, bool)>
			{
				("1_far", new Vector3(-62f, 42f, -66f), new Vector3(0f, 26f, 0f), 0.5f, true),
				("2_mid", new Vector3(-30f, 14f, -27f), new Vector3(0f, 24f, 0f), 0.42f, true),
				("3_base", new Vector3(-16f, 3f, -24f), new Vector3(0f, 9f, 0f), 0.5f, true),
				("4_stair", turretCentre + new Vector3(1.6f, 2.2f, -1.6f), turretCentre + new Vector3(0f, 22f, 0f), 0.5f, false),
				("5_deck", new Vector3(16f, 50f, -27f), new Vector3(0f, 42f, 0f), 0.5f, true),
				("6_night", new Vector3(-30f, 14f, -27f), new Vector3(0f, 24f, 0f), 0.02f, true),
				("7_turret", new Vector3(38f, 14f, -32f), turretCentre + new Vector3(0f, 22f, 0f), 0.45f, true),
				("8_side", new Vector3(34f, 10f, 30f), new Vector3(0f, 22f, 0f), 0.4f, true)
			};
			int mask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "terrain", "piece", "vehicle" });
			RaycastHit val6 = default(RaycastHit);
			foreach (var v in list)
			{
				Vector3 cam = anchor.World(v.Item2);
				Vector3 target = anchor.World(v.Item3);
				float deck = ZoneSystem.instance.GetGroundHeight(cam);
				Vector3 val2 = cam;
				val2.y = deck + 0.3f;
				CamPos = null;
				yield return Tp(player, val2 + ((Component)anchor).transform.rotation * new Vector3(0f, 0f, -2f));
				yield return (object)new WaitForSeconds(2f);
				deck = ZoneSystem.instance.GetGroundHeight(cam);
				if (cam.y < deck + 1.6f)
				{
					cam.y = deck + 1.6f;
				}
				if (v.Item5)
				{
					int num2 = 0;
					Vector3 val3 = target - cam;
					RaycastHit[] array2 = Physics.SphereCastAll(cam, 7f, ((Vector3)(ref val3)).normalized, ((Vector3)(ref val3)).magnitude);
					for (int num3 = 0; num3 < array2.Length; num3++)
					{
						RaycastHit val4 = array2[num3];
						ZNetView val5 = (((Object)(object)((RaycastHit)(ref val4)).collider != (Object)null) ? ((Component)((RaycastHit)(ref val4)).collider).GetComponentInParent<ZNetView>() : null);
						if (!((Object)(object)val5 == (Object)null) && val5.IsValid() && !((Component)val5).transform.IsChildOf(((Component)anchor).transform) && (!((Object)(object)((Component)val5).GetComponent<TreeBase>() == (Object)null) || !((Object)(object)((Component)val5).GetComponent<TreeLog>() == (Object)null) || !((Object)(object)((Component)val5).GetComponent<Destructible>() == (Object)null)))
						{
							val5.ClaimOwnership();
							ZNetScene.instance.Destroy(((Component)val5).gameObject);
							num2++;
						}
					}
					if (num2 > 0)
					{
						Note($"{v.Item1}: {num2} trees felled for the view");
						yield return (object)new WaitForSeconds(0.5f);
					}
				}
				int num4 = 0;
				while (v.Item5 && num4 < 16 && Physics.Linecast(cam, target, ref val6, mask) && !((Component)((RaycastHit)(ref val6)).collider).transform.IsChildOf(((Component)anchor).transform) && (!((Object)(object)anchor.Zeppelin != (Object)null) || !((Component)((RaycastHit)(ref val6)).collider).transform.IsChildOf(((Component)anchor.Zeppelin).transform)))
				{
					cam.y += 5f;
					num4++;
				}
				if (v.Item5)
				{
					Note($"{v.Item1}: camera {cam.y - deck:F0} m above ground");
				}
				env.m_debugTime = v.Item4;
				CamPos = cam;
				CamRot = Quaternion.LookRotation(target - cam);
				yield return (object)new WaitForSeconds(6f);
				yield return Shot(v.Item1);
			}
			CamPos = null;
		}

		private static float MistShare(Vector3 p)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Invalid comparison between Unknown and I4
			int num = 0;
			int num2 = 0;
			for (float num3 = 50f; num3 <= 200f; num3 += 50f)
			{
				int num4 = 0;
				while (num4 < 16)
				{
					float num5 = (float)num4 * (float)Math.PI / 8f;
					if ((int)WorldGenerator.instance.GetBiome(p + new Vector3(Mathf.Cos(num5) * num3, 0f, Mathf.Sin(num5) * num3)) == 512)
					{
						num2++;
					}
					num4++;
					num++;
				}
			}
			return (float)num2 / (float)num;
		}

		private IEnumerator Steps(Player player)
		{
			ZNetScene instance = ZNetScene.instance;
			Check("zeppelin prefab", (Object)(object)instance.GetPrefab("Skidbladnir") != (Object)null);
			Check("tower prefab", (Object)(object)instance.GetPrefab("Skidbladnir_Tower") != (Object)null, $"{TowerPrefab.PieceCount} pieces" + ((TowerPrefab.Missing.Count > 0) ? (", missing " + string.Join("/", TowerPrefab.Missing)) : ""));
			Check("tower has all its piece kinds", TowerPrefab.Missing.Count == 0 || (TowerPrefab.Missing.Count == 1 && TowerPrefab.Missing[0] == "tip"));
			Check("Ivaldi prefab", (Object)(object)instance.GetPrefab("Skidbladnir_Ivaldi") != (Object)null);
			Check("elder gjall prefab", (Object)(object)instance.GetPrefab("Skidbladnir_ElderGjall") != (Object)null);
			Check("grappling hook item", (Object)(object)ObjectDB.instance.GetItemPrefab(Items.HookPrefabName) != (Object)null, Items.HookPrefabName + " (" + Items.HookItemName + ")");
			Recipe val = ((IEnumerable<Recipe>)ObjectDB.instance.m_recipes).FirstOrDefault((Func<Recipe, bool>)((Recipe r) => (Object)(object)r != (Object)null && (Object)(object)r.m_item != (Object)null && ((Object)r.m_item).name == Items.HookPrefabName));
			AutoTest autoTest = this;
			bool ok = (Object)(object)val != (Object)null && val.m_resources.Length != 0;
			object detail;
			if (!((Object)(object)val == (Object)null))
			{
				CraftingStation craftingStation = val.m_craftingStation;
				detail = (((craftingStation != null) ? ((Object)craftingStation).name : null) ?? "no station") + ": " + string.Join(", ", val.m_resources.Select((Requirement r) => $"{r.m_amount} {((Object)r.m_resItem).name}"));
			}
			else
			{
				detail = "";
			}
			autoTest.Check("grappling hook recipe", ok, (string)detail);
			Items.Need questHook = Items.QuestHook;
			Check("Ivaldi's hook ingredient found", questHook != null && ((Object)questHook.Item).name == SkidbladnirPlugin.HookItem.Value, (questHook != null) ? $"{questHook.Amount} {((Object)questHook.Item).name} ({questHook.Name} = {Loc.L(questHook.Name)})" : (SkidbladnirPlugin.HookItem.Value + " missing"));
			List<Items.Need> toxin = Items.Toxin;
			Check("the toxin is one Ashlands item, found", toxin.Count == 1 && ((Object)toxin[0].Item).name == SkidbladnirPlugin.ToxinItem.Value, string.Join(", ", toxin.Select((Items.Need n) => $"{n.Amount} {((Object)n.Item).name} ({n.Name} = {Loc.L(n.Name)})")) + ((toxin.Count == 0) ? (SkidbladnirPlugin.ToxinItem.Value + " missing") : ""));
			List<Items.Need> recipeCost = Items.RecipeCost;
			Check("ship recipe cost all found", recipeCost.Count == SkidbladnirPlugin.RecipeCost.Value.Split(',').Length, string.Join(", ", recipeCost.Select((Items.Need n) => $"{n.Amount} {((Object)n.Item).name} ({Loc.L(n.Name)})")));
			Piece keelPiece = (((Object)(object)ZeppelinKeel.Prefab != (Object)null) ? ZeppelinKeel.Prefab.GetComponent<Piece>() : null);
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Hammer");
			bool flag = itemPrefab != null && itemPrefab.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_buildPieces?.m_pieces.Contains(ZeppelinKeel.Prefab) == true;
			AutoTest autoTest2 = this;
			bool ok2 = (Object)(object)keelPiece != (Object)null && flag && keelPiece.m_resources.Length == recipeCost.Count && (Object)(object)keelPiece.m_craftingStation != (Object)null;
			object detail2;
			if (!((Object)(object)keelPiece == (Object)null))
			{
				CraftingStation craftingStation2 = keelPiece.m_craftingStation;
				detail2 = string.Format("station {0}, {1} resources", ((craftingStation2 != null) ? ((Object)craftingStation2).name : null) ?? "none", keelPiece.m_resources.Length);
			}
			else
			{
				detail2 = "no piece";
			}
			autoTest2.Check("ship piece on the hammer, with a station and cost", ok2, (string)detail2);
			Check("ship recipe locked in a new world", !TowerAnchor.RecipeUnlocked && (Object)(object)keelPiece != (Object)null && !player.HaveRequirements(keelPiece, (RequirementMode)1));
			Check("no tower before anyone finds it", TowerAnchor.Instances.Count == 0 && SkidWorld.Tower == null);
			yield return WaitFor(() => SkidWorld.SitesReady, 300f);
			Check("five tower sites picked", SkidWorld.Sites.Count == 5, SkidWorld.SiteReport);
			if (SkidWorld.Sites.Count == 0)
			{
				yield break;
			}
			bool flag2 = SkidWorld.Sites.All((Vector3 s) => (int)WorldGenerator.instance.GetBiome(s) == 512);
			float num = float.MaxValue;
			for (int num2 = 0; num2 < SkidWorld.Sites.Count; num2++)
			{
				for (int num3 = num2 + 1; num3 < SkidWorld.Sites.Count; num3++)
				{
					num = Mathf.Min(num, Vector3.Distance(SkidWorld.Sites[num2], SkidWorld.Sites[num3]));
				}
			}
			Check("sites are in the Mistlands, 1 km apart", flag2 && num >= 999f, $"closest pair {num:F0} m");
			int num4 = Mathf.Clamp(SiteIndex, 0, SkidWorld.Sites.Count - 1);
			Vector3 site = SkidWorld.Sites[num4];
			Note($"testing at site {num4}");
			yield return Tp(player, site + new Vector3(60f, 5f, 60f), distant: true);
			yield return WaitFor(() => TowerAnchor.Instances.Count > 0, 60f);
			TowerAnchor anchor = TowerAnchor.Instances.FirstOrDefault();
			Check("tower raised when a player comes near a site", (Object)(object)anchor != (Object)null, ((Object)(object)anchor != (Object)null) ? $"at {((Component)anchor).transform.position}" : "");
			if ((Object)(object)anchor == (Object)null)
			{
				yield break;
			}
			Vector3 val2 = ((Component)anchor).transform.position - site;
			AutoTest autoTest3 = this;
			Vector2 val3 = new Vector2(val2.x, val2.z);
			autoTest3.Check("tower raised at the site the player found", ((Vector2)(ref val3)).magnitude < 1f, $"seated {val2.y:F2} m from the site's height");
			yield return WaitFor(() => anchor.Built && Object.FindObjectsOfType<IvaldiNpc>().Length != 0, 60f);
			Check("tower built: Ivaldi on the deck, no ship yet", anchor.Built && Object.FindObjectsOfType<IvaldiNpc>().Length != 0 && (Object)(object)anchor.Zeppelin == (Object)null && anchor.Stage == 0);
			IvaldiNpc ivaldi = Object.FindObjectsOfType<IvaldiNpc>().FirstOrDefault();
			if ((Object)(object)ivaldi == (Object)null)
			{
				yield break;
			}
			yield return (object)new WaitForSeconds(2f);
			IvaldiLook component = ((Component)ivaldi).GetComponent<IvaldiLook>();
			List<Transform> list = (from t in ((Component)ivaldi).GetComponentsInChildren<Transform>(true)
				where ((Object)t).name.StartsWith("ivaldi_")
				select t).ToList();
			Check("Ivaldi at his configured size", (Object)(object)component != (Object)null && Mathf.Abs(component.CurrentScale - IvaldiLook.Scale) < 0.01f, ((Object)(object)component != (Object)null) ? $"scale {component.CurrentScale:F2}" : "no look");
			Check("his smith's look is on, look-only", !SkidbladnirPlugin.IvaldiCustomLook.Value || (list.Count > 0 && list.All((Transform t) => ((Component)t).GetComponentsInChildren<Collider>(true).Length == 0 && ((Component)t).GetComponentsInChildren<ZNetView>(true).Length == 0 && ((Component)t).GetComponentsInChildren<Rigidbody>(true).Length == 0)), $"{list.Count} props; {IvaldiLook.Report}");
			List<string> list2 = (from i in ((Component)ivaldi).GetComponent<Humanoid>().GetInventory().GetAllItems()
				where i.m_equipped
				select (!((Object)(object)i.m_dropPrefab != (Object)null)) ? i.m_shared.m_name : ((Object)i.m_dropPrefab).name).ToList();
			Check("he holds his hammer (equipped, scaled down, nothing he can drop)", !SkidbladnirPlugin.IvaldiCustomLook.Value || ((Object)(object)component.HammerInstance != (Object)null && component.HammerInstance.GetComponentsInChildren<Collider>(true).All((Collider val30) => !val30.enabled) && (Object)(object)((Component)ivaldi).GetComponent<CharacterDrop>() == (Object)null), IvaldiLook.HammerReport + "; equipped " + string.Join(", ", list2));
			Check("he has his beard (the male dvergr hair)", list2.Contains("DvergerHairMale"), string.Join(", ", list2));
			float num5 = ((Component)anchor).transform.InverseTransformPoint(((Component)ivaldi).transform.position).y - 40.5f;
			Check("Ivaldi stands on his deck", ((Component)ivaldi).GetComponent<Character>().IsOnGround() && Mathf.Abs(num5) < 0.5f, $"{num5:F2} m from deck height");
			Inventory inv = ((Humanoid)player).GetInventory();
			int num6 = inv.CountItems(Items.QuestHookName, -1, true);
			if (num6 > 0)
			{
				inv.RemoveItem(Items.QuestHookName, num6, -1, true);
				Note($"removed {num6} grappling hook(s) left from an earlier run");
			}
			List<Items.Need> hull = Items.Hull;
			Check("hull cost found", hull.Count > 0, string.Join(", ", hull.Select((Items.Need n) => $"{n.Amount} {((Object)n.Item).name}")));
			string said = IvaldiNpc.Converse(player, anchor, give: false);
			Note("Ivaldi: " + said);
			foreach (Items.Need item in hull)
			{
				inv.AddItem(((Object)item.Item).name, item.Amount, 1, 0, 0L, "", false, false);
			}
			said = IvaldiNpc.Converse(player, anchor, give: true);
			Note("Ivaldi: " + said);
			yield return WaitFor(() => (Object)(object)anchor.Zeppelin != (Object)null, 20f);
			Check("he takes the plates and builds the hull", anchor.HullDone && anchor.Stage == 1 && (Object)(object)anchor.Zeppelin != (Object)null && hull.All((Items.Need n) => inv.CountItems(n.Name, -1, true) == 0));
			ZeppelinFlight zep = anchor.Zeppelin;
			if ((Object)(object)zep == (Object)null)
			{
				yield break;
			}
			yield return (object)new WaitForSeconds(1.5f);
			Check("new hull has no ballistas", Ballistas(zep).All((BallistaMount ballistaMount) => !((Component)ballistaMount).gameObject.activeSelf));
			Check("only one tower in the world", TowerAnchor.Instances.Count == 1 && SkidWorld.Tower != null);
			int num7 = Object.FindObjectsOfType<TreeBase>().Count((TreeBase t) => InFootprint(anchor, ((Component)t).transform.position));
			Check("footprint cleared of trees", num7 == 0, $"{num7} left");
			yield return WaitFor(() => !anchor.Leveling, 90f);
			yield return (object)new WaitForSeconds(2f);
			Vector3 tcen = TowerPrefab.TurretCentre;
			List<float> list3 = new List<float>();
			List<float> list4 = new List<float>();
			List<float> list5 = new List<float>();
			for (float num8 = -3f; num8 <= 3f; num8 += 1f)
			{
				for (float num9 = -3f; num9 <= 3f; num9 += 1f)
				{
					if (num8 * num8 + num9 * num9 <= 10.240001f)
					{
						list3.Add(Ground(tcen + new Vector3(num8, 0f, num9)));
					}
				}
			}
			for (float num10 = -10f; num10 <= 10f; num10 += 2.5f)
			{
				for (float num11 = -10f; num11 <= 10f; num11 += 2.5f)
				{
					if (num10 * num10 + num11 * num11 < 110f)
					{
						list4.Add(Ground(new Vector3(num10, 0f, num11)));
					}
				}
			}
			for (float num12 = 4.5f; num12 <= 8f; num12 += 0.75f)
			{
				list5.Add(Ground(tcen + TowerPrefab.DoorDir * num12));
			}
			Note($"ground vs base: turret {list3.Min():F2}..{list3.Max():F2}, tower {list4.Min():F2}..{list4.Max():F2}, door approach {list5.Min():F2}..{list5.Max():F2} (turret floor top {0.5f})");
			Check("ground inside the turret is under its floor", list3.Max() <= 0.05f, $"highest {list3.Max():F2} m above the base");
			Check("ground under the tower is level with its base", list4.Max() <= 0.05f && list4.Min() > -0.6f, $"{list4.Min():F2}..{list4.Max():F2} m");
			float num13 = 0f;
			List<string> list6 = new List<string>();
			for (float num14 = 4.5f; num14 + 1.5f <= anchor.RampEnd; num14 += 1.5f)
			{
				float num15 = Ground(tcen + TowerPrefab.DoorDir * num14);
				float num16 = Ground(tcen + TowerPrefab.DoorDir * (num14 + 1.5f));
				num13 = Mathf.Max(num13, Mathf.Atan2(Mathf.Abs(num16 - num15), 1.5f) * 57.29578f);
				list6.Add($"{num15:F1}");
			}
			Note($"ground beyond the ramp's end ({anchor.RampEnd:F0} m out): " + string.Join(" ", from i in Enumerable.Range(0, 7)
				select $"{Ground(tcen + TowerPrefab.DoorDir * (anchor.RampEnd + (float)i)):F1}"));
			Check("the ramp from the door to the land is walkable", num13 < 35f, string.Format("steepest {0:F0}°; heights out from the door: {1}", num13, string.Join(" ", list6)));
			Check("the door's approach meets the turret floor", list5.Max() <= 0.52f && list5.Min() > -0.3f, $"{list5.Min():F2}..{list5.Max():F2} m");
			yield return (object)new WaitForSeconds(5f);
			float num17 = Vector3.Distance(((Component)zep).transform.position, anchor.World(TowerAnchor.MoorLocal));
			Check("moored zeppelin stays put", num17 < 1.5f, $"{num17:F2} m from its mooring");
			Check("zeppelin not operational yet", !zep.Operational);
			Vector3 val4 = ((Component)anchor).transform.position + ((Component)anchor).transform.rotation * new Vector3(55f, 0f, -45f);
			val4.y = ZoneSystem.instance.GetGroundHeight(val4) + 1f;
			yield return Tp(player, val4);
			yield return (object)new WaitForSeconds(2f);
			Look(player, ((Component)anchor).transform.position + Vector3.up * 30f);
			yield return (object)new WaitForSeconds(1.5f);
			yield return Shot("1_tower");
			Vector3 c0 = TowerPrefab.TurretCentre;
			Vector3 val5 = c0 + TowerPrefab.DoorDir * 11f;
			CamPos = anchor.World(val5 + Vector3.up * 2.2f);
			CamRot = Quaternion.LookRotation(anchor.World(c0 + Vector3.up * 1.5f) - CamPos.Value);
			yield return (object)new WaitForSeconds(1f);
			yield return Shot("stair_door");
			Vector3 val6 = TowerPrefab.QuadrantDir(-1);
			Vector3 normalized = ((Vector3)(ref val6)).normalized;
			CamPos = anchor.World(c0 + TowerPrefab.DoorDir * 2.6f + Vector3.up * 1.7f);
			CamRot = Quaternion.LookRotation(anchor.World(c0 + normalized * 1.2f + Vector3.up * 0.6f) - CamPos.Value);
			yield return (object)new WaitForSeconds(1f);
			yield return Shot("stair_base");
			CamPos = null;
			List<string> list7 = new List<string>();
			for (float num18 = 3f; num18 <= 12f; num18 += 1f)
			{
				Vector3 val7 = anchor.World(c0 + TowerPrefab.DoorDir * num18);
				val7.y = ZoneSystem.instance.GetGroundHeight(val7);
				Collider[] array = Physics.OverlapCapsule(val7 + Vector3.up * 0.6f, val7 + Vector3.up * 1.7f, 0.4f);
				foreach (Collider val8 in array)
				{
					if ((Object)(object)val8 == (Object)null || val8.isTrigger || (Object)(object)val8.attachedRigidbody == (Object)(object)((Component)player).GetComponent<Rigidbody>() || LayerMask.LayerToName(((Component)val8).gameObject.layer) == "terrain")
					{
						continue;
					}
					Transform val9 = ((Component)val8).transform;
					string text = ((Object)val8).name;
					for (int num20 = 0; num20 < 3; num20++)
					{
						if (!((Object)(object)val9.parent != (Object)null))
						{
							break;
						}
						val9 = val9.parent;
						text = ((Object)val9).name + "/" + text;
					}
					list7.Add($"{num18:F0}m: {text} ({LayerMask.LayerToName(((Component)val8).gameObject.layer)})");
				}
			}
			Note("solid things on the door's approach: " + ((list7.Count == 0) ? "none" : string.Join("; ", list7.Distinct())));
			yield return Tp(player, anchor.World(c0 + TowerPrefab.DoorDir * 9f + Vector3.up * 1f));
			yield return (object)new WaitForSeconds(1.5f);
			Transform val10 = ((Component)anchor).transform.Find("Geometry");
			List<Vector3> list8 = (((Object)(object)val10 == (Object)null) ? new List<Vector3>() : (from l in (from t in ((Component)val10).GetComponentsInChildren<Transform>(true)
					where ((Object)t).name == "piece_dvergr_spiralstair"
					select t).Select(delegate(Transform t)
				{
					//IL_000d: Unknown result type (might be due to invalid IL or missing references)
					//IL_0012: Unknown result type (might be due to invalid IL or missing references)
					//IL_0017: Unknown result type (might be due to invalid IL or missing references)
					//IL_0067: 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)
					//IL_007a: 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)
					Renderer[] componentsInChildren = ((Component)t).GetComponentsInChildren<Renderer>(true);
					Bounds val30 = (Bounds)((componentsInChildren.Length != 0) ? componentsInChildren.Select((Renderer r) => r.bounds).Aggregate(delegate(Bounds x, Bounds y)
					{
						//IL_0002: Unknown result type (might be due to invalid IL or missing references)
						//IL_0008: Unknown result type (might be due to invalid IL or missing references)
						((Bounds)(ref x)).Encapsulate(y);
						return x;
					}) : new Bounds(t.position, Vector3.zero));
					return ((Component)anchor).transform.InverseTransformPoint(((Bounds)(ref val30)).center);
				})
				orderby l.y
				select l).ToList());
			List<Vector3> route = new List<Vector3>
			{
				c0 + TowerPrefab.DoorDir * 7f,
				c0 + TowerPrefab.DoorDir * 4.6f,
				c0 + TowerPrefab.DoorDir * 2.4f
			};
			if (list8.Count > 1)
			{
				Vector3 val11 = list8[0] - c0;
				val11.y = 0f;
				Vector3 val12 = list8[1] - c0;
				val12.y = 0f;
				float num21 = Mathf.Sign(TowerPrefab.SpiralDelta);
				Vector3 val13 = Quaternion.Euler(0f, (0f - num21) * 45f, 0f) * ((Vector3)(ref val11)).normalized;
				float num22 = Vector3.SignedAngle(TowerPrefab.DoorDir, val13, Vector3.up);
				for (float num23 = 30f; num23 < Mathf.Abs(num22); num23 += 30f)
				{
					route.Add(c0 + Quaternion.Euler(0f, Mathf.Sign(num22) * num23, 0f) * TowerPrefab.DoorDir * 2.2f);
				}
				route.Add(c0 + val13 * 1.2f);
			}
			int reached = 0;
			float bestY = -99f;
			List<string> walkLog = new List<string>();
			Note("stair steps (angle°/radius/height from the turret centre): " + string.Join(" ", list8.Take(6).Select(delegate(Vector3 s)
			{
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				//IL_000a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_0038: 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_0059: 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_0070: Unknown result type (might be due to invalid IL or missing references)
				//IL_007d: Unknown result type (might be due to invalid IL or missing references)
				object arg2 = Vector3.SignedAngle(Vector3.forward, new Vector3(s.x - c0.x, 0f, s.z - c0.z), Vector3.up);
				Vector2 val30 = new Vector2(s.x - c0.x, s.z - c0.z);
				return $"{arg2:F0}°/{((Vector2)(ref val30)).magnitude:F2}/{s.y:F1}";
			})));
			foreach (Vector3 wp in route)
			{
				bool ok3 = false;
				yield return WalkTo(player, anchor, wp, 6f, delegate(bool r)
				{
					ok3 = r;
				});
				if (!ok3 && reached == 0)
				{
					yield return (object)new WaitForSeconds(2f);
					yield return WalkTo(player, anchor, wp, 8f, delegate(bool r)
					{
						ok3 = r;
					});
				}
				bestY = Mathf.Max(bestY, ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position).y);
				Vector3 val14 = ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position) - c0;
				object[] obj = new object[6]
				{
					reached,
					ok3 ? "ok" : "STUCK",
					Vector3.SignedAngle(Vector3.forward, new Vector3(val14.x, 0f, val14.z), Vector3.up),
					null,
					null,
					null
				};
				val3 = new Vector2(val14.x, val14.z);
				obj[3] = ((Vector2)(ref val3)).magnitude;
				obj[4] = val14.y;
				obj[5] = Vector3.SignedAngle(Vector3.forward, new Vector3(wp.x - c0.x, 0f, wp.z - c0.z), Vector3.up);
				walkLog.Add(string.Format("{0}:{1} at {2:F0}°/{3:F1}m/y{4:F1} (wp {5:F0}°)", obj));
				if (!ok3)
				{
					break;
				}
				reached++;
			}
			if (reached == route.Count)
			{
				float spin = Mathf.Sign(TowerPrefab.SpiralDelta);
				float climbT = 0f;
				float lastGain = 0f;
				float lastY = -99f;
				while (climbT < 60f)
				{
					Vector3 val15 = ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position);
					if (val15.y > 10.5f)
					{
						break;
					}
					if (val15.y > lastY + 0.3f)
					{
						lastY = val15.y;
						lastGain = climbT;
					}
					if (climbT - lastGain > 6f)
					{
						Vector3 val16 = val15 - c0;
						object arg = Vector3.SignedAngle(Vector3.forward, new Vector3(val16.x, 0f, val16.z), Vector3.up);
						val3 = new Vector2(val16.x, val16.z);
						walkLog.Add($"climb STUCK at {arg:F0}°/{((Vector2)(ref val3)).magnitude:F1}m/y{val15.y:F1}");
						break;
					}
					Vector3 val17 = val15 - c0;
					val17.y = 0f;
					Vector3 normalized2 = ((Vector3)(ref val17)).normalized;
					Vector3 val18 = Quaternion.Euler(0f, spin * 90f, 0f) * normalized2 + normalized2 * Mathf.Clamp((1.2f - ((Vector3)(ref val17)).magnitude) * 3f, -1f, 1f);
					Vector3 val19 = ((Component)anchor).transform.TransformDirection(val18);
					val19.y = 0f;
					WalkDir = ((Vector3)(ref val19)).normalized;
					Look(player, ((Character)player).GetEyePoint() + ((Vector3)(ref val19)).normalized);
					climbT += Time.deltaTime;
					yield return null;
				}
				WalkDir = null;
				bestY = Mathf.Max(bestY, ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position).y);
				walkLog.Add($"climbed to y{((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position).y:F1} in {climbT:F0} s");
				reached++;
			}
			route.Add(Vector3.zero);
			float walkedUp = ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position).y;
			Check("walk in from outside, through the door and onto the stair", reached == route.Count && bestY >= 1.5f, $"{reached}/{route.Count} legs, up to {bestY:F1} m above the base");
			Note($"the walking bot's full climb reached {walkedUp:F1} m (the spiral itself is checked step by step below)");
			Note("walk: " + string.Join("; ", walkLog) + ((LastWalkState.Length > 0) ? (" | stalled: " + LastWalkState) : ""));
			if (reached < route.Count || walkedUp <= 9.5f)
			{
				yield return Shot("stair_stuck");
				Vector3 val20 = ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position) - c0;
				val20.y = 0f;
				CamPos = anchor.World(c0 - ((Vector3)(ref val20)).normalized * 3f + Vector3.up * (walkedUp + 1.6f));
				CamRot = Quaternion.LookRotation(((Component)player).transform.position + Vector3.up * 0.8f - CamPos.Value);
				yield return (object)new WaitForSeconds(0.5f);
				yield return Shot("stair_stuck_view");
				CamPos = null;
			}
			int bad = 0;
			int headBumps = 0;
			List<string> notes = new List<string>();
			int solid = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "terrain" });
			for (int k = 1; k < 40; k += 3)
			{
				Vector3 val21 = Quaternion.Euler(0f, TowerPrefab.SpiralStart + (float)k * TowerPrefab.SpiralDelta, 0f) * TowerPrefab.SpiralQuarter;
				val21.y = 0f;
				Vector3 wp = c0 + ((Vector3)(ref val21)).normalized * 1.25f + Vector3.up * (0.5f + (float)k + 0.6f);
				yield return Tp(player, anchor.World(wp + Vector3.up * 0.4f));
				yield return (object)new WaitForSeconds(1f);
				float num24 = ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position).y - wp.y;
				if (!((Character)player).IsOnGround() || !(Mathf.Abs(num24) < 1.2f))
				{
					bad++;
					notes.Add(string.Format("step {0}: {1}, {2:F1} m off", k, ((Character)player).IsOnGround() ? "grounded" : "not grounded", num24));
				}
				if (Physics.Raycast(((Component)player).transform.position + Vector3.up * 0.3f, Vector3.up, 2f, solid))
				{
					headBumps++;
					notes.Add($"step {k}: low ceiling");
				}
			}
			Check("the spiral stair holds you at every step", bad == 0, (bad == 0) ? $"{40} steps, checked every 3rd" : string.Join("; ", notes));
			Check("headroom all the way up the stair", headBumps == 0, string.Join("; ", notes.Where((string n) => n.Contains("ceiling"))));
			(string, Vector3)[] array2 = new(string, Vector3)[3]
			{
				("top landing", c0 + TowerPrefab.QuadrantDir(40) + Vector3.up * 40.5f),
				("bridge to the deck", new Vector3(12f, 40.5f, -10f)),
				("deck", new Vector3(5f, 40.5f, -10f))
			};
			for (int k = 0; k < array2.Length; k++)
			{
				var (what, val22) = array2[k];
				yield return Tp(player, anchor.World(val22 + Vector3.up * 0.5f));
				yield return (object)new WaitForSeconds(2f);
				float num25 = ((Component)anchor).transform.InverseTransformPoint(((Component)player).transform.position).y - 40.5f;
				Check("stand on the " + what, ((Character)player).IsOnGround() && Mathf.Abs(num25) < 1f, $"{num25:F1} m from deck height");
			}
			Boarding componentInChildren = ((Component)anchor).GetComponentInChildren<Boarding>();
			Check("hook and chain to climb aboard", (Object)(object)componentInChildren != (Object)null && componentInChildren.GetHoverText().Length > 0);
			if ((Object)(object)componentInChildren != (Object)null && componentInChildren.Interact((Humanoid)(object)player, hold: false, alt: false))
			{
				yield return WaitFor(() => !((Character)player).IsTeleporting(), 20f);
				yield return (object)new WaitForSeconds(2f);
				Check("E at the chain puts you aboard", (Object)(object)((Character)player).GetStandingOnShip() == (Object)(object)zep.Ship);
				yield return Tp(player, anchor.World(new Vector3(5f, 41f, -10f)));
			}
			said = IvaldiNpc.Converse(player, anchor, give: false);
			Note("Ivaldi: " + said);
			Check("Ivaldi asks for hooks after the hull", anchor.Hooks == 0 && anchor.Stage == 1);
			AddHooks(inv, 3);
			IvaldiNpc.Converse(player, anchor, give: false);
			Check("talking (E) takes nothing", anchor.Hooks == 0 && inv.CountItems(Items.QuestHookName, -1, true) == 3);
			said = IvaldiNpc.Converse(player, anchor, give: true);
			Note("Ivaldi: " + said);
			yield return (object)new WaitForSeconds(1f);
			Check("he takes 3 hooks", anchor.Hooks == 3 && inv.CountItems(Items.QuestHookName, -1, true) == 0, $"hooks {anchor.Hooks}");
			AddHooks(inv, 2);
			said = IvaldiNpc.Converse(player, anchor, give: true);
			Note("Ivaldi: " + said);
			yield return (object)new WaitForSeconds(1f);
			Check("he takes only the 4th hook", anchor.Hooks == 4 && inv.CountItems(Items.QuestHookName, -1, true) == 1, $"hooks {anchor.Hooks}, left in bag {inv.CountItems(Items.QuestHookName, -1, true)}");
			said = IvaldiNpc.Converse(player, anchor, give: true);
			Note("Ivaldi: " + said);
			Check("without the stone there's no toxin", !anchor.Toxin && anchor.Stage == 2);
			yield return (object)new WaitForSeconds(1.5f);
			Check("hooks fitted: ballistas shown, empty", Ballistas(zep).All((BallistaMount ballistaMount) => ((Component)ballistaMount).gameObject.activeSelf && Armed(ballistaMount) == false), BallistaState(zep));
			foreach (Items.Need item2 in toxin)
			{
				inv.AddItem(((Object)item2.Item).name, item2.Amount, 1, 0, 0L, "", false, false);
			}
			said = IvaldiNpc.Converse(player, anchor, give: true);
			Note("Ivaldi: " + said);
			yield return (object)new WaitForSeconds(1f);
			Check("he takes the toxin: armed", anchor.Toxin && anchor.Armed && anchor.Stage == 3 && toxin.All((Items.Need n) => inv.CountItems(n.Name, -1, true) == 0));
			yield return (object)new WaitForSeconds(1.5f);
			Check("armed: ballistas loaded", Ballistas(zep).All((BallistaMount ballistaMount) => ((Component)ballistaMount).gameObject.activeSelf && Armed(ballistaMount) != false), BallistaState(zep));
			anchor.SetStage(0);
			yield return (object)new WaitForSeconds(2f);
			Check("skid_stage 0: no ship, nothing delivered", anchor.Stage == 0 && (Object)(object)anchor.Zeppelin == (Object)null && anchor.Hooks == 0 && !anchor.Toxin);
			anchor.SetStage(3);
			yield return WaitFor(() => (Object)(object)anchor.Zeppelin != (Object)null, 10f);
			zep = anchor.Zeppelin;
			yield return (object)new WaitForSeconds(1.5f);
			Check("skid_stage 3: ship moored, ballistas loaded", anchor.Stage == 3 && (Object)(object)zep != (Object)null && Ballistas(zep).All((BallistaMount ballistaMount) => ((Component)ballistaMount).gameObject.activeSelf && Armed(ballistaMount) != false), ((Object)(object)zep != (Object)null) ? BallistaState(zep) : "no ship");
			if ((Object)(object)zep == (Object)null)
			{
				yield break;
			}
			yield return (object)new WaitForSeconds(4f);
			ivaldi.Say(said);
			Look(player, ((Component)ivaldi).transform.position + Vector3.up * 1.2f);
			yield return (object)new WaitForSeconds(1f);
			yield return Shot("2_ivaldi");
			Character ic = ((Component)ivaldi).GetComponent<Character>();
			float health = ic.GetHealth();
			HitData val23 = new HitData();
			val23.m_damage.m_blunt = 500f;
			val23.m_point = ic.GetCenterPoint();
			ic.Damage(val23);
			yield return (object)new WaitForSeconds(1f);
			Check("Ivaldi can't be hurt", (Object)(object)ic != (Object)null && !ic.IsDead() && ic.GetHealth() >= health, $"health {health:F0} -> {ic.GetHealth():F0}");
			int before = anchor.EldersNearby();
			Character spawned = anchor.SpawnElder();
			yield return (object)new WaitForSeconds(1f);
			Check("elder gjall spawner works", (Object)(object)spawned != (Object)null && anchor.EldersNearby() == before + 1, ((Object)(object)spawned != (Object)null) ? $"at {Vector3.Distance(((Component)spawned).transform.position, ((Component)anchor).transform.position):F0} m, scale {((Component)spawned).transform.localScale.x:F1}, health {spawned.GetMaxHealth():F0}" : "");
			if ((Object)(object)spawned != (Object)null)
			{
				ZNetScene.instance.Destroy(((Component)spawned).gameObject);
			}
			ZeppelinCapture cap = ((Component)zep).GetComponent<ZeppelinCapture>();
			for (int k = 0; k < 2; k++)
			{
				string what = ((k == 0) ? "port" : "starboard");
				Transform val24 = cap.Harness(k);
				Vector3 val25 = ((Component)zep).transform.right * ((k == 0) ? (-1f) : 1f);
				Character elder = anchor.SpawnElder(val24.position + val25 * 12f + Vector3.up * 4f);
				if (k == 0 && (Object)(object)elder != (Object)null)
				{
					Look(player, ((Component)elder).transform.position);
					yield return (object)new WaitForSeconds(1.2f);
					yield return Shot("3_capture_port");
				}
				string key = ((k == 0) ? "skid_gjall_p" : "skid_gjall_s");
				yield return WaitFor(() => ((Component)zep).GetComponent<ZNetView>().GetZDO().GetBool(key, false), 30f);
				Check("elder gjall captured on the " + what + " side", ((Component)zep).GetComponent<ZNetView>().GetZDO().GetBool(key, false));
				yield return (object)new WaitForSeconds(1f);
				Check("the live " + what + " gjall is gone", (Object)(object)elder == (Object)null);
			}
			Check("zeppelin operational after both captures", zep.Operational && anchor.Stage == 5);
			yield return WaitFor(() => anchor.Done && TowerAnchor.RecipeUnlocked, 10f);
			Check("the first ship ends the quest and unlocks the recipe for the world", anchor.Done && TowerAnchor.RecipeUnlocked);
			said = IvaldiNpc.Converse(player, anchor, give: true);
			Note("Ivaldi: " + said);
			Check("afterwards Ivaldi only says farewell", said == Loc.L("$skid_say_farewell"));
			Check("the ship piece shows on the hammer now", (Object)(object)keelPiece != (Object)null && player.HaveRequirements(keelPiece, (RequirementMode)1));
			yield return (object)new WaitForSeconds(1f);
			Vector3 pos = ((Component)anchor).transform.position + ((Component)anchor).transform.rotation * new Vector3(-60f, 0f, 25f);
			pos.y = ((Component)anchor).transform.position.y + 40.5f + 5f;
			yield return Tp(player, pos);
			Look(player, ((Component)zep).transform.position + Vector3.up * 6f);
			yield return (object)new WaitForSeconds(0.5f);
			yield return Shot("4_harnessed");
			Ship ship = zep.Ship;
			Vector3 seat = (((Object)(object)ship.m_shipControlls.m_attachPoint != (Object)null) ? ship.m_shipControlls.m_attachPoint.position : ((Component)ship.m_shipControlls).transform.position);
			yield return Tp(player, seat + ((Component)zep).transform.forward * 1.5f + Vector3.up * 0.4f);
			yield return (object)new WaitForSeconds(1f);
			Check("teleported aboard, standing on the ship", (Object)(object)((Character)player).GetStandingOnShip() == (Object)(object)ship, $"{Vector3.Distance(((Component)player).transform.position, seat):F1} m from the helm");
			ship.m_shipControlls.Interact((Humanoid)(object)player, false, false);
			yield return (object)new WaitForSeconds(1f);
			Check("player at the helm", (Object)(object)player.GetControlledShip() == (Object)(object)ship);
			for (int k = 0; k < 3; k++)
			{
				ship.Forward();
				yield return (object)new WaitForSeconds(0.4f);
			}
			Check("speed set to full", (int)ship.GetSpeedSetting() == 4, ((object)ship.GetSpeedSetting()/*cast due to .constrained prefix*/).ToString());
			LiftOverride = 1f;
			Vector3 p0 = ((Component)zep).transform.position;
			yield return (object)new WaitForSeconds(10f);
			Look(player, ((Component)zep).transform.position + ((Component)zep).transform.forward * 30f);
			yield return Shot("5_flying");
			yield return (object)new WaitForSeconds(10f);
			Vector3 position = ((Component)zep).transform.position;
			LiftOverride = 0f;
			val3 = new Vector2(position.x - p0.x, position.z - p0.z);
			float num26 = ((Vector2)(ref val3)).magnitude / 20f;
			float num27 = position.y - p0.y;
			Check("flies forward at full speed", num26 > SkidbladnirPlugin.SpeedFull.Value * 0.45f, $"{num26:F1} m/s average over 20 s (full = {SkidbladnirPlugin.SpeedFull.Value})");
			Check("climbs with Jump held", num27 > 20f, $"{num27:F1} m in 20 s (climb rate {SkidbladnirPlugin.ClimbSpeed.Value} m/s)");
			Check("player still aboard after flying", Vector3.Distance(((Component)player).transform.position, ((Component)zep).transform.position) < 25f);
			ship.Stop();
			yield return (object)new WaitForSeconds(6f);
			val6 = ((Component)zep).GetComponent<Rigidbody>().linearVelocity;
			float magnitude = ((Vector3)(ref val6)).magnitude;
			Check("stops and hovers", magnitude < 2f, $"{magnitude:F1} m/s after 6 s");
			LiftOverride = null;
			ZeppelinBombing bombing = ((Component)zep).GetComponent<ZeppelinBombing>();
			Transform obj2 = ((Component)zep).transform.Find(ZeppelinPrefab.BallistaNames[0]);
			BallistaReapply portBallista = ((obj2 != null) ? ((Component)obj2).GetComponent<BallistaReapply>() : null);
			Check("ballistas offer 'reapply toxin' when flying", (Object)(object)portBallista != (Object)null && portBallista.GetHoverText().Length > 0, ((Object)(object)portBallista != (Object)null) ? portBallista.GetHoverText().Replace("\n", " | ") : "none");
			int drops0 = ZeppelinBombing.DropsDone;
			int bursts0 = ZeppelinBombing.BurstsDone;
			int hits0 = ZeppelinBombing.HitsDone;
			bool ok4 = (Object)(object)portBallista != (Object)null && portBallista.Interact((Humanoid)(object)player, hold: false, alt: false);
			Check("reapply accepted", ok4);
			yield return WaitFor(() => ZeppelinBombing.BurstsDone - bursts0 >= SkidbladnirPlugin.BombTicks.Value, 30f);
			Check("the gjall drops ticks", ZeppelinBombing.DropsDone > drops0);
			Check("every tick bursts on impact", ZeppelinBombing.BurstsDone - bursts0 >= SkidbladnirPlugin.BombTicks.Value, $"{ZeppelinBombing.BurstsDone - bursts0} bursts, {ZeppelinBombing.HitsDone - hits0} things hit");
			Check("the ship took no harm", (Object)(object)zep != (Object)null && ((Component)zep).GetComponent<ZNetView>().IsValid());
			Check("a second dose waits for the cooldown", (Object)(object)bombing != (Object)null && bombing.CooldownLeft(0) > 0f && !portBallista.Interact((Humanoid)(object)player, hold: false, alt: false), $"{bombing?.CooldownLeft(0):F0} s left");
			if (ship.IsPlayerInBoat(player))
			{
				ship.m_shipControlls.Interact((Humanoid)(object)player, false, false);
			}
			((Character)player).AttachStop();
			Vector3 yard = ((Component)anchor).transform.position + ((Component)anchor).transform.rotation * new Vector3(70f, 0f, 60f);
			yard.y = ZoneSystem.instance.GetGroundHeight(yard);
			yield return Tp(player, yard + new Vector3(0f, 1f, -30f));
			yield return (object)new WaitForSeconds(2f);
			yard.y = ZoneSystem.instance.GetGroundHeight(yard);
			int built0 = ZeppelinKeel.Built;
			int ships0 = ZeppelinFlight.Instances.Count;
			if ((Object)(object)ZeppelinKeel.Prefab != (Object)null)
			{
				Object.Instantiate<GameObject>(ZeppelinKeel.Prefab, yard, Quaternion.identity);
			}
			yield return WaitFor(() => ZeppelinKeel.Built > built0, 10f);
			yield return (object)new WaitForSeconds(1f);
			ZeppelinFlight second = (from f in ZeppelinFlight.Instances
				where (Object)(object)f != (Object)null && (Object)(object)f != (Object)(object)zep
				orderby Vector3.Distance(((Component)f).transform.position, yard)
				select f).FirstOrDefault();
			Vector3 val26 = (((Object)(object)second != (Object)null) ? (((Component)second).transform.position - yard) : Vector3.zero);
			AutoTest autoTest4 = this;
			int ok5;
			if (ZeppelinKeel.Built > built0 && (Object)(object)second != (Object)null)
			{
				val3 = new Vector2(val26.x, val26.z);
				if (((Vector2)(ref val3)).magnitude < 15f && val26.y > -2f)
				{
					ok5 = ((val26.y < 90f) ? 1 : 0);
					goto IL_4534;
				}
			}
			ok5 = 0;
			goto IL_4534;
			IL_4534:
			object detail3;
			if (!((Object)(object)second != (Object)null))
			{
				detail3 = "none";
			}
			else
			{
				object[] array3 = new object[4];
				val3 = new Vector2(val26.x, val26.z);
				array3[0] = ((Vector2)(ref val3)).magnitude;
				array3[1] = val26.y;
				array3[2] = ships0;
				array3[3] = ZeppelinFlight.Instances.Count;
				detail3 = string.Format("{0:F1} m across and {1:F1} m above the frame (risen clear of the trees); ships {2} -> {3}", array3);
			}
			autoTest4.Check("a placed frame becomes a new Skidbladnir", (byte)ok5 != 0, (string)detail3);
			Check("the frame is gone", Object.FindObjectsOfType<ZeppelinKeel>().All((ZeppelinKeel zeppelinKeel) => (Object)(object)((Component)zeppelinKeel).GetComponent<ZNetView>() == (Object)null || !((Component)zeppelinKeel).GetComponent<ZNetView>().IsValid()));
			if ((Object)(object)second == (Object)null)
			{
				yield break;
			}
			Check("the new ship is flight-ready and its own", second.Operational && (Object)(object)zep != (Object)null && zep.Operational && ((Component)second).GetComponent<ZNetView>().GetZDO() != ((Component)zep).GetComponent<ZNetView>().GetZDO() && ((Component)second).GetComponent<ZNetView>().GetZDO().GetVec3("skid_moor_pos", Vector3.zero) == Vector3.zero);
			Look(player, ((Component)second).transform.position + Vector3.up * 4f);
			yield return (object)new WaitForSeconds(1f);
			yield return Shot("6_second_ship");
			Ship ship2 = second.Ship;
			Vector3 val27 = (((Object)(object)ship2.m_shipControlls.m_attachPoint != (Object)null) ? ship2.m_shipControlls.m_attachPoint.position : ((Component)ship2.m_shipControlls).transform.position);
			yield return Tp(player, val27 + ((Component)second).transform.forward * 1.5f + Vector3.up * 0.4f);
			yield return (object)new WaitForSeconds(1f);
			Check("standing on the second ship", (Object)(object)((Character)player).GetStandingOnShip() == (Object)(object)ship2, "standing on " + (((Object)(object)((Character)player).GetStandingOnShip() == (Object)null) ? "nothing" : (((Object)(object)((Character)player).GetStandingOnShip() == (Object)(object)ship) ? "the first ship" : "?")));
			ship2.m_shipControlls.Interact((Humanoid)(object)player, false, false);
			yield return (object)new WaitForSeconds(1f);
			Check("at the second ship's helm", (Object)(object)player.GetControlledShip() == (Object)(object)ship2);
			for (int k = 0; k < 3; k++)
			{
				ship2.Forward();
				yield return (object)new WaitForSeconds(0.4f);
			}
			Rigidbody component2 = ((Component)second).GetComponent<Rigidbody>();
			Note($"second ship: owner {((Component)second).GetComponent<ZNetView>().IsOwner()}, speed {ship2.GetSpeedSetting()}, aboard {ship2.IsPlayerInBoat(player)}, " + $"kinematic {((component2 != null) ? new bool?(component2.isKinematic) : ((bool?)null))}, sleeping {((component2 != null) ? new bool?(component2.IsSleeping()) : ((bool?)null))}, at {((Component)second).transform.position}");
			LiftOverride = 1f;
			Vector3 q0 = ((Component)second).transform.position;
			Vector3 first0 = ((Component)zep).transform.position;
			yield return (object)new WaitForSeconds(12f);
			Vector3 position2 = ((Component)second).transform.position;
			LiftOverride = null;
			val3 = new Vector2(position2.x - q0.x, position2.z - q0.z);
			float num28 = ((Vector2)(ref val3)).magnitude / 12f;
			Check("the second ship flies", num28 > SkidbladnirPlugin.SpeedFull.Value * 0.35f && position2.y - q0.y > 8f, $"{num28:F1} m/s, climbed {position2.y - q0.y:F1} m in 12 s");
			Vector3 val28 = ((Component)second).transform.TransformPoint(second.HullCenter);
			IEnumerable<string> values = (from h in Physics.OverlapBox(val28, second.HullExtents + Vector3.one * 0.5f, ((Component)second).transform.rotation)
				where (Object)(object)h != (Object)null && !h.isTrigger && !((Component)h).transform.IsChildOf(((Component)second).transform) && (Object)(object)h.attachedRigidbody != (Object)(object)((Component)player).GetComponent<Rigidbody>()
				select h).Select(delegate(Collider h)
			{
				ZNetView componentInParent = ((Component)h).GetComponentInParent<ZNetView>();
				return (((componentInParent != null) ? ((Object)componentInParent).name : null) ?? ((Object)h).name) + "/" + LayerMask.LayerToName(((Component)h).gameObject.layer);
			}).Distinct().Take(12);
			Note("second ship hull touches: " + string.Join(", ", values) + $"; ground {ZoneSystem.instance.GetGroundHeight(val28):F1}, hull centre {val28.y:F1}");
			Check("the first ship stays where it was left", (Object)(object)zep != (Object)null && Vector3.Distance(((Component)zep).transform.position, first0) < 3f, ((Object)(object)zep != (Object)null) ? $"{Vector3.Distance(((Component)zep).transform.position, first0):F1} m" : "gone");
			ship2.Stop();
			if (ship2.IsPlayerInBoat(player))
			{
				ship2.m_shipControlls.Interact((Humanoid)(object)player, false, false);
			}
			((Character)player).AttachStop();
			Vector3 val29 = A0(anchor) + ((Component)anchor).transform.rotation * new Vector3(-70f, 0f, -60f);
			val29.y = ZoneSystem.instance.GetGroundHeight(val29) + 1f;
			yield return Tp(player, val29);
			yield return (object)new WaitForSeconds(1f);
			FieldInfo ghostField = AccessTools.Field(typeof(Player), "m_placementGhost");
			object oldGhost = ghostField.GetValue(player);
			ghostField.SetValue(player, ZeppelinKeel.Prefab);
			CamPos = ((Character)player).GetEyePoint();
			CamRot = Quaternion.LookRotation(((Component)player).transform.forward.Flat() + Vector3.up * 1.2f);
			yield return null;
			yield return null;
			object[] array4 = new object[6]
			{
				Vector3.zero,
				Vector3.zero,
				null,
				null,
				null,
				false
			};
			bool flag3 = (bool)AccessTools.Method(typeof(Player), "PieceRayTest", (Type[])null, (Type[])null).Invoke(player, array4);
			ghostField.SetValue(player, oldGhost);
			CamPos = null;
			Vector3 airPoint = (Vector3)array4[0];
			float num29 = airPoint.y - ZoneSystem.instance.GetGroundHeight(airPoint);
			Check("aiming at the sky with the ship frame gives a spot in the air", flag3 && num29 > 8f, $"{num29:F1} m above the ground, {Vector3.Distance(((Character)player).GetEyePoint(), airPoint):F1} m out");
			if (flag3 && (Object)(object)ZeppelinKeel.Prefab != (Object)null)
			{
				int b0 = ZeppelinKeel.Built;
				Object.Instantiate<GameObject>(ZeppelinKeel.Prefab, airPoint, Quaternion.LookRotation(((Component)player).transform.forward.Flat()));
				yield return WaitFor(() => ZeppelinKeel.Built > b0, 10f);
				yield return (object)new WaitForSeconds(1f);
				ZeppelinFlight zeppelinFlight = (from f in ZeppelinFlight.Instances
					where (Object)(object)f != (Object)null
					orderby Vector3.Distance(((Component)f).transform.position, airPoint)
					select f).FirstOrDefault();
				float num30 = (((Object)(object)zeppelinFlight != (Object)null) ? (((Component)zeppelinFlight).transform.position.y - ZoneSystem.instance.GetGroundHeight(((Component)zeppelinFlight).transform.position)) : 0f);
				Check("a frame placed in the air makes a ship up there", (Object)(object)zeppelinFlight != (Object)null && zeppelinFlight.Operational && Vector3.Distance(((Component)zeppelinFlight).transform.position, airPoint) < 12f && num30 > 6f, ((Object)(object)zeppelinFlight != (Object)null) ? $"{Vector3.Distance(((Component)zeppelinFlight).transform.position, airPoint):F1} m from the frame, {num30:F1} m above the ground" : "none");
			}
			float Ground(Vector3 local)
			{
				//IL_000b: 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_0021: Unknown result type (might be due to invalid IL or missing references)
				return ZoneSystem.instance.GetGroundHeight(anchor.World(local)) - ((Component)anchor).transform.position.y;
			}
		}

		private static Vector3 A0(TowerAnchor a)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return ((Component)a).transform.position;
		}

		private static IEnumerator WalkTo(Player p, TowerAnchor a, Vector3 local, float timeout, Action<bool> done)
		{
			//IL_0015: 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)
			Vector3 target = a.World(local);
			float t = 0f;
			bool ok = false;
			while (t < timeout)
			{
				Vector3 val = target - ((Component)p).transform.position;
				val.y = 0f;
				if (((Vector3)(ref val)).magnitude < 0.4f)
				{
					ok = true;
					break;
				}
				WalkDir = ((Vector3)(ref val)).normalized;
				Look(p, ((Character)p).GetEyePoint() + ((Vector3)(ref val)).normalized);
				t += Time.deltaTime;
				yield return null;
			}
			WalkDir = null;
			if (!ok)
			{
				object value = AccessTools.Field(typeof(Character), "m_moveDir").GetValue(p);
				object[] obj = new object[8]
				{
					((Character)p).IsOnGround(),
					null,
					null,
					null,
					null,
					null,
					null,
					null
				};
				Vector3 velocity = ((Character)p).GetVelocity();
				obj[1] = ((Vector3)(ref velocity)).magnitude;
				obj[2] = value;
				obj[3] = ((Character)p).IsAttached();
				obj[4] = ((Character)p).IsTeleporting();
				obj[5] = ((Character)p).IsEncumbered();
				obj[6] = ((Character)p).IsDead();
				obj[7] = ((Character)p).InWater();
				LastWalkState = string.Format("onGround {0}, vel {1:F1}, moveDir {2}, attached {3}, teleporting {4}, encumbered {5}, dead {6}, inWater {7}", obj);
			}
			done(ok);
		}

		private static BallistaMount[] Ballistas(ZeppelinFlight zep)
		{
			return ((Component)zep).GetComponentsInChildren<BallistaMount>(true);
		}

		private static bool? Armed(BallistaMount b)
		{
			if (!((Object)(object)b.Armed != (Object)null))
			{
				if (!((Object)(object)b.Unarmed != (Object)null))
				{
					return null;
				}
				return !b.Unarmed.activeSelf;
			}
			return b.Armed.activeSelf;
		}

		private static string BallistaState(ZeppelinFlight zep)
		{
			return string.Join(", ", from b in Ballistas(zep)
				select ((Object)b).name + ": " + (((Component)b).gameObject.activeSelf ? "shown" : "hidden") + "/" + ((!Armed(b).HasValue) ? "?" : ((Armed(b) == true) ? "loaded" : "empty")));
		}

		private static void AddHooks(Inventory inv, int n)
		{
			for (int i = 0; i < n; i++)
			{
				inv.AddItem(Items.QuestHookPrefab, 1, 1, 0, 0L, "", false, false);
			}
		}

		private static bool InFootprint(TowerAnchor a, Vector3 p)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = ((Component)a).transform.InverseTransformPoint(p);
			if (Mathf.Abs(val.x) < 10f)
			{
				return Mathf.Abs(val.z) < 18f;
			}
			return false;
		}

		private IEnumerator IvaldiTour(Player player)
		{
			yield return WaitFor(() => SkidWorld.SitesReady, 300f);
			if (SkidWorld.Sites.Count == 0)
			{
				Check("tower sites", ok: false);
				yield break;
			}
			Vector3 val = SkidWorld.Sites.OrderByDescending(MistShare).First();
			yield return Tp(player, val + new Vector3(0f, 5f, -40f), distant: true);
			yield return WaitFor(() => TowerAnchor.Instances.Count > 0 && TowerAnchor.Instances[0].Built, 90f);
			TowerAnchor anchor = TowerAnchor.Instances.FirstOrDefault();
			Check("tower raised and built", (Object)(object)anchor != (Object)null && anchor.Built);
			if ((Object)(object)anchor == (Object)null)
			{
				yield break;
			}
			anchor.SetStage(3);
			yield return WaitFor(() => (Object)(object)anchor.Zeppelin != (Object)null && Object.FindObjectsOfType<IvaldiNpc>().Length != 0, 30f);
			IvaldiNpc iv = Object.FindObjectsOfType<IvaldiNpc>().FirstOrDefault();
			Check("ship moored, Ivaldi on the deck", (Object)(object)anchor.Zeppelin != (Object)null && (Object)(object)iv != (Object)null);
			if ((Object)(object)iv == (Object)null)
			{
				yield break;
			}
			float deck = 40.5f;
			yield return Tp(player, anchor.World(new Vector3(-6f, deck + 0.5f, -14f)));
			yield return (object)new WaitForSeconds(3f);
			try
			{
				File.WriteAllText(Path.Combine(_shotDir, "dverger.txt"), DumpDverger(((Component)iv).gameObject));
				Note("dverger.txt written");
			}
			catch (Exception ex)
			{
				Note("dump failed: " + ex);
			}
			EnvMan env = EnvMan.instance;
			env.m_debugEnv = "Clear";
			env.m_debugTimeOfDay = true;
			if ((Object)(object)Hud.instance != (Object)null)
			{
				Hud.instance.m_userHidden = true;
			}
			GameObject visual = Traverse.Create((object)player).Field("m_visual").GetValue<GameObject>();
			if ((Object)(object)visual != (Object)null)
			{
				visual.SetActive(false);
			}
			Vector3 home = anchor.World(TowerPrefab.IvaldiHome);
			Vector3 face = ((Component)anchor).transform.rotation * Vector3.back;
			(string name, float angle, float dist, float camH, float atH)[] views = new(string, float, float, float, float)[3]
			{
				("front", 0f, 1.9f, 1f, 0.9f),
				("threequarter", 40f, 1.9f, 1.05f, 0.9f),
				("side", 90f, 1.9f, 1f, 0.9f)
			};
			List<(float t, Vector3 dir)> suns = new List<(float, Vector3)>();
			for (float t = 0.33f; t <= 0.671f; t += 0.02f)
			{
				env.m_debugTime = t;
				yield return null;
				yield return null;
				Vector3 val2 = (((Object)(object)env.m_dirLight != (Object)null) ? ((Component)env.m_dirLight).transform.forward : Vector3.down);
				float num = Mathf.Asin(Mathf.Clamp(0f - val2.y, -1f, 1f)) * 57.29578f;
				if (num >= 15f && num <= 40f)
				{
					suns.Add((t, val2));
				}
			}
			if (suns.Count == 0)
			{
				suns.Add((0.38f, Vector3.down));
			}
			int mask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" });
			float ss = IvaldiLook.Scale;
			float bestT = suns[0].t;
			float num2 = float.MinValue;
			Vector3 val3 = face;
			Vector3 val5;
			foreach (var item3 in suns)
			{
				float item = item3.t;
				Vector3 item2 = item3.dir;
				for (int num3 = -90; num3 <= 90; num3 += 15)
				{
					Vector3 val4 = Quaternion.AngleAxis((float)num3, Vector3.up) * face;
					val5 = new Vector3(item2.x, 0f, item2.z);
					float num4 = Vector3.Dot(((Vector3)(ref val5)).normalized, -val4) - (float)Mathf.Abs(num3) / 1800f;
					if (num4 > num2 && Clear(val4))
					{
						num2 = num4;
						bestT = item;
						val3 = val4;
					}
				}
			}
			env.m_debugTime = bestT;
			face = val3;
			yield return null;
			Vector3 val6 = (((Object)(object)env.m_dirLight != (Object)null) ? ((Component)env.m_dirLight).transform.forward : Vector3.down);
			AutoTest autoTest = this;
			string text = $"photo light: time {bestT:F2}, sun {Mathf.Asin(Mathf.Clamp(0f - val6.y, -1f, 1f)) * 57.29578f:F0}° up, ";
			val5 = new Vector3(val6.x, 0f, val6.z);
			autoTest.Note(text + $"{Vector3.Dot(((Vector3)(ref val5)).normalized, -face):F2} onto his front, turned {Vector3.SignedAngle(((Component)anchor).transform.rotation * Vector3.back, face, Vector3.up):F0}° from the deck's line");
			IvaldiPose = face;
			IvaldiLook.LookOverride = true;
			Rigidbody component = ((Component)iv).GetComponent<Rigidbody>();
			((Component)iv).transform.position = home;
			if ((Object)(object)component != (Object)null)
			{
				component.position = home;
				component.linearVelocity = Vector3.zero;
			}
			((Component)iv).transform.rotation = Quaternion.LookRotation(face);
			yield return (object)new WaitForSeconds(4f);
			IvaldiLook look = ((Component)iv).GetComponent<IvaldiLook>();
			float s = (((Object)(object)look != (Object)null) ? look.CurrentScale : 1f);
			Character component2 = ((Component)iv).GetComponent<Character>();
			float num5 = ((Component)anchor).transform.InverseTransformPoint(((Component)iv).transform.position).y - deck;
			Check("Ivaldi at his size, standing on the deck", Mathf.Abs(s - IvaldiLook.Scale) < 0.01f && component2.IsOnGround() && Mathf.Abs(num5) < 0.5f, string.Format("scale {0:F2}, {1:F2} m from deck height, {2}", s, num5, component2.IsOnGround() ? "grounded" : "not grounded"));
			Check("he holds the hammer (equipped, scaled)", (Object)(object)look != (Object)null && (Object)(object)look.HammerInstance != (Object)null, IvaldiLook.HammerReport);
			bool free;
			string text2 = Clearance(iv, anchor, out free);
			Note(text2);
			Check("Ivaldi clears the ship and the tower wherever he wanders", free, text2);
			if (IvaldiPoseRef)
			{
				yield return PoseCompare(player, visual, iv, look, home, face, s);
			}
			IvaldiLook.HeadOverride = "Hood";
			yield return (object)new WaitForSeconds(2f);
			Note("grip: " + IvaldiLook.GripBones + "; " + IvaldiLook.HammerReport);
			(string name, float angle, float up, float dist)[] array = new(string, float, float, float)[6]
			{
				("hand_side", 90f, 0.05f, 0.5f),
				("hand_front", 15f, 0.05f, 0.5f),
				("hand_threequarter", 50f, 0.12f, 0.5f),
				("hand_below", 70f, -0.35f, 0.45f),
				("hand_behind", 140f, 0.05f, 0.5f),
				("hand_above", 60f, 0.4f, 0.4f)
			};
			for (int num6 = 0; num6 < array.Length; num6++)
			{
				(string name, float angle, float up, float dist) v = array[num6];
				Vector3 val7 = (((Object)(object)look != (Object)null && look.FistCentre.HasValue) ? look.FistCentre.Value : (((Component)iv).transform.position + Vector3.up * 0.6f * s));
				Vector3 val8 = Quaternion.AngleAxis(v.angle, Vector3.up) * face;
				CamPos = val7 + val8 * v.dist * s + Vector3.up * v.up * s;
				CamRot = Quaternion.LookRotation(val7 - CamPos.Value);
				yield return (object)new WaitForSeconds(1.5f);
				yield return Shot(v.name);
			}
			if (IvaldiSweep && !IvaldiPoseRef && (Object)(object)look != (Object)null)
			{
				bestT = SkidbladnirPlugin.IvaldiHammerThickness.Value;
				float t = IvaldiLook.FingerCurl;
				Vector2 shift0 = IvaldiLook.GripShift;
				(float, float, Vector2)[] sweepCases = SweepCases;
				for (int num6 = 0; num6 < sweepCases.Length; num6++)
				{
					var (num7, num8, val9) = sweepCases[num6];
					SkidbladnirPlugin.IvaldiHammerThickness.Value = num7;
					IvaldiLook.FingerCurl = num8;
					IvaldiLook.GripShift = val9;
					string tag = $"sweep_t{num7 * 10f:F0}_c{num8:F0}_k{val9.x:F0}_f{val9.y:F0}";
					yield return (object)new WaitForSeconds(0.5f);
					array = new(string, float, float, float)[5]
					{
						("front", 15f, 0.05f, 0.45f),
						("threequarter", 50f, 0.12f, 0.45f),
						("side", 90f, 0.05f, 0.45f),
						("below", 70f, -0.3f, 0.4f),
						("body", 90f, 0.4f, 1.9f)
					};
					for (int num9 = 0; num9 < array.Length; num9++)
					{
						(string name, float angle, float up, float dist) v = array[num9];
						Vector3 val10 = (Vector3)(((??)look.FistCentre) ?? (((Component)iv).transform.position + Vector3.up * 0.6f * s));
						Vector3 val11 = ((v.name == "body") ? (((Component)iv).transform.position + Vector3.up * 0.9f * s) : val10);
						CamPos = ((v.name == "body") ? ((Component)iv).transform.position : val10) + Quaternion.AngleAxis(v.angle, Vector3.up) * face * v.dist * s + Vector3.up * v.up * s;
						CamRot = Quaternion.LookRotation(val11 - CamPos.Value);
						yield return (object)new WaitForSeconds(1f);
						yield return Shot(tag + "_" + v.name);
					}
				}
				SkidbladnirPlugin.IvaldiHammerThickness.Value = bestT;
				IvaldiLook.FingerCurl = t;
				IvaldiLook.GripShift = shift0;
				yield return (object)new WaitForSeconds(0.5f);
			}
			string[] array2 = ((!IvaldiHands) ? new string[3] { "Bare", "Hood", "Circlet" } : new string[1] { "Hood" });
			for (int num6 = 0; num6 < array2.Length; num6++)
			{
				string tag = (IvaldiLook.HeadOverride = array2[num6]);
				yield return (object)new WaitForSeconds(2f);
				Note(tag + ": " + IvaldiLook.Report);
				(string name, float angle, float dist, float camH, float atH)[] array3 = views;
				for (int num9 = 0; num9 < array3.Length; num9++)
				{
					(string name, float angle, float dist, float camH, float atH) v2 = array3[num9];
					Vector3 position = ((Component)iv).transform.position;
					Vector3 val12 = Quaternion.AngleAxis(v2.angle, Vector3.up) * face;
					CamPos = position + val12 * v2.dist * s + Vector3.up * v2.camH * s;
					CamRot = Quaternion.LookRotation(position + Vector3.up * v2.atH * s - CamPos.Value);
					yield return (object)new WaitForSeconds(2f);
					yield return Shot(tag.ToLowerInvariant() + "_" + v2.name);
				}
			}
			array2 = new string[3] { "Bare", "Hood", "Circlet" };
			for (int num6 = 0; num6 < array2.Length; num6++)
			{
				string tag = (IvaldiLook.HeadOverride = array2[num6]);
				yield return (object)new WaitForSeconds(1.5f);
				Vector3 position2 = ((Component)iv).transform.position;
				CamPos = position2 + Quaternion.AngleAxis(30f, Vector3.up) * face * 1.4f * s + Vector3.up * 1.35f * s;
				CamRot = Quaternion.LookRotation(position2 + Vector3.up * 1.15f * s - CamPos.Value);
				yield return (object)new WaitForSeconds(1.5f);
				yield return Shot(tag.ToLowerInvariant() + "_head");
			}
			IvaldiLook.HeadOverride = null;
			bool[] array4 = (IvaldiHands ? new bool[1] : new bool[2] { false, true });
			foreach (bool back in array4)
			{
				IvaldiLook.BackToolOverride = back;
				yield return (object)new WaitForSeconds(2f);
				Vector3 position3 = ((Component)iv).transform.position;
				CamPos = position3 + Quaternion.AngleAxis(150f, Vector3.up) * face * 1.9f * s + Vector3.up * 1.1f * s;
				CamRot = Quaternion.LookRotation(position3 + Vector3.up * 0.9f * s - CamPos.Value);
				yield return (object)new WaitForSeconds(2f);
				yield return Shot(back ? "default_back_pickaxe" : "default_back");
			}
			IvaldiLook.BackToolOverride = null;
			yield return (object)new WaitForSeconds(1f);
			Vector3 position4 = ((Component)iv).transform.position;
			CamPos = position4 + Quaternion.AngleAxis(25f, Vector3.up) * face * 1.1f * s + Vector3.up * 1.25f * s;
			CamRot = Quaternion.LookRotation(position4 + Vector3.up * 1.05f * s - CamPos.Value);
			yield return (object)new WaitForSeconds(2f);
			yield return Shot("default_close");
			CamPos = anchor.World(new Vector3(2f, deck + 2.4f, -21f));
			CamRot = Quaternion.LookRotation(anchor.World(new Vector3(6.5f, deck + 2.4f, -8f)) - CamPos.Value);
			yield return (object)new WaitForSeconds(3f);
			yield return Shot("default_ship_wide");
			Note("default: " + IvaldiLook.Report);
			CamPos = null;
			IvaldiPose = null;
			IvaldiLook.LookOverride = null;
			IvaldiLook.ScaleOverride = null;
			bool Clear(Vector3 f)
			{
				//IL_000e: 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)
				return views.Concat<(string, float, float, float, float)>(new(string, float, float, float, float)[1] { ("back", 150f, 1.9f, 1.1f, 0.9f) }).All(delegate((string, float, float, float, float) tuple2)
				{
					//IL_000b: 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_001b: Unknown result type (might be due to invalid IL or missing references)
					//IL_0021: Unknown result type (might be due to invalid IL or missing references)
					//IL_0026: Unknown result type (might be due to invalid IL or missing references)
					//IL_0031: 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_004b: 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_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_0070: Unknown result type (might be due to invalid IL or missing references)
					//IL_007c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0081: 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_009c: Unknown result type (might be due to invalid IL or missing references)
					//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
					//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
					//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val13 = home + Quaternion.AngleAxis(tuple2.Item2, Vector3.up) * f * tuple2.Item3 * ss + Vector3.up * tuple2.Item4 * ss;
					return !Physics.Linecast(home + Vector3.up * tuple2.Item5 * ss, val13, mask) && !Physics.CheckSphere(val13, 0.3f, mask);
				});
			}
		}

		private IEnumerator PoseCompare(Player player, GameObject visual, IvaldiNpc iv, IvaldiLook look, Vector3 home, Vector3 face, float s)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: 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)
			string[] array = new string[4] { "SledgeDemolisher", "Hammer", "MaceIron", "MaceSilver" };
			foreach (string text in array)
			{
				GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text);
				ItemDrop val = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val != (Object)null)
				{
					Note($"{text}: type {val.m_itemData.m_shared.m_itemType}, skill {val.m_itemData.m_shared.m_skillType}, animation {val.m_itemData.m_shared.m_animationState}");
				}
			}
			Vector3 start = ((Component)player).transform.position;
			Rigidbody pbody = ((Component)player).GetComponent<Rigidbody>();
			int mask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "vehicle" });
			if ((Object)(object)visual != (Object)null)
			{
				visual.SetActive(true);
			}
			(string name, float angle)[] views = new(string, float)[3]
			{
				("front", 0f),
				("side", 90f),
				("threequarter", 40f)
			};
			string[] array2 = new string[2] { "Hammer", "MaceIron" };
			foreach (string itemName in array2)
			{
				GameObject itemPrefab2 = ObjectDB.instance.GetItemPrefab(itemName);
				if ((Object)(object)itemPrefab2 == (Object)null)
				{
					Note(itemName + " not found");
					continue;
				}
				ItemData item = itemPrefab2.GetComponent<ItemDrop>().m_itemData.Clone();
				item.m_dropPrefab = itemPrefab2;
				item.m_stack = 1;
				item.m_durability = item.GetMaxDurability();
				((Humanoid)player).GetInventory().AddItem(item);
				((Humanoid)player).EquipItem(item, false);
				string tag = itemName.ToLowerInvariant();
				(string name, float angle)[] array3 = views;
				for (int k = 0; k < array3.Length; k++)
				{
					(string name, float angle) v = array3[k];
					var (pos, dist) = Beside(v.angle, 3.6f);
					Place(pos);
					yield return (object)new WaitForSeconds(2.5f);
					Vector3 dir = Quaternion.AngleAxis(v.angle, Vector3.up) * face;
					CamPos = pos + dir * Mathf.Min(2.2f, dist) + Vector3.up * 1.1f;
					CamRot = Quaternion.LookRotation(pos + Vector3.up * 0.95f - CamPos.Value);
					Place(pos);
					yield return (object)new WaitForSeconds(1f);
					yield return Shot("ref_player_" + tag + "_" + v.name);
					if (v.name == "front")
					{
						Note($"player at rest with {itemName} (facing {Vector3.Angle(((Component)player).transform.forward, face):F0}° off his line): " + MeasurePlayer(player));
						Transform val2 = PlayerBone(player, "RightHand");
						if ((Object)(object)val2 != (Object)null)
						{
							CamPos = val2.position + (((Component)player).transform.right * 0.8f + ((Component)player).transform.forward * 0.3f) + Vector3.up * 0.1f;
							CamRot = Quaternion.LookRotation(val2.position - CamPos.Value);
							yield return (object)new WaitForSeconds(1f);
							yield return Shot("ref_player_" + tag + "_hand");
						}
					}
					if (!(itemName != "MaceIron"))
					{
						Vector3 val3 = (home + pos) * 0.5f;
						Place(pos);
						CamPos = val3 + dir * dist + Vector3.up * 1.1f;
						CamRot = Quaternion.LookRotation(val3 + Vector3.up * 0.85f - CamPos.Value);
						yield return (object)new WaitForSeconds(1f);
						yield return Shot("compare_" + v.name);
						if (v.name == "front")
						{
							Note($"player facing {Vector3.Angle(((Component)player).transform.forward, face):F0}° off his line in compare_front");
						}
					}
				}
				((Humanoid)player).UnequipItem(item, false);
				((Humanoid)player).GetInventory().RemoveItem(item);
			}
			Note("Ivaldi at rest: " + MeasureIvaldi(iv, look));
			Transform[] bones = ((Component)iv).GetComponent<VisEquipment>().m_bodyModel.bones;
			AutoTest autoTest = this;
			Transform transform = ((Component)iv).transform;
			Transform upper = B("l.upperarm");
			Transform fore = B("l.lowerarm");
			Transform hand = B("l.hand");
			Transform obj = B("l.hand");
			autoTest.Note("Ivaldi's left arm (mirror source): " + Measure(transform, upper, fore, hand, (obj != null) ? obj.Find("l.middle1") : null, B("hip"), B("head"), null, Vector3.zero));
			Note("arm: " + IvaldiLook.ArmBones);
			Place(start);
			if ((Object)(object)visual != (Object)null)
			{
				visual.SetActive(false);
			}
			yield return (object)new WaitForSeconds(1f);
			if (!IvaldiSweep)
			{
				yield break;
			}
			float back0 = IvaldiLook.ArmBack;
			float in0 = IvaldiLook.ArmIn;
			float drop0 = IvaldiLook.ForeDrop;
			Vector3 hang0 = IvaldiLook.HammerHang;
			(float, float, float)[] array4 = new(float, float, float)[5]
			{
				(40f, 0f, 0f),
				(40f, 12f, 0f),
				(40f, 24f, 0f),
				(40f, 12f, 0.4f),
				(40f, 24f, 0.4f)
			};
			for (int j = 0; j < array4.Length; j++)
			{
				(float, float, float) tuple2 = array4[j];
				float item2 = tuple2.Item1;
				float item3 = tuple2.Item2;
				float item4 = tuple2.Item3;
				IvaldiLook.ArmBack = item2;
				IvaldiLook.ArmIn = item3;
				IvaldiLook.ForeDrop = item4;
				_ = IvaldiLook.HammerHang;
				string itemName = $"pose_b{item2:F0}_i{item3:F0}_d{item4 * 10f:F0}";
				yield return (object)new WaitForSeconds(0.5f);
				(string name, float angle)[] array3 = new(string, float)[3]
				{
					("front", 0f),
					("side", 90f),
					("threequarter", 40f)
				};
				for (int k = 0; k < array3.Length; k++)
				{
					(string, float) tuple3 = array3[k];
					string tag = tuple3.Item1;
					float item5 = tuple3.Item2;
					Vector3 position = ((Component)iv).transform.position;
					CamPos = position + Quaternion.AngleAxis(item5, Vector3.up) * face * 1.9f * s + Vector3.up * 1f * s;
					CamRot = Quaternion.LookRotation(position + Vector3.up * 0.8f * s - CamPos.Value);
					yield return (object)new WaitForSeconds(1f);
					yield return Shot(itemName + "_" + tag);
				}
				Note(itemName + ": " + MeasureIvaldi(iv, look));
			}
			IvaldiLook.ArmBack = back0;
			IvaldiLook.ArmIn = in0;
			IvaldiLook.ForeDrop = drop0;
			IvaldiLook.HammerHang = hang0;
			yield return (object)new WaitForSeconds(0.5f);
			Transform B(string n)
			{
				return ((IEnumerable<Transform>)bones).FirstOrDefault((Func<Transform, bool>)((Transform b) => (Object)(object)b != (Object)null && ((Object)b).name == n));
			}
			(Vector3 pos, float dist) Beside(float angle, float want)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: 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_0017: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_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_0059: 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_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_006f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0071: Unknown result type (might be due to invalid IL or missing references)
				//IL_007b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0080: Unknown result type (might be due to invalid IL or missing references)
				//IL_0085: Unknown result type (might be due to invalid IL or missing references)
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				//IL_0091: Unknown result type (might be due to invalid IL or missing references)
				//IL_0096: 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_013a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0144: Unknown result type (might be due to invalid IL or missing references)
				//IL_0149: 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_00b3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
				//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_00ce: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d3: 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_00d9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e8: 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)
				Vector3 val4 = Quaternion.AngleAxis(angle, Vector3.up) * face;
				Vector3 val5 = Vector3.Cross(Vector3.up, val4);
				Vector3 normalized = ((Vector3)(ref val5)).normalized;
				float[] array5 = new float[2] { 1f, -1f };
				RaycastHit val7 = default(RaycastHit);
				foreach (float num in array5)
				{
					Vector3 val6 = home + normalized * num * 1.35f;
					if (!Physics.CheckCapsule(val6 + Vector3.up * 0.55f, val6 + Vector3.up * 1.5f, 0.4f, mask))
					{
						float item6 = (Physics.Raycast((home + val6) * 0.5f + Vector3.up * 0.9f, val4 + Vector3.up * 0.06f, ref val7, want, mask) ? Mathf.Max(((RaycastHit)(ref val7)).distance - 0.3f, 1.5f) : want);
						return (pos: val6, dist: item6);
					}
				}
				return (pos: home - face * 1.35f, dist: want);
			}
			void Place(Vector3 val4)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: 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_0030: Unknown result type (might be due to invalid IL or missing references)
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
				((Component)player).transform.SetPositionAndRotation(val4, Quaternion.LookRotation(face));
				if ((Object)(object)pbody != (Object)null)
				{
					pbody.position = val4;
					pbody.rotation = Quaternion.LookRotation(face);
					pbody.linearVelocity = Vector3.zero;
				}
				((Character)player).SetLookDir(face, 0f);
				if (LookYaw != null && LookYaw.FieldType == typeof(Quaternion))
				{
					LookYaw.SetValue(player, Quaternion.LookRotation(face));
				}
			}
		}

		private static Transform PlayerBone(Player p, string name)
		{
			return ((IEnumerable<Transform>)((Component)p).GetComponentsInChildren<Transform>(true)).FirstOrDefault((Func<Transform, bool>)((Transform t) => ((Object)t).name == name && !((Component)t).GetComponentsInParent<Transform>(true).Any((Transform a) => ((Object)a).name.StartsWith("attach_skin"))));
		}

		private static string MeasurePlayer(Player p)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			VisEquipment component = ((Component)p).GetComponent<VisEquipment>();
			GameObject val = (GameObject)(((Object)(object)component != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
			List<string> list = new string[6] { "RightArm", "RightForeArm", "RightHand", "RightHandMiddle1", "Hips", "Head" }.Where((string n) => (Object)(object)PlayerBone(p, n) == (Object)null).ToList();
			Transform val2 = PlayerBone(p, "RightHand");
			if ((Object)(object)val2 != (Object)null)
			{
				SkidbladnirPlugin.Log.LogInfo((object)($"player hand bone {BonePath(val2, ((Component)p).transform)} at {((Component)p).transform.InverseTransformPoint