Decompiled source of FlabaNpcFramework v1.0.0

BepInEx/plugins/FlabaNpcFramework/FlabaNpcFramework.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FlabaNpcFramework.API;
using FlabaNpcFramework.Npc;
using FlabaNpcFramework.Packs;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.SceneManagement;

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FlabaNpcFramework
{
	internal static class ModConfig
	{
		internal static ConfigEntry<KeyCode> ReloadKey;

		internal static ConfigEntry<bool> ReloadEnabled;

		internal static void Bind(ConfigFile config)
		{
			ReloadEnabled = config.Bind<bool>("Authoring", "ReloadEnabled", false, "Lets you re-read the JSON packs in-game instead of restarting. Turn this on while you are making NPCs. It is a development tool: rebuilt NPCs are new NPCs to the game, so their quest progress starts over, and in multiplayer only your own copy changes.");
			ReloadKey = config.Bind<KeyCode>("Authoring", "ReloadKey", (KeyCode)289, "Key that re-reads the packs and rebuilds the NPCs on the island you are on. Only does anything when ReloadEnabled is true.");
		}
	}
	[BepInPlugin("com.flaba.npcframework", "FlabaNpcFramework", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.flaba.npcframework";

		public const string PluginName = "FlabaNpcFramework";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		private readonly Harmony _harmony = new Harmony("com.flaba.npcframework");

		private NpcSpawner _spawner;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"FlabaNpcFramework 1.0.0 loading...");
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			try
			{
				NpcPackLoader.LoadAll();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"NPC pack loading failed: {arg}");
			}
			try
			{
				_harmony.PatchAll();
				int num = 0;
				foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods())
				{
					Log.LogInfo((object)("  patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name));
					num++;
				}
				Log.LogInfo((object)$"Harmony patched {num} method(s).");
			}
			catch (Exception arg2)
			{
				Log.LogError((object)$"Harmony patch failed: {arg2}");
			}
			try
			{
				_spawner = new NpcSpawner();
				_spawner.Hook();
			}
			catch (Exception arg3)
			{
				Log.LogError((object)$"NpcSpawner init failed: {arg3}");
			}
			Log.LogInfo((object)"FlabaNpcFramework 1.0.0 loaded.");
		}

		private void Update()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (_spawner != null && ModConfig.ReloadEnabled.Value && Input.GetKeyDown(ModConfig.ReloadKey.Value))
				{
					_spawner.Reload();
				}
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Update hotkey handling threw: {arg}");
			}
		}

		private void OnDestroy()
		{
			try
			{
				_spawner?.Unhook();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"NpcSpawner unhook failed: {arg}");
			}
			try
			{
				_harmony.UnpatchSelf();
			}
			catch (Exception arg2)
			{
				Log.LogError((object)$"Unpatch failed: {arg2}");
			}
		}
	}
}
namespace FlabaNpcFramework.Patches
{
	[HarmonyPatch]
	internal static class NpcDialoguePatch
	{
		[HarmonyPatch(typeof(NPC), "ValidateNpcText")]
		[HarmonyPrefix]
		private static bool ValidateNpcTextPrefix(NPC __instance, NPCQuest quest, LocalizedString[] targetLines, byte lineIndex, byte progression)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null || (Object)(object)quest == (Object)null || !NpcSpawner.TryGetDefinition(__instance, out var definition))
				{
					return true;
				}
				string text = Resolve(definition, quest, targetLines, lineIndex, progression);
				if (text == null)
				{
					return true;
				}
				Transform val = NpcSpawner.TextTargetOf(__instance);
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				PlayerUI.SetNpcText(text, val);
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"NPC dialogue failed, falling back to vanilla: {arg}");
				return true;
			}
		}

		private static string Resolve(NpcDefinition definition, NPCQuest quest, LocalizedString[] targetLines, byte lineIndex, byte progression)
		{
			byte totalItems = quest.TotalItems;
			string line;
			if (targetLines == quest.Lines)
			{
				if (definition.Lines.Length == 0)
				{
					return null;
				}
				line = definition.Lines[Mathf.Clamp((int)lineIndex, 0, definition.Lines.Length - 1)];
			}
			else if (targetLines == quest.OnItemReceivedLines)
			{
				line = definition.ItemReceivedLine;
			}
			else if (targetLines == quest.OnQuestCompletedLines)
			{
				line = definition.CompletedLine;
			}
			else if (targetLines == quest.HoldingItemLines)
			{
				line = definition.HoldingItemLine;
			}
			else
			{
				if (targetLines != quest.AlreadyCompletedLines)
				{
					return null;
				}
				line = definition.AlreadyDoneLine;
			}
			return FillProgressTokens(line, progression, totalItems);
		}

		private static string FillProgressTokens(string line, byte progression, byte total)
		{
			if (string.IsNullOrEmpty(line))
			{
				return line;
			}
			return line.Replace("{count}", progression.ToString(CultureInfo.InvariantCulture)).Replace("{total}", total.ToString(CultureInfo.InvariantCulture));
		}
	}
}
namespace FlabaNpcFramework.Packs
{
	internal static class NpcPackLoader
	{
		internal static string PacksDirectory => Path.Combine(Path.Combine(Paths.PluginPath, "FlabaNpcFramework"), "packs");

