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 ReloadKey; internal static ConfigEntry ReloadEnabled; internal static void Bind(ConfigFile config) { ReloadEnabled = config.Bind("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("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 list = new List(); 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 Placed = new Dictionary(); private static FieldRef _idRef; private static FieldRef> _questsRef; private static FieldRef _characterInteractableRef; private static FieldRef _skinColorRef; private static FieldRef IdRef => _idRef ?? (_idRef = AccessTools.FieldRefAccess("_id")); private static FieldRef> QuestsRef => _questsRef ?? (_questsRef = AccessTools.FieldRefAccess>("_quests")); private static FieldRef SkinColorRef => _skinColorRef ?? (_skinColorRef = AccessTools.FieldRefAccess("_skinColor")); private static FieldRef CharacterInteractableRef => _characterInteractableRef ?? (_characterInteractableRef = AccessTools.FieldRefAccess("_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(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(source, val2.transform); ((Object)val3).name = "FlabaNpc_" + (definition.Source ?? "npc"); byte b = PickFreeId(); IdRef.Invoke(val3) = b; QuestsRef.Invoke(val3) = new List { 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(); ((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 { 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()); } 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(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(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 hashSet = new HashSet(); try { if (AccessTools.Field(typeof(NPCManager), "_idToNpc")?.GetValue(null) is Dictionary 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 list = new List(); 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 into) { GameObject[] rootGameObjects = ((Scene)(ref from)).GetRootGameObjects(); for (int j = 0; j < rootGameObjects.Length; j++) { NPC[] componentsInChildren = rootGameObjects[j].GetComponentsInChildren(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 npcs) { List list = new List(); 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(); 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 Definitions = new List(); public static IReadOnlyList Npcs => Definitions; public static string Version => "1.0.0"; public static event Action 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); } } }