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 CreatureBait v0.5.2
plugins/CreatureBait.dll
Decompiled an hour ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using UnityEngine; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("CreatureBait")] [assembly: AssemblyDescription("Valheim CreatureBait Mod by DrummerCraig")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CreatureBait")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("B17C8A40-1E2D-4C3F-9A55-7E1F2D3B4C50")] [assembly: AssemblyFileVersion("0.5.2")] [assembly: AssemblyVersion("0.5.2.0")] namespace CreatureBait { internal static class BaitItems { internal const string PrefabPrefix = "CreatureBait_"; private static readonly Dictionary<string, string> _trophyByBait = new Dictionary<string, string>(); private static readonly Dictionary<string, string> _baitByTrophy = new Dictionary<string, string>(); private static readonly Dictionary<string, ItemDrop> _itemDropByTrophy = new Dictionary<string, ItemDrop>(); private const string OverlayResource = "CreatureBait.bait_overlay.bytes"; private static bool _overlayLoaded; private static bool _overlayOk; private static Color[] _overlayPixels; private static int _overlayW; private static int _overlayH; public static bool IsBaitPrefab(string prefabName) { if (!string.IsNullOrEmpty(prefabName)) { return _trophyByBait.ContainsKey(prefabName); } return false; } internal static string MakeBaitPrefabName(string trophyName) { string text = trophyName ?? string.Empty; if (text.StartsWith("Trophy", StringComparison.Ordinal) && text.Length > "Trophy".Length) { text = text.Substring("Trophy".Length); } return "CreatureBait_" + text + "Bait"; } public static bool TryGetTrophyForBait(string baitPrefabName, out string trophyName) { return _trophyByBait.TryGetValue(baitPrefabName ?? string.Empty, out trophyName); } public static bool TryGetBaitItemDrop(string trophyName, out ItemDrop itemDrop) { return _itemDropByTrophy.TryGetValue(trophyName ?? string.Empty, out itemDrop); } public static void EnsureRegistered(string trigger = null) { //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown if ((Object)(object)ZNetScene.instance == (Object)null) { return; } if (!TrophyCatalog.IsReady) { TrophyCatalog.Build(); } int num = 0; foreach (string item in TrophyCatalog.AllTrophyNames()) { if (_baitByTrophy.ContainsKey(item) || !TrophyCatalog.TryGetTrophyPrefab(item, out var trophyPrefab) || (Object)(object)trophyPrefab == (Object)null) { continue; } ItemDrop component = trophyPrefab.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { continue; } string text = MakeBaitPrefabName(item); GameObject val; try { val = PrefabManager.Instance.CreateClonedPrefab(text, trophyPrefab); } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Failed to clone Bait prefab for " + item + ": " + ex.Message)); } continue; } if ((Object)(object)val == (Object)null) { continue; } ItemDrop component2 = val.GetComponent<ItemDrop>(); if ((Object)(object)component2 == (Object)null || component2.m_itemData == null || component2.m_itemData.m_shared == null) { continue; } Biome effectiveBiome = TrophyCatalog.GetEffectiveBiome(item); string text2 = BaitNaming.MakeBaitNameLocalized(component.m_itemData.m_shared.m_name); string localizedName; string text3 = (TrophyCatalog.TryGetCreatureName(item, out localizedName) ? localizedName : text2); component2.m_itemData.m_shared.m_name = Colorize(text2, effectiveBiome); component2.m_itemData.m_shared.m_description = "Drop in the wild to attract " + text3 + "."; component2.m_itemData.m_shared.m_itemType = (ItemType)16; ApplyBaitIcon(component2, effectiveBiome); try { ItemManager.Instance.AddItem(new CustomItem(val, false)); ItemManager.Instance.RegisterItemInObjectDB(val); } catch (Exception ex2) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[CreatureBait] Failed to register Bait item for " + item + ": " + ex2.Message)); } continue; } _trophyByBait[text] = item; _baitByTrophy[item] = text; _itemDropByTrophy[item] = component2; num++; } if (num > 0) { ManualLogSource log3 = CreatureBaitPlugin.Log; if (log3 != null) { log3.LogInfo((object)$"[CreatureBait] Registered {num} Bait item(s); {_baitByTrophy.Count} total."); } } } private static string Colorize(string name, Biome biome) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) return "<color=" + BiomeColorHex(biome) + ">" + name + "</color>"; } private static void ApplyBaitIcon(ItemDrop baitDrop, Biome biome) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Sprite[] icons = baitDrop.m_itemData.m_shared.m_icons; if (icons != null && icons.Length != 0 && !((Object)(object)icons[0] == (Object)null)) { Sprite val = BuildBaitSprite(icons[0], BiomeColor(biome)); if ((Object)(object)val != (Object)null) { baitDrop.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { val }; } } } private static Sprite BuildBaitSprite(Sprite src, Color labelColor) { //IL_0013: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: 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) //IL_00d9: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: 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_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) Texture texture = (Texture)(object)src.texture; if ((Object)(object)texture == (Object)null) { return null; } Rect textureRect = src.textureRect; int num = Mathf.Max(1, Mathf.RoundToInt(((Rect)(ref textureRect)).width)); int num2 = Mathf.Max(1, Mathf.RoundToInt(((Rect)(ref textureRect)).height)); RenderTexture temporary = RenderTexture.GetTemporary(num, num2, 0, (RenderTextureFormat)0); RenderTexture active = RenderTexture.active; try { Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(((Rect)(ref textureRect)).width / (float)texture.width, ((Rect)(ref textureRect)).height / (float)texture.height); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(((Rect)(ref textureRect)).x / (float)texture.width, ((Rect)(ref textureRect)).y / (float)texture.height); RenderTexture.active = temporary; GL.Clear(true, true, new Color(0f, 0f, 0f, 0f)); Graphics.Blit(texture, temporary, val, val2); Texture2D val3 = new Texture2D(num, num2, (TextureFormat)4, false); val3.ReadPixels(new Rect(0f, 0f, (float)num, (float)num2), 0, 0); EnsureOverlayLoaded(); Color[] pixels = val3.GetPixels(); for (int i = 0; i < pixels.Length; i++) { Color val4 = pixels[i]; if (_overlayOk) { int num3 = i % num; int num4 = i / num; int num5 = ((_overlayW == num) ? num3 : Mathf.Min(_overlayW - 1, num3 * _overlayW / num)); int num6 = ((_overlayH == num2) ? num4 : Mathf.Min(_overlayH - 1, num4 * _overlayH / num2)); Color val5 = _overlayPixels[num6 * _overlayW + num5]; float a = val5.a; if (a > 0.001f) { float num7 = val5.r * labelColor.r; float num8 = val5.g * labelColor.g; float num9 = val5.b * labelColor.b; float num10 = a + val4.a * (1f - a); if (num10 > 0.0001f) { val4.r = (num7 * a + val4.r * val4.a * (1f - a)) / num10; val4.g = (num8 * a + val4.g * val4.a * (1f - a)) / num10; val4.b = (num9 * a + val4.b * val4.a * (1f - a)) / num10; val4.a = num10; } } } pixels[i] = val4; } val3.SetPixels(pixels); val3.Apply(); return Sprite.Create(val3, new Rect(0f, 0f, (float)num, (float)num2), new Vector2(0.5f, 0.5f), src.pixelsPerUnit); } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Tinted icon build failed: " + ex.Message)); } return null; } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private static Color BiomeColor(Biome biome) { //IL_0000: 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_000f: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (!ColorUtility.TryParseHtmlString(BiomeColorHex(biome), ref result)) { return Color.white; } return result; } private static void EnsureOverlayLoaded() { //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) if (_overlayLoaded) { return; } _overlayLoaded = true; try { using Stream stream = typeof(BaitItems).Assembly.GetManifestResourceStream("CreatureBait.bait_overlay.bytes"); if (stream == null) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)"[CreatureBait] Overlay resource 'CreatureBait.bait_overlay.bytes' not found; baits will be tint-only."); } return; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { if ((num = stream.Read(array, i, array.Length - i)) <= 0) { break; } } if (array.Length < 8) { return; } int num2 = BitConverter.ToInt32(array, 0); int num3 = BitConverter.ToInt32(array, 4); if (num2 <= 0 || num3 <= 0 || array.Length < 8 + num2 * num3 * 4) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogWarning((object)"[CreatureBait] Overlay blob malformed; baits will be tint-only."); } return; } Color[] array2 = (Color[])(object)new Color[num2 * num3]; int num4 = 8; for (int j = 0; j < array2.Length; j++) { array2[j] = new Color((float)(int)array[num4] / 255f, (float)(int)array[num4 + 1] / 255f, (float)(int)array[num4 + 2] / 255f, (float)(int)array[num4 + 3] / 255f); num4 += 4; } _overlayPixels = array2; _overlayW = num2; _overlayH = num3; _overlayOk = true; } catch (Exception ex) { ManualLogSource log3 = CreatureBaitPlugin.Log; if (log3 != null) { log3.LogWarning((object)("[CreatureBait] Overlay load error: " + ex.Message + "; baits will be tint-only.")); } } } private static string BiomeColorHex(Biome biome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected I4, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if ((int)biome <= 16) { switch (biome - 1) { default: if ((int)biome != 8) { if ((int)biome != 16) { break; } return "#E8C33A"; } return "#6FCF6F"; case 1: return "#A98B5A"; case 3: return "#7FD0FF"; case 0: return "#CBE86A"; case 2: break; } } else if ((int)biome <= 64) { if ((int)biome == 32) { return "#FF5A3C"; } if ((int)biome == 64) { return "#BFEFFF"; } } else { if ((int)biome == 256) { return "#3F8FE0"; } if ((int)biome == 512) { return "#B06BE0"; } } return "#CBE86A"; } } internal sealed class BaitLureBehaviour : MonoBehaviour { private const string ZdoKeySpawnTime = "CreatureBait_SpawnTime"; private const string ZdoKeyLastSpawn = "CreatureBait_LastSpawn"; private const string ZdoKeyTotalSpawns = "CreatureBait_TotalSpawns"; private const float HoldToRemoveSeconds = 0.6f; private const float HoldGapResetSeconds = 0.4f; private const float MaxSpawnDistance = 128f; private const float MessageRadius = 128f; private const float TickSeconds = 1f; private ItemDrop _item; private ZNetView _nview; private GameObject _characterPrefab; private Character _characterTemplate; private string _trophyName; private float _nextTickRealtime; private double _lastMessageRealtime; private long _lastSeenTotal = -1L; private bool _biomeOk; private bool _inInterior; private bool _biomeValid; private Vector3 _lastBiomePos; private int _lastBiomeGen = -1; private float _holdStart; private float _lastHoldInteract; private readonly List<Character> _spawned = new List<Character>(); private void Awake() { _item = ((Component)this).GetComponent<ItemDrop>(); _nview = ((Component)this).GetComponent<ZNetView>(); _nextTickRealtime = Time.realtimeSinceStartup + Random.Range(0f, 1f); } private void Update() { if (Time.realtimeSinceStartup < _nextTickRealtime) { return; } _nextTickRealtime = Time.realtimeSinceStartup + 1f; try { Tick(); } catch (Exception arg) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogError((object)$"[CreatureBait] BaitLureBehaviour tick error: {arg}"); } } } private void Tick() { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_item == (Object)null || (Object)(object)_nview == (Object)null || !_nview.IsValid() || (Object)(object)ZNet.instance == (Object)null || (Object)(object)_item == (Object)null || !BaitItems.IsBaitPrefab(TrophyCatalog.StripCloneSuffix(((Object)((Component)this).gameObject).name))) { return; } CreatureBaitConfig settings = CreatureBaitPlugin.Settings; if (settings == null || !settings.EnableValue) { return; } EnsureResolved(); if ((Object)(object)_characterPrefab == (Object)null || (Object)(object)_characterTemplate == (Object)null) { return; } ZDO zDO = _nview.GetZDO(); if (zDO == null) { return; } if (_nview.IsOwner()) { EnsureBiome(); if (_biomeOk) { RunAttraction(zDO, settings); } } else if (zDO.GetOwner() == 0L) { _nview.ClaimOwnership(); } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { Vector3 val = ((Component)localPlayer).transform.position - ((Component)this).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > 16384f)) { EnsureBiome(); RunDisplay(zDO, settings, _biomeOk, _inInterior); } } } private void EnsureBiome() { //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_0040: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: 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) Vector3 position = ((Component)this).transform.position; int generation = RecipeConfig.Generation; if (_biomeValid && generation == _lastBiomeGen) { Vector3 val = position - _lastBiomePos; if (((Vector3)(ref val)).sqrMagnitude <= 1f) { return; } } _inInterior = Character.InInterior(position); _biomeOk = !_inInterior && TrophyCatalog.OriginatesAt(_trophyName, position); _lastBiomePos = position; _lastBiomeGen = generation; _biomeValid = true; } private void RunAttraction(ZDO zdo, CreatureBaitConfig settings) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) long utcTicks = DateTimeOffset.UtcNow.UtcTicks; long num = zdo.GetLong("CreatureBait_SpawnTime", 0L); if (num == 0L) { zdo.Set("CreatureBait_SpawnTime", utcTicks); num = utcTicks; } if ((double)(utcTicks - num) / 10000000.0 < (double)settings.BaitAcclimationTimeValue) { return; } long num2 = zdo.GetLong("CreatureBait_LastSpawn", 0L); double num3 = (double)(utcTicks - num2) / 10000000.0; if (num2 != 0L && num3 < (double)settings.CreatureAttractionCadenceValue) { return; } int baitLifetimeAttractionsValue = settings.BaitLifetimeAttractionsValue; long num4 = zdo.GetLong("CreatureBait_TotalSpawns", 0L); int num5 = (int)(baitLifetimeAttractionsValue - num4); int num6 = settings.MaximumActiveCreaturesValue - CountAliveAndPrune(); int num7 = Mathf.Min(settings.AttractionMultiplierValue, num5); if (num7 > num6) { num7 = num6; } if (num7 <= 0) { return; } zdo.Set("CreatureBait_LastSpawn", utcTicks); int num8 = 0; for (int i = 0; i < num7; i++) { GameObject val = TrySpawnOne(); if ((Object)(object)val == (Object)null) { break; } Character component = val.GetComponent<Character>(); if ((Object)(object)component != (Object)null) { int num9 = StarRoll.RollLevel(settings, Heightmap.FindBiome(((Component)this).transform.position)); if (num9 > 1) { component.SetLevel(num9); } _spawned.Add(component); } num8++; } if (num8 > 0) { long num10 = num4 + num8; zdo.Set("CreatureBait_TotalSpawns", num10); if (num10 >= baitLifetimeAttractionsValue) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogDebug((object)$"[CreatureBait] {((Object)((Component)this).gameObject).name} reached its attraction budget ({num10}); consuming Bait."); } ConsumeBait(); } } else { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogDebug((object)("[CreatureBait] No valid attraction spot near " + ((Object)((Component)this).gameObject).name + " this attempt.")); } } } private void RunDisplay(ZDO zdo, CreatureBaitConfig settings, bool biomeOk, bool inInterior) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) if (inInterior) { return; } if (!biomeOk) { _lastSeenTotal = -1L; ShowThrottled("Invalid biome...", BaitMessageType.InWorld); return; } long num = zdo.GetLong("CreatureBait_SpawnTime", 0L); long num2 = zdo.GetLong("CreatureBait_TotalSpawns", 0L); if (num == 0L) { _lastSeenTotal = num2; return; } int baitLifetimeAttractionsValue = settings.BaitLifetimeAttractionsValue; int num3 = (int)Math.Max(0L, baitLifetimeAttractionsValue - num2); double num4 = (double)(DateTimeOffset.UtcNow.UtcTicks - num) / 10000000.0; int baitAcclimationTimeValue = settings.BaitAcclimationTimeValue; if (num4 < (double)baitAcclimationTimeValue) { int num5 = (int)Math.Ceiling((double)baitAcclimationTimeValue - num4); if (num5 < 1) { num5 = 1; } _lastSeenTotal = num2; BaitMessenger.ShowLocal(((Component)this).transform.position, $"Scent is dispersing... {num5}s", BaitMessageType.InWorld); return; } if (_lastSeenTotal >= 0 && num2 > _lastSeenTotal) { BaitMessageType attractedNotificationLocationValue = settings.AttractedNotificationLocationValue; if (attractedNotificationLocationValue != BaitMessageType.None) { long num6 = num2 - _lastSeenTotal; string arg = ((num6 > 1) ? $"Bait attracted {num6} {CreatureName()}" : ("Bait attracted " + CreatureName())); BaitMessenger.ShowLocal(((Component)this).transform.position, $"{arg}... {num3} left...", attractedNotificationLocationValue); } } _lastSeenTotal = num2; if (Time.realtimeSinceStartupAsDouble - _lastMessageRealtime >= 5.0) { _lastMessageRealtime = Time.realtimeSinceStartupAsDouble; BaitMessenger.ShowLocal(((Component)this).transform.position, $"Attracting...{num3} left...", BaitMessageType.InWorld); } } private string CreatureName() { string text = (((Object)(object)_characterTemplate != (Object)null) ? _characterTemplate.m_name : null); if (string.IsNullOrEmpty(text)) { text = (((Object)(object)_characterPrefab != (Object)null) ? ((Object)_characterPrefab).name : _trophyName); } return text; } private void ShowThrottled(string text, BaitMessageType type) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!(Time.realtimeSinceStartupAsDouble - _lastMessageRealtime < 5.0)) { _lastMessageRealtime = Time.realtimeSinceStartupAsDouble; BaitMessenger.ShowLocal(((Component)this).transform.position, text, type); } } private int CountAliveAndPrune() { for (int num = _spawned.Count - 1; num >= 0; num--) { Character val = _spawned[num]; if ((Object)(object)val == (Object)null || val.IsDead()) { _spawned.RemoveAt(num); } } return _spawned.Count; } private GameObject TrySpawnOne() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: 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_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_characterPrefab == (Object)null || (Object)(object)_characterTemplate == (Object)null) { return null; } Vector3 position = ((Component)this).transform.position; Vector2i zone = ZoneSystem.GetZone(position); float num = 64f * (float)((!((Object)(object)ZoneSystem.instance != (Object)null)) ? 1 : ZoneSystem.instance.m_activeArea); float num2 = Mathf.Min(CreatureBaitPlugin.Settings.MinimumAttractionRadiusValue, num); float num3 = 128f; if (num3 < num2) { num3 = num2; } for (int i = 0; i < 10; i++) { Vector3 val = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f) * Vector3.forward; Vector3 val2 = position + val * num2; int num4 = Mathf.CeilToInt((num3 - num2) / 2f); int j; for (j = 0; j < num4; j++) { Vector3 val3 = val2 + val * 2f; if (!ZNetScene.InActiveArea(ZoneSystem.GetZone(val3), zone)) { j--; break; } val2 = val3; } if (num2 < num3 && j > 0) { val2 = position + val * (num2 + 2f * Random.Range(0f, (float)j)); } if (IsValidSpawnSpot(val2, zone)) { if ((Object)(object)ZNetScene.instance == (Object)null || ZDOMan.instance == null) { return null; } GameObject val4 = Object.Instantiate<GameObject>(_characterPrefab, val2, Quaternion.Euler(0f, Random.Range(0f, 360f), 0f)); if ((Object)(object)val4 == (Object)null) { return null; } ZNetView component = val4.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || component.GetZDO() == null) { Object.Destroy((Object)(object)val4); return null; } ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogDebug((object)$"[CreatureBait] Attracted {((Object)_characterPrefab).name} at {val2} (bait {((Object)((Component)this).gameObject).name})."); } return val4; } } return null; } private bool IsValidSpawnSpot(Vector3 pos, Vector2i originSector) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } if (!ZNetScene.InActiveArea(ZoneSystem.GetZone(pos), originSector)) { return false; } float y = default(float); if (!ZoneSystem.instance.GetGroundHeight(pos, ref y)) { return false; } pos.y = y; bool flag = (Object)(object)_characterTemplate != (Object)null && _characterTemplate.m_canSwim; bool flag2 = (Object)(object)_characterTemplate != (Object)null && _characterTemplate.m_flying; if (pos.y < 30f && !flag && !flag2) { return false; } if ((Object)(object)EffectArea.IsPointInsideArea(pos, (Type)4, 20f) != (Object)null) { return false; } return true; } private void EnsureResolved() { if (((Object)(object)_characterPrefab != (Object)null && (Object)(object)_characterTemplate != (Object)null) || (Object)(object)_item == (Object)null || _item.m_itemData == null) { return; } if (string.IsNullOrEmpty(_trophyName)) { string text = TrophyCatalog.StripCloneSuffix(((Object)((Component)this).gameObject).name); if (!BaitItems.TryGetTrophyForBait(text, out _trophyName) || string.IsNullOrEmpty(_trophyName)) { _trophyName = text; } } if (!TrophyCatalog.IsReady) { TrophyCatalog.Build(); } if (TrophyCatalog.TryGetCharacterPrefab(_trophyName, out var characterPrefab)) { _characterPrefab = characterPrefab; _characterTemplate = characterPrefab.GetComponent<Character>(); } } private void ConsumeBait() { if ((Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(((Component)this).gameObject); } } public bool HandleInteract(Humanoid character, bool hold) { if (!hold) { _holdStart = 0f; if (character != null) { ((Character)character).Message((MessageType)2, "Hold E to discard bait", 0, (Sprite)null); } return true; } float time = Time.time; if (_holdStart <= 0f || time - _lastHoldInteract > 0.4f) { _holdStart = time; } _lastHoldInteract = time; if (time - _holdStart >= 0.6f) { if ((Object)(object)_nview != (Object)null && _nview.IsValid()) { _nview.ClaimOwnership(); } if (character != null) { ((Character)character).Message((MessageType)2, "Bait discarded", 0, (Sprite)null); } ConsumeBait(); } return true; } } internal static class BaitMessenger { private static MethodInfo _addInworld; private static bool _addInworldResolved; private static MethodInfo AddInworldMethod() { if (!_addInworldResolved) { _addInworldResolved = true; _addInworld = typeof(DamageText).GetMethod("AddInworldText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } return _addInworld; } public static void ShowLocal(Vector3 pos, string text, BaitMessageType type) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (type == BaitMessageType.None || string.IsNullOrEmpty(text)) { return; } if (Localization.instance != null) { text = Localization.instance.Localize(text); } switch (type) { case BaitMessageType.TopLeft: if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false); } break; case BaitMessageType.Center: if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); } break; case BaitMessageType.InWorld: ShowInWorldLocal(pos, text); break; } } private static void ShowInWorldLocal(Vector3 pos, string text) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) DamageText instance = DamageText.instance; if ((Object)(object)instance == (Object)null) { return; } float num = 0f; Camera main = Camera.main; if ((Object)(object)main != (Object)null) { num = Vector3.Distance(((Component)main).transform.position, pos); if (num > instance.m_maxTextDistance) { return; } } MethodInfo methodInfo = AddInworldMethod(); if (methodInfo == null) { instance.ShowText((TextType)0, pos, text, false); return; } methodInfo.Invoke(instance, new object[5] { (object)(TextType)0, pos, num, text, false }); } } internal static class BaitNaming { public static string MakeBaitNameLocalized(string nameOrToken) { if (string.IsNullOrEmpty(nameOrToken)) { return "Bait"; } return MakeBaitName((Localization.instance != null) ? Localization.instance.Localize(nameOrToken) : nameOrToken); } public static string MakeBaitName(string original) { if (string.IsNullOrEmpty(original)) { return "Bait"; } int num = IndexOfIgnoreCase(original, "Trophy"); if (num >= 0) { return original.Substring(0, num) + "Bait" + original.Substring(num + "Trophy".Length); } return original + " (Bait)"; } private static int IndexOfIgnoreCase(string haystack, string needle) { if (haystack == null || needle == null || needle.Length == 0 || haystack.Length < needle.Length) { return -1; } int num = haystack.Length - needle.Length; for (int i = 0; i <= num; i++) { bool flag = true; for (int j = 0; j < needle.Length; j++) { if (char.ToLowerInvariant(haystack[i + j]) != char.ToLowerInvariant(needle[j])) { flag = false; break; } } if (flag) { return i; } } return -1; } } internal static class BaitRecipes { public const string RecipeNamePrefix = "CreatureBait_Recipe_"; private static readonly Dictionary<Biome, (string Food1, string Food2)> BiomeFoods = new Dictionary<Biome, (string, string)> { { (Biome)1, ("Mushroom", "Raspberry") }, { (Biome)8, ("MushroomYellow", "Carrot") }, { (Biome)2, ("Turnip", "Ooze") }, { (Biome)4, ("Onion", "FreezeGland") }, { (Biome)16, ("Barley", "Cloudberry") }, { (Biome)512, ("MushroomJotunPuffs", "MushroomMagecap") }, { (Biome)32, ("Vineberry", "MushroomSmokePuff") } }; private const int SeedFoodAmount = 10; private const int SeedTrophyAmount = 1; private static readonly Dictionary<string, string> NameOverrides = new Dictionary<string, string> { { "TrophyKvastur", "Kvastur Bait" } }; private static readonly Dictionary<string, (string F1, string F2)> FoodOverrides = new Dictionary<string, (string, string)> { { "TrophyAbomination", ("Bloodbag", null) } }; private static readonly Dictionary<string, string[]> FoodAliases = new Dictionary<string, string[]> { { "Cloudberry", new string[1] { "Cloudberries" } }, { "MushroomSmokePuff", new string[3] { "Smokepuff", "MushroomSmokepuff", "Sap" } }, { "Vineberry", new string[1] { "VineBerry" } } }; private static readonly Dictionary<string, string> StationTokens = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "Cauldron", "$piece_cauldron" }, { "Workbench", "$piece_workbench" } }; private static readonly Dictionary<string, CraftingStation> _stationCache = new Dictionary<string, CraftingStation>(); private static readonly Biome[] BiomeOrder; public static string TrophyNameFromRecipe(string recipeName) { if (string.IsNullOrEmpty(recipeName) || !recipeName.StartsWith("CreatureBait_Recipe_", StringComparison.Ordinal)) { return null; } string text = recipeName.Substring("CreatureBait_Recipe_".Length); int num = text.IndexOf('@'); if (num < 0) { return text; } return text.Substring(0, num); } public static void RegisterAll() { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Expected O, but got Unknown //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_0362: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Expected O, but got Unknown //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_03a2: Expected O, but got Unknown if ((Object)(object)ObjectDB.instance == (Object)null) { return; } if (!TrophyCatalog.IsReady) { TrophyCatalog.Build(); } RecipeConfig.KeyOrder = CompareByBiomeThenName; Dictionary<string, RecipeEntry> seedDefaults = BuildSeed(); RecipeConfig.LoadOrSeed(Paths.ConfigPath, seedDefaults); bool flag = false; foreach (KeyValuePair<string, RecipeEntry> entry in RecipeConfig.Entries) { if (entry.Value != null) { if (string.IsNullOrWhiteSpace(entry.Value.Biomes)) { entry.Value.Biomes = ((object)TrophyCatalog.GetEffectiveBiome(entry.Key)/*cast due to .constrained prefix*/).ToString(); flag = true; } if (entry.Value.TrophyAmount <= 0) { entry.Value.TrophyAmount = 1; flag = true; } if (string.IsNullOrWhiteSpace(entry.Value.Stations)) { entry.Value.Stations = DefaultStationsFor(TrophyCatalog.GetEffectiveBiome(entry.Key)); flag = true; } } } if (flag || RecipeConfig.NeedsReorder()) { RecipeConfig.Save(); } _stationCache.Clear(); if ((Object)(object)ResolveStationByToken("$piece_cauldron") == (Object)null) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)"[CreatureBait] Cauldron not found; recipes not registered this pass (YAML written)."); } return; } int num = 0; int num2 = 0; foreach (KeyValuePair<string, RecipeEntry> entry2 in RecipeConfig.Entries) { string key = entry2.Key; RecipeEntry value = entry2.Value; if (value == null || !value.Enabled) { num2++; continue; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(key); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null) { num2++; continue; } if (!BaitItems.TryGetBaitItemDrop(key, out var itemDrop) || (Object)(object)itemDrop == (Object)null) { num2++; continue; } ItemDrop val2 = LookupItem(value.Food1); ItemDrop val3 = LookupItem(value.Food2); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogWarning((object)$"[CreatureBait] Missing food prefab for {key} (food1={value.Food1}={(Object)(object)val2 != (Object)null}, food2={value.Food2}={(Object)(object)val3 != (Object)null}); skipping."); } num2++; continue; } foreach (var item3 in ResolveStations(value.Stations)) { string item = item3.Friendly; CraftingStation item2 = item3.Station; string text = "CreatureBait_Recipe_" + key + "@" + item; if (!((Object)(object)FindExistingRecipe(text) != (Object)null)) { Recipe val4 = ScriptableObject.CreateInstance<Recipe>(); ((Object)val4).name = text; val4.m_item = itemDrop; val4.m_amount = 1; val4.m_enabled = true; val4.m_minStationLevel = 1; val4.m_craftingStation = item2; val4.m_resources = (Requirement[])(object)new Requirement[3] { new Requirement { m_resItem = val, m_amount = Math.Max(1, value.TrophyAmount), m_amountPerLevel = 0, m_recover = false }, new Requirement { m_resItem = val2, m_amount = Math.Max(1, value.Amount1), m_amountPerLevel = 0, m_recover = false }, new Requirement { m_resItem = val3, m_amount = Math.Max(1, value.Amount2), m_amountPerLevel = 0, m_recover = false } }; ObjectDB.instance.m_recipes.Add(val4); num++; } } } ManualLogSource log3 = CreatureBaitPlugin.Log; if (log3 != null) { log3.LogInfo((object)$"[CreatureBait] Registered {num} Bait recipes ({num2} skipped)."); } } private unsafe static Dictionary<string, RecipeEntry> BuildSeed() { //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: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, RecipeEntry> dictionary = new Dictionary<string, RecipeEntry>(); foreach (string item in TrophyCatalog.AllTrophyNames()) { Biome effectiveBiome = TrophyCatalog.GetEffectiveBiome(item); if (!BiomeFoods.TryGetValue(effectiveBiome, out (string, string) value)) { continue; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(item); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); string value2; string name = (NameOverrides.TryGetValue(item, out value2) ? value2 : (((Object)(object)val != (Object)null) ? BaitNaming.MakeBaitNameLocalized(val.m_itemData.m_shared.m_name) : item)); var (food, food2) = value; if (FoodOverrides.TryGetValue(item, out (string, string) value3)) { if (!string.IsNullOrEmpty(value3.Item1)) { (food, _) = value3; } if (!string.IsNullOrEmpty(value3.Item2)) { food2 = value3.Item2; } } dictionary[item] = new RecipeEntry { Name = name, Food1 = food, Food2 = food2, Amount1 = 10, Amount2 = 10, Biomes = ((object)(*(Biome*)(&effectiveBiome))/*cast due to .constrained prefix*/).ToString(), TrophyAmount = 1, Stations = DefaultStationsFor(effectiveBiome), Enabled = true }; } return dictionary; } private static ItemDrop LookupItem(string prefabName) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); if ((Object)(object)itemPrefab != (Object)null) { return itemPrefab.GetComponent<ItemDrop>(); } if (FoodAliases.TryGetValue(prefabName, out var value)) { string[] array = value; foreach (string text in array) { GameObject itemPrefab2 = ObjectDB.instance.GetItemPrefab(text); if ((Object)(object)itemPrefab2 != (Object)null) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)("[CreatureBait] Food prefab '" + prefabName + "' not found; using alias '" + text + "'.")); } return itemPrefab2.GetComponent<ItemDrop>(); } } } return null; } private static Recipe FindExistingRecipe(string recipeName) { List<Recipe> recipes = ObjectDB.instance.m_recipes; for (int i = 0; i < recipes.Count; i++) { Recipe val = recipes[i]; if ((Object)(object)val != (Object)null && ((Object)val).name == recipeName) { return val; } } return null; } private static string DefaultStationsFor(Biome biome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)biome != 1) { return "Cauldron"; } return "Cauldron,Workbench"; } private static int BiomeRank(Biome biome) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) int num = Array.IndexOf(BiomeOrder, biome); if (num >= 0) { return num; } return int.MaxValue; } private static int CompareByBiomeThenName(string a, string b) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) int num = BiomeRank(TrophyCatalog.GetEffectiveBiome(a)); int num2 = BiomeRank(TrophyCatalog.GetEffectiveBiome(b)); if (num != num2) { return num.CompareTo(num2); } return string.Compare(a, b, StringComparison.OrdinalIgnoreCase); } private static List<(string Friendly, CraftingStation Station)> ResolveStations(string csv) { List<(string, CraftingStation)> list = new List<(string, CraftingStation)>(); if (!string.IsNullOrWhiteSpace(csv)) { string[] array = csv.Split(new char[1] { ',' }); foreach (string obj in array) { string text = obj.Trim(); if (text.Length == 0) { continue; } if (!TryCanonicalStation(text, out var canonical, out var token)) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Unknown crafting station '" + text + "' in recipe config; ignoring.")); } } else { if (list.Exists(((string, CraftingStation) r) => r.Item1 == canonical)) { continue; } CraftingStation val = ResolveStationByToken(token); if ((Object)(object)val == (Object)null) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[CreatureBait] Crafting station '" + canonical + "' (" + token + ") not found; skipping it for this recipe.")); } } else { list.Add((canonical, val)); } } } } if (list.Count == 0) { CraftingStation val2 = ResolveStationByToken("$piece_cauldron"); if ((Object)(object)val2 != (Object)null) { list.Add(("Cauldron", val2)); } } return list; } private static bool TryCanonicalStation(string friendly, out string canonical, out string token) { foreach (KeyValuePair<string, string> stationToken in StationTokens) { if (string.Equals(stationToken.Key, friendly, StringComparison.OrdinalIgnoreCase)) { canonical = stationToken.Key; token = stationToken.Value; return true; } } canonical = null; token = null; return false; } private static CraftingStation ResolveStationByToken(string token) { if (_stationCache.TryGetValue(token, out var value) && (Object)(object)value != (Object)null) { return value; } if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if (!((Object)(object)prefab == (Object)null)) { CraftingStation component = prefab.GetComponent<CraftingStation>(); if ((Object)(object)component != (Object)null && component.m_name == token) { _stationCache[token] = component; return component; } } } return null; } static BaitRecipes() { Biome[] array = new Biome[9]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); BiomeOrder = (Biome[])(object)array; } } public enum BaitMessageType { None, TopLeft, Center, InWorld } internal sealed class CreatureBaitConfig { private const string Section = "CreatureBait"; public static readonly Biome[] StarBiomes; private static readonly string[] StarBiomeLabels; private readonly List<ConfigurationManagerAttributes> _lockable = new List<ConfigurationManagerAttributes>(); private bool _usingServer; private bool _sEnable; private int _sBaitAcclimationTime; private int _sCreatureAttractionCadence; private float _sMinimumAttractionRadius; private int _sBaitLifetimeAttractions; private int _sAttractionMultiplier; private int _sMaximumActiveCreatures; private bool _sFloatInWater; private float[] _sStarMultiplier; private int[] _sStarMaxStars; public ConfigEntry<bool> Enable { get; } public ConfigEntry<int> BaitAcclimationTime { get; } public ConfigEntry<int> CreatureAttractionCadence { get; } public ConfigEntry<float> MinimumAttractionRadius { get; } public ConfigEntry<int> BaitLifetimeAttractions { get; } public ConfigEntry<int> AttractionMultiplier { get; } public ConfigEntry<int> MaximumActiveCreatures { get; } public ConfigEntry<bool> FloatInWater { get; } public ConfigEntry<BaitMessageType> AttractedNotificationLocation { get; } public ConfigEntry<float>[] StarMultiplier { get; } public ConfigEntry<int>[] StarMaxStars { get; } public bool UsingServerConfig => _usingServer; public bool EnableValue { get { if (!_usingServer) { return Enable.Value; } return _sEnable; } } public int BaitAcclimationTimeValue { get { if (!_usingServer) { return BaitAcclimationTime.Value; } return _sBaitAcclimationTime; } } public int CreatureAttractionCadenceValue { get { if (!_usingServer) { return CreatureAttractionCadence.Value; } return _sCreatureAttractionCadence; } } public float MinimumAttractionRadiusValue { get { if (!_usingServer) { return MinimumAttractionRadius.Value; } return _sMinimumAttractionRadius; } } public int BaitLifetimeAttractionsValue { get { if (!_usingServer) { return BaitLifetimeAttractions.Value; } return _sBaitLifetimeAttractions; } } public int AttractionMultiplierValue { get { if (!_usingServer) { return AttractionMultiplier.Value; } return _sAttractionMultiplier; } } public int MaximumActiveCreaturesValue { get { if (!_usingServer) { return MaximumActiveCreatures.Value; } return _sMaximumActiveCreatures; } } public bool FloatInWaterValue { get { if (!_usingServer) { return FloatInWater.Value; } return _sFloatInWater; } } public BaitMessageType AttractedNotificationLocationValue => AttractedNotificationLocation.Value; private ConfigurationManagerAttributes NewLock() { ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes(); _lockable.Add(configurationManagerAttributes); return configurationManagerAttributes; } public CreatureBaitConfig(ConfigFile cfg) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Expected O, but got Unknown //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Expected O, but got Unknown //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Expected O, but got Unknown //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Expected O, but got Unknown Enable = cfg.Bind<bool>("CreatureBait", "Enable", true, new ConfigDescription("Master toggle. When true, dropped Bait items attract mobs of the trophy's species.", (AcceptableValueBase)null, new object[1] { NewLock() })); BaitAcclimationTime = cfg.Bind<int>("CreatureBait", "BaitAcclimationTime", 60, new ConfigDescription("Seconds a dropped Bait acclimates before it starts attracting creatures.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(30, 3600), new object[1] { NewLock() })); CreatureAttractionCadence = cfg.Bind<int>("CreatureBait", "CreatureAttractionCadence", 120, new ConfigDescription("Seconds between attraction events while the Bait is active.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(60, 3600), new object[1] { NewLock() })); MinimumAttractionRadius = cfg.Bind<float>("CreatureBait", "MinimumAttractionRadius", 12f, new ConfigDescription("Minimum meters from the Bait where creatures are attracted. Clamped at runtime to the simulation's active-area radius.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 64f), new object[1] { NewLock() })); BaitLifetimeAttractions = cfg.Bind<int>("CreatureBait", "BaitLifetimeAttractions", 10, new ConfigDescription("Total creatures a single dropped Bait attracts over its lifetime before it is consumed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 20), new object[1] { NewLock() })); AttractionMultiplier = cfg.Bind<int>("CreatureBait", "AttractionMultiplier", 2, new ConfigDescription("How many creatures a Bait attracts per attraction event (attracted together). The final batch is capped so lifetime attractions never exceed BaitLifetimeAttractions, at which point the Bait is consumed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), new object[1] { NewLock() })); MaximumActiveCreatures = cfg.Bind<int>("CreatureBait", "MaximumActiveCreatures", 6, new ConfigDescription("Cap on how many of this Bait's attracted creatures may be alive at once. While that many are alive the Bait pauses (no new attractions) until some die or wander off; it does not count against the lifetime total.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), new object[1] { NewLock() })); StarMultiplier = new ConfigEntry<float>[StarBiomes.Length]; StarMaxStars = new ConfigEntry<int>[StarBiomes.Length]; for (int i = 0; i < StarBiomes.Length; i++) { string text = $"Stars - {i + 1} {StarBiomeLabels[i]}"; StarMultiplier[i] = cfg.Bind<float>(text, "ChanceMultiplier", 1f, new ConfigDescription("Multiplier on the vanilla per-step star (level) chance for creatures attracted in " + StarBiomeLabels[i] + ". 1 = normal world odds (incl. world modifiers); higher makes stars more likely; 0 disables extra stars. Capped by MaxStars.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), new object[1] { NewLock() })); StarMaxStars[i] = cfg.Bind<int>(text, "MaxStars", 2, new ConfigDescription("Maximum stars a creature attracted in " + StarBiomeLabels[i] + " may roll (0 = never starred; 2 = vanilla common max).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), new object[1] { NewLock() })); } FloatInWater = cfg.Bind<bool>("CreatureBait", "FloatInWater", true, new ConfigDescription("When true, dropped Bait items and creature trophies float on water instead of sinking. Applies to items as they spawn or a zone loads; items already dropped update on the next world/zone load. Bait does not have to float to work.", (AcceptableValueBase)null, new object[1] { NewLock() })); AttractedNotificationLocation = cfg.Bind<BaitMessageType>("CreatureBait", "AttractedNotificationLocation", BaitMessageType.TopLeft, "Where the 'Bait attracted <n> <creature>... N left...' notice appears when a Bait attracts creatures (None to hide it). The acclimating/attracting status always emanates in-world from the Bait itself and can't be relocated."); } public (float mult, int max) StarSettingsFor(Biome biome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) int num = StarIndex(biome); if (num < 0) { return (mult: 1f, max: 2); } if (_usingServer && _sStarMultiplier != null && _sStarMaxStars != null && num < _sStarMultiplier.Length && num < _sStarMaxStars.Length) { return (mult: _sStarMultiplier[num], max: _sStarMaxStars[num]); } return (mult: StarMultiplier[num].Value, max: StarMaxStars[num].Value); } private static int StarIndex(Biome biome) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between I4 and Unknown for (int i = 0; i < StarBiomes.Length; i++) { if ((int)StarBiomes[i] == (int)biome) { return i; } } return -1; } public void ApplyServer(bool enable, int baitAcclimationTime, int creatureAttractionCadence, float minimumAttractionRadius, int baitLifetimeAttractions, int attractionMultiplier, int maximumActiveCreatures, float[] starMultiplier, int[] starMaxStars, bool floatInWater) { _sEnable = enable; _sBaitAcclimationTime = baitAcclimationTime; _sCreatureAttractionCadence = creatureAttractionCadence; _sMinimumAttractionRadius = minimumAttractionRadius; _sBaitLifetimeAttractions = baitLifetimeAttractions; _sAttractionMultiplier = attractionMultiplier; _sMaximumActiveCreatures = maximumActiveCreatures; _sStarMultiplier = starMultiplier; _sStarMaxStars = starMaxStars; _sFloatInWater = floatInWater; _usingServer = true; } public void ResetToLocal() { _usingServer = false; } public void ApplyLocalFromBundle(bool enable, int baitAcclimationTime, int creatureAttractionCadence, float minimumAttractionRadius, int baitLifetimeAttractions, int attractionMultiplier, int maximumActiveCreatures, float[] starMultiplier, int[] starMaxStars, bool floatInWater) { Enable.Value = enable; BaitAcclimationTime.Value = baitAcclimationTime; CreatureAttractionCadence.Value = creatureAttractionCadence; MinimumAttractionRadius.Value = minimumAttractionRadius; BaitLifetimeAttractions.Value = baitLifetimeAttractions; AttractionMultiplier.Value = attractionMultiplier; MaximumActiveCreatures.Value = maximumActiveCreatures; FloatInWater.Value = floatInWater; if (starMultiplier != null && starMaxStars != null) { int num = Math.Min(StarBiomes.Length, Math.Min(starMultiplier.Length, starMaxStars.Length)); for (int i = 0; i < num; i++) { StarMultiplier[i].Value = starMultiplier[i]; StarMaxStars[i].Value = starMaxStars[i]; } } } public void SetUiLocked(bool locked) { for (int i = 0; i < _lockable.Count; i++) { _lockable[i].ReadOnly = locked; } } static CreatureBaitConfig() { Biome[] array = new Biome[9]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); StarBiomes = (Biome[])(object)array; StarBiomeLabels = new string[9] { "Meadows", "Black Forest", "Swamp", "Mountain", "Plains", "Mistlands", "Ash Lands", "Deep North", "Ocean" }; } } public sealed class ConfigurationManagerAttributes { public bool? ReadOnly = false; } [BepInPlugin("drummercraig.creaturebait", "CreatureBait", "0.5.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class CreatureBaitPlugin : BaseUnityPlugin { public const string PluginGuid = "drummercraig.creaturebait"; public const string PluginName = "CreatureBait"; public const string PluginVersion = "0.5.2"; private Harmony _harmony; internal static CreatureBaitPlugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static CreatureBaitConfig Settings { get; private set; } private void Awake() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Settings = new CreatureBaitConfig(((BaseUnityPlugin)this).Config); ServerSync.HookConfigWatch(((BaseUnityPlugin)this).Config); _harmony = new Harmony("drummercraig.creaturebait"); _harmony.PatchAll(typeof(CreatureBaitPlugin).Assembly); Log.LogInfo((object)string.Format("[CreatureBait] v{0} loaded. Enable={1}", "0.5.2", Settings.Enable.Value)); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[CreatureBait] unloaded."); } } } public sealed class RecipeEntry { public string Name { get; set; } = string.Empty; public string Food1 { get; set; } = string.Empty; public string Food2 { get; set; } = string.Empty; public int Amount1 { get; set; } = 10; public int Amount2 { get; set; } = 10; public string Biomes { get; set; } = string.Empty; public int TrophyAmount { get; set; } public string Stations { get; set; } = string.Empty; public bool Enabled { get; set; } = true; } public sealed class RecipeDocument { public Dictionary<string, RecipeEntry> Recipes { get; set; } = new Dictionary<string, RecipeEntry>(); } internal static class RecipeConfig { public const string FileName = "drummercraig.creaturebait.yml"; private static readonly string[] LegacyRelativePaths = new string[2] { "CreatureBait.recipes.yml", Path.Combine("CreatureBait", "CreatureBait.yml") }; private static RecipeDocument _doc; private static string _path; private static int _generation; private static readonly Dictionary<string, Biome> _biomeCache = new Dictionary<string, Biome>(); private static int _biomeCacheGen = -1; private static RecipeDocument _serverDoc; public static Comparison<string> KeyOrder; public static int Generation => _generation; private static RecipeDocument ActiveDoc => _serverDoc ?? _doc; public static bool UsingServerConfig => _serverDoc != null; public static IReadOnlyDictionary<string, RecipeEntry> Entries => ((ActiveDoc != null) ? ActiveDoc.Recipes : null) ?? new Dictionary<string, RecipeEntry>(); public static string FilePath => _path; public static RecipeDocument LoadOrSeed(string configDir, IDictionary<string, RecipeEntry> seedDefaults) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown _path = Path.Combine(configDir, "drummercraig.creaturebait.yml"); bool flag = false; try { if (!File.Exists(_path)) { string[] legacyRelativePaths = LegacyRelativePaths; foreach (string text in legacyRelativePaths) { string text2 = Path.Combine(configDir, text); if (File.Exists(text2)) { File.Move(text2, _path); ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)("[CreatureBait] Migrated recipe config from " + text + " to drummercraig.creaturebait.yml.")); } break; } } } } catch (Exception ex) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogError((object)("[CreatureBait] Recipe config migration failed: " + ex.Message)); } } if (_doc == null) { if (File.Exists(_path)) { try { string text3 = File.ReadAllText(_path); _doc = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(PascalCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build() .Deserialize<RecipeDocument>(text3) ?? new RecipeDocument(); if (_doc.Recipes == null) { _doc.Recipes = new Dictionary<string, RecipeEntry>(); } } catch (Exception ex2) { ManualLogSource log3 = CreatureBaitPlugin.Log; if (log3 != null) { log3.LogError((object)("[CreatureBait] Failed to read drummercraig.creaturebait.yml: " + ex2.Message + ". Re-seeding from defaults.")); } _doc = new RecipeDocument(); flag = true; } } else { _doc = new RecipeDocument(); flag = true; } } if (seedDefaults != null) { foreach (KeyValuePair<string, RecipeEntry> seedDefault in seedDefaults) { if (!_doc.Recipes.ContainsKey(seedDefault.Key)) { _doc.Recipes[seedDefault.Key] = seedDefault.Value; flag = true; } } } if (flag) { Save(); } _generation++; return _doc; } public static bool NeedsReorder() { if (KeyOrder == null || _doc?.Recipes == null || _doc.Recipes.Count < 2) { return false; } List<string> list = new List<string>(_doc.Recipes.Keys); List<string> list2 = new List<string>(list); list2.Sort(KeyOrder); for (int i = 0; i < list.Count; i++) { if (list[i] != list2[i]) { return true; } } return false; } private static void ReorderInPlace() { if (KeyOrder == null || _doc?.Recipes == null || _doc.Recipes.Count < 2) { return; } List<string> list = new List<string>(_doc.Recipes.Keys); list.Sort(KeyOrder); Dictionary<string, RecipeEntry> dictionary = new Dictionary<string, RecipeEntry>(_doc.Recipes.Count); foreach (string item in list) { dictionary[item] = _doc.Recipes[item]; } _doc.Recipes = dictionary; } public static void Save() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown if (_doc == null || string.IsNullOrEmpty(_path)) { return; } _generation++; ReorderInPlace(); try { ISerializer val = ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(PascalCaseNamingConvention.Instance).Build(); string text = "# CreatureBait recipe definitions. Auto-generated on first run; edit freely.\n# Each entry is keyed by the trophy prefab name. 'Name' is display-only.\n# 'Food1'/'Food2' are item prefab ids; 'Amount1'/'Amount2' default to 10.\n# 'TrophyAmount' is how many trophies a craft consumes (default 1).\n# 'Biomes' is a comma-delimited list of biomes the Bait works in\n# (e.g. 'Meadows,BlackForest'); defaults to the trophy's home biome.\n# 'Stations' is a comma-delimited list of crafting stations\n# ('Cauldron', 'Workbench'); defaults to Cauldron (+Workbench for Meadows).\n# Set 'Enabled: false' to remove a bait from the Cauldron.\n"; File.WriteAllText(_path, text + val.Serialize((object)_doc)); ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)string.Format("[CreatureBait] Wrote {0} ({1} entries).", "drummercraig.creaturebait.yml", _doc.Recipes.Count)); } } catch (Exception ex) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogError((object)("[CreatureBait] Failed to write drummercraig.creaturebait.yml: " + ex.Message)); } } } public static string GetDisplayName(string trophyName) { RecipeDocument activeDoc = ActiveDoc; if (trophyName != null && activeDoc != null && activeDoc.Recipes != null && activeDoc.Recipes.TryGetValue(trophyName, out var value) && !string.IsNullOrEmpty(value.Name)) { return value.Name; } return null; } public static string SerializeDoc() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (_doc == null) { return string.Empty; } try { return ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(PascalCaseNamingConvention.Instance).Build().Serialize((object)_doc); } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogError((object)("[CreatureBait] Failed to serialise recipes for sync: " + ex.Message)); } return string.Empty; } } public static void ApplyServerYaml(string yaml) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if (string.IsNullOrWhiteSpace(yaml)) { return; } try { RecipeDocument recipeDocument = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(PascalCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build() .Deserialize<RecipeDocument>(yaml) ?? new RecipeDocument(); if (recipeDocument.Recipes == null) { recipeDocument.Recipes = new Dictionary<string, RecipeEntry>(); } _serverDoc = recipeDocument; _generation++; ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)$"[CreatureBait] Adopted server recipe config ({recipeDocument.Recipes.Count} entries)."); } } catch (Exception ex) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogError((object)("[CreatureBait] Failed to apply server recipe config: " + ex.Message)); } } } public static void ResetToLocal() { _serverDoc = null; _generation++; } public static bool TryGetBiomes(string trophyName, out Biome biomes) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) //IL_0055: Expected I4, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected I4, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Invalid comparison between Unknown and I4 biomes = (Biome)0; if (trophyName == null) { return false; } if (_biomeCacheGen != _generation) { _biomeCache.Clear(); _biomeCacheGen = _generation; } if (_biomeCache.TryGetValue(trophyName, out var value)) { biomes = (Biome)(int)value; return (int)value > 0; } Biome val = ParseBiomes(trophyName); _biomeCache[trophyName] = val; biomes = (Biome)(int)val; return (int)val > 0; } private static Biome ParseBiomes(string trophyName) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) RecipeDocument activeDoc = ActiveDoc; if (activeDoc == null || activeDoc.Recipes == null || !activeDoc.Recipes.TryGetValue(trophyName, out var value) || value == null || string.IsNullOrWhiteSpace(value.Biomes)) { return (Biome)0; } Biome val = (Biome)0; string[] array = value.Biomes.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } if (Enum.TryParse<Biome>(text, ignoreCase: true, out Biome result)) { val |= result; continue; } ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Unknown biome '" + text + "' in recipe '" + trophyName + "'; ignoring.")); } } return val; } } internal static class ServerSync { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class OnNewConnectionPatch { private static void Postfix(ZNetPeer peer, ZNet __instance) { if (peer != null && peer.m_rpc != null && !((Object)(object)__instance == (Object)null)) { if (__instance.IsServer()) { peer.m_rpc.Register<string>("CreatureBait_Version", (Action<ZRpc, string>)OnServerReceiveClientVersion); peer.m_rpc.Register<ZPackage>("CreatureBait_AdminConfig", (Action<ZRpc, ZPackage>)OnAdminConfig); peer.m_rpc.Invoke("CreatureBait_Version", new object[1] { "0.5.2" }); return; } _serverRpc = peer.m_rpc; _serverSentVersion = false; _recipeBuffer = null; _recipeReceived = 0; peer.m_rpc.Register<string>("CreatureBait_Version", (Action<ZRpc, string>)OnClientReceiveServerVersion); peer.m_rpc.Register<ZPackage>("CreatureBait_Config", (Action<ZRpc, ZPackage>)OnServerConfig); peer.m_rpc.Register<ZPackage>("CreatureBait_RecipesChunk", (Action<ZRpc, ZPackage>)OnServerRecipesChunk); peer.m_rpc.Register<bool>("CreatureBait_Admin", (Action<ZRpc, bool>)OnServerAdminStatus); peer.m_rpc.Invoke("CreatureBait_Version", new object[1] { "0.5.2" }); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class RpcPeerInfoPatch { private static bool Prefix(ZRpc rpc, ZNet __instance) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Invalid comparison between Unknown and I4 if (rpc == null || (Object)(object)__instance == (Object)null) { return true; } if (__instance.IsServer()) { if (!_validatedPeers.Contains(rpc)) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)"[CreatureBait] Disconnecting client without a matching CreatureBait version."); } rpc.Invoke("Error", new object[1] { 3 }); return false; } return true; } if (!_serverSentVersion) { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogWarning((object)"[CreatureBait] Server is not running CreatureBait. Disconnecting (incompatible version)."); } if ((int)ZNet.GetConnectionStatus() != 3) { SetClientConnectionError((ConnectionStatus)3); } if ((Object)(object)Game.instance != (Object)null) { Game.instance.Logout(true, true); } return false; } return true; } private static void Postfix(ZRpc rpc, ZNet __instance) { if (rpc == null || (Object)(object)__instance == (Object)null || !__instance.IsServer() || !_validatedPeers.Contains(rpc)) { return; } try { rpc.Invoke("CreatureBait_Config", new object[1] { WriteConfigBundle(CreatureBaitPlugin.Settings) }); SendRecipes(rpc); ZNetPeer obj = FindPeerByRpc(__instance, rpc); object obj2; if (obj == null) { obj2 = null; } else { ISocket socket = obj.m_socket; obj2 = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj2; rpc.Invoke("CreatureBait_Admin", new object[1] { !string.IsNullOrEmpty(text) && SenderIsAdmin(__instance, text) }); } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Server sync push failed: " + ex.Message)); } } } } [HarmonyPatch(typeof(ZNet), "Shutdown")] private static class ShutdownPatch { private static void Postfix() { _recipeBuffer = null; _recipeReceived = 0; _serverRpc = null; _validatedPeers.Clear(); _serverSentVersion = false; IsAdmin = false; CreatureBaitPlugin.Settings?.ResetToLocal(); RecipeConfig.ResetToLocal(); RefreshLockState(); } } private struct ConfigBundle { public bool Enable; public int Acclim; public int Cadence; public float Radius; public int Lifetime; public int Multiplier; public int MaxActive; public float[] StarMult; public int[] StarMax; public bool Float; } private const string RpcConfig = "CreatureBait_Config"; private const string RpcRecipes = "CreatureBait_RecipesChunk"; private const string RpcAdminConfig = "CreatureBait_AdminConfig"; private const string RpcVersion = "CreatureBait_Version"; private const string RpcAdmin = "CreatureBait_Admin"; private const int ChunkBytes = 16384; private static byte[] _recipeBuffer; private static int _recipeReceived; private static ZRpc _serverRpc; private static bool _suppressChange; private static readonly HashSet<ZRpc> _validatedPeers = new HashSet<ZRpc>(); private static bool _serverSentVersion; private static FieldInfo _adminListField; private static FieldInfo _peersField; private static MethodInfo _listContainsId; internal static bool IsAdmin { get; private set; } private static void OnServerReceiveClientVersion(ZRpc rpc, string clientVersion) { if (string.Equals(clientVersion, "0.5.2", StringComparison.Ordinal)) { _validatedPeers.Add(rpc); ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)("[CreatureBait] Client presented matching version " + clientVersion + ".")); } } else { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[CreatureBait] Client version '" + clientVersion + "' != server '0.5.2'; will be disconnected at handshake.")); } } } private static void OnServerAdminStatus(ZRpc rpc, bool isAdmin) { IsAdmin = isAdmin; ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)$"[CreatureBait] Server reports admin status: {isAdmin}"); } RefreshLockState(); } private static void OnClientReceiveServerVersion(ZRpc rpc, string serverVersion) { _serverSentVersion = true; if (!string.Equals(serverVersion, "0.5.2", StringComparison.Ordinal)) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Server version '" + serverVersion + "' != client '0.5.2'; will be disconnected (incompatible version).")); } } else { ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogInfo((object)("[CreatureBait] Server version " + serverVersion + " matches.")); } } } private static void SetClientConnectionError(ConnectionStatus status) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) AccessTools.Field(typeof(ZNet), "m_connectionStatus")?.SetValue(null, status); } public static void HookConfigWatch(ConfigFile cfg) { if (cfg != null) { cfg.SettingChanged += OnConfigChanged; } } private static void OnConfigChanged(object sender, SettingChangedEventArgs args) { if (_suppressChange) { return; } CreatureBaitConfig settings = CreatureBaitPlugin.Settings; ZNet instance = ZNet.instance; if (settings == null || (Object)(object)instance == (Object)null) { RefreshLockState(); return; } if (instance.IsServer()) { BroadcastConfig(); } else if (_serverRpc != null && IsAdmin) { try { _serverRpc.Invoke("CreatureBait_AdminConfig", new object[1] { WriteConfigBundle(settings) }); } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Failed to send admin config: " + ex.Message)); } } } RefreshLockState(); } public static void RefreshLockState() { CreatureBaitConfig settings = CreatureBaitPlugin.Settings; if (settings != null) { ZNet instance = ZNet.instance; bool uiLocked = (Object)(object)instance != (Object)null && !instance.IsServer() && settings.UsingServerConfig && !IsAdmin; settings.SetUiLocked(uiLocked); } } private static void OnAdminConfig(ZRpc rpc, ZPackage z) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || z == null) { return; } ZNetPeer obj = FindPeerByRpc(instance, rpc); object obj2; if (obj == null) { obj2 = null; } else { ISocket socket = obj.m_socket; obj2 = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj2; if (string.IsNullOrEmpty(text) || !SenderIsAdmin(instance, text)) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Rejected config change from non-admin peer '" + text + "'.")); } return; } CreatureBaitConfig settings = CreatureBaitPlugin.Settings; if (settings != null) { ConfigBundle configBundle = ReadConfigBundle(z); _suppressChange = true; try { settings.ApplyLocalFromBundle(configBundle.Enable, configBundle.Acclim, configBundle.Cadence, configBundle.Radius, configBundle.Lifetime, configBundle.Multiplier, configBundle.MaxActive, configBundle.StarMult, configBundle.StarMax, configBundle.Float); } finally { _suppressChange = false; } ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogInfo((object)("[CreatureBait] Admin '" + text + "' updated server config; broadcasting to all peers.")); } BroadcastConfig(); } } private static bool SenderIsAdmin(ZNet net, string host) { try { if (_adminListField == null) { _adminListField = typeof(ZNet).GetField("m_adminList", BindingFlags.Instance | BindingFlags.NonPublic); } if (_listContainsId == null) { _listContainsId = typeof(ZNet).GetMethod("ListContainsId", BindingFlags.Instance | BindingFlags.NonPublic); } object obj = _adminListField?.GetValue(net); if (obj == null || _listContainsId == null) { return false; } return (bool)_listContainsId.Invoke(net, new object[2] { obj, host }); } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Admin check failed (" + ex.Message + "); denying.")); } return false; } } private static ZNetPeer FindPeerByRpc(ZNet net, ZRpc rpc) { List<ZNetPeer> list = Peers(net); if (list == null) { return null; } foreach (ZNetPeer item in list) { if (item != null && item.m_rpc == rpc) { return item; } } return null; } private static List<ZNetPeer> Peers(ZNet net) { try { if (_peersField == null) { _peersField = typeof(ZNet).GetField("m_peers", BindingFlags.Instance | BindingFlags.NonPublic); } return _peersField?.GetValue(net) as List<ZNetPeer>; } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Peer list access failed: " + ex.Message)); } return null; } } private static void BroadcastConfig() { ZNet instance = ZNet.instance; CreatureBaitConfig settings = CreatureBaitPlugin.Settings; if ((Object)(object)instance == (Object)null || !instance.IsServer() || settings == null) { return; } try { List<ZNetPeer> list = Peers(instance); if (list == null) { return; } foreach (ZNetPeer item in list) { if (item?.m_rpc != null) { item.m_rpc.Invoke("CreatureBait_Config", new object[1] { WriteConfigBundle(settings) }); } } } catch (Exception ex) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)("[CreatureBait] Config broadcast failed: " + ex.Message)); } } } private static void SendRecipes(ZRpc rpc) { byte[] bytes = Encoding.UTF8.GetBytes(RecipeConfig.SerializeDoc() ?? string.Empty); int num = bytes.Length; if (num == 0) { rpc.Invoke("CreatureBait_RecipesChunk", new object[1] { NewChunk(0, 0, new byte[0]) }); return; } int num2 = 0; for (int i = 0; i < num; i += 16384) { int num3 = Math.Min(16384, num - i); byte[] array = new byte[num3]; Array.Copy(bytes, i, array, 0, num3); rpc.Invoke("CreatureBait_RecipesChunk", new object[1] { NewChunk(num, i, array) }); num2++; } ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)$"[CreatureBait] Pushed recipe config to client: {num} bytes in {num2} chunk(s)."); } } private static ZPackage NewChunk(int total, int offset, byte[] slice) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(total); val.Write(offset); val.Write(slice); return val; } private static ZPackage WriteConfigBundle(CreatureBaitConfig s) { //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(); if (s == null) { return val; } val.Write(s.Enable.Value); val.Write(s.BaitAcclimationTime.Value); val.Write(s.CreatureAttractionCadence.Value); val.Write(s.MinimumAttractionRadius.Value); val.Write(s.BaitLifetimeAttractions.Value); val.Write(s.AttractionMultiplier.Value); val.Write(s.MaximumActiveCreatures.Value); int num = CreatureBaitConfig.StarBiomes.Length; val.Write(num); for (int i = 0; i < num; i++) { val.Write(s.StarMultiplier[i].Value); val.Write(s.StarMaxStars[i].Value); } val.Write(s.FloatInWater.Value); return val; } private static ConfigBundle ReadConfigBundle(ZPackage z) { z.SetPos(0); ConfigBundle result = new ConfigBundle { Enable = z.ReadBool(), Acclim = z.ReadInt(), Cadence = z.ReadInt(), Radius = z.ReadSingle(), Lifetime = z.ReadInt(), Multiplier = z.ReadInt(), MaxActive = z.ReadInt() }; int num = z.ReadInt(); result.StarMult = new float[num]; result.StarMax = new int[num]; for (int i = 0; i < num; i++) { result.StarMult[i] = z.ReadSingle(); result.StarMax[i] = z.ReadInt(); } result.Float = z.ReadBool(); return result; } private static void OnServerConfig(ZRpc rpc, ZPackage z) { if (z == null || CreatureBaitPlugin.Settings == null) { return; } ConfigBundle configBundle = ReadConfigBundle(z); if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { CreatureBaitPlugin.Settings.ApplyServer(configBundle.Enable, configBundle.Acclim, configBundle.Cadence, configBundle.Radius, configBundle.Lifetime, configBundle.Multiplier, configBundle.MaxActive, configBundle.StarMult, configBundle.StarMax, configBundle.Float); ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogInfo((object)$"[CreatureBait] Adopted server config (Enable={configBundle.Enable}, BaitAcclimationTime={configBundle.Acclim}, BaitLifetimeAttractions={configBundle.Lifetime}, AttractionMultiplier={configBundle.Multiplier}, MaximumActiveCreatures={configBundle.MaxActive}, per-biome stars x{configBundle.StarMult.Length})."); } RefreshLockState(); } } private static void OnServerRecipesChunk(ZRpc rpc, ZPackage z) { if (z == null) { return; } z.SetPos(0); int num = z.ReadInt(); int num2 = z.ReadInt(); byte[] array = z.ReadByteArray(); if ((!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) && num > 0) { if (_recipeBuffer == null || _recipeBuffer.Length != num) { _recipeBuffer = new byte[num]; _recipeReceived = 0; } if (array != null && num2 >= 0 && num2 + array.Length <= num) { Array.Copy(array, 0, _recipeBuffer, num2, array.Length); _recipeReceived += array.Length; } if (_recipeReceived >= num) { RecipeConfig.ApplyServerYaml(Encoding.UTF8.GetString(_recipeBuffer)); _recipeBuffer = null; _recipeReceived = 0; } } } } internal static class StarRoll { public static int RollLevel(CreatureBaitConfig settings, Biome biome) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (settings == null) { return 1; } var (num, num2) = settings.StarSettingsFor(biome); if (num2 <= 0 || num <= 0f) { return 1; } int i; for (i = 1; i <= num2; i++) { if (!(Random.Range(0f, 100f) <= SpawnSystem.GetLevelUpChance(0f) * num)) { break; } } return i; } } internal static class TrophyBiomeOverrides { public static readonly Dictionary<string, Biome> Map = new Dictionary<string, Biome> { { "TrophyDeer", (Biome)1 }, { "TrophyBoar", (Biome)1 }, { "TrophyNeck", (Biome)1 }, { "TrophyFrostTroll", (Biome)4 }, { "TrophySGolem", (Biome)4 }, { "TrophyGrowth", (Biome)16 }, { "TrophyBjorn", (Biome)16 }, { "TrophyBjornUndead", (Biome)16 }, { "TrophyKvastur", (Biome)2 } }; } internal static class TrophyCatalog { private static Dictionary<string, Character> _characterByTrophy; private static Dictionary<int, GameObject> _characterPrefabByTrophyHash; private static Dictionary<int, Biome> _biomeByTrophyHash; private static Dictionary<string, GameObject> _trophyPrefabByName; public static bool IsReady => _characterByTrophy != null; public static IReadOnlyDictionary<string, Character> CharacterByTrophy => _characterByTrophy; public static void Build() { //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Invalid comparison between Unknown and I4 //IL_01d8: Unknown result type (might be due to invalid IL or missing references) _characterByTrophy = new Dictionary<string, Character>(); _characterPrefabByTrophyHash = new Dictionary<int, GameObject>(); _biomeByTrophyHash = new Dictionary<int, Biome>(); _trophyPrefabByName = new Dictionary<string, GameObject>(); if ((Object)(object)ZNetScene.instance == (Object)null || ZNetScene.instance.m_prefabs == null) { ManualLogSource log = CreatureBaitPlugin.Log; if (log != null) { log.LogWarning((object)"[CreatureBait] TrophyCatalog.Build called before ZNetScene available; aborting."); } return; } foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab == (Object)null) { continue; } Character component = prefab.GetComponent<Character>(); if ((Object)(object)component == (Object)null || component.m_boss) { continue; } CharacterDrop component2 = prefab.GetComponent<CharacterDrop>(); if ((Object)(object)component2 == (Object)null || component2.m_drops == null) { continue; } GameObject val = null; foreach (Drop drop in component2.m_drops) { if (drop != null && !((Object)(object)drop.m_prefab == (Object)null)) { ItemDrop component3 = drop.m_prefab.GetComponent<ItemDrop>(); if (!((Object)(object)component3 == (Object)null) && component3.m_itemData != null && component3.m_itemData.m_shared != null && (int)component3.m_itemData.m_shared.m_itemType == 13) { val = drop.m_prefab; break; } } } if (!((Object)(object)val == (Object)null)) { string name = ((Object)val).name; if (!_characterByTrophy.TryGetValue(name, out var value) || name.Contains(((Object)prefab).name) || ((Object)prefab).name.Length < ((Object)value).name.Length) { _characterByTrophy[name] = component; int stableHashCode = StringExtensionMethods.GetStableHashCode(name); _characterPrefabByTrophyHash[stableHashCode] = prefab; _biomeByTrophyHash[stableHashCode] = InferBiome(component); _trophyPrefabByName[name] = val; } } } ManualLogSource log2 = CreatureBaitPlugin.Log; if (log2 != null) { log2.LogInfo((object)$"[CreatureBait] TrophyCatalog built: {_characterByTrophy.Count} trophy->creature mappings."); } } public static bool TryGetCharacterPrefab(string trophyPrefabName, out GameObject characterPrefab) { characterPrefab = null; if (_characterPrefabByTrophyHash == null || string.IsNullOrEmpty(trophyPrefabName)) { return false; } return _characterPrefabByTrophyHash.TryGetValue(StringExtensionMethods.GetStableHashCode(trophyPrefabName), out characterPrefab); } public static bool TryGetTrophyPrefab(string trophyPrefabName, out GameObject trophyPrefab) { trophyPrefab = null; if (_trophyPrefabByName == null || string.IsNullOrEmpty(trophyPrefabName)) { return false; } return _trophyPrefabByName.TryGetValue(trophyPrefabName, out trophyPrefab); } public static Biome GetBiome(string trophyPrefabName) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (_biomeByTrophyHash == null || string.IsNullOrEmpty(trophyPrefabName)) { return (Biome)0; } if (!_biomeByTrophyHash.TryGetValue(StringExtensionMethods.GetStableHashCode(trophyPrefabName), out var value)) { return (Biome)0; } return value; } public static Biome GetEffectiveBiome(string trophyPrefabName) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!TrophyBiomeOverrides.Map.TryGetValue(trophyPrefabName, out var value)) { return GetBiome(trophyPrefabName); } return value; } public static Biome AllowedBiomes(string trophyPrefabName) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) string text = StripCloneSuffix(trophyPrefabName); if (RecipeConfig.TryGetBiomes(text, out var biomes) && (int)biomes != 0) { return biomes; } return GetEffectiveBiome(text); } public static bool OriginatesAt(string trophyNameOrClone, Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 Biome val = AllowedBiomes(trophyNameOrClone); if ((int)val == 0) { return true; } return (val & Heightmap.FindBiome(pos)) > 0; } public static string StripCloneSuffix(string n) { if (string.IsNullOrEmpty(n)) { return n; } int num = n.IndexOf('('); if (num <= 0) { return n; } return n.Substring(0, num).TrimEnd(Array.Empty<char>()); } public static IEnumerable<string> AllTrophyNames() { if (_characterByTrophy == null) { return Enumerable.Empty<string>(); } return _characterByTrophy.Keys; } public static bool TryGetCreatureName(string trophyPrefabName, out string localizedName) { localizedName = null; if (!TryGetCharacterPrefab(trophyPrefabName, out var characterPrefab) || (Object)(object)characterPrefab == (Object)null) { return false; } Character component = characterPrefab.GetComponent<Character>(); string text = (((Object)(object)component != (Object)null) ? component.m_name : null); if (string.IsNullOrEmpty(text)) { return false; } localizedName = ((Localization.instance != null) ? Localization.instance.Localize(text) : text); return true; } private static Biome InferBiome(Character character) { //IL_002f: 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_0035: 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_0065: Expected I4, but got Unknown string te