		internal static void LoadAll()
		{
			string packsDirectory = PacksDirectory;
			try
			{
				if (!Directory.Exists(packsDirectory))
				{
					Directory.CreateDirectory(packsDirectory);
					Plugin.Log.LogInfo((object)("Created pack folder: " + packsDirectory));
					return;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not access the pack folder '" + packsDirectory + "': " + ex.Message));
				return;
			}
			string[] files;
			try
			{
				files = Directory.GetFiles(packsDirectory, "*.json", SearchOption.AllDirectories);
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogError((object)("Could not list packs in '" + packsDirectory + "': " + ex2.Message));
				return;
			}
			if (files.Length == 0)
			{
				Plugin.Log.LogInfo((object)"No NPC packs found.");
				return;
			}
			int num = 0;
			string[] array = files;
			for (int i = 0; i < array.Length; i++)
			{
				if (LoadPack(array[i]))
				{
					num++;
				}
			}
			Plugin.Log.LogMessage((object)$"Loaded {num} of {files.Length} NPC pack(s).");
		}

		private static bool LoadPack(string path)
		{
			//IL_00b8: Expected O, but got Unknown
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
			try
			{
				JObject obj = JObject.Parse(File.ReadAllText(path));
				string text = ((string)obj["name"]) ?? fileNameWithoutExtension;
				JToken obj2 = obj["npcs"];
				JArray val = (JArray)(object)((obj2 is JArray) ? obj2 : null);
				if (val == null)
				{
					return true;
				}
				int num = 0;
				for (int i = 0; i < ((JContainer)val).Count; i++)
				{
					try
					{
						Read(val[i], text);
						num++;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogError((object)$"  npcs[{i}] in '{text}' was skipped: {ex.Message}");
					}
				}
				Plugin.Log.LogMessage((object)$"Pack '{text}': {num} npc(s).");
				return true;
			}
			catch (JsonException ex2)
			{
				JsonException ex3 = ex2;
				Plugin.Log.LogError((object)("Pack '" + fileNameWithoutExtension + "' is not valid JSON and was skipped: " + ((Exception)(object)ex3).Message));
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Pack '{fileNameWithoutExtension}' could not be loaded and was skipped: {arg}");
				return false;
			}
		}

		private static void Read(JToken token, string source)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			NpcDefinition npcDefinition = new NpcDefinition
			{
				IslandId = Byte(token, "island", required: true),
				UnlocksIsland = Byte(token, "unlocksIsland", required: false),
				WantedItem = (string)token[(object)"wantedItem"],
				WantedAmount = (int)Float(token, "wantedAmount", 3f),
				StandOn = (string)token[(object)"standOn"],
				Position = Vector(token, "position"),
				Yaw = Float(token, "yaw", 180f),
				SnapToGround = Bool(token, "snapToGround", fallback: true),
				LooksLike = (string)token[(object)"looksLike"],
				SkinColor = Color(token, "skinColor"),
				Source = source,
				FromPack = true
			};
			string text = (string)token[(object)"reward"];
			if (!string.IsNullOrEmpty(text))
			{
				npcDefinition.Reward = ParseReward(text);
			}
			JToken obj = token[(object)"lines"];
			JArray val = (JArray)(object)((obj is JArray) ? obj : null);
			if (val != null)
			{
				List<string> list = new List<string>();
				foreach (JToken item in val)
				{
					string text2 = (string)item;
					if (!string.IsNullOrWhiteSpace(text2))
					{
						list.Add(text2);
					}
				}
				npcDefinition.Lines = list.ToArray();
			}
			npcDefinition.ItemReceivedLine = ((string)token[(object)"itemReceivedLine"]) ?? npcDefinition.ItemReceivedLine;
			npcDefinition.CompletedLine = ((string)token[(object)"completedLine"]) ?? npcDefinition.CompletedLine;
			npcDefinition.HoldingItemLine = ((string)token[(object)"holdingItemLine"]) ?? npcDefinition.HoldingItemLine;
			npcDefinition.AlreadyDoneLine = ((string)token[(object)"alreadyDoneLine"]) ?? npcDefinition.AlreadyDoneLine;
			if (npcDefinition.Lines.Length == 0)
			{
				throw new FormatException("an npc needs at least one line of dialogue");
			}
			NpcFramework.Add(npcDefinition);
		}

