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);
}
}
}