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 ValheimExpeditions v0.2.0
BepInEx/plugins/ValheimExpeditions.dll
Decompiled 18 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; using UnityEngine.Rendering; using ValheimExpeditions.Config; using ValheimExpeditions.Storage; using ValheimExpeditions.Vehicles; using ValheimExpeditions.Zones; using ValheimHouseholds.Utils; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ValheimExpeditions")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+69ddb477b4df4e9601adba9f4986dcc5782df902")] [assembly: AssemblyProduct("ValheimExpeditions")] [assembly: AssemblyTitle("ValheimExpeditions")] [assembly: AssemblyVersion("0.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ValheimExpeditions { [BepInPlugin("com.householdheim.expeditions", "ValheimExpeditions", "0.2.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string GUID = "com.householdheim.expeditions"; public const string NAME = "ValheimExpeditions"; public const string VERSION = "0.2.0"; public static ManualLogSource Log; private static Harmony _harmony; private void Awake() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; CartConfig.Init(((BaseUnityPlugin)this).Config); ZoneLinesConfig.Init(((BaseUnityPlugin)this).Config); ExpeditionsState.Load(); _harmony = new Harmony("com.householdheim.expeditions"); _harmony.PatchAll(typeof(CartDamageProtection)); _harmony.PatchAll(typeof(CartStorageExpansion)); _harmony.PatchAll(typeof(CartChildZNetViewBinding)); _harmony.PatchAll(typeof(ZoneLineCommands)); _harmony.PatchAll(typeof(ZoneLineRpc)); _harmony.PatchAll(typeof(ZoneLineCrossingDetector)); PrefabManager.OnVanillaPrefabsAvailable += CartPiece.Register; FreezeManager.OnFreezeChanged += OnVHFreezeChanged; Log.LogInfo((object)"ValheimExpeditions 0.2.0 loaded"); } private void OnDestroy() { FreezeManager.OnFreezeChanged -= OnVHFreezeChanged; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchAll("com.householdheim.expeditions"); } } private static void OnVHFreezeChanged(bool isFrozen, string reason) { Log.LogInfo((object)string.Format("VH freeze state changed: frozen={0} reason={1}", isFrozen, reason ?? "none")); } public static void TriggerVictoryFreeze(string householdName) { FreezeManager.SetFrozen(true, "Victory: " + householdName + " reached the Marker!"); } } } namespace ValheimExpeditions.Zones { public static class AuthHelper { public static bool IsAdmin(ZNetPeer peer) { if (peer == null || (Object)(object)ZNet.instance == (Object)null) { return false; } if (!ZNet.instance.IsServer()) { return false; } ISocket socket = peer.m_socket; string text = ((socket != null) ? socket.GetHostName() : null) ?? ""; if (string.IsNullOrEmpty(text)) { return false; } try { return ZNet.instance.IsAdmin(text); } catch { return false; } } } public static class HouseholdLookup { private static readonly object _lock = new object(); private static Dictionary<string, int> _cache = new Dictionary<string, int>(); private static float _lastReadTime = -999f; private const float CacheTtlSeconds = 5f; private static string VhJsonPath => Path.Combine(Paths.ConfigPath, "ValheimHouseholds.json"); public static int GetFactionId(string playerName) { if (string.IsNullOrEmpty(playerName)) { return 0; } RefreshIfStale(); lock (_lock) { if (_cache.TryGetValue(playerName, out var value)) { return value; } return 0; } } private static void RefreshIfStale() { if (Time.time - _lastReadTime < 5f) { return; } lock (_lock) { if (Time.time - _lastReadTime < 5f) { return; } _lastReadTime = Time.time; _cache.Clear(); if (!File.Exists(VhJsonPath)) { return; } try { ParsePlayerAssignments(File.ReadAllText(VhJsonPath)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("HouseholdLookup: failed to read VH JSON: " + ex.Message)); } } } private static void ParsePlayerAssignments(string json) { foreach (string item in EnumerateObjects(ExtractArray(json, "playerAssignments"))) { string text = ExtractString(item, "name"); int value = ExtractInt(item, "factionId"); if (!string.IsNullOrEmpty(text)) { _cache[text] = value; } } } private static string ExtractArray(string json, string key) { int num = json.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return ""; } int num2 = json.IndexOf(':', num); if (num2 < 0) { return ""; } int num3 = json.IndexOf('[', num2); if (num3 < 0) { return ""; } int num4 = 0; for (int i = num3; i < json.Length; i++) { switch (json[i]) { case '[': num4++; break; case ']': num4--; if (num4 == 0) { return json.Substring(num3 + 1, i - num3 - 1); } break; } } return ""; } private static IEnumerable<string> EnumerateObjects(string arrayBody) { if (string.IsNullOrEmpty(arrayBody)) { yield break; } int depth = 0; int num = -1; bool inString = false; bool escape = false; for (int i = 0; i < arrayBody.Length; i++) { char c = arrayBody[i]; if (escape) { escape = false; continue; } switch (c) { case '\\': escape = true; continue; case '"': inString = !inString; continue; } if (inString) { continue; } switch (c) { case '{': if (depth == 0) { num = i; } depth++; break; case '}': depth--; if (depth == 0 && num >= 0) { yield return arrayBody.Substring(num, i - num + 1); num = -1; } break; } } } private static string ExtractString(string obj, string key) { int num = obj.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return ""; } int num2 = obj.IndexOf(':', num); if (num2 < 0) { return ""; } int num3 = obj.IndexOf('"', num2 + 1); if (num3 < 0) { return ""; } int num4 = obj.IndexOf('"', num3 + 1); if (num4 < 0) { return ""; } return obj.Substring(num3 + 1, num4 - num3 - 1); } private static int ExtractInt(string obj, string key) { int num = obj.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return 0; } int num2 = obj.IndexOf(':', num); if (num2 < 0) { return 0; } int i; for (i = num2 + 1; i < obj.Length && (char.IsDigit(obj[i]) || obj[i] == '-' || char.IsWhiteSpace(obj[i])); i++) { } if (int.TryParse(obj.Substring(num2 + 1, i - num2 - 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return 0; } } public static class ZoneLineAnnouncer { public static void Announce(int factionId, int zoneIndex, ZoneLineSpec spec) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZRoutedRpc.instance != null) { ExpeditionsState.MarkCrossed(factionId, zoneIndex); string householdName = ZoneLinesConfig.GetHouseholdName(factionId); string text = ZoneLinesConfig.AnnouncementTemplate.Value.Replace("{household}", householdName).Replace("{zone}", spec.Label); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "VEXP_ZoneCrossed", new object[1] { text }); Plugin.Log.LogInfo((object)$"ZoneCrossed: factionId={factionId} zoneIndex={zoneIndex} text='{text}'"); } } } [HarmonyPatch] public static class ZoneLineCommands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; public static ConsoleEvent <>9__1_1; public static ConsoleEvent <>9__1_2; internal void <RegisterCommands>b__1_0(ConsoleEventArgs args) { HandleShowZones(args); } internal void <RegisterCommands>b__1_1(ConsoleEventArgs args) { HandleResetCrossings(args); } internal void <RegisterCommands>b__1_2(ConsoleEventArgs args) { HandleAnnounceTest(args); } } private static bool _registered; [HarmonyPatch(typeof(Terminal), "InitTerminal")] [HarmonyPostfix] private static void RegisterCommands() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_00b3: 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_00a9: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { HandleShowZones(args); }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("vexp_showzones", "<on|off> — Toggle zone-line visuals (planes + minimap pins) for ALL clients. Admin only.", (ConsoleEvent)obj, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__1_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { HandleResetCrossings(args); }; <>c.<>9__1_1 = val2; obj2 = (object)val2; } new ConsoleCommand("vexp_resetcrossings", "— Clear all zone-crossing records, allowing re-announcements. Admin only.", (ConsoleEvent)obj2, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__1_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { HandleAnnounceTest(args); }; <>c.<>9__1_2 = val3; obj3 = (object)val3; } new ConsoleCommand("vexp_announce_test", "<zoneIndex> — Fire a synthetic announcement for the given zone (0-based). Admin only.", (ConsoleEvent)obj3, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); Plugin.Log.LogInfo((object)"Registered console commands: vexp_showzones, vexp_resetcrossings, vexp_announce_test"); } private static void HandleShowZones(ConsoleEventArgs args) { if (args.Args.Length < 2) { args.Context.AddString("Usage: vexp_showzones <on|off>"); return; } bool flag; switch (args.Args[1].Trim().ToLowerInvariant()) { case "on": case "1": case "true": flag = true; break; case "off": case "0": case "false": flag = false; break; default: args.Context.AddString("Expected 'on' or 'off'."); return; } if ((Object)(object)ZNet.instance == (Object)null) { args.Context.AddString("Not connected."); return; } if (ZNet.instance.IsServer()) { ZoneLinesConfig.Enabled.Value = flag; ZoneLineRpc.BroadcastPush(flag); args.Context.AddString("ZoneLines " + (flag ? "ON" : "OFF") + " (broadcast to all clients)."); return; } long num = 0L; try { num = Traverse.Create((object)ZRoutedRpc.instance).Field("m_serverPeerID").GetValue<long>(); } catch { } if (num == 0L) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { num = serverPeer.m_uid; } } if (num == 0L) { args.Context.AddString("No server peer."); return; } ZRoutedRpc.instance.InvokeRoutedRPC(num, "VEXP_ShowZonesReq", new object[1] { flag }); args.Context.AddString("Requested ZoneLines " + (flag ? "ON" : "OFF") + " from server."); } private static void HandleResetCrossings(ConsoleEventArgs args) { if ((Object)(object)ZNet.instance == (Object)null) { args.Context.AddString("Not connected."); return; } if (!ZNet.instance.IsServer()) { args.Context.AddString("This command only works on the server."); return; } ExpeditionsState.Reset(); args.Context.AddString("Zone crossing records cleared."); } private static void HandleAnnounceTest(ConsoleEventArgs args) { if (args.Args.Length < 2) { args.Context.AddString("Usage: vexp_announce_test <zoneIndex>"); return; } if ((Object)(object)ZNet.instance == (Object)null) { args.Context.AddString("Not connected."); return; } if (!ZNet.instance.IsServer()) { args.Context.AddString("This command only works on the server."); return; } if (!int.TryParse(args.Args[1].Trim(), out var result) || result < 0 || result >= ZoneLineRegistry.GetAll().Count) { List<ZoneLineSpec> all = ZoneLineRegistry.GetAll(); args.Context.AddString($"Invalid zone index. Valid range: 0 to {all.Count - 1}"); return; } ZoneLineSpec zoneLineSpec = ZoneLineRegistry.GetAll()[result]; if (ZRoutedRpc.instance != null) { string text = ZoneLinesConfig.AnnouncementTemplate.Value.Replace("{household}", "[TEST]").Replace("{zone}", zoneLineSpec.Label); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "VEXP_ZoneCrossed", new object[1] { text }); args.Context.AddString($"Test announcement fired for zone {result}."); } } } [HarmonyPatch(typeof(ZNet), "Update")] public static class ZoneLineCrossingDetector { private static float _lastTick; [HarmonyPostfix] private static void Tick(ZNet __instance) { if ((Object)(object)__instance == (Object)null || !__instance.IsServer()) { return; } float time = Time.time; if (time - _lastTick < 1f) { return; } _lastTick = time; List<ZoneLineSpec> all = ZoneLineRegistry.GetAll(); if (all.Count == 0) { return; } float[] array = new float[all.Count]; for (int i = 0; i < all.Count; i++) { array[i] = ZoneLineRegistry.XPercentToWorldX(all[i].XPercent); } foreach (ZNetPeer peer in __instance.GetPeers()) { if (peer == null || string.IsNullOrEmpty(peer.m_playerName)) { continue; } int factionId = HouseholdLookup.GetFactionId(peer.m_playerName); if (factionId == 0) { continue; } float x = peer.m_refPos.x; for (int j = 0; j < array.Length; j++) { if (!(x < array[j]) && !ExpeditionsState.HasCrossed(factionId, j)) { ZoneLineAnnouncer.Announce(factionId, j, all[j]); } } } } } public static class ZoneLineManager { private static readonly List<GameObject> _planes = new List<GameObject>(); private static readonly List<PinData> _pins = new List<PinData>(); private static bool _shown; public static bool IsShown => _shown; public static void Show() { if (_shown) { return; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) { _shown = true; return; } foreach (ZoneLineSpec item in ZoneLineRegistry.GetAll()) { _planes.Add(ZoneLineWorldVisual.Spawn(item)); _pins.AddRange(ZoneLineMapPins.Spawn(item)); } _shown = true; Plugin.Log.LogInfo((object)$"ZoneLines: shown ({_planes.Count} planes, {_pins.Count} pins)"); } public static void Hide() { foreach (GameObject plane in _planes) { ZoneLineWorldVisual.Destroy(plane); } _planes.Clear(); ZoneLineMapPins.Remove(_pins); _shown = false; Plugin.Log.LogInfo((object)"ZoneLines: hidden"); } public static void Rebuild() { Hide(); Show(); } public static void ApplyDesiredState(bool wantShown) { if (wantShown && !_shown) { Show(); } else if (!wantShown && _shown) { Hide(); } } } public static class ZoneLineMapPins { private static readonly Dictionary<Color, Sprite> _spriteCache = new Dictionary<Color, Sprite>(); private static Sprite GetSprite(Color color) { //IL_0005: 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_001b: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) if (_spriteCache.TryGetValue(color, out var value)) { return value; } Texture2D val = new Texture2D(8, 8, (TextureFormat)4, false); Color[] array = (Color[])(object)new Color[64]; for (int i = 0; i < 64; i++) { array[i] = color; } val.SetPixels(array); val.Apply(); Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, 8f, 8f), new Vector2(0.5f, 0.5f)); _spriteCache[color] = val2; return val2; } public static List<PinData> Spawn(ZoneLineSpec spec) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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) List<PinData> list = new List<PinData>(); if ((Object)(object)Minimap.instance == (Object)null) { return list; } float num = ZoneLineRegistry.XPercentToWorldX(spec.XPercent); float worldRadius = ZoneLineRegistry.GetWorldRadius(); int num2 = Mathf.Max(2, ZoneLinesConfig.MapPinsPerZone.Value); float num3 = worldRadius * 2f / (float)(num2 - 1); Sprite sprite = GetSprite(spec.Color); Vector3 val = default(Vector3); for (int i = 0; i < num2; i++) { float num4 = 0f - worldRadius + num3 * (float)i; ((Vector3)(ref val))..ctor(num, 0f, num4); string text = ((i == num2 / 2) ? spec.Label : ""); PinData val2 = Minimap.instance.AddPin(val, (PinType)3, text, false, false, 0L, default(PlatformUserID)); val2.m_icon = sprite; list.Add(val2); } return list; } public static void Remove(List<PinData> pins) { if ((Object)(object)Minimap.instance == (Object)null || pins == null) { return; } foreach (PinData pin in pins) { Minimap.instance.RemovePin(pin); } pins.Clear(); } } public static class ZoneLineRegistry { public static List<ZoneLineSpec> GetAll() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) return new List<ZoneLineSpec> { new ZoneLineSpec { XPercent = ZoneLinesConfig.Zone1XPercent.Value, Label = (ZoneLinesConfig.Zone1Label.Value ?? "ZONE 1"), Color = ParseHex(ZoneLinesConfig.Zone1ColorHex.Value ?? "#4488FF", new Color(0.267f, 0.533f, 1f)) }, new ZoneLineSpec { XPercent = ZoneLinesConfig.Zone2XPercent.Value, Label = (ZoneLinesConfig.Zone2Label.Value ?? "ZONE 2"), Color = ParseHex(ZoneLinesConfig.Zone2ColorHex.Value ?? "#FFAA00", new Color(1f, 0.667f, 0f)) }, new ZoneLineSpec { XPercent = ZoneLinesConfig.Zone3XPercent.Value, Label = (ZoneLinesConfig.Zone3Label.Value ?? "ZONE 3 — VICTORY"), Color = ParseHex(ZoneLinesConfig.Zone3ColorHex.Value ?? "#FF4444", new Color(1f, 0.267f, 0.267f)) } }; } public static float GetWorldRadius() { try { FieldInfo field = typeof(WorldGenerator).GetField("WorldRadius", BindingFlags.Static | BindingFlags.Public); if (field != null && field.FieldType == typeof(float)) { return (float)field.GetValue(null); } } catch { } return 10000f; } public static float XPercentToWorldX(float xPercent) { float worldRadius = GetWorldRadius(); return (xPercent - 0.5f) * 2f * worldRadius; } public static Color ParseHex(string hex, Color fallback) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (ColorUtility.TryParseHtmlString(hex, ref result)) { return result; } return fallback; } } [HarmonyPatch] public static class ZoneLineRpc { private const string RpcReq = "VEXP_ShowZonesReq"; private const string RpcPush = "VEXP_ShowZonesPush"; [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void OnZRoutedRpcCtor(ZRoutedRpc __instance) { if (__instance != null) { __instance.Register<bool>("VEXP_ShowZonesReq", (Action<long, bool>)delegate(long sender, bool wantOn) { HandleReqServer(sender, wantOn); }); __instance.Register<bool>("VEXP_ShowZonesPush", (Action<long, bool>)delegate(long sender, bool isOn) { HandlePushClient(isOn); }); __instance.Register<string>("VEXP_ZoneCrossed", (Action<long, string>)delegate(long sender, string text) { HandleZoneCrossedClient(text); }); Plugin.Log.LogInfo((object)"Registered RPCs VEXP_ShowZonesReq, VEXP_ShowZonesPush, VEXP_ZoneCrossed"); } } private static void HandleZoneCrossedClient(string text) { try { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } Plugin.Log.LogInfo((object)("VEXP_ZoneCrossed: '" + text + "'")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("VEXP_ZoneCrossed handler failed: " + ex.Message)); } } [HarmonyPatch(typeof(ZRoutedRpc), "AddPeer")] [HarmonyPostfix] private static void OnAddPeer(ZRoutedRpc __instance, ZNetPeer peer) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && peer != null) { ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "VEXP_ShowZonesPush", new object[1] { ZoneLinesConfig.Enabled.Value }); } } private static void HandleReqServer(long sender, bool wantOn) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { if (!AuthHelper.IsAdmin(ZNet.instance.GetPeer(sender))) { Plugin.Log.LogWarning((object)$"VEXP_ShowZonesReq: non-admin {sender} denied"); return; } ZoneLinesConfig.Enabled.Value = wantOn; BroadcastPush(wantOn); } } public static void BroadcastPush(bool isOn) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "VEXP_ShowZonesPush", new object[1] { isOn }); } } private static void HandlePushClient(bool isOn) { ZoneLineManager.ApplyDesiredState(isOn); } } public struct ZoneLineSpec { public float XPercent; public string Label; public Color Color; } public static class ZoneLineWorldVisual { public static GameObject Spawn(ZoneLineSpec spec) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) float num = ZoneLineRegistry.XPercentToWorldX(spec.XPercent); float worldRadius = ZoneLineRegistry.GetWorldRadius(); GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)obj).name = "VEXP_ZoneLine_" + spec.Label; Collider component = obj.GetComponent<Collider>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } float value = ZoneLinesConfig.PlaneHeight.Value; float value2 = ZoneLinesConfig.PlaneThickness.Value; float num2 = ZoneLinesConfig.PlaneBaseY.Value + value * 0.5f; obj.transform.position = new Vector3(num, num2, 0f); obj.transform.localScale = new Vector3(value2, value, worldRadius * 2f); MeshRenderer component2 = obj.GetComponent<MeshRenderer>(); Material val = new Material(Shader.Find("Sprites/Default")); Color color = spec.Color; color.a = Mathf.Clamp01(ZoneLinesConfig.PlaneAlpha.Value); val.color = color; val.renderQueue = 3000; ((Renderer)component2).material = val; ((Renderer)component2).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)component2).receiveShadows = false; Object.DontDestroyOnLoad((Object)(object)obj); return obj; } public static void Destroy(GameObject go) { if ((Object)(object)go != (Object)null) { Object.Destroy((Object)(object)go); } } } } namespace ValheimExpeditions.Vehicles { public static class CartPiece { private static bool _registered; public const string KilnChildName = "Cart_Kiln"; public static void Register() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown if (!_registered) { _registered = true; PieceConfig val = new PieceConfig(); val.PieceTable = "Hammer"; val.Category = "Expeditions"; val.Name = "Expeditions Cart"; val.Description = "Admin-only cart for expeditions. Requires SwordCheat to craft."; val.Requirements = (RequirementConfig[])(object)new RequirementConfig[1] { new RequirementConfig { Item = "SwordCheat", Amount = 1 } }; CustomPiece val2 = new CustomPiece("piece_expeditions_cart", "Cart", val); val2.PiecePrefab.GetComponent<Piece>().m_canBeRemoved = true; if ((Object)(object)val2.PiecePrefab.GetComponent<S01_WinterCart>() == (Object)null) { val2.PiecePrefab.AddComponent<S01_WinterCart>(); } BakeKilnChild(val2.PiecePrefab); PieceManager.Instance.AddPiece(val2); Plugin.Log.LogInfo((object)"CartPiece registered"); } } private static void BakeKilnChild(GameObject cartPrefab) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) string value = CartConfig.S01_KilnPrefab.Value; GameObject prefab = Cache.GetPrefab<GameObject>(value); if ((Object)(object)prefab == (Object)null) { Plugin.Log.LogWarning((object)("Kiln prefab '" + value + "' not found in PrefabManager.Cache; cart will have no warmth source")); return; } GameObject val = Object.Instantiate<GameObject>(prefab, cartPrefab.transform); ((Object)val).name = "Cart_Kiln"; ZNetView component = val.GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } Piece component2 = val.GetComponent<Piece>(); if ((Object)(object)component2 != (Object)null) { Object.DestroyImmediate((Object)(object)component2); } WearNTear component3 = val.GetComponent<WearNTear>(); if ((Object)(object)component3 != (Object)null) { Object.DestroyImmediate((Object)(object)component3); } Vector3 val2 = CartConfig.ParseVector3(CartConfig.S01_KilnOffset.Value, new Vector3(0f, 0.5f, 0f)); float value2 = CartConfig.S01_KilnScale.Value; val.transform.localPosition = val2; val.transform.localRotation = Quaternion.identity; val.transform.localScale = new Vector3(value2, value2, value2); if ((Object)(object)val.GetComponent<CartWarmthZone>() == (Object)null) { val.AddComponent<CartWarmthZone>(); } Plugin.Log.LogInfo((object)$"Baked kiln '{value}' onto cart prefab at offset {val2} scale {value2}"); } } public class CartTemplate : MonoBehaviour { private void Awake() { Plugin.Log.LogInfo((object)$"CartTemplate initialized; runtime type={((object)this).GetType().Name}; hasS01={(Object)(object)((Component)this).GetComponent<S01_WinterCart>() != (Object)null}"); } } [HarmonyPatch] public static class CartDamageProtection { [HarmonyPatch(typeof(WearNTear), "ApplyDamage")] [HarmonyPrefix] private static void BlockCartDamage(WearNTear __instance, ref float damage) { if (CartConfig.DamageProtectionEnabled.Value && !((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponentInParent<CartTemplate>() != (Object)null) { damage = 0f; } } } [HarmonyPatch] public static class CartChildZNetViewBinding { private static readonly FieldRef<Smelter, ZNetView> SmelterNview = AccessTools.FieldRefAccess<Smelter, ZNetView>("m_nview"); private static readonly FieldRef<Fire, ZNetView> FireNview = AccessTools.FieldRefAccess<Fire, ZNetView>("m_nview"); [HarmonyPatch(typeof(Smelter), "Awake")] [HarmonyPostfix] private static void BindSmelterToCartZNetView(Smelter __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)SmelterNview.Invoke(__instance) != (Object)null) { return; } CartTemplate componentInParent = ((Component)__instance).GetComponentInParent<CartTemplate>(); if (!((Object)(object)componentInParent == (Object)null)) { ZNetView component = ((Component)componentInParent).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null)) { SmelterNview.Invoke(__instance) = component; Plugin.Log.LogInfo((object)("[Cart] Bound child Smelter '" + ((Object)__instance).name + "' to parent cart ZNetView")); } } } [HarmonyPatch(typeof(Fire), "Awake")] [HarmonyPostfix] private static void BindFireToCartZNetView(Fire __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)FireNview.Invoke(__instance) != (Object)null) { return; } CartTemplate componentInParent = ((Component)__instance).GetComponentInParent<CartTemplate>(); if (!((Object)(object)componentInParent == (Object)null)) { ZNetView component = ((Component)componentInParent).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null)) { FireNview.Invoke(__instance) = component; Plugin.Log.LogInfo((object)("[Cart] Bound child Fire '" + ((Object)__instance).name + "' to parent cart ZNetView")); } } } } [HarmonyPatch] public static class CartStorageExpansion { private static readonly FieldRef<Inventory, int> InventoryWidth = AccessTools.FieldRefAccess<Inventory, int>("m_width"); private static readonly FieldRef<Inventory, int> InventoryHeight = AccessTools.FieldRefAccess<Inventory, int>("m_height"); [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] private static void ExpandCartStorage(Container __instance) { if ((Object)(object)__instance == (Object)null) { return; } CartTemplate componentInParent = ((Component)__instance).GetComponentInParent<CartTemplate>(); ManualLogSource log = Plugin.Log; string[] obj = new string[6] { "[CartStorage] Container.Awake fired on '", ((Object)__instance).name, "' parent='", null, null, null }; Transform parent = ((Component)__instance).transform.parent; obj[3] = ((parent != null) ? ((Object)parent).name : null) ?? "<none>"; obj[4] = "' CartTemplate="; obj[5] = (((Object)(object)componentInParent != (Object)null) ? "FOUND" : "null"); log.LogInfo((object)string.Concat(obj)); if (!((Object)(object)componentInParent == (Object)null)) { int value = CartConfig.CartStorageWidth.Value; int value2 = CartConfig.CartStorageHeight.Value; __instance.m_width = value; __instance.m_height = value2; Inventory inventory = __instance.GetInventory(); if (inventory != null) { InventoryWidth.Invoke(inventory) = value; InventoryHeight.Invoke(inventory) = value2; Plugin.Log.LogInfo((object)$"[CartStorage] Resized to {value}x{value2}; inventory now {inventory.GetWidth()}x{inventory.GetHeight()}"); } else { Plugin.Log.LogWarning((object)"[CartStorage] inv was null after Awake — could not resize"); } } } } public class CartWarmthZone : MonoBehaviour { private Fire _fire; private Smelter _smelter; private FieldInfo _fireBurningField; private MethodInfo _smelterGetFuelMethod; private readonly List<ParticleSystem> _particleBuffer = new List<ParticleSystem>(); public float WarmthRadius => CartConfig.WarmthRadius.Value; private void Awake() { _fire = ((Component)this).GetComponentInChildren<Fire>(); if ((Object)(object)_fire != (Object)null) { _fireBurningField = typeof(Fire).GetField("m_burning", BindingFlags.Instance | BindingFlags.NonPublic); } _smelter = ((Component)this).GetComponentInChildren<Smelter>(); if ((Object)(object)_smelter != (Object)null) { _smelterGetFuelMethod = typeof(Smelter).GetMethod("GetFuel", BindingFlags.Instance | BindingFlags.NonPublic); } } private void Update() { //IL_0026: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (!CartConfig.ColdProtectionEnabled.Value || !IsBurning()) { return; } List<Player> allPlayers = Player.GetAllPlayers(); if (allPlayers == null) { return; } Vector3 position = ((Component)this).transform.position; foreach (Player item in allPlayers) { if (!((Object)(object)item == (Object)null) && !(Vector3.Distance(((Component)item).transform.position, position) > WarmthRadius)) { ((Character)item).OnNearFire(position); } } } private bool IsBurning() { if ((Object)(object)_fire != (Object)null && _fireBurningField != null) { return (bool)_fireBurningField.GetValue(_fire); } if ((Object)(object)_smelter != (Object)null && _smelterGetFuelMethod != null && (float)_smelterGetFuelMethod.Invoke(_smelter, null) > 0f) { return true; } ((Component)this).GetComponentsInChildren<ParticleSystem>(_particleBuffer); for (int i = 0; i < _particleBuffer.Count; i++) { if (_particleBuffer[i].isPlaying) { return true; } } return false; } } public class S01_WinterCart : CartTemplate { private void Start() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) Transform val = ((Component)this).transform.Find("Cart_Kiln"); if (!((Object)(object)val == (Object)null)) { Vector3 localPosition = CartConfig.ParseVector3(CartConfig.S01_KilnOffset.Value, new Vector3(0f, 0.5f, 0f)); float value = CartConfig.S01_KilnScale.Value; val.localPosition = localPosition; val.localScale = new Vector3(value, value, value); } } } } namespace ValheimExpeditions.Storage { public class ExpeditionsState { private static readonly object _lock = new object(); private static Dictionary<int, HashSet<int>> _crossedZones = new Dictionary<int, HashSet<int>>(); private static bool _loaded; private static string FilePath => Path.Combine(Paths.ConfigPath, "Expeditions.json"); public static void Load() { lock (_lock) { _crossedZones.Clear(); if (!File.Exists(FilePath)) { Save(); return; } try { ParseJson(File.ReadAllText(FilePath)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to load ExpeditionsState: " + ex.Message)); } _loaded = true; } } public static bool HasCrossed(int factionId, int zoneIndex) { lock (_lock) { if (!_crossedZones.TryGetValue(factionId, out var value)) { return false; } return value.Contains(zoneIndex); } } public static void MarkCrossed(int factionId, int zoneIndex) { lock (_lock) { if (!_crossedZones.TryGetValue(factionId, out var value)) { value = new HashSet<int>(); _crossedZones[factionId] = value; } value.Add(zoneIndex); Save(); } } public static void Reset() { lock (_lock) { _crossedZones.Clear(); Save(); Plugin.Log.LogInfo((object)"ExpeditionsState: reset all zone crossing records."); } } private static void Save() { try { string contents = SerializeManual(); File.WriteAllText(FilePath, contents); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to save ExpeditionsState: " + ex.Message)); } } private static string SerializeManual() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n"); stringBuilder.Append(" \"crossedZones\": [\n"); int num = 0; foreach (KeyValuePair<int, HashSet<int>> item in _crossedZones.OrderBy((KeyValuePair<int, HashSet<int>> k) => k.Key)) { if (num > 0) { stringBuilder.Append(",\n"); } stringBuilder.Append(" { \"factionId\": ").Append(item.Key.ToString()).Append(", \"zoneIndices\": ["); int num2 = 0; foreach (int item2 in item.Value.OrderBy((int z) => z)) { if (num2 > 0) { stringBuilder.Append(", "); } stringBuilder.Append(item2.ToString()); num2++; } stringBuilder.Append("] }"); num++; } stringBuilder.Append("\n ]\n"); stringBuilder.Append("}\n"); return stringBuilder.ToString(); } private static void ParseJson(string json) { if (string.IsNullOrWhiteSpace(json)) { return; } foreach (string item in EnumerateObjects(ExtractArray(json, "crossedZones"))) { int num = ExtractInt(item, "factionId"); if (num != 0) { HashSet<int> value = new HashSet<int>(ExtractIntArray(item, "zoneIndices")); _crossedZones[num] = value; } } } private static string Quote(string s) { if (s == null) { return "\"\""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 2); stringBuilder.Append('"'); foreach (char c in s) { switch (c) { case '\\': stringBuilder.Append("\\\\"); continue; case '"': stringBuilder.Append("\\\""); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("X4")); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } private static string ExtractArray(string json, string key) { int num = json.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return ""; } int num2 = json.IndexOf(':', num); if (num2 < 0) { return ""; } int num3 = json.IndexOf('[', num2); if (num3 < 0) { return ""; } int num4 = 0; for (int i = num3; i < json.Length; i++) { switch (json[i]) { case '[': num4++; break; case ']': num4--; if (num4 == 0) { return json.Substring(num3 + 1, i - num3 - 1); } break; } } return ""; } private static IEnumerable<string> EnumerateObjects(string arrayBody) { if (string.IsNullOrEmpty(arrayBody)) { yield break; } int depth = 0; int num = -1; bool inString = false; bool escape = false; for (int i = 0; i < arrayBody.Length; i++) { char c = arrayBody[i]; if (escape) { escape = false; continue; } switch (c) { case '\\': escape = true; continue; case '"': inString = !inString; continue; } if (inString) { continue; } switch (c) { case '{': if (depth == 0) { num = i; } depth++; break; case '}': depth--; if (depth == 0 && num >= 0) { yield return arrayBody.Substring(num, i - num + 1); num = -1; } break; } } } private static int ExtractInt(string obj, string key) { int num = obj.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return 0; } int num2 = obj.IndexOf(':', num); if (num2 < 0) { return 0; } int i; for (i = num2 + 1; i < obj.Length && (char.IsDigit(obj[i]) || obj[i] == '-' || char.IsWhiteSpace(obj[i])); i++) { } if (int.TryParse(obj.Substring(num2 + 1, i - num2 - 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return 0; } private static List<int> ExtractIntArray(string obj, string key) { List<int> list = new List<int>(); int num = obj.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return list; } int num2 = obj.IndexOf(':', num); if (num2 < 0) { return list; } int num3 = obj.IndexOf('[', num2); if (num3 < 0) { return list; } int num4 = 0; for (int i = num3; i < obj.Length; i++) { switch (obj[i]) { case '[': num4++; continue; case ']': num4--; if (num4 != 0) { continue; } break; default: continue; } string[] array = obj.Substring(num3 + 1, i - num3 - 1).Split(new char[1] { ',' }); for (int j = 0; j < array.Length; j++) { if (int.TryParse(array[j].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { list.Add(result); } } break; } return list; } } } namespace ValheimExpeditions.Config { internal class ConfigurationManagerAttributes { public bool? IsAdminOnly; } public static class CartConfig { public static ConfigEntry<bool> DamageProtectionEnabled; public static ConfigEntry<int> CartStorageWidth; public static ConfigEntry<int> CartStorageHeight; public static ConfigEntry<bool> ColdProtectionEnabled; public static ConfigEntry<float> WarmthRadius; public static ConfigEntry<string> S01_KilnPrefab; public static ConfigEntry<string> S01_KilnOffset; public static ConfigEntry<float> S01_KilnScale; public static void Init(ConfigFile config) { DamageProtectionEnabled = config.Bind<bool>("Cart", "DamageProtectionEnabled", true, Synced("[Hot-reload] Block all damage to the expedition cart (Character.DamageFloat prefix).")); CartStorageWidth = config.Bind<int>("Cart.Storage", "Width", 8, Synced("[New carts only] Inventory grid width for the cart's native storage. Vanilla cart is 6.")); CartStorageHeight = config.Bind<int>("Cart.Storage", "Height", 4, Synced("[New carts only] Inventory grid height for the cart's native storage. Vanilla cart is 3.")); ColdProtectionEnabled = config.Bind<bool>("Cart.Warmth", "ColdProtectionEnabled", true, Synced("[Hot-reload] Players within WarmthRadius of a lit kiln-attached CartWarmthZone are kept warm (vanilla OnNearFire). Disable globally here.")); WarmthRadius = config.Bind<float>("Cart.Warmth", "WarmthRadius", 10f, Synced("[Hot-reload] Meters from the warmth source within which players receive the OnNearFire pulse each frame.")); S01_KilnPrefab = config.Bind<string>("Cart.S01_Winter", "KilnPrefab", "charcoal_kiln", Synced("[Restart required] Prefab name baked into the cart prefab as the warmth source at registration. Default vanilla Kiln is 'charcoal_kiln'. Changing this only affects newly placed carts after a restart.")); S01_KilnOffset = config.Bind<string>("Cart.S01_Winter", "KilnOffset", "0,0.5,0", Synced("[New carts only] Local offset of kiln on cart bed. Format: x,y,z")); S01_KilnScale = config.Bind<float>("Cart.S01_Winter", "KilnScale", 0.4f, Synced("[New carts only] Uniform scale applied to the spawned kiln. 1.0 = normal size, 0.4 = 40%.")); } private static ConfigDescription Synced(string description) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { IsAdminOnly = true } }); } public static Vector3 ParseVector3(string s, Vector3 fallback) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(s)) { return fallback; } string[] array = s.Split(new char[1] { ',' }); if (array.Length != 3) { return fallback; } if (!float.TryParse(array[0].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return fallback; } if (!float.TryParse(array[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return fallback; } if (!float.TryParse(array[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result3)) { return fallback; } return new Vector3(result, result2, result3); } } public static class ZoneLinesConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<float> Zone1XPercent; public static ConfigEntry<string> Zone1Label; public static ConfigEntry<string> Zone1ColorHex; public static ConfigEntry<float> Zone2XPercent; public static ConfigEntry<string> Zone2Label; public static ConfigEntry<string> Zone2ColorHex; public static ConfigEntry<float> Zone3XPercent; public static ConfigEntry<string> Zone3Label; public static ConfigEntry<string> Zone3ColorHex; public static ConfigEntry<float> PlaneHeight; public static ConfigEntry<float> PlaneThickness; public static ConfigEntry<float> PlaneBaseY; public static ConfigEntry<float> PlaneAlpha; public static ConfigEntry<int> MapPinsPerZone; public static ConfigEntry<string> Household1Name; public static ConfigEntry<string> Household2Name; public static ConfigEntry<string> Household3Name; public static ConfigEntry<string> AnnouncementTemplate; public static string GetHouseholdName(int factionId) { return factionId switch { 1 => Household1Name?.Value ?? "Ironforge (Red Team)", 2 => Household2Name?.Value ?? "Frosthelm (White Team)", 3 => Household3Name?.Value ?? "Stormwind (Blue Team)", _ => "Unknown", }; } public static void Init(ConfigFile config) { Enabled = config.Bind<bool>("ZoneLines", "Enabled", false, Synced("[Hot-reload] Enable zone-line visuals (planes + minimap pins).")); Zone1XPercent = config.Bind<float>("ZoneLines.Zone1", "XPercent", 0.25f, Synced("[Hot-reload] West-east position as 0..1 (0=west edge, 0.5=center, 1=east edge).")); Zone1Label = config.Bind<string>("ZoneLines.Zone1", "Label", "ZONE 1", Synced("[Hot-reload] Label shown on minimap.")); Zone1ColorHex = config.Bind<string>("ZoneLines.Zone1", "ColorHex", "#4488FF", Synced("[Hot-reload] Color in hex format (e.g. #FF0000 for red).")); Zone2XPercent = config.Bind<float>("ZoneLines.Zone2", "XPercent", 0.5f, Synced("[Hot-reload] West-east position as 0..1.")); Zone2Label = config.Bind<string>("ZoneLines.Zone2", "Label", "ZONE 2", Synced("[Hot-reload] Label shown on minimap.")); Zone2ColorHex = config.Bind<string>("ZoneLines.Zone2", "ColorHex", "#FFAA00", Synced("[Hot-reload] Color in hex format.")); Zone3XPercent = config.Bind<float>("ZoneLines.Zone3", "XPercent", 0.75f, Synced("[Hot-reload] West-east position as 0..1.")); Zone3Label = config.Bind<string>("ZoneLines.Zone3", "Label", "ZONE 3 — VICTORY", Synced("[Hot-reload] Label shown on minimap.")); Zone3ColorHex = config.Bind<string>("ZoneLines.Zone3", "ColorHex", "#FF4444", Synced("[Hot-reload] Color in hex format.")); PlaneHeight = config.Bind<float>("ZoneLines.Visuals", "PlaneHeight", 500f, Synced("[Hot-reload] Height of the zone-line plane in metres.")); PlaneThickness = config.Bind<float>("ZoneLines.Visuals", "PlaneThickness", 1f, Synced("[Hot-reload] Thickness of the plane along X axis in metres.")); PlaneBaseY = config.Bind<float>("ZoneLines.Visuals", "PlaneBaseY", -50f, Synced("[Hot-reload] Base Y position (bottom of plane). Water level is y=30.")); PlaneAlpha = config.Bind<float>("ZoneLines.Visuals", "PlaneAlpha", 0.35f, Synced("[Hot-reload] Plane opacity (0..1). 0=invisible, 1=opaque.")); MapPinsPerZone = config.Bind<int>("ZoneLines.Minimap", "PinsPerZone", 25, Synced("[Hot-reload] Number of pins to place along each zone line. More = smoother line, higher cost.")); Household1Name = config.Bind<string>("ZoneLines.Households", "Household1Name", "Ironforge (Red Team)", Synced("[Hot-reload] Display name for household/faction 1.")); Household2Name = config.Bind<string>("ZoneLines.Households", "Household2Name", "Frosthelm (White Team)", Synced("[Hot-reload] Display name for household/faction 2.")); Household3Name = config.Bind<string>("ZoneLines.Households", "Household3Name", "Stormwind (Blue Team)", Synced("[Hot-reload] Display name for household/faction 3.")); AnnouncementTemplate = config.Bind<string>("ZoneLines.Announcement", "AnnouncementTemplate", "{household} reached {zone}!", Synced("[Hot-reload] Message template for zone-crossing announcements. Use {household} and {zone} as placeholders.")); } private static ConfigDescription Synced(string description) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { IsAdminOnly = true } }); } } }