		private static NpcReward ParseReward(string reward)
		{
			return reward.Trim().ToLowerInvariant() switch
			{
				"unlockisland" => NpcReward.UnlockIsland, 
				"unlockgrill" => NpcReward.UnlockGrill, 
				"unlockboat" => NpcReward.UnlockBoat, 
				"money" => NpcReward.Money, 
				"nothing" => NpcReward.Nothing, 
				_ => throw new FormatException("'" + reward + "' is not a reward. Use unlockIsland, unlockGrill, unlockBoat, money or nothing."), 
			};
		}

		private static byte Byte(JToken token, string property, bool required)
		{
			JToken val = token[(object)property];
			if (val == null)
			{
				if (required)
				{
					throw new FormatException("missing '" + property + "'");
				}
				return 0;
			}
			int num = (int)val;
			if (num < 0 || num > 255)
			{
				throw new FormatException("'" + property + "' must be between 0 and 255");
			}
			return (byte)num;
		}

		private static float Float(JToken token, string property, float fallback)
		{
			JToken val = token[(object)property];
			if (val != null)
			{
				return Convert.ToSingle(val, CultureInfo.InvariantCulture);
			}
			return fallback;
		}

		private static bool Bool(JToken token, string property, bool fallback)
		{
			JToken val = token[(object)property];
			if (val != null)
			{
				return (bool)val;
			}
			return fallback;
		}

		private static Vector3? Color(JToken token, string property)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			JToken val = token[(object)property];
			if (val == null)
			{
				return null;
			}
			JArray val2 = (JArray)(object)((val is JArray) ? val : null);
			if (val2 != null)
			{
				if (((JContainer)val2).Count != 3)
				{
					throw new FormatException("'" + property + "' needs three numbers, or a hex string");
				}
				return new Vector3((float)val2[0], (float)val2[1], (float)val2[2]);
			}
			string text = (((string)val) ?? string.Empty).Trim().TrimStart('#');
			if (text.Length != 6 || !int.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
			{
				throw new FormatException("'" + property + "' should look like \"#C68642\"");
			}
			return new Vector3((float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f);
		}

		private static Vector3 Vector(JToken token, string property)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			JToken val = token[(object)property];
			if (val == null)
			{
				return Vector3.zero;
			}
			JArray val2 = (JArray)(object)((val is JArray) ? val : null);
			if (val2 != null)
			{
				if (((JContainer)val2).Count != 3)
				{
					throw new FormatException("'" + property + "' needs exactly three numbers");
				}
				return new Vector3((float)val2[0], (float)val2[1], (float)val2[2]);
			}
			return new Vector3(Convert.ToSingle(val[(object)"x"] ?? JToken.op_Implicit(0), CultureInfo.InvariantCulture), Convert.ToSingle(val[(object)"y"] ?? JToken.op_Implicit(0), CultureInfo.InvariantCulture), Convert.ToSingle(val[(object)"z"] ?? JToken.op_Implicit(0), CultureInfo.InvariantCulture));
		}
	}
}
namespace FlabaNpcFramework.Npc
{
	internal sealed class NpcSpawner
	{
		private static readonly Dictionary<NPC, NpcDefinition> Placed = new Dictionary<NPC, NpcDefinition>();

