Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Fuling Heckler v1.0.0
plugins/FulingHeckler.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using HeaveHo.Shared; using Microsoft.CodeAnalysis; using Nick.Shared; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI.")] [assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } internal sealed class ConfigurationManagerAttributes { public bool? IsAdvanced; public int? Order; public Action<ConfigEntryBase> CustomDrawer; public bool? HideDefaultButton; public bool? ReadOnly; public bool? Browsable; public string Category; public string DispName; public string Description; } namespace HeaveHo.Shared { internal sealed class ServerSettings : IDisposable { public enum ServerMod { NotConnected, Checking, Installed, Missing } private sealed class ConfigurationManagerAttributes { public bool? ReadOnly; } private sealed class SyncedText { public string Key; public Func<string> Own; public Action<string> Use; public string Given; public bool FromServer; public bool Pending; public byte[] Packed; public bool TooBig; public MemoryStream Incoming; public int NextPart; } private sealed class Sending { public ZRpc Rpc; public string Key; public byte[] Data; public int Next; public int Parts; } private const int Protocol = 2; private const float ChangeSendGap = 0.5f; private const float AnswerWait = 20f; private const float FileWait = 1f; private const int PartSize = 65536; private const int CompressFrom = 1024; private const int WarnBytes = 262144; private const int MaxBytes = 8388608; private const int PaceBelow = 10240; private readonly string _send; private readonly string _ask; private readonly string _dAsk; private readonly string _dHello; private readonly string _dSend; private readonly string _edit; private readonly string _dTextAsk; private readonly string _dText; private readonly string _dAgain; private readonly ConfigFile _config; private readonly ManualLogSource _log; private readonly string _name; private readonly List<ConfigEntryBase> _entries = new List<ConfigEntryBase>(); private readonly Dictionary<ConfigEntryBase, object> _own = new Dictionary<ConfigEntryBase, object>(); private readonly Dictionary<ConfigEntryBase, object> _server = new Dictionary<ConfigEntryBase, object>(); private readonly List<GameObject> _guardedPieces = new List<GameObject>(); private readonly List<Recipe> _guardedRecipes = new List<Recipe>(); private ZRoutedRpc _registeredOn; private bool _applying; private bool _reloading; private bool _changePending; private float _nextChangeSend; private float _askedAt = -1f; private int _serverProtocol; private bool _directSeen; private readonly HashSet<ZRpc> _directPeers = new HashSet<ZRpc>(); private readonly List<SyncedText> _texts = new List<SyncedText>(); private readonly Dictionary<ZRpc, HashSet<string>> _textPeers = new Dictionary<ZRpc, HashSet<string>>(); private bool _textPending; private float _nextTextSend; private bool _readOnlyShown; private float _nextAdminCheck; private FileSystemWatcher _watcher; private volatile bool _fileTouched; private float _reloadAt = -1f; private string _fileText; private readonly List<Sending> _outbox = new List<Sending>(); private static readonly MethodInfo UpdateAvailablePieces = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null); private static readonly List<ServerSettings> Live = new List<ServerSettings>(); private static Harmony _saveHarmony; public string Version { get; } public bool FromServer => _server.Count > 0; public ServerMod Server { get; private set; } public string ServerVersion { get; private set; } public bool CanEditServer { get { if (!IsServer) { if (Server == ServerMod.Installed && _serverProtocol >= 1 && (Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.LocalPlayerIsAdminOrHost(); } return false; } return true; } } private static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public event Action ServerChanged; public ServerSettings(string guid, ConfigFile config, ManualLogSource log) { _send = guid + ".settings"; _ask = guid + ".settings.ask"; _dAsk = guid + ".settings.d.ask"; _dHello = guid + ".settings.d.hello"; _dSend = guid + ".settings.d.values"; _edit = guid + ".settings.d.edit"; _dTextAsk = guid + ".settings.d.textask"; _dAgain = guid + ".settings.d.again"; _dText = guid + ".settings.d.text"; _config = config; _log = log; BepInPlugin val = PluginOf(guid); _name = ((val != null) ? val.Name : null) ?? guid; Version = ((val == null) ? null : val.Version?.ToString()) ?? ""; Live.Add(this); if (Live.Count == 1) { PatchSave(); } WatchFile(); } public ConfigEntry<T> Add<T>(ConfigEntry<T> entry) { if (entry == null || _entries.Contains((ConfigEntryBase)(object)entry)) { return entry; } _entries.Add((ConfigEntryBase)(object)entry); entry.SettingChanged += OnChanged; GiveManagerAttributes((ConfigEntryBase)(object)entry); return entry; } private static void GiveManagerAttributes(ConfigEntryBase entry) { ConfigDescription description = entry.Description; if (description == null || description == ConfigDescription.Empty || (description.Tags ?? new object[0]).Any(IsManagerAttributes)) { return; } try { object[] value = new object[1] { new ConfigurationManagerAttributes() }.Concat(description.Tags ?? new object[0]).ToArray(); AccessTools.Field(typeof(ConfigDescription), "<Tags>k__BackingField")?.SetValue(description, value); } catch (Exception) { } } public ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range = null, params object[] tags) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown if (!description.Contains("[Synced with Server]")) { description += " [Synced with Server]"; } return Add<T>(_config.Bind<T>(section, key, value, new ConfigDescription(description, range, tags ?? new object[0]))); } public void NeedsServer(GameObject piecePrefab) { if (!((Object)(object)piecePrefab == (Object)null)) { _guardedPieces.RemoveAll((GameObject p) => (Object)(object)p == (Object)null || ((Object)p).name == ((Object)piecePrefab).name); _guardedPieces.Add(piecePrefab); Guard(); } } public void NeedsServer(Recipe recipe) { if (!((Object)(object)recipe == (Object)null)) { _guardedRecipes.RemoveAll((Recipe r) => (Object)(object)r == (Object)null || (Object)(object)r == (Object)(object)recipe || (!string.IsNullOrEmpty(((Object)recipe).name) && ((Object)r).name == ((Object)recipe).name)); _guardedRecipes.Add(recipe); Guard(); } } public void AddText(string key, Func<string> own, Action<string> use) { if (string.IsNullOrEmpty(key) || own == null || use == null) { ManualLogSource log = _log; if (log != null) { log.LogError((object)"AddText needs a key, own and use"); } return; } _texts.RemoveAll((SyncedText x) => x.Key == key); SyncedText syncedText = new SyncedText { Key = key, Own = own, Use = use }; _texts.Add(syncedText); Give(syncedText, OwnText(syncedText)); ZRpc val = ((!((Object)(object)ZNet.instance != (Object)null) || IsServer) ? null : ZNet.instance.GetServerPeer()?.m_rpc); if (_registeredOn != null && val != null) { val.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText); AskTexts(val); } } public void TextChanged(string key) { SyncedText syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key); if (syncedText != null) { syncedText.Packed = null; if (!syncedText.FromServer) { Give(syncedText, OwnText(syncedText)); } if (IsServer) { syncedText.Pending = true; _textPending = true; } } } public bool TextFromServer(string key) { return _texts.Any((SyncedText x) => x.Key == key && x.FromServer); } public void Update() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != _registeredOn) { Unregister(); Restore(); RestoreTexts(give: true); _changePending = false; _textPending = false; SetServer(ServerMod.NotConnected, null, 0); if (instance != null) { Register(instance); } } float unscaledTime = Time.unscaledTime; if (_changePending && unscaledTime >= _nextChangeSend) { SendChange(); } if (_textPending && unscaledTime >= _nextTextSend) { SendTexts(); } if (_outbox.Count > 0) { Pump(); } if (Server == ServerMod.Checking && _askedAt >= 0f && unscaledTime - _askedAt > 20f) { if (_guardedPieces.Count + _guardedRecipes.Count > 0) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("The server didn't answer: " + _name + " isn't installed there (or its copy is older than 2026-10-11), so its pieces and items can't be made on this server.")); } } else { ManualLogSource log2 = _log; if (log2 != null) { log2.LogInfo((object)("The server didn't answer: " + _name + " isn't installed there, or its copy is older" + (FromServer ? " (its settings apply)." : " (your own settings apply)."))); } } SetServer(ServerMod.Missing, null, 0); } if (_fileTouched) { _fileTouched = false; _reloadAt = unscaledTime + 1f; } if (_reloadAt >= 0f && unscaledTime >= _reloadAt) { _reloadAt = -1f; ReloadFile(); } if (FromServer && unscaledTime >= _nextAdminCheck) { _nextAdminCheck = unscaledTime + 1f; ShowReadOnly(); } } public void Dispose() { Unregister(); Restore(); RestoreTexts(give: false); SetServer(ServerMod.NotConnected, null, 0); foreach (ConfigEntryBase entry in _entries) { Unsubscribe(entry); } _entries.Clear(); _texts.Clear(); try { _watcher?.Dispose(); } catch (Exception) { } _watcher = null; Live.Remove(this); if (Live.Count == 0) { UnpatchSave(); } } private void Register(ZRoutedRpc rpc) { Forget(rpc, _send); Forget(rpc, _ask); rpc.Register<ZPackage>(_send, (Action<long, ZPackage>)OnRoutedValues); rpc.Register(_ask, (Action<long>)OnRoutedAsk); rpc.m_onNewPeer = (Action<long>)Delegate.Combine(rpc.m_onNewPeer, new Action<long>(OnNewPeer)); _registeredOn = rpc; if (IsServer) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { ListenTo(peer); if (peer != null) { ZRpc rpc2 = peer.m_rpc; if (rpc2 != null) { rpc2.Invoke(_dAgain, Array.Empty<object>()); } } } return; } if ((Object)(object)ZNet.instance != (Object)null) { SetServer(ServerMod.Checking, null, 0); if (ZNet.instance.GetServerPeer() != null) { AskServer(); } } } private void Unregister() { if (_registeredOn == null) { return; } ZRoutedRpc registeredOn = _registeredOn; registeredOn.m_onNewPeer = (Action<long>)Delegate.Remove(registeredOn.m_onNewPeer, new Action<long>(OnNewPeer)); Forget(_registeredOn, _send); Forget(_registeredOn, _ask); if ((Object)(object)ZNet.instance != (Object)null) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer?.m_rpc != null) { string[] array = new string[7] { _dAsk, _dHello, _dSend, _edit, _dTextAsk, _dText, _dAgain }; foreach (string name in array) { ForgetDirect(peer.m_rpc, name); } } } } _directPeers.Clear(); _textPeers.Clear(); _outbox.Clear(); _directSeen = false; _registeredOn = null; } private static void Forget(ZRoutedRpc rpc, string name) { if (AccessTools.Field(typeof(ZRoutedRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary) { dictionary.Remove(StringExtensionMethods.GetStableHashCode(name)); } } private static void ForgetDirect(ZRpc rpc, string name) { if (AccessTools.Field(typeof(ZRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary) { dictionary.Remove(StringExtensionMethods.GetStableHashCode(name)); } } private void OnNewPeer(long uid) { if (IsServer) { ZNetPeer peer = ZNet.instance.GetPeer(uid); ListenTo(peer); if (peer?.m_rpc != null) { SendDirect(peer.m_rpc); } SendRouted(uid); } else if ((Object)(object)ZNet.instance != (Object)null) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid == uid) { AskServer(); } } } private void ListenTo(ZNetPeer peer) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown if (peer?.m_rpc != null) { peer.m_rpc.Register(_dAsk, new Method(OnDirectAsk)); peer.m_rpc.Register<ZPackage>(_edit, (Action<ZRpc, ZPackage>)OnEdit); peer.m_rpc.Register<ZPackage>(_dTextAsk, (Action<ZRpc, ZPackage>)OnTextAsk); } } private void OnDirectAsk(ZRpc rpc) { if (IsServer) { _directPeers.Add(rpc); SendDirect(rpc); } } private void OnRoutedAsk(long sender) { if (IsServer) { SendRouted(sender); } } private void SendDirect(ZRpc rpc) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(2); val.Write(Version); rpc.Invoke(_dHello, new object[1] { val }); if (_entries.Count > 0) { rpc.Invoke(_dSend, new object[1] { Values() }); } } private void SendRouted(long target) { if (ZRoutedRpc.instance != null && _entries.Count > 0) { ZRoutedRpc.instance.InvokeRoutedRPC(target, _send, new object[1] { Values() }); } } private ZPackage Values() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(_entries.Count); foreach (ConfigEntryBase entry in _entries) { val.Write(entry.Definition.Section); val.Write(entry.Definition.Key); val.Write(TomlTypeConverter.ConvertToString(entry.BoxedValue, entry.SettingType)); } return val; } private void AskServer() { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null); if (val?.m_rpc == null) { return; } _askedAt = Time.unscaledTime; val.m_rpc.Register<ZPackage>(_dHello, (Action<ZRpc, ZPackage>)OnDirectHello); val.m_rpc.Register<ZPackage>(_dSend, (Action<ZRpc, ZPackage>)OnDirectValues); val.m_rpc.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText); val.m_rpc.Register(_dAgain, new Method(OnAskAgain)); val.m_rpc.Invoke(_dAsk, Array.Empty<object>()); AskTexts(val.m_rpc); try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(val.m_uid, _ask, Array.Empty<object>()); } } catch (Exception) { } } private static bool IsServerConnection(ZRpc rpc) { if (!IsServer) { ZNet instance = ZNet.instance; return ((instance == null) ? null : instance.GetServerPeer()?.m_rpc) == rpc; } return false; } private void OnAskAgain(ZRpc rpc) { if (IsServerConnection(rpc)) { rpc.Invoke(_dAsk, Array.Empty<object>()); AskTexts(rpc); } } private void OnDirectHello(ZRpc rpc, ZPackage pkg) { if (!IsServerConnection(rpc)) { return; } try { int protocol = pkg.ReadInt(); string text = pkg.ReadString(); if (text != Version && text != ServerVersion) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("The server has " + _name + " " + text + ", you have " + Version + ": some things may not match. Use the same version as the server.")); } } _directSeen = true; SetServer(ServerMod.Installed, text, protocol); } catch (Exception ex) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Could not read the server's hello: " + ex.Message)); } } } private void OnDirectValues(ZRpc rpc, ZPackage pkg) { if (IsServerConnection(rpc)) { TakeValues(pkg); } } private void OnRoutedValues(long sender, ZPackage pkg) { if (!_directSeen && !IsServer) { ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null); if (val != null && val.m_uid == sender) { TakeValues(pkg); } } } private void TakeValues(ZPackage pkg) { try { int num = pkg.ReadInt(); List<string> list = new List<string>(); for (int i = 0; i < num; i++) { string section = pkg.ReadString(); string key = pkg.ReadString(); string text = pkg.ReadString(); ConfigEntryBase val = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key)); if (val != null) { object obj; try { obj = TomlTypeConverter.ConvertToValue(text, val.SettingType); } catch (Exception) { continue; } if (!_own.ContainsKey(val)) { _own[val] = val.BoxedValue; } _server[val] = obj; if (!object.Equals(val.BoxedValue, obj)) { Apply(val, obj); list.Add(section + "/" + key + " = " + text); } } } ManualLogSource log = _log; if (log != null) { log.LogInfo((object)($"Settings from the server apply while you play here ({_server.Count})" + ((list.Count > 0) ? (": " + string.Join(", ", list.ToArray())) : ""))); } ShowReadOnly(); } catch (Exception ex2) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Could not read the server's settings: " + ex2.Message)); } } } private void Apply(ConfigEntryBase e, object value) { bool saveOnConfigSet = _config.SaveOnConfigSet; _applying = true; try { _config.SaveOnConfigSet = false; e.BoxedValue = value; } finally { _config.SaveOnConfigSet = saveOnConfigSet; _applying = false; } } private void Restore() { if (_server.Count == 0) { return; } foreach (KeyValuePair<ConfigEntryBase, object> item in _own) { Apply(item.Key, item.Value); } _own.Clear(); _server.Clear(); ShowReadOnly(); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)"Your own settings apply again"); } } private void OnChanged(object sender, EventArgs args) { if (_applying) { return; } ConfigEntryBase val = (ConfigEntryBase)((sender is ConfigEntryBase) ? sender : null); if (val == null) { return; } if (_server.TryGetValue(val, out var value)) { if (!_reloading && CanEditServer) { SendEdit(val); } else { _own[val] = val.BoxedValue; } Apply(val, value); } else if (IsServer) { _changePending = true; if (Time.unscaledTime >= _nextChangeSend) { SendChange(); } } } private void SendChange() { _changePending = false; _nextChangeSend = Time.unscaledTime + 0.5f; if (!IsServer) { return; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer?.m_rpc == null) { continue; } if (_directPeers.Contains(peer.m_rpc)) { if (_entries.Count > 0) { peer.m_rpc.Invoke(_dSend, new object[1] { Values() }); } } else { SendRouted(peer.m_uid); } } } private void SendEdit(ConfigEntryBase e) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null); if (val?.m_rpc != null) { ZPackage val2 = new ZPackage(); val2.Write(e.Definition.Section); val2.Write(e.Definition.Key); val2.Write(TomlTypeConverter.ConvertToString(e.BoxedValue, e.SettingType)); val.m_rpc.Invoke(_edit, new object[1] { val2 }); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("Asked the server to set " + e.Definition.Section + "/" + e.Definition.Key)); } } } private void OnEdit(ZRpc rpc, ZPackage pkg) { try { if (!IsServer) { return; } ISocket socket = rpc.GetSocket(); string text = ((socket != null) ? socket.GetHostName() : null) ?? ""; ZNetPeer val = ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc)); string section = pkg.ReadString(); string key = pkg.ReadString(); string text2 = pkg.ReadString(); if (!ZNet.instance.IsAdmin(text)) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)((val?.m_playerName ?? text) + " tried to change " + section + "/" + key + " but isn't on the admin list")); } if (_entries.Count > 0) { rpc.Invoke(_dSend, new object[1] { Values() }); } return; } ConfigEntryBase val2 = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key)); if (val2 != null) { val2.BoxedValue = TomlTypeConverter.ConvertToValue(text2, val2.SettingType); ManualLogSource log2 = _log; if (log2 != null) { log2.LogInfo((object)("Admin " + (val?.m_playerName ?? text) + " set " + section + "/" + key + " = " + text2)); } } } catch (Exception ex) { ManualLogSource log3 = _log; if (log3 != null) { log3.LogWarning((object)("Could not take an admin's change: " + ex.Message)); } } } private void ShowReadOnly() { bool flag = FromServer && !CanEditServer; if (flag == _readOnlyShown) { return; } _readOnlyShown = flag; foreach (ConfigEntryBase entry in _entries) { ConfigDescription description = entry.Description; object[] array = ((description != null) ? description.Tags : null) ?? new object[0]; foreach (object obj in array) { if (IsManagerAttributes(obj)) { try { AccessTools.Field(obj.GetType(), "ReadOnly")?.SetValue(obj, flag ? ((object)true) : null); } catch (Exception) { } } } } } private static bool IsManagerAttributes(object tag) { if (tag != null) { return tag.GetType().Name == "ConfigurationManagerAttributes"; } return false; } private string OwnText(SyncedText t) { try { return t.Own() ?? ""; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not read " + t.Key + ": " + ex.Message)); } return t.Given ?? ""; } } private void Give(SyncedText t, string text) { if (t.Given != null && t.Given == text) { return; } t.Given = text; try { t.Use(text); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not use " + t.Key + ": " + ex)); } } } private void RestoreTexts(bool give) { foreach (SyncedText text in _texts) { text.Incoming = null; if (!text.FromServer) { continue; } text.FromServer = false; if (give) { Give(text, OwnText(text)); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("Your own " + text.Key + " applies again")); } } } } private void AskTexts(ZRpc server) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown if (_texts.Count == 0 || server == null) { return; } ZPackage val = new ZPackage(); val.Write(_texts.Count); foreach (SyncedText text in _texts) { val.Write(text.Key); } server.Invoke(_dTextAsk, new object[1] { val }); } private void OnTextAsk(ZRpc rpc, ZPackage pkg) { try { if (!IsServer) { return; } int num = pkg.ReadInt(); HashSet<string> hashSet = new HashSet<string>(); for (int i = 0; i < num && i < 1000; i++) { string key = pkg.ReadString(); if (_texts.Any((SyncedText x) => x.Key == key)) { hashSet.Add(key); } } if (_textPeers.TryGetValue(rpc, out var value)) { hashSet.UnionWith(value); } foreach (ZRpc item in _textPeers.Keys.Where((ZRpc r) => !r.IsConnected()).ToList()) { _textPeers.Remove(item); } _textPeers[rpc] = hashSet; foreach (SyncedText text in _texts) { if (hashSet.Contains(text.Key)) { SendText(rpc, text); } } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not answer a player's ask for server data: " + ex.Message)); } } } private void SendTexts() { _textPending = false; _nextTextSend = Time.unscaledTime + 0.5f; if (!IsServer) { return; } foreach (SyncedText text in _texts) { if (!text.Pending) { continue; } text.Pending = false; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer?.m_rpc != null && _textPeers.TryGetValue(peer.m_rpc, out var value) && value.Contains(text.Key)) { SendText(peer.m_rpc, text); } } } } private void SendText(ZRpc rpc, SyncedText t) { byte[] array = Packed(t); if (array != null) { _outbox.RemoveAll((Sending s) => s.Rpc == rpc && s.Key == t.Key); _outbox.Add(new Sending { Rpc = rpc, Key = t.Key, Data = array, Parts = Math.Max(1, (array.Length + 65536 - 1) / 65536) }); Pump(); } } private void Pump() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown for (int num = _outbox.Count - 1; num >= 0; num--) { Sending sending = _outbox[num]; try { while (sending.Next < sending.Parts && sending.Rpc.IsConnected() && sending.Rpc.GetSocket().GetSendQueueSize() < 10240) { int num2 = sending.Next * 65536; int num3 = Math.Min(65536, sending.Data.Length - num2); byte[] array = new byte[num3]; Buffer.BlockCopy(sending.Data, num2, array, 0, num3); ZPackage val = new ZPackage(); val.Write(sending.Key); val.Write(sending.Next); val.Write(sending.Parts); val.Write(array); sending.Rpc.Invoke(_dText, new object[1] { val }); sending.Next++; } if (sending.Next >= sending.Parts || !sending.Rpc.IsConnected()) { _outbox.RemoveAt(num); } } catch (Exception ex) { _outbox.RemoveAt(num); ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not send " + sending.Key + " to a player: " + ex.Message)); } } } } private byte[] Packed(SyncedText t) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown if (t.Packed == null) { string text = OwnText(t); ZPackage val = new ZPackage(); val.Write(text); ZPackage val2 = new ZPackage(); bool flag = text.Length > 1024; val2.Write(flag); if (flag) { val2.WriteCompressed(val); } else { val2.Write(val); } t.Packed = val2.GetArray(); t.TooBig = t.Packed.Length > 8388608; string arg = $"{t.Packed.Length / 1024} KB" + (flag ? $" gzipped ({text.Length / 1024} KB of text)" : ""); if (t.TooBig) { ManualLogSource log = _log; if (log != null) { log.LogError((object)$"{t.Key} is too big to send to players ({arg}, at most {8} MB): they keep their own."); } } else if (t.Packed.Length > 262144) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)$"{t.Key} is big to send to every player who joins ({arg}): it takes about {(float)t.Packed.Length / 153600f:0} s of their connection, and their world loads slower meanwhile."); } } } if (!t.TooBig) { return t.Packed; } return null; } private void OnText(ZRpc rpc, ZPackage pkg) { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown if (!IsServerConnection(rpc)) { return; } SyncedText syncedText = null; try { string key = pkg.ReadString(); int num = pkg.ReadInt(); int num2 = pkg.ReadInt(); byte[] array = pkg.ReadByteArray(); syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key); if (syncedText == null) { return; } if (num2 < 1 || num2 > 129 || num < 0 || num >= num2) { throw new InvalidDataException($"part {num} of {num2}"); } if (num == 0) { syncedText.Incoming = new MemoryStream(); syncedText.NextPart = 0; } if (syncedText.Incoming == null || num != syncedText.NextPart) { throw new InvalidDataException($"part {num} came out of order"); } syncedText.Incoming.Write(array, 0, array.Length); syncedText.NextPart = num + 1; if (num >= num2 - 1) { ZPackage val = new ZPackage(syncedText.Incoming.ToArray()); syncedText.Incoming = null; ZPackage val2 = (val.ReadBool() ? val.ReadCompressedPackage() : val.ReadPackage()); string text = val2.ReadString(); syncedText.FromServer = true; Give(syncedText, text); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"The server's {key} applies while you play here ({text.Length / 1024} KB)"); } } } catch (Exception ex) { if (syncedText != null) { syncedText.Incoming = null; } ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Could not read the server's data" + ((syncedText != null) ? (" for " + syncedText.Key) : "") + ": " + ex.Message)); } } } private void SetServer(ServerMod state, string version, int protocol) { if (state != ServerMod.Checking) { _askedAt = -1f; } bool flag = state != Server || version != ServerVersion; Server = state; ServerVersion = version; _serverProtocol = protocol; if (!flag) { return; } Guard(); try { this.ServerChanged?.Invoke(); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("ServerChanged: " + ex)); } } } private void Guard() { bool flag = Server == ServerMod.NotConnected || Server == ServerMod.Installed; bool flag2 = false; foreach (GameObject guardedPiece in _guardedPieces) { Piece val = (((Object)(object)guardedPiece != (Object)null) ? guardedPiece.GetComponent<Piece>() : null); if ((Object)(object)val != (Object)null && val.m_enabled != flag) { val.m_enabled = flag; flag2 = true; } } foreach (Recipe guardedRecipe in _guardedRecipes) { if ((Object)(object)guardedRecipe != (Object)null && guardedRecipe.m_enabled != flag) { guardedRecipe.m_enabled = flag; flag2 = true; } } if (flag2 && (Object)(object)Player.m_localPlayer != (Object)null) { try { UpdateAvailablePieces?.Invoke(Player.m_localPlayer, null); } catch (Exception) { } } } private void WatchFile() { try { string configFilePath = _config.ConfigFilePath; _watcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath)) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite) }; FileSystemEventHandler value = delegate { _fileTouched = true; }; _watcher.Changed += value; _watcher.Created += value; _watcher.Renamed += delegate { _fileTouched = true; }; _watcher.EnableRaisingEvents = true; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Edits to the config file apply after a restart (can't watch it: " + ex.Message + ")")); } } } private void ReloadFile() { string text = ReadFile(_config.ConfigFilePath); if (text == null) { if (File.Exists(_config.ConfigFilePath)) { _reloadAt = Time.unscaledTime + 1f; } } else { if (text == _fileText) { return; } _fileText = text; bool saveOnConfigSet = _config.SaveOnConfigSet; _reloading = true; try { _config.SaveOnConfigSet = false; _config.Reload(); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not read the edited config file (check the line you changed): " + ex.Message)); } } finally { _config.SaveOnConfigSet = saveOnConfigSet; _reloading = false; } } } private void PatchSave() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0067: Expected O, but got Unknown try { _saveHarmony = new Harmony(_send + ".save"); _saveHarmony.Patch((MethodBase)AccessTools.Method(typeof(ConfigFile), "Save", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ServerSettings), "BeforeSave", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ServerSettings), "AfterSave", (Type[])null), (HarmonyMethod)null); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not keep the server's values out of your config file: " + ex.Message)); } } } private static void UnpatchSave() { try { Harmony saveHarmony = _saveHarmony; if (saveHarmony != null) { saveHarmony.UnpatchSelf(); } } catch (Exception) { } _saveHarmony = null; } private static void BeforeSave(ConfigFile __instance) { foreach (ServerSettings item in Live) { if (item._config != __instance) { continue; } foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._own) { SetQuietly(item2.Key, item2.Value); } } } private static Exception AfterSave(ConfigFile __instance, Exception __exception) { foreach (ServerSettings item in Live) { if (item._config != __instance) { continue; } foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._server) { SetQuietly(item2.Key, item2.Value); } item._fileText = ReadFile(__instance.ConfigFilePath); } return __exception; } private static string ReadFile(string path) { try { return File.Exists(path) ? File.ReadAllText(path) : null; } catch (Exception) { return null; } } private static void SetQuietly(ConfigEntryBase e, object value) { AccessTools.Field(((object)e).GetType(), "_typedValue")?.SetValue(e, value); } private static BepInPlugin PluginOf(string guid) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown Type[] array; try { array = typeof(ServerSettings).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } Type[] array2 = array; foreach (Type type in array2) { object[] customAttributes = type.GetCustomAttributes(typeof(BepInPlugin), inherit: false); for (int num2 = 0; num2 < customAttributes.Length; num2++) { BepInPlugin val = (BepInPlugin)customAttributes[num2]; if (val.GUID == guid || guid.StartsWith(val.GUID + ".")) { return val; } } } return null; } private void Unsubscribe(ConfigEntryBase e) { ((object)e).GetType().GetEvent("SettingChanged")?.RemoveEventHandler(e, new EventHandler(OnChanged)); } } } namespace FulingHeckler { internal static class Commands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__4_0; internal void <Register>b__4_0(ConsoleEventArgs args) { Run(args.Context, args.Args); } } private const string Command = "heckler"; private const string Usage = "heckler summon [emote] | heckler torch | heckler lurker|redlight|tombstone|copycat|campfire|thief|gifter | heckler freeze | heckler unfreeze | heckler where | heckler portrait|photo [distance] [turn] [height] [size] [above] | heckler status"; private static readonly FieldRef<Dictionary<string, ConsoleCommand>> TerminalCommands = AccessTools.StaticFieldRefAccess<Dictionary<string, ConsoleCommand>>(AccessTools.Field(typeof(Terminal), "commands")); private static readonly Dictionary<string, SceneKind> SceneNames = new Dictionary<string, SceneKind> { { "lurker", SceneKind.Lurker }, { "redlight", SceneKind.RedLight }, { "tombstone", SceneKind.Tombstone }, { "copycat", SceneKind.Copycat }, { "campfire", SceneKind.Campfire }, { "thief", SceneKind.CoinThief }, { "gifter", SceneKind.Gifter } }; private const int PortraitLayer = 31; internal static void Register() { //IL_0033: 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) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown object obj = <>c.<>9__4_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { Run(args.Context, args.Args); }; <>c.<>9__4_0 = val; obj = (object)val; } new ConsoleCommand("heckler", "Fuling Heckler: heckler summon [emote] | heckler torch | heckler lurker|redlight|tombstone|copycat|campfire|thief|gifter | heckler freeze | heckler unfreeze | heckler where | heckler portrait|photo [distance] [turn] [height] [size] [above] | heckler status", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } internal static void Unregister() { TerminalCommands.Invoke().Remove("heckler"); } private static string Portrait(FulingActor actor, float distance, float turn, float height, int size, float above, bool scene = false) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Expected O, but got Unknown //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Expected O, but got Unknown //IL_0238: Unknown result type (might be due to invalid IL or missing references) Renderer[] componentsInChildren = ((Component)actor).GetComponentsInChildren<Renderer>(true); Dictionary<GameObject, int> dictionary = new Dictionary<GameObject, int>(); GameObject val = null; RenderTexture val2 = null; try { Animator componentInChildren = ((Component)actor).GetComponentInChildren<Animator>(); Transform val3 = (((Object)(object)componentInChildren != (Object)null && componentInChildren.isHuman) ? componentInChildren.GetBoneTransform((HumanBodyBones)10) : null); Vector3 val4 = (((Object)(object)val3 != (Object)null) ? val3.position : (((Component)actor).transform.position + Vector3.up * 1.4f)) + Vector3.up * height; Vector3 position = val4 + Quaternion.Euler(0f, turn, 0f) * ((Component)actor).transform.forward * distance + Vector3.up * above; if (!scene) { Renderer[] array = componentsInChildren; foreach (Renderer val5 in array) { dictionary[((Component)val5).gameObject] = ((Component)val5).gameObject.layer; ((Component)val5).gameObject.layer = 31; } } val = new GameObject("FulingHeckler_PortraitCamera"); Camera val6 = val.AddComponent<Camera>(); if (scene && (Object)(object)Camera.main != (Object)null) { val6.CopyFrom(Camera.main); } ((Behaviour)val6).enabled = false; if (!scene) { val6.cullingMask = int.MinValue; val6.clearFlags = (CameraClearFlags)2; val6.backgroundColor = new Color(0f, 0f, 0f, 0f); } val6.fieldOfView = (scene ? 40f : 30f); val6.nearClipPlane = 0.05f; val6.allowHDR = false; ((Component)val6).transform.position = position; ((Component)val6).transform.LookAt(val4); int num = (scene ? (size * 9 / 16) : size); val2 = (val6.targetTexture = new RenderTexture(size, num, 24, (RenderTextureFormat)0) { antiAliasing = 8 }); val6.aspect = (float)size / (float)num; val6.Render(); RenderTexture active = RenderTexture.active; RenderTexture.active = val2; Texture2D val8 = new Texture2D(size, num, (TextureFormat)(scene ? 3 : 4), false); val8.ReadPixels(new Rect(0f, 0f, (float)size, (float)num), 0, 0); val8.Apply(); RenderTexture.active = active; string text = Path.Combine(Paths.ConfigPath, "FulingHeckler_portraits"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, DateTime.Now.ToString("yyyyMMdd-HHmmss-fff") + ".png"); File.WriteAllBytes(text2, ImageConversion.EncodeToPNG(val8)); Object.Destroy((Object)(object)val8); return text2; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Portrait failed (" + ex.GetType().Name + ": " + ex.Message + ").")); return null; } finally { foreach (KeyValuePair<GameObject, int> item in dictionary) { if ((Object)(object)item.Key != (Object)null) { item.Key.layer = item.Value; } } if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if ((Object)(object)val2 != (Object)null) { val2.Release(); Object.Destroy((Object)(object)val2); } } } private static void Run(Terminal t, string[] a) { //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) string text = ((a.Length > 1) ? a[1].ToLowerInvariant() : "status"); FulingActor current = FulingActor.Current; switch (text) { case "summon": Plugin.Instance.TestSummon(emoteTest: false, null, (a.Length > 2) ? string.Join(" ", a, 2, a.Length - 2) : null); break; case "torch": Plugin.Instance.TestSummon(emoteTest: false, true); break; case "freeze": case "unfreeze": if ((Object)(object)current == (Object)null) { t.AddString("No Fuling is out."); break; } current.Freeze(text == "freeze"); t.AddString((text == "freeze") ? "He holds still (heckler unfreeze lets him go on)." : "He carries on."); break; case "where": { if ((Object)(object)current == (Object)null) { t.AddString("No Fuling is out."); break; } Vector3 position = ((Component)current).transform.position; t.AddString($"Fuling at {position.x:0.00} {position.y:0.00} {position.z:0.00}, facing {((Component)current).transform.eulerAngles.y:0}"); break; } case "photo": case "portrait": { if ((Object)(object)current == (Object)null) { t.AddString("No Fuling is out."); break; } string text2 = Portrait(current, F(2, 1.3f), F(3, 0f), F(4, -0.1f), (int)F(5, 1024f), F(6, 0f), text == "photo"); t.AddString((text2 != null) ? ("Portrait saved: " + text2) : "Couldn't take the portrait (see the log)."); break; } case "status": t.AddString(string.Format("Fuling Heckler {0}: {1}, {2} quips ({3}), ", "1.0.0", Plugin.Enabled.Value ? "on" : "off", Plugin.Quips.Count, Plugin.Language) + string.Format("{0} loot entries, {1} fire(s) loaded, a Fuling {2}.", Plugin.Loot.Entries.Count, Scenes.Fires.Count, ((Object)(object)current != (Object)null) ? "is out" : "isn't out")); break; default: { if (SceneNames.TryGetValue(text, out var value)) { Plugin.Instance.TestScene(value); } else { t.AddString("heckler summon [emote] | heckler torch | heckler lurker|redlight|tombstone|copycat|campfire|thief|gifter | heckler freeze | heckler unfreeze | heckler where | heckler portrait|photo [distance] [turn] [height] [size] [above] | heckler status"); } break; } } float F(int i, float d) { if (a.Length <= i || !float.TryParse(a[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return d; } return result; } } } public enum When { First, Repeat, Milestone, Any } public enum DeathCategory { Environment, Falling, Trees, Creatures, FriendlyFire, Other } public class DeathInfo { public string HitType; public string KillerPrefab; public string KillerName; public bool KillerIsPlayer; public bool KillerIsSelf; } public class DeathEvent { public string Key; public DeathCategory Category; public string KillerName; public List<string> Chain; } public static class DeathClassifier { private static readonly HashSet<string> Bosses = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Eikthyr", "gd_king", "Bonemass", "Dragon", "GoblinKing", "SeekerQueen", "Fader" }; public static DeathEvent Classify(DeathInfo d) { string text; DeathCategory category; switch (d.HitType ?? "") { case "Drowning": text = "drowning"; category = DeathCategory.Environment; break; case "Fall": text = "fall"; category = DeathCategory.Falling; break; case "CinderFire": case "Burning": text = "burning"; category = DeathCategory.Environment; break; case "Freezing": text = "freezing"; category = DeathCategory.Environment; break; case "Poisoned": text = "poison"; category = DeathCategory.Environment; break; case "Smoke": text = "smoke"; category = DeathCategory.Environment; break; case "EdgeOfWorld": text = "edge"; category = DeathCategory.Environment; break; case "Water": case "AshlandsOcean": text = "boiled"; category = DeathCategory.Environment; break; case "Tree": text = "tree"; category = DeathCategory.Trees; break; case "Self": text = "self"; category = DeathCategory.FriendlyFire; break; case "Cart": text = "cart"; category = DeathCategory.Other; break; case "Boat": text = "boat"; category = DeathCategory.Other; break; case "Structural": text = "structural"; category = DeathCategory.Other; break; case "Stalagtite": text = "stalactite"; category = DeathCategory.Environment; break; case "AshlandsLava": text = "lava"; category = DeathCategory.Environment; break; case "Incinerator": text = "incinerator"; category = DeathCategory.Other; break; case "Catapult": case "Turret": text = "siege"; category = DeathCategory.Other; break; default: text = null; category = DeathCategory.Other; break; } List<string> list = new List<string>(); if (text == null) { if (d.KillerIsSelf) { text = "self"; category = DeathCategory.FriendlyFire; } else if (d.KillerIsPlayer) { text = "friend"; category = DeathCategory.FriendlyFire; } else { if (!string.IsNullOrEmpty(d.KillerPrefab)) { text = "enemy:" + d.KillerPrefab.ToLowerInvariant(); category = DeathCategory.Creatures; list.Add(text); if (Bosses.Contains(d.KillerPrefab)) { list.Add("boss"); } list.Add("enemy"); list.Add("generic"); return new DeathEvent { Key = text, Category = category, KillerName = d.KillerName, Chain = list }; } text = "unknown"; category = DeathCategory.Other; } } list.Add(text); list.Add("generic"); return new DeathEvent { Key = text, Category = category, KillerName = d.KillerName, Chain = list }; } } public class QuipLine { public string Event; public When When; public string Text; public bool UsesKiller => Text.Contains("{killer}"); } public class QuipBook { public readonly List<QuipLine> Lines = new List<QuipLine>(); public readonly List<string> Warnings = new List<string>(); private static readonly Dictionary<string, string> RetiredLines = new Dictionary<string, string> { { "enemy:drake | any | Ice-bird freeze you! {me} stay away from mountain. Mountain is mean.", "enemy:hatchling | any | Ice-bird freeze you! {me} stay away from mountain. Mountain is mean." }, { "generic | repeat | Tall-one die {count} time. {me} counting on fingers. Need more fingers.", "generic | repeat | Tall-one die {n} times. {me} counting on fingers. Need more fingers." }, { "fall | repeat | Tall-one jump, tall-one go splat. {count} splat! {me} love this game.", "fall | repeat | Tall-one jump, tall-one go splat. {n} splats! {me} love this game." }, { "tree | repeat | Tree kill you {count} time. {me} think trees on Fuling side.", "tree | repeat | Tree kill you {n} times. {me} think trees on Fuling side." }, { "enemy:deer | repeat | Deer get you {count} time. Deer is not even angry. Deer just walking.", "enemy:deer | repeat | Deer get you {n} times. Deer not even angry. Deer just walking." }, { "enemy:blob | any | Puddle beat you. Angry puddle. {me} has seen everything now.", "enemy:blob | any | Puddle beat you. Angry puddle. {me} seen everything now." }, { "enemy:goblin | any | My cousin get you! Cousin will be so happy! {me} bring cake.", "enemy:goblin | any | {me} cousin get you! Cousin will be so happy! {me} bring cake." }, { "enemy:goblinarcher | any | Cousin with bow! Good shot cousin!", "enemy:goblinarcher | any | Cousin with bow! Good shot, cousin!" } }; private static readonly HashSet<string> AddedLines = new HashSet<string> { "lava | first | Floor is lava! Tall-one not know game? {me} know game. {me} WIN game.", "lava | repeat | Hot rock again! {me} smell you from far away. Crispy.", "lava | repeat | Tall-one go swimming in fire-water. {me} stay on cold rock. Smart {me}.", "incinerator | any | Tall-one jump in big junk-box! Box eat tall-one! {me} put junk in there, not tall-ones!", "incinerator | any | Poof! Tall-one gone! Like junk! {me} laughing SO hard!" }; public int Count => Lines.Count; public void Load(IEnumerable<string> rawLines) { Lines.Clear(); Warnings.Clear(); int num = 0; foreach (string rawLine in rawLines) { num++; string text = rawLine.Trim(); if (text.Length == 0 || text.StartsWith("#")) { continue; } string[] array = text.Split(new char[1] { '|' }, 3); if (array.Length < 3) { Warnings.Add($"line {num}: expected 'event | when | text', skipped."); continue; } if (!Enum.TryParse<When>(array[1].Trim(), ignoreCase: true, out var result) || !Enum.IsDefined(typeof(When), result)) { Warnings.Add($"line {num}: unknown 'when' '{array[1].Trim()}' (use first, repeat, milestone or any), skipped."); continue; } string text2 = array[2].Trim(); if (text2.Length != 0) { Lines.Add(new QuipLine { Event = array[0].Trim().ToLowerInvariant(), When = result, Text = text2 }); } } } public static string ReadBuiltInQuips(string language) { if (string.IsNullOrEmpty(language) || language == "English") { return ReadDefaultQuips(); } Assembly executingAssembly = Assembly.GetExecutingAssembly(); using Stream stream = executingAssembly.GetManifestResourceStream("FulingHeckler.Quips." + language + ".txt"); if (stream == null) { return null; } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); return streamReader.ReadToEnd(); } public static bool SameText(string a, string b) { return Norm(a).SequenceEqual(Norm(b)); static IEnumerable<string> Norm(string t) { string value; return from l in (t ?? "").Replace("\r", "").Split(new char[1] { '\n' }) select string.Join(" | ", from x in l.Split(new char[1] { '|' }) select string.Join(" ", x.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))) into l where l.Length > 0 && !l.StartsWith("#") select (!RetiredLines.TryGetValue(l, out value)) ? l : value into l where !AddedLines.Contains(l) select l; } } public static string ReadDefaultQuips() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); using Stream stream = executingAssembly.GetManifestResourceStream("FulingHeckler.DefaultQuips.txt"); if (stream == null) { return "generic | any | Hehehe! Fuling has no words. Fuling laughing too hard.\n"; } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); return streamReader.ReadToEnd(); } } public static class Placeholders { public static string TallOne = "tall-one"; public static string Something = "something"; public static Func<int, string> OrdinalWord; public static string Fill(string text, string playerName, string killerName, int count) { return text.Replace("{name}", string.IsNullOrEmpty(playerName) ? TallOne : playerName).Replace("{killer}", string.IsNullOrEmpty(killerName) ? Something : killerName).Replace("{count}", Ordinal(count)) .Replace("{n}", count.ToString()); } public static string FillMe(string text, string ownName, double nameChance, Random rng) { if (string.IsNullOrEmpty(text) || text.IndexOf("{me}", StringComparison.OrdinalIgnoreCase) < 0) { return text; } string replacement = ((!string.IsNullOrEmpty(ownName) && rng.NextDouble() < nameChance) ? ownName : "Fuling"); return Regex.Replace(text, "\\{me\\}", replacement, RegexOptions.IgnoreCase); } public static string Ordinal(int n) { if (OrdinalWord != null) { return OrdinalWord(n); } int num = n % 100; string text = ((num >= 11 && num <= 13) ? "th" : ((n % 10 == 1) ? "st" : ((n % 10 == 2) ? "nd" : ((n % 10 == 3) ? "rd" : "th")))); return n + text; } } public class PickerSettings { public float ChanceToComment = 0.6f; public bool AlwaysCommentOnFirst = true; public float CooldownSeconds = 90f; public HashSet<int> Milestones = new HashSet<int> { 5, 10, 25, 50, 100 }; public static HashSet<int> ParseMilestones(string s) { HashSet<int> hashSet = new HashSet<int>(); string[] array = (s ?? "").Split(new char[1] { ',' }); foreach (string text in array) { if (int.TryParse(text.Trim(), out var result) && result > 1) { hashSet.Add(result); } } return hashSet; } } public class Decision { public string Line; public bool IsFirst; public bool IsMilestone; public string Reason; public List<string> DeathChain; public bool IsSpecial { get { if (!IsFirst) { return IsMilestone; } return true; } } } public class ImpressionBook { public readonly Dictionary<string, List<string>> Moves = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); public static ImpressionBook Parse(string s) { ImpressionBook impressionBook = new ImpressionBook(); string[] array = (s ?? "").Split(new char[1] { '|' }); foreach (string text in array) { int num = text.LastIndexOf(':'); if (num > 0) { string text2 = text.Substring(0, num).Trim(); List<string> list = NameList.Parse(text.Substring(num + 1)); if (text2.Length > 0 && list.Count > 0) { impressionBook.Moves[text2] = list; } } } return impressionBook; } public List<string> For(IList<string> chain) { if (chain != null) { foreach (string item in chain) { if (item != null && Moves.TryGetValue(item, out var value)) { return value; } } } if (!Moves.TryGetValue("generic", out var value2)) { return new List<string>(); } return value2; } } public static class QuipPicker { private static readonly Queue<string> Recent = new Queue<string>(); private const int RecentMemory = 10; public static Decision Decide(QuipBook book, DeathEvent ev, int count, PickerSettings s, float secondsSinceLastComment, bool categoryEnabled, Random rng) { if (!categoryEnabled) { return Quiet("category turned off"); } bool flag = count == 1; bool flag2 = s.Milestones.Contains(count); if (!((flag && s.AlwaysCommentOnFirst) || flag2)) { if (secondsSinceLastComment < s.CooldownSeconds) { return Quiet("on cooldown"); } if (rng.NextDouble() >= (double)s.ChanceToComment) { return Quiet("chose to stay home"); } } When[] whens = ((!flag) ? ((!flag2) ? new When[2] { When.Repeat, When.Any } : new When[3] { When.Milestone, When.Repeat, When.Any }) : new When[2] { When.First, When.Any }); QuipLine quipLine = Find(book, ev.Chain, whens, rng); if (quipLine == null) { return Quiet("no matching lines in quips file"); } string text = (flag2 ? "milestone" : (flag ? "first time" : "repeat")); return new Decision { Line = quipLine.Text, IsFirst = flag, IsMilestone = flag2, Reason = "Fuling heckles (" + text + ")", DeathChain = ev.Chain }; } private static QuipLine Find(QuipBook book, List<string> chain, When[] whens, Random rng) { foreach (string key in chain) { foreach (When w in whens) { List<QuipLine> list = book.Lines.Where((QuipLine q) => q.Event == key && q.When == w).ToList(); if (list.Count > 0) { return PickAvoidingRepeats(list, rng); } } } return null; } private static QuipLine PickAvoidingRepeats(List<QuipLine> candidates, Random rng) { List<QuipLine> list = candidates.Where((QuipLine q) => !Recent.Contains(q.Text)).ToList(); List<QuipLine> list2 = ((list.Count > 0) ? list : candidates); QuipLine quipLine = list2[rng.Next(list2.Count)]; Recent.Enqueue(quipLine.Text); while (Recent.Count > 10) { Recent.Dequeue(); } return quipLine; } public static string PickForTest(QuipBook book, Random rng) { List<QuipLine> list = book.Lines.Where((QuipLine q) => !q.UsesKiller).ToList(); if (list.Count != 0) { return list[rng.Next(list.Count)].Text; } return null; } private static Decision Quiet(string reason) { return new Decision { Line = null, Reason = reason }; } } public class DeathMemory { private readonly string _folder; private readonly Dictionary<string, Dictionary<string, int>> _cache = new Dictionary<string, Dictionary<string, int>>(); public DeathMemory(string folder) { _folder = folder; } public int Increment(string playerName, string key) { Dictionary<string, int> dictionary = Load(playerName); dictionary.TryGetValue(key, out var value); value = (dictionary[key] = value + 1); Save(playerName, dictionary); return value; } public int Get(string playerName, string key) { Load(playerName).TryGetValue(key, out var value); return value; } public void Reset(string playerName) { _cache[playerName] = new Dictionary<string, int>(); try { File.Delete(PathFor(playerName)); } catch { } } private Dictionary<string, int> Load(string playerName) { if (_cache.TryGetValue(playerName, out var value)) { return value; } value = new Dictionary<string, int>(); try { string path = PathFor(playerName); if (File.Exists(path)) { string[] array = File.ReadAllLines(path); foreach (string text in array) { int num = text.LastIndexOf('='); if (num > 0 && int.TryParse(text.Substring(num + 1), out var result)) { value[text.Substring(0, num)] = result; } } } } catch { } _cache[playerName] = value; return value; } private void Save(string playerName, Dictionary<string, int> t) { try { Directory.CreateDirectory(_folder); File.WriteAllLines(PathFor(playerName), (from kv in t orderby kv.Key select kv.Key + "=" + kv.Value).ToArray()); } catch { } } private string PathFor(string playerName) { string text = new string((playerName ?? "unknown").Select((char c) => (!Path.GetInvalidFileNameChars().Contains(c)) ? c : '_').ToArray()); return Path.Combine(_folder, text + ".txt"); } } public static class NameList { public static List<string> Parse(string s) { return (from x in (s ?? "").Split(new char[1] { ',' }) select x.Trim() into x where x.Length > 0 select x).ToList(); } } public class Fit { public float Up; public float Forward; public float Side; public float Tilt; public float Turn; public float Size = 1f; public static readonly Fit Identity = new Fit(); public override string ToString() { CultureInfo c = CultureInfo.InvariantCulture; return string.Join(", ", new float[6] { Up, Forward, Side, Tilt, Turn, Size }.Select((float v) => v.ToString("0.###", c)).ToArray()); } public static bool TryParse(string s, out Fit fit) { fit = null; string[] array = (s ?? "").Split(new char[1] { ',' }); if (array.Length != 6) { return false; } float[] array2 = new float[6]; for (int i = 0; i < 6; i++) { if (!float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i])) { return false; } } fit = new Fit { Up = array2[0], Forward = array2[1], Side = array2[2], Tilt = array2[3], Turn = array2[4], Size = ((array2[5] <= 0f) ? 1f : array2[5]) }; return true; } } public class FitBook { public readonly Dictionary<string, Fit> Fits = new Dictionary<string, Fit>(StringComparer.OrdinalIgnoreCase); public static FitBook Parse(string s) { FitBook fitBook = new FitBook(); string[] array = (s ?? "").Split(new char[1] { '|' }); foreach (string text in array) { int num = text.IndexOf(':'); if (num > 0) { string text2 = text.Substring(0, num).Trim(); if (text2.Length > 0 && Fit.TryParse(text.Substring(num + 1), out var fit)) { fitBook.Fits[text2] = fit; } } } return fitBook; } public Fit For(string helmet) { if (helmet != null && Fits.TryGetValue(helmet, out var value)) { return value; } if (!Fits.TryGetValue("default", out var value2)) { return Fit.Identity; } return value2; } public bool HasOwn(string helmet) { if (helmet != null) { return Fits.ContainsKey(helmet); } return false; } public override string ToString() { IOrderedEnumerable<string> source = Fits.Keys.OrderBy<string, string>((string k) => (!k.Equals("default", StringComparison.OrdinalIgnoreCase)) ? k : "", StringComparer.OrdinalIgnoreCase); return string.Join(" | ", source.Select((string k) => $"{k}: {Fits[k]}").ToArray()); } } public enum LootTier { Junk, Meh, Nice, Treasure } public class LootEntry { public string Item; public double Weight; public int Min = 1; public int Max = 1; public LootTier Tier; } public class LootTable { public readonly List<LootEntry> Entries = new List<LootEntry>(); public readonly List<string> Warnings = new List<string>(); public void Load(IEnumerable<string> rawLines) { Entries.Clear(); Warnings.Clear(); int num = 0; foreach (string rawLine in rawLines) { num++; string text = rawLine.Trim(); if (text.Length == 0 || text.StartsWith("#")) { continue; } string[] array = (from p in text.Split(new char[1] { '|' }) select p.Trim()).ToArray(); if (array.Length < 2 || array[0].Length == 0) { Warnings.Add($"line {num}: expected 'item | weight | amount | tier', skipped."); continue; } LootEntry lootEntry = new LootEntry { Item = array[0] }; if (!double.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out lootEntry.Weight) || lootEntry.Weight <= 0.0) { Warnings.Add($"line {num}: weight '{array[1]}' must be a positive number, skipped."); continue; } if (array.Length >= 3 && array[2].Length > 0) { string[] array2 = array[2].Split(new char[1] { '-' }); if (!int.TryParse(array2[0].Trim(), out lootEntry.Min) || lootEntry.Min < 1) { Warnings.Add($"line {num}: amount '{array[2]}' should look like 2 or 1-3, skipped."); continue; } lootEntry.Max = lootEntry.Min; if (array2.Length > 1 && (!int.TryParse(array2[1].Trim(), out lootEntry.Max) || lootEntry.Max < lootEntry.Min)) { Warnings.Add($"line {num}: amount '{array[2]}' should look like 2 or 1-3, skipped."); continue; } } if (array.Length >= 4 && array[3].Length > 0 && !Enum.TryParse<LootTier>(array[3], ignoreCase: true, out lootEntry.Tier)) { Warnings.Add($"line {num}: tier '{array[3]}' should be junk, meh, nice or treasure; using junk."); lootEntry.Tier = LootTier.Junk; } Entries.Add(lootEntry); } } public LootEntry Pick(Random rng) { double num = Entries.Sum((LootEntry e) => e.Weight); if (num <= 0.0) { return null; } double num2 = rng.NextDouble() * num; foreach (LootEntry entry in Entries) { num2 -= entry.Weight; if (num2 < 0.0) { return entry; } } return Entries[Entries.Count - 1]; } public static string ReadDefault() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); using Stream stream = executingAssembly.GetManifestResourceStream("FulingHeckler.DefaultLoot.txt"); if (stream == null) { return "Pukeberries | 1 | 1-2 | junk\n"; } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); return streamReader.ReadToEnd(); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerOnDeathPatch { private static Player _lastHandled; private static void Prefix(Player __instance) { try { if ((Object)(object)Plugin.Instance == (Object)null || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)__instance == (Object)(object)_lastHandled) { return; } _lastHandled = __instance; DeathInfo deathInfo = new DeathInfo(); HitData value = Traverse.Create((object)__instance).Field("m_lastHit").GetValue<HitData>(); if (value != null) { deathInfo.HitType = ((object)Unsafe.As<HitType, HitType>(ref value.m_hitType)/*cast due to .constrained prefix*/).ToString(); Character attacker = value.GetAttacker(); if ((Object)(object)attacker != (Object)null) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { deathInfo.KillerIsPlayer = true; deathInfo.KillerIsSelf = (Object)(object)val == (Object)(object)__instance; deathInfo.KillerName = val.GetPlayerName(); } else { deathInfo.KillerPrefab = Utils.GetPrefabName(((Component)attacker).gameObject); deathInfo.KillerName = ((Localization.instance != null) ? Localization.instance.Localize(attacker.m_name) : attacker.m_name); } } } Plugin.Instance.OnLocalPlayerDied(__instance, deathInfo); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Fuling couldn't read that death: " + ex.Message)); } } } } internal class Delivery : MonoBehaviour { private Decision _pending; private Player _deadBody; private float _aliveSince = -1f; private static readonly FieldRef<Chat, float> ChatHideTimer = AccessTools.FieldRefAccess<Chat, float>("m_hideTimer"); public void QueueAfterRespawn(Player deadBody, Decision d) { _pending = d; _deadBody = deadBody; _aliveSince = -1f; } private void Update() { if (_pending == null) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)localPlayer == (Object)(object)_deadBody || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting()) { _aliveSince = -1f; return; } if (_aliveSince < 0f) { _aliveSince = Time.time; } if (!(Time.time - _aliveSince < Plugin.DeliveryDelaySeconds.Value)) { Decision pending = _pending; _pending = null; string line = pending.Line; bool isSpecial = pending.IsSpecial; bool isMilestone = pending.IsMilestone; List<string> deathChain = pending.DeathChain; Deliver(localPlayer, line, isSpecial, isMilestone, emoteTest: false, null, isTest: false, null, null, forceOutfit: false, fitting: false, deathChain); } } public void Deliver(Player p, string line, bool special, bool milestone, bool emoteTest = false, bool? forceTorch = null, bool isTest = false, string forcedEmote = null, string forcedHelmet = null, bool forceOutfit = false, bool fitting = false, List<string> deathChain = null) { DeliveryMode value = Plugin.DeliveryModeSetting.Value; if (isTest || emoteTest || forceTorch.HasValue || value == DeliveryMode.Always || (value == DeliveryMode.SpecialOnly && special)) { Performance performance = new Performance(); performance.Line = line; performance.IsMilestone = milestone; performance.DeathChain = deathChain; performance.EmoteTest = emoteTest; performance.ForceTorch = forceTorch; performance.ForcedEmote = forcedEmote; performance.ForcedHelmet = forcedHelmet; performance.ForceOutfit = forceOutfit; performance.Fitting = fitting; performance.FallbackToText = ShowLine; Performance show = performance; if (FulingActor.TrySpawn(p, show)) { return; } } ShowLine(line); } internal static void ShowLine(string text) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) TextPosition value = Plugin.TextPos.Value; if (value == TextPosition.Off) { Plugin.Log.LogInfo((object)"Fuling's line by text: none (Text Position is Off)."); return; } string text2 = Plugin.FillMe(text); Plugin.Log.LogInfo((object)$"Fuling's line by text ({value}): {text2}"); if (value == TextPosition.Chat && (Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString(Look.Name, text2, (Type)1, false); try { ChatHideTimer.Invoke(Chat.instance) = 0f; return; } catch { return; } } if (!((Object)(object)MessageHud.instance == (Object)null)) { string text3 = "<color=#E07A3A><b>" + Look.Name + ":</b></color> " + text2; MessageType val = (MessageType)((value != TextPosition.Center) ? 1 : 2); MessageHud.instance.ShowMessage(val, text3, 0, (Sprite)null, false, true); } } public static void ShowPlainText(string text) { if (!((Object)(object)MessageHud.instance == (Object)null)) { MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } } internal class EmotePlayer { private static List<AnimationClip> _playerClips; private static bool _loggedClips; private readonly Animator _animator; private readonly RuntimeAnimatorController _original; private readonly AnimationClip _stagger; private readonly Dictionary<AnimationClip, AnimationClip> _base = new Dictionary<AnimationClip, AnimationClip>(); private static bool _loggedOwnClips; private static readonly Dictionary<string, string> Aliases = new Dictionary<string, string> { { "comehere", "GetOverHere" }, { "challenge", "ChallengeAccepted" } }; public bool Ready { get { if ((Object)(object)_original != (Object)null) { return (Object)(object)_stagger != (Object)null; } return false; } } public EmotePlayer(Animator fuling) { _animator = fuling; _original = (((Object)(object)fuling != (Object)null) ? fuling.runtimeAnimatorController : null); if ((Object)(object)_original == (Object)null) { return; } _stagger = ((IEnumerable<AnimationClip>)_original.animationClips).FirstOrDefault((Func<AnimationClip, bool>)((AnimationClip c) => (Object)(object)c != (Object)null && ((Object)c).name.IndexOf("stagger", StringComparison.OrdinalIgnoreCase) >= 0)); if ((Object)(object)_stagger == (Object)null) { Plugin.Log.LogWarning((object)"Emotes: couldn't find the Fuling's stagger animation to borrow; he'll use his own moves."); } if (!_loggedOwnClips) { _loggedOwnClips = true; Plugin.Log.LogInfo((object)("Fuling's own animations: " + string.Join(", ", (from n in (from c in _original.animationClips where (Object)(object)c != (Object)null select ((Object)c).name).Distinct() orderby n select n).ToArray()))); } } public void SetTorchPose(string runName, string idleName) { if ((Object)(object)_original == (Object)null) { return; } AnimationClip val = Find(runName); AnimationClip val2 = Find(idleName); List<string> list = new List<string>(); foreach (AnimationClip item in _original.animationClips.Where((AnimationClip c) => (Object)(object)c != (Object)null).Distinct()) { HashSet<string> hashSet = new HashSet<string>(Regex.Split(((Object)item).name.ToLowerInvariant(), "[^a-z0-9]+")); if (!hashSet.Contains("attack") && !hashSet.Contains("sleep") && !hashSet.Contains("dead") && !hashSet.Contains("death")) { if ((Object)(object)val != (Object)null && hashSet.Contains("run")) { _base[item] = val; list.Add(((Object)item).name + "->" + ((Object)val).name); } else if ((Object)(object)val2 != (Object)null && hashSet.Any((string w) => w == "idle" || w.StartsWith("idle"))) { _base[item] = val2; list.Add(((Object)item).name + "->" + ((Object)val2).name); } } } Plugin.Log.LogInfo((object)((list.Count > 0) ? ("Torch pose: " + string.Join(", ", list.ToArray())) : $"Torch pose: nothing to swap (run '{runName}' found={(Object)(object)val != (Object)null}, idle '{idleName}' found={(Object)(object)val2 != (Object)null}).")); Apply(null); } private void Apply(AnimationClip emote) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown if ((Object)(object)_original == (Object)null) { return; } if (_base.Count == 0 && (Object)(object)emote == (Object)null) { if ((Object)(object)_animator.runtimeAnimatorController != (Object)(object)_original) { _animator.runtimeAnimatorController = _original; } return; } AnimatorOverrideController val = new AnimatorOverrideController(_original); foreach (KeyValuePair<AnimationClip, AnimationClip> item in _base) { val[item.Key] = item.Value; } if ((Object)(object)emote != (Object)null && (Object)(object)_stagger != (Object)null) { val[_stagger] = emote; } _animator.runtimeAnimatorController = (RuntimeAnimatorController)(object)val; } public static List<AnimationClip> PlayerClips() { if (_playerClips != null && _playerClips.Count > 0) { return _playerClips; } List<AnimationClip> list = new List<AnimationClip>(); Player localPlayer = Player.m_localPlayer; Animator val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).GetComponentInChildren<Animator>() : null); if ((Object)(object)val != (Object)null && (Object)(object)val.runtimeAnimatorController != (Object)null) { list = (from c in val.runtimeAnimatorController.animationClips where (Object)(object)c != (Object)null group c by ((Object)c).name into g select g.First() into c orderby ((Object)c).name select c).ToList(); } if (!_loggedClips && list.Count > 0) { _loggedClips = true; Plugin.Log.LogInfo((object)($"Player animations ({list.Count}): " + string.Join(", ", list.Select((AnimationClip c) => ((Object)c).name).ToArray()))); } _playerClips = list; return list; } public static AnimationClip Find(string wanted) { if (string.IsNullOrEmpty(wanted)) { return null; } if (Aliases.TryGetValue(Norm(wanted), out var value)) { wanted = value; } string w = Norm(wanted); List<AnimationClip> list = (from c in PlayerClips() where Norm(((Object)c).name).Contains(w) select c).ToList(); if (list.Count == 0) { return null; } return ((IEnumerable<AnimationClip>)list).FirstOrDefault((Func<AnimationClip, bool>)((AnimationClip c) => Norm(((Object)c).name) == w)) ?? ((IEnumerable<AnimationClip>)list).FirstOrDefault((Func<AnimationClip, bool>)((AnimationClip c) => ((Object)c).name.IndexOf("emote", StringComparison.OrdinalIgnoreCase) >= 0)) ?? list.OrderBy((AnimationClip c) => ((Object)c).name.Length).First(); } private static string Norm(string s) { return new string((s ?? "").Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray()); } public AnimationClip TryPlay(IList<string> wanted, Random rng) { if (!Ready || wanted == null || wanted.Count == 0) { return null; } List<AnimationClip> list = (from c in wanted.Select(Find) where (Object)(object)c != (Object)null select c).Distinct().ToList(); if (list.Count == 0) { Plugin.Log.LogInfo((object)("Emotes: none of [" + string.Join(", ", wanted.ToArray()) + "] matched a player animation. See the 'Player animations' log line for names.")); return null; } AnimationClip val = list[rng.Next(list.Count)]; Apply(val); _animator.SetTrigger("stagger"); Plugin.DebugLog("Fuling performs " + ((Object)val).name + "."); return val; } public void Restore() { Apply(null); } } internal static class EmoteExperiment { public static void Run(Animator fuling, EmotePlayer emotes, Random rng) { ManualLogSource log = Plugin.Log; Player localPlayer = Player.m_localPlayer; Animator val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).GetComponentInChildren<Animator>() : null); if ((Object)(object)fuling == (Object)null || (Object)(object)val == (Object)null) { log.LogWarning((object)"Emote test: no Fuling or no player."); return; } HashSet<string> hashSet = new HashSet<string>(from t in ((Component)val).GetComponentsInChildren<Transform>(true) select ((Object)t).name); HashSet<string> hashSet2 = new HashSet<string>(from t in ((Component)fuling).GetComponentsInChildren<Transform>(true) select ((Object)t).name); log.LogInfo((object)($"Emote test: player rig humanoid={val.isHuman}, Fuling rig humanoid={fuling.isHuman}. " + $"Bone names in common: {hashSet.Count(hashSet2.Contains)} of the player's {hashSet.Count}.")); EmotePlayer.PlayerClips(); AnimationClip val2 = emotes?.TryPlay(new string[1] { "wave" }, rng); log.LogInfo((object)(((Object)(object)val2 != (Object)null) ? ("Emote test: playing '" + ((Object)val2).name + "'.") : "Emote test: couldn't play a wave.")); } } internal enum SceneKind { Heckle, Lurker, RedLight, Tombstone, Copycat, Campfire, CoinThief, Gifter } internal class Performance { public SceneKind Scene; public Vector3? SpawnAt; public Vector3? FacePoint; public Quaternion? SpawnRotation; public string SeatPose; public string Line; public bool IsMilestone; public List<string> DeathChain; public bool EmoteTest; public bool? ForceTorch; public string ForcedEmote; public string ForcedHelmet; public bool ForceOutfit; public bool Fitting; public Action<string> FallbackToText; public bool Puppet; public string PuppetHelmet; public List<string> PuppetOutfit; public bool PuppetTorch; } internal class FulingActor : MonoBehaviour { private const float SpawnDistance = 18f; private const float RunSpeed = 6f; private const float LeaveDistance = 28f; private const float MaxLifetime = 60f; private const float BubbleHeight = 2.1f; private const float BubbleCullDistance = 30f; private const float TurnSpeed = 540f; private static FulingActor _current; private static bool _loggedAnimatorParams; private static GameObject _staging; private static readonly Random Rng = new Random(); private Animator _animator; private readonly HashSet<string> _animParams = new HashSet<string>(); private Performance _show; private EmotePlayer _emotes; private bool _hasTorch; private GameObject _prefab; private Transform _sourceVisual; private Transform _copyVisual; private SkinnedMeshRenderer _body; private GameObject _helmet; private HelmetFitter _fitter; private bool _endFitting; private SkinColor _skin; private bool _animReady; private bool _talkDone; private bool _playingDead; private static readonly string[] BuiltInFleeLines = new string[5] { "Gotta go!", "Too close! {me} go!", "Personal space! Bye!", "NOPE!", "{me} not touch you! Bye bye!" }; private string _helmetItem; private string _emoteNow = ""; private bool _deadNow; private readonly List<string> _outfitWorn = new List<string>(); private float _speedNow; private SoundMoment? _loopSent; private float _pokeSpotted = -99f; private float _pokeWatched = -99f; private float _pokeClose = -99f; private VisitCaster _net; private float _nextMove; private float _nextState; private static readonly Dictionary<string, FulingActor> Puppets = new Dictionary<string, FulingActor>(); private static readonly HashSet<string> FailedPuppets = new HashSet<string>(); private string _puppetKey; private Vector3 _offset; private Vector3 _targetPos; private float _targetYaw; private float _lastHeard; private float _bornAt; private float _nextPoke; private float _seenFor; private SoundMoment? _puppetLoop; private long _directorId; private int _visit; private float _notice; private float _watchAngle; private float _catch; private bool _isEcho; private float _frozenClose; private string _pendingEmote; private float _pendingEmoteAt; private const float StepUp = 0.3f; private const float BodyRadius = 0.4f; private const float KneeHeight = 0.35000002f; private const float ChestHeight = 0.9f; private const float RepathSeconds = 0.5f; private static readonly float[] Sidesteps = new float[6] { 35f, -35f, 70f, -70f, 105f, -105f }; private static int _groundMask = -1; private static int _terrainLayer = -1; private static readonly List<Vector3> _probePath = new List<Vector3>(); private readonly List<Vector3> _path = new List<Vector3>(); private bool _frozen; private Vector3 _pathGoal; private float _pathAt = -99f; private bool _havePath; public static FulingActor Current => _current; public string HelmetName { get { if (!((Object)(object)_helmet != (Object)null)) { return null; } return ((Object)_helmet).name; } } public bool IsFitting { get { if (_show != null && _show.Fitting) { return !_endFitting; } return false; } } internal Vector3 Offset => _offset; private Vector3 Head => ((Component)this).transform.position + Vector3.up * 1.2f; private static int GroundMask { get { if (_groundMask == -1) { _groundMask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "terrain", "piece", "vehicle" }); _terrainLayer = LayerMask.NameToLayer("terrain"); } return _groundMask; } } public static bool TrySpawn(Player player, Performance show) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)player == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } if ((Object)(object)_current != (Object)null) { Object.Destroy((Object)(object)((Component)_current).gameObject); } string type = Look.Type; GameObject prefab = ZNetScene.instance.GetPrefab(type); if ((Object)(object)prefab == (Object)null) { Plugin.Log.LogWarning((object)("No creature called '" + type + "' in this game. Check 'Fuling Type' in the config.")); return false; } Vector3 spawn; if (show.SpawnAt.HasValue) { spawn = show.SpawnAt.Value; } else if (!FindSpawnPoint(player, out spawn)) { Plugin.DebugLog("No dry ground nearby for the Fuling; using text instead."); return false; } Animator componentInChildren = prefab.GetComponentInChildren<Animator>(true); GameObject val = BuildVisualCopy(prefab, componentInChildren, spawn); if ((Object)(object)val == (Object)null) { return false; } FulingActor fulingActor = val.AddComponent<FulingActor>(); fulingActor._show = show; fulingActor._prefab = prefab; fulingActor._sourceVisual = ((Component)componentInChildren).transform; fulingActor._copyVisual = val.transform.Find("Visual"); fulingActor._skin = new SkinColor(((Component)fulingActor._copyVisual).GetComponentsInChildren<Renderer>(true)); fulingActor._skin.Apply(); fulingActor.DressUp(); fulingActor.InitAnimator(); _current = fulingActor; return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't summon the Fuling (" + ex.GetType().Name + ": " + ex.Message + "). Using text instead.")); return false; } } private static GameObject BuildVisualCopy(GameObject prefab, Animator sourceAnim, Vector3 position) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)sourceAnim == (Object)null) { Plugin.Log.LogWarning((object)("'" + ((Object)prefab).name + "' has no animated model to borrow.")); return null; } if ((Object)(object)_staging == (Object)null) { _staging = new GameObject("FulingHeckler_Staging"); _staging.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_staging); } GameObject val = Object.Instantiate<GameObject>(((Component)sourceAnim).gameObject, _staging.transform, false); ((Object)val).name = "Visual"; Gear.Strip(val); if ((Object)(object)val.GetComponentInChildren<ZNetView>(true) != (Object)null) { Plugin.Log.LogError((object)"Safety check failed: the Fuling copy still had network components. Not spawning."); Object.DestroyImmediate((Object)(object)val); return null; } GameObject val2 = new GameObject("FulingHeckler_Fuling"); val2.transform.position = position; val.transform.SetParent(val2.transform, false); val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; return val2; } private void DressUp() { Gear.LogOwnGearOnce(_prefab); _body = Gear.FindBodyOnCopy(_prefab, _sourceVisual, _copyVisual); if (_show.Puppet) { DressAsTold(); return; } string text = _show.ForcedHelmet; if (text == null && Rng.NextDouble() < (double)Look.HelmetChance) { List<string> list = NameList.Parse(Look.Helmets); if (list.Count > 0) { text = list[Rng.Next(list.Count)]; } } if (text != null) { WearHelmet(text, _show.ForcedHelmet != null); } foreach (string item in NameList.Parse(Look.OutfitItems)) { if (_show.ForceOutfit || !(Rng.NextDouble() >= (double)Look.OutfitPieceChance)) { if (Gear.TryAttach(item, null, _body, _copyVisual, 1f, out var reason)) { _outfitWorn.Add(item); Plugin.DebugLog("Fuling is wearing " + item + "."); } else { Plugin.Log.LogInfo((object)("Fuling couldn't wear " + item + ": " + reason)); } } } bool? forceTorch = _show.ForceTorch; bool num; if (!forceTorch.HasValue) { if (!IsNight()) { return; } num = Rng.NextDouble() < (double)Look.TorchAtNightChance; } else { num = forceTorch == true; } if (num) { Transform rigidJoint = Gear.FindSlotOnCopy(_prefab, _sourceVisual, _copyVisual, "m_rightHand"); if (Gear.TryAttach(Look.TorchItem, rigidJoint, _body, _copyVisual, Look.HeldItemSize, out var reason2)) { Plugin.DebugLog("Fuling brought a torch."); _hasTorch = true; } else { Plugin.Log.LogInfo((object)("Fuling couldn't hold " + Look.TorchItem + ": " + reason2)); } } } public bool WearHelmet(string name, bool logResult) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_helmet != (Object)null) { Object.Destroy((Object)(object)_helmet); _helmet = null; _fitter = null; } _helmetItem = null; Transform val = Gear.FindSlotOnCopy(_prefab, _sourceVisual, _copyVisual, "m_helmet"); string reason; GameObject attached; Vector3 itemWorldScale; bool flag = Gear.TryAttach(name, val, _body, _copyVisual, 1f, out reason, out attached, out itemWorldScale); if (flag) { _helmet = attached; _helmetItem = name; if ((Object)(object)val != (Object)null && (Object)(object)attached.transform.parent == (Object)(object)val) { _fitter = attached.AddComponent<HelmetFitter>(); _fitter.ItemName = name; _fitter.Init(((Component)this).transform, val, itemWorldScale); _fitter.Apply((_show.Fitting ? Plugin.SavedFits() : Look.Fits()).For(name)); } } if (logResult) { Plugin.Log.LogInfo((object)(flag ? ("Helmet: wearing " + name + (((Object)(object)_fitter != (Object)null) ? "" : " (bends with the body, no fitting needed)") + ".") : ("Helmet: couldn't wear " + name + ": " + reason))); } else if (!flag) { Plugin.Log.LogInfo((object)("Fuling couldn't wear " + name + ": " + reason)); } return flag; } public void EndFitting() { _endFitting = true; } private void Update() { _skin?.Apply(); if (IsFitting && (Object)(object)_fitter != (Object)null) { _fitter.Apply(Plugin.SliderFit()); } } internal static bool IsNight() { try { return (Object)(object)EnvMan.instance != (Object)null && EnvMan.IsNight(); } catch { return false; } } private static bool FindSpawnPoint(Player player, out Vector3 spawn) { return PickSpot(player, SpawnCandidates(player), out spawn); } private static IEnumerable<Vector3> SpawnCandidates(Player player) { Vector3 origin = ((Component)player).transform.position; Vector3 look = ViewForward(player); float[] angles = new float[9] { 0f, 25f, -25f, 50f, -50f, 75f, -75f, 110f, -110f }; float[] array = new float[2] { 18f, 10.8f }; float[] array2 = array; foreach (float dist in array2) { float[] array3 = angles; foreach (float num in array3) { yield return origin + Quaternion.Euler(0f, num + Random.Range(-10f, 10f), 0f) * look * dist; } } } private static Vector3 ViewForward(Player player) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) Transform val = (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform : null); Vector3 val2 = (((Object)(object)val != (Object)null) ? val.forward : ((Component)player).transform.forward); val2.y = 0f; if (!(((Vector3)(ref val2)).sqrMagnitude < 0.01f)) { return ((Vector3)(ref val2)).normalized; } return Vector3.forward; } private static bool IsUnderwater(Vector3 p) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) float num = 30f; try { if ((Object)(object)ZoneSystem.instance != (Object)null) { num = ZoneSystem.instance.m_waterLevel; } } catch { } return p.y < num - 0.3f; } private void InitAnimator() { if (_animReady) { return; } _animReady = true; _animator = ((Component)this).GetComponentInChildren<Animator>(true); if (!((Object)(object)_animator != (Object)null)) { return; } _animator.applyRootMotion = false; _animator.cullingMode = (AnimatorCullingMode)0; try { _animator.fireEvents = false; } catch { } AnimatorControllerParameter[] parameters = _animator.parameters; foreach (AnimatorControllerParameter val in parameters) { _animParams.Add(val.name); } _emotes = new EmotePlayer(_animator); if (_hasTorch && Plugin.TorchPose.Value) { _emotes.SetTorchPose(Plugin.TorchRunAnimation.Value, Plugin.TorchIdleAnimation.Value); } if (!_loggedAnimatorParams) { _loggedAnimatorParams = true; string text = string.Join(", ", _animator.parameters.Select((AnimatorControllerParameter x) => $"{x.name} ({x.type})").ToArray()); Plugin.Log.LogInfo((object)("Fuling animator parameters: " + text)); } } private void Start() { InitAnimator(); if (_show.Puppet) { StartPuppet(); return; } if (!_show.Fitting) { Object.Destroy((Object)(object)((Component)this).gameObject, (_show.Scene == SceneKind.Heckle) ? 60f : 240f); } StartBroadcasting(); switch (_show.Scene) { case SceneKind.Lurker: ((MonoBehaviour)this).StartCoroutine(Lurk()); break; case SceneKind.RedLight: ((MonoBehaviour)this).StartCoroutine(RedLight()); break; case SceneKind.Tombstone: ((MonoBehaviour)this).StartCoroutine(TombstoneDance()); break; case SceneKind.Campfire: ((MonoBehaviour)this).StartCoroutine(CampfireSquat()); break; case SceneKind.Copycat: PoofHere(Plugin.ArrivalEffects.Value); ((MonoBehaviour)this).StartCoroutine(Copycat()); break; case SceneKind.Gifter: ((MonoBehaviour)this).StartCoroutine(Gifter()); break; case SceneKind.CoinThief: PoofHere(Plugin.ArrivalEffects.Value); Sfx(SoundMoment.Arrive); ((MonoBehaviour)this).StartCoroutine(CoinThief()); break; default: PoofHere(Plugin.ArrivalEffects.Value); Sfx(SoundMoment.Arrive); ((MonoBehaviour)this).StartCoroutine(Perform()); break; } } private IEnumerator Perform() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } float num = Mathf.Min(Plugin.StopDistanceMin.Value, Plugin.StopDistanceMax.Value); float num2 = Mathf.Max(Plugin.StopDistanceMin.Value, Plugin.StopDistanceMax.Value); float stopAt = (_show.Fitting ? 3f : Random.Range(num, num2)); float timeout = 10f; while (timeout > 0f) { localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } Vector3 val = Flat(((Component)localPlayer).transform.position - ((Component)this).transform.position); float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude <= stopAt + 0.15f) { break; } MoveToward(((Vector3)(ref val)).normalized, Mathf.Min(6f * Time.deltaTime, magnitude - stopAt), 6f, ((Component)localPlayer).transform.position); timeout -= Time.deltaTime; yield return null; } if (_show.Fitting) { SetSpeed(0f); TrySayLine("{me} stand still. Make {me} pretty!", 4f); while (!_endFitting) { Player localPlayer2 = Player.m_localPlayer; if ((Object)(object)localPlayer2 == (Object)null) { Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } FaceDirection(Flat(((Component)localPlayer2).transform.position - ((Component)this).transform.position)); yield return null; } TrySayLine("{me} look GOOD