		private static FieldRef<NPC, byte> _idRef;

		private static FieldRef<NPC, List<NPCQuest>> _questsRef;

		private static FieldRef<NPC, Interactable> _characterInteractableRef;

		private static FieldRef<NPC, Vector3> _skinColorRef;

		private static FieldRef<NPC, byte> IdRef => _idRef ?? (_idRef = AccessTools.FieldRefAccess<NPC, byte>("_id"));

		private static FieldRef<NPC, List<NPCQuest>> QuestsRef => _questsRef ?? (_questsRef = AccessTools.FieldRefAccess<NPC, List<NPCQuest>>("_quests"));

		private static FieldRef<NPC, Vector3> SkinColorRef => _skinColorRef ?? (_skinColorRef = AccessTools.FieldRefAccess<NPC, Vector3>("_skinColor"));

		private static FieldRef<NPC, Interactable> CharacterInteractableRef => _characterInteractableRef ?? (_characterInteractableRef = AccessTools.FieldRefAccess<NPC, Interactable>("_characterInteractable"));

		internal void Hook()
		{
			SceneManager.sceneLoaded += OnSceneLoaded;
		}

		internal void Unhook()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			Placed.Clear();
		}

		internal static bool TryGetDefinition(NPC npc, out NpcDefinition definition)
		{
			return Placed.TryGetValue(npc, out definition);
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (((Scene)(ref scene)).buildIndex > 0 && NpcFramework.Npcs.Count != 0)
				{
					Placed.Clear();
					Populate(scene);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"NpcSpawner failed: {arg}");
			}
		}

		private void Populate(Scene scene)
		{
			//IL_0000: 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_0063: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				byte b = CurrentIslandId(scene);
				int num = 0;
				foreach (NpcDefinition npc in NpcFramework.Npcs)
				{
					if (npc.IslandId == b)
					{
						NPC val = FindSourceNpc(scene, npc.LooksLike);
						if ((Object)(object)val == (Object)null)
						{
							Plugin.Log.LogError((object)("There is no NPC loaded anywhere to clone, so '" + npc.Source + "' cannot be placed. This normally means the island scene had not finished loading."));
							break;
						}
						if (Spawn(npc, val, scene))
						{
							num++;
						}
					}
				}
				if (num > 0)
				{
					Plugin.Log.LogMessage((object)$"Placed {num} NPC(s) on island {b}.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"NpcSpawner failed: {arg}");
			}
		}

		internal void Reload()
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Plugin.Log.LogMessage((object)"Reloading NPC packs...");
				int num = 0;
				foreach (NPC item in new List<NPC>(Placed.Keys))
				{
					if ((Object)(object)item != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)item).gameObject);
						num++;
					}
				}
				Placed.Clear();
				int num2 = NpcFramework.RemovePackDefinitions();
				Plugin.Log.LogInfo((object)$"  removed {num} placed NPC(s) and {num2} pack definition(s).");
				NpcPackLoader.LoadAll();
				Scene scene = FindIslandScene();
				if (!((Scene)(ref scene)).IsValid())
				{
					Plugin.Log.LogMessage((object)"  no island loaded; NPCs will appear when you next sail somewhere.");
					return;
				}
				Populate(scene);
				Plugin.Log.LogMessage((object)"Reload complete.");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"NPC reload failed: {arg}");
			}
		}

		private static Scene FindIslandScene()
		{
			//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_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < SceneManager.sceneCount; i++)
			{
				Scene sceneAt = SceneManager.GetSceneAt(i);
				if (((Scene)(ref sceneAt)).isLoaded && ((Scene)(ref sceneAt)).buildIndex > 0)
				{
					return sceneAt;
				}
			}
			return default(Scene);
		}

		private bool Spawn(NpcDefinition definition, NPC source, Scene scene)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				float groundOffset = MeasureSourceGroundOffset(source);
				Transform val = ResolveParent(definition, scene);
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogError((object)("No island root for '" + definition.Source + "'; NPC not placed."));
					return false;
				}
				GameObject val2 = new GameObject("FlabaNpc_Staging");
				val2.SetActive(false);
				NPC val3 = Object.Instantiate<NPC>(source, val2.transform);
				((Object)val3).name = "FlabaNpc_" + (definition.Source ?? "npc");
				byte b = PickFreeId();
				IdRef.Invoke(val3) = b;
				QuestsRef.Invoke(val3) = new List<NPCQuest> { BuildQuest(definition) };
				if (definition.SkinColor.HasValue)
				{
					SkinColorRef.Invoke(val3) = definition.SkinColor.Value;
				}
				((Component)val3).transform.SetParent(val, false);
				((Component)val3).transform.localPosition = definition.Position;
				((Component)val3).transform.localEulerAngles = new Vector3(0f, definition.Yaw, 0f);
				((Component)val3).gameObject.SetActive(true);
				Object.Destroy((Object)(object)val2);
				if (definition.SnapToGround)
				{
					SnapToSurface(((Component)val3).transform, groundOffset);
				}
				Placed[val3] = definition;
				NpcFramework.RaiseNpcPlaced(definition, ((Component)val3).gameObject);
				Plugin.Log.LogMessage((object)($"NPC '{definition.Source}' placed on island {definition.IslandId} as id {b}, " + "cloned from '" + ((Object)source).name + "', under '" + ((Object)val).name + "'."));
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not place NPC from '{definition.Source}': {arg}");
				return false;
			}
		}

		private static NPCQuest BuildQuest(NpcDefinition definition)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			NPCQuest val = ScriptableObject.CreateInstance<NPCQuest>();
			((Object)val).name = "FlabaQuest_" + (definition.Source ?? "npc");
			Set(val, "_type", ToQuestType(definition.Reward));
			Set(val, "_islandToUnlock", (byte)Mathf.Clamp(definition.UnlocksIsland + 1, 0, 255));
			Item val2 = FindItemPrefab(definition.WantedItem);
			if ((Object)(object)val2 != (Object)null)
			{
				Set(val, "_onlyCreatures", false);
				Set(val, "_questItems", new List<Item> { val2 });
				Plugin.Log.LogInfo((object)$"  wants '{((Object)val2).name}' (id {val2.ID}) x{definition.WantedAmount}.");
			}
			else
			{
				if (!string.IsNullOrWhiteSpace(definition.WantedItem))
				{
					Plugin.Log.LogWarning((object)("  item '" + definition.WantedItem + "' not found; accepting any creature instead."));
				}
				Set(val, "_onlyCreatures", true);
				Set(val, "_questItems", new List<Item>());
			}
			Set(val, "_totalItems", (byte)Mathf.Clamp(definition.WantedAmount, 1, 255));
			Set(val, "_linesLocalized", new LocalizedString[Mathf.Max(1, definition.Lines.Length)]);
			Set(val, "_onItemReceivedLinesLocalized", new LocalizedString[1]);
			Set(val, "_onQuestCompletedLinesLocalized", new LocalizedString[1]);
			Set(val, "_holdingItemLinesLocalized", new LocalizedString[1]);
			Set(val, "_alreadyCompletedLinesLocalized", new LocalizedString[1]);
			return val;
		}

		private static QuestType ToQuestType(NpcReward reward)
		{
			return (QuestType)(reward switch
			{
				NpcReward.UnlockGrill => 2, 
				NpcReward.UnlockBoat => 4, 
				NpcReward.Money => 1, 
				NpcReward.Nothing => 5, 
				_ => 3, 
			});
		}

		private static Transform ResolveParent(NpcDefinition definition, Scene scene)
		{
			Transform val = null;
			GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			foreach (GameObject val2 in rootGameObjects)
			{
				Island componentInChildren = val2.GetComponentInChildren<Island>(true);
				if ((Object)(object)componentInChildren != (Object)null && (Object)(object)val == (Object)null)
				{
					val = ((Component)componentInChildren).transform.root;
				}
				if (string.IsNullOrWhiteSpace(definition.StandOn))
				{
					continue;
				}
				Transform[] componentsInChildren = val2.GetComponentsInChildren<Transform>(true);
				foreach (Transform val3 in componentsInChildren)
				{
					if (((Object)val3).name.Equals(definition.StandOn, StringComparison.OrdinalIgnoreCase))
					{
						return val3;
					}
				}
			}
			if (!string.IsNullOrWhiteSpace(definition.StandOn))
			{
				Plugin.Log.LogWarning((object)("  '" + definition.StandOn + "' not found on this island; standing on the island itself. Is the framework that builds it installed?"));
			}
			return val;
		}

		private static void SnapToSurface(Transform npc, float groundOffset)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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)
			//IL_002b: 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_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: 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_0085: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Physics.SyncTransforms();
				RaycastHit val = default(RaycastHit);
				if (!Physics.Raycast(npc.position + Vector3.up * 2.5f, Vector3.down, ref val, 10f, LayerMask.op_Implicit(GameInfo.LevelLayer)))
				{
					Plugin.Log.LogWarning((object)"  nothing solid beneath the NPC; left at its configured height.");
					return;
				}
				float y = npc.position.y;
				npc.position = ((RaycastHit)(ref val)).point + Vector3.up * groundOffset;
				Plugin.Log.LogInfo((object)($"  dropped {y - npc.position.y:0.##}m onto '{((Object)((RaycastHit)(ref val)).collider).name}', " + $"seated {groundOffset:0.###}m above it."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("  could not snap NPC to ground (" + ex.Message + ")."));
			}
		}

		private static float MeasureSourceGroundOffset(NPC source)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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)
			//IL_002b: 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_0053: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				RaycastHit val = default(RaycastHit);
				if (Physics.Raycast(((Component)source).transform.position + Vector3.up * 1.5f, Vector3.down, ref val, 8f, LayerMask.op_Implicit(GameInfo.LevelLayer)))
				{
					return Mathf.Max(0f, ((Component)source).transform.position.y - ((RaycastHit)(ref val)).point.y);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("  reference measurement failed (" + ex.Message + ")."));
			}
			return 0f;
		}

		private static byte PickFreeId()
		{
			HashSet<byte> hashSet = new HashSet<byte>();
			try
			{
				if (AccessTools.Field(typeof(NPCManager), "_idToNpc")?.GetValue(null) is Dictionary<byte, NPC> dictionary)
				{
					foreach (byte key in dictionary.Keys)
					{
						hashSet.Add(key);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not read existing NPC ids (" + ex.Message + "); guessing a high one."));
			}
			for (int num = 250; num >= 100; num--)
			{
				if (!hashSet.Contains((byte)num))
				{
					return (byte)num;
				}
			}
			return 250;
		}

		private static NPC FindSourceNpc(Scene scene, string looksLike)
		{
			//IL_0006: 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_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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)
			List<NPC> list = new List<NPC>();
			CollectNpcs(scene, list);
			for (int i = 0; i < SceneManager.sceneCount; i++)
			{
				Scene sceneAt = SceneManager.GetSceneAt(i);
				if (sceneAt != scene && ((Scene)(ref sceneAt)).isLoaded)
				{
					CollectNpcs(sceneAt, list);
				}
			}
			if (!string.IsNullOrWhiteSpace(looksLike))
			{
				foreach (NPC item in list)
				{
					if (((Object)item).name.Equals(looksLike, StringComparison.OrdinalIgnoreCase))
					{
						return item;
					}
				}
				Plugin.Log.LogWarning((object)("No NPC named '" + looksLike + "' is loaded. Using another instead. Available: " + ((list.Count == 0) ? "(none)" : string.Join(", ", NamesOf(list)))));
			}
			if (list.Count <= 0)
			{
				return null;
			}
			return list[0];
			static void CollectNpcs(Scene from, List<NPC> into)
			{
				GameObject[] rootGameObjects = ((Scene)(ref from)).GetRootGameObjects();
				for (int j = 0; j < rootGameObjects.Length; j++)
				{
					NPC[] componentsInChildren = rootGameObjects[j].GetComponentsInChildren<NPC>(true);
					foreach (NPC val in componentsInChildren)
					{
						if ((Object)(object)val != (Object)null && !((Object)val).name.StartsWith("FlabaNpc", StringComparison.Ordinal))
						{
							into.Add(val);
						}
					}
				}
			}
		}

		private static string[] NamesOf(List<NPC> npcs)
		{
			List<string> list = new List<string>();
			foreach (NPC npc in npcs)
			{
				if (!list.Contains(((Object)npc).name))
				{
					list.Add(((Object)npc).name);
				}
			}
			return list.ToArray();
		}

		private static Item FindItemPrefab(string name)
		{
			//IL_0038: 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)
			if (string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			Item val = null;
			try
			{
				Item[] array = Resources.FindObjectsOfTypeAll<Item>();
				foreach (Item val2 in array)
				{
					if (!((Object)(object)val2 == (Object)null) && ((Object)val2).name.Equals(name, StringComparison.OrdinalIgnoreCase))
					{
						Scene scene = ((Component)val2).gameObject.scene;
						if (!((Scene)(ref scene)).IsValid())
						{
							return val2;
						}
						val = val ?? val2;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("  item lookup failed (" + ex.Message + ")."));
			}
			return val;
		}

		internal static Transform TextTargetOf(NPC npc)
		{
			try
			{
				Interactable val = CharacterInteractableRef.Invoke(npc);
				return ((Object)(object)val != (Object)null) ? val.TextTarget : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static byte CurrentIslandId(Scene scene)
		{
			if ((Object)(object)OnlineIslandManager.Instance != (Object)null)
			{
				return OnlineIslandManager.CurIsland;
			}
			return (byte)Mathf.Clamp(((Scene)(ref scene)).buildIndex - 1, 0, 255);
		}

		private static void Set(NPCQuest quest, string field, object value)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(NPCQuest), field);
			if (fieldInfo == null)
			{
				Plugin.Log.LogError((object)("NPCQuest." + field + " not found; the quest will misbehave."));
			}
			else
			{
				fieldInfo.SetValue(quest, value);
			}
		}
	}
}
namespace FlabaNpcFramework.API
{
	public sealed class NpcDefinition
	{
		public byte IslandId;

		public string[] Lines = new string[0];

		public string ItemReceivedLine = "Keep them coming. ({count}/{total})";

		public string CompletedLine = "That'll do. Here - take this.";

		public string HoldingItemLine = "Go on, take it.";

		public string AlreadyDoneLine = "You already know the way.";

		public string WantedItem;

		public int WantedAmount = 3;

		public NpcReward Reward;

		public byte UnlocksIsland;

		public string StandOn;

		public Vector3 Position;

		public float Yaw = 180f;

		public bool SnapToGround = true;

		public string LooksLike;

		public Vector3? SkinColor;

		public string Source;

		internal bool FromPack;
	}
	public enum NpcReward
	{
		UnlockIsland,
		UnlockGrill,
		UnlockBoat,
		Money,
		Nothing
	}
	public static class NpcFramework
	{
		private static readonly List<NpcDefinition> Definitions = new List<NpcDefinition>();

		public static IReadOnlyList<NpcDefinition> Npcs => Definitions;

		public static string Version => "1.0.0";

		public static event Action<NpcDefinition, GameObject> NpcPlaced;

		public static void Add(NpcDefinition definition)
		{
			if (definition == null)
			{
				Plugin.Log.LogError((object)"Something tried to register a null NPC definition. Ignored.");
				return;
			}
			if (definition.Lines == null || definition.Lines.Length == 0)
			{
				Plugin.Log.LogError((object)("NPC from '" + (definition.Source ?? "unknown") + "' has no dialogue and was ignored."));
				return;
			}
			Definitions.Add(definition);
			Plugin.Log.LogInfo((object)string.Format("Registered NPC from '{0}' for island {1}.", definition.Source ?? "unknown", definition.IslandId));
		}

		internal static void RaiseNpcPlaced(NpcDefinition definition, GameObject npc)
		{
			try
			{
				NpcFramework.NpcPlaced?.Invoke(definition, npc);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"An NpcFramework.NpcPlaced subscriber threw: {arg}");
			}
		}

		internal static void Clear()
		{
			Definitions.Clear();
		}

		internal static int RemovePackDefinitions()
		{
			return Definitions.RemoveAll((NpcDefinition d) => d.FromPack);
		}
	}
}