using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using HarmonyLib; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ValheimDonationSystem")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5b258b157d6112848426d2c5a5db377a2cbf4355")] [assembly: AssemblyProduct("ValheimDonationSystem")] [assembly: AssemblyTitle("ValheimDonationSystem")] [assembly: AssemblyVersion("1.0.0.0")] public static class ArmorVfx { public sealed class Aura { public string Id; public string Slot; public string Suffix; public string Display; public string ParentPrefab; public string[] ChildHints; public bool AnyChildOk; public bool WholeCreature; public string Fallback; public float Scale = 1f; public float Raise; public bool TameParticles; public string[] StripChildHints; public bool HasLight; public Color LightColor; public string GlowFromPrefab; public string[] GlowChildHints; public float GlowScale = 1f; public float Slash; public float Pierce; public float Blunt; public float Fire; public float Frost; public float Spirit; } public const string ItemKey = "vc_armor_vfx"; public static readonly Vector3 CompanionOffset = new Vector3(-0.75f, 1.55f, 0f); public static readonly Dictionary Registry = new Dictionary { ["bat"] = new Aura { Id = "bat", Slot = "head", Display = "Bat", Suffix = "of the Bat", ParentPrefab = "Bat", WholeCreature = true, Scale = 0.8f, Slash = 2f }, ["ghostlight"] = new Aura { Id = "ghostlight", Slot = "head", Display = "Ghost", Suffix = "of the Ghost", ParentPrefab = "Ghost", ChildHints = new string[5] { "glow", "wisp", "mist", "particle", "body" }, AnyChildOk = true, HasLight = true, LightColor = new Color(0.65f, 1f, 0.8f), Fallback = "fx_ItemSparkles", Scale = 0.4f, Slash = 2f }, ["deathsquito"] = new Aura { Id = "deathsquito", Slot = "head", Display = "Deathsquito", Suffix = "of the Deathsquito", ParentPrefab = "Deathsquito", WholeCreature = true, Scale = 0.6f, Pierce = 2f }, ["hatchling"] = new Aura { Id = "hatchling", Slot = "head", Display = "Drake Hatchling", Suffix = "of the Drake", ParentPrefab = "Hatchling", WholeCreature = true, Scale = 0.35f, Raise = 0.25f, Frost = 2f }, ["wraith"] = new Aura { Id = "wraith", Slot = "head", Display = "Wraith", Suffix = "of the Wraith", ParentPrefab = "Wraith", WholeCreature = true, Scale = 0.35f, Slash = 2f }, ["volture"] = new Aura { Id = "volture", Slot = "head", Display = "Volture", Suffix = "of the Volture", ParentPrefab = "Volture", WholeCreature = true, Scale = 0.3f, Raise = 0.3f, Pierce = 3f }, ["gjall"] = new Aura { Id = "gjall", Slot = "head", Display = "Gjall", Suffix = "of the Gjall", ParentPrefab = "Gjall", WholeCreature = true, Scale = 0.08f, Raise = 0.35f, TameParticles = true, StripChildHints = new string[4] { "drip", "droplet", "tar", "gland" }, Blunt = 2f, Fire = 1f }, ["fallen_valkyrie"] = new Aura { Id = "fallen_valkyrie", Slot = "head", Display = "Fallen Valkyrie", Suffix = "of the Valkyrie", ParentPrefab = "FallenValkyrie", WholeCreature = true, Scale = 0.15f, Raise = 0.3f, TameParticles = true, StripChildHints = new string[1] { "smoke" }, GlowFromPrefab = "Wraith", GlowChildHints = new string[3] { "smoke _local", "_local", "evil_smoke" }, GlowScale = 0.35f, Spirit = 2f } }; public static readonly string[] Slots = new string[3] { "head", "chest", "legs" }; private static MethodInfo _localize; private static PropertyInfo _locInstance; private static bool _locResolved; private static StatusEffect _slowFall; private static bool _slowFallResolved; private static bool _refl; private static FieldInfo _fHelmetItem; private static FieldInfo _fChestItem; private static FieldInfo _fLegItem; private static FieldInfo _fShoulderItem; private static FieldInfo _fHelmetInst; private static FieldInfo _fHelmetBone; private static FieldInfo _fChestInsts; private static FieldInfo _fLegInsts; private static FieldInfo _fBodyModel; private static readonly Dictionary _prefabCache = new Dictionary(); private static readonly Dictionary _sourceCache = new Dictionary(); public static bool IsSlot(string s) { return Array.IndexOf(Slots, s) >= 0; } public static string ZKey(string slot) { return "vc_vfx_" + slot; } public static string SlotFor(string auraId) { if (!Registry.TryGetValue(auraId ?? "", out var value)) { return null; } return value.Slot; } public static ZNetView NView(Component c) { if (!((Object)(object)c != (Object)null)) { return null; } return c.GetComponent(); } public static string LocalizeName(string token) { if (string.IsNullOrEmpty(token)) { return token; } try { if (!_locResolved) { _locResolved = true; Type type = AccessTools.TypeByName("Localization"); if (type != null) { _locInstance = AccessTools.Property(type, "instance"); _localize = AccessTools.Method(type, "Localize", new Type[1] { typeof(string) }, (Type[])null); } } object obj = _locInstance?.GetValue(null); if (obj != null && _localize != null) { return (string)_localize.Invoke(obj, new object[1] { token }); } } catch { } return token; } public static StatusEffect SlowFallEffect() { if (_slowFallResolved) { return _slowFall; } if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } _slowFallResolved = true; try { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("CapeFeather"); _slowFall = ((itemPrefab == null) ? null : itemPrefab.GetComponent()?.m_itemData?.m_shared?.m_equipStatusEffect); } catch { } Debug.Log((object)("[Valcoin][ArmorVfx] SlowFall effect -> " + (((Object)(object)_slowFall != (Object)null) ? "ok (CapeFeather)" : "NOT FOUND"))); return _slowFall; } public static bool WearsSlowFallItem(Humanoid h) { Reflect(); StatusEffect val = SlowFallEffect(); if ((Object)(object)h == (Object)null || (Object)(object)val == (Object)null) { return false; } try { object? obj = _fShoulderItem?.GetValue(h); StatusEffect val2 = ((ItemData)(((obj is ItemData) ? obj : null)?)).m_shared?.m_equipStatusEffect; return (Object)(object)val2 != (Object)null && val2.NameHash() == val.NameHash(); } catch { return false; } } private static void Reflect() { if (!_refl) { _refl = true; _fHelmetItem = AccessTools.Field(typeof(Humanoid), "m_helmetItem"); _fChestItem = AccessTools.Field(typeof(Humanoid), "m_chestItem"); _fLegItem = AccessTools.Field(typeof(Humanoid), "m_legItem"); _fShoulderItem = AccessTools.Field(typeof(Humanoid), "m_shoulderItem"); _fHelmetInst = AccessTools.Field(typeof(VisEquipment), "m_helmetItemInstance"); _fHelmetBone = AccessTools.Field(typeof(VisEquipment), "m_helmet"); _fChestInsts = AccessTools.Field(typeof(VisEquipment), "m_chestItemInstances"); _fLegInsts = AccessTools.Field(typeof(VisEquipment), "m_legItemInstances"); _fBodyModel = AccessTools.Field(typeof(VisEquipment), "m_bodyModel"); } } public static ItemData EquippedIn(Humanoid h, string slot) { Reflect(); if ((Object)(object)h == (Object)null) { return null; } try { switch (slot) { case "head": { object? obj3 = _fHelmetItem?.GetValue(h); return (ItemData)((obj3 is ItemData) ? obj3 : null); } case "chest": { object? obj2 = _fChestItem?.GetValue(h); return (ItemData)((obj2 is ItemData) ? obj2 : null); } case "legs": { object? obj = _fLegItem?.GetValue(h); return (ItemData)((obj is ItemData) ? obj : null); } } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] EquippedIn: " + ex.Message)); } return null; } public static string EquippedAura(Humanoid h, string slot) { ItemData val = EquippedIn(h, slot); if (val?.m_customData == null) { return null; } if (!val.m_customData.TryGetValue("vc_armor_vfx", out var value) || !Registry.ContainsKey(value)) { return null; } return value; } public static bool ApplyToEquipped(string aura, string slot, out string msg) { if (!Registry.TryGetValue(aura ?? "", out var value)) { msg = "Unknown armor effect."; return false; } slot = value.Slot; ItemData val = EquippedIn((Humanoid)(object)Player.m_localPlayer, slot); if (val == null) { msg = "You have no " + slot + " armor equipped — equip a piece, then buy again."; return false; } if (val.m_customData == null) { val.m_customData = new Dictionary(); } val.m_customData["vc_armor_vfx"] = aura; MirrorLocalToZdo(); string text = LocalizeName(val.m_shared.m_name); msg = "Applied " + value.Display + " to your " + text + " — now \"" + text + " " + value.Suffix + "\"."; Debug.Log((object)("[Valcoin][ArmorVfx] Applied " + aura + " to " + slot + " (" + text + ").")); return true; } public static void MirrorLocalToZdo() { Player localPlayer = Player.m_localPlayer; ZNetView val = NView((Component)(object)localPlayer); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null || !val.IsOwner()) { return; } string[] slots = Slots; foreach (string slot in slots) { string text = EquippedAura((Humanoid)(object)localPlayer, slot) ?? ""; try { val2.Set(ZKey(slot), text); } catch { } } } public static GameObject ResolvePrefab(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (_prefabCache.TryGetValue(name, out var value)) { return value; } GameObject val = null; try { if ((Object)(object)ZNetScene.instance != (Object)null) { val = ZNetScene.instance.GetPrefab(name); } } catch { } if ((Object)(object)val == (Object)null) { try { if ((Object)(object)ObjectDB.instance != (Object)null) { val = ObjectDB.instance.GetItemPrefab(name); } } catch { } } if ((Object)(object)val == (Object)null) { try { GameObject[] array = Resources.FindObjectsOfTypeAll(); foreach (GameObject val2 in array) { if ((Object)(object)val2 != (Object)null && ((Object)val2).name == name) { val = val2; break; } } } catch { } } _prefabCache[name] = val; Debug.Log((object)("[Valcoin][ArmorVfx] Resolve prefab '" + name + "' -> " + (((Object)(object)val != (Object)null) ? "ok" : "NOT FOUND"))); return val; } public static GameObject ResolveSource(Aura def) { if (_sourceCache.TryGetValue(def.Id, out var value)) { return value; } if (def.WholeCreature) { GameObject val = ResolvePrefab(def.ParentPrefab); _sourceCache[def.Id] = val; return val; } GameObject val2 = null; if (!string.IsNullOrEmpty(def.ParentPrefab) && def.ChildHints != null) { GameObject val3 = ResolvePrefab(def.ParentPrefab); if ((Object)(object)val3 != (Object)null) { val2 = FindParticleChild(val3, def.ChildHints, def.AnyChildOk, def.Id); } } if ((Object)(object)val2 == (Object)null) { val2 = ResolvePrefab(def.Fallback); } _sourceCache[def.Id] = val2; return val2; } public static GameObject FindGlowChild(GameObject donor, string[] hints, string label) { if ((Object)(object)donor == (Object)null) { return null; } GameObject val = null; GameObject val2 = null; List list = new List(); Transform[] componentsInChildren = donor.GetComponentsInChildren(true); foreach (Transform val3 in componentsInChildren) { if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)(object)donor || (Object)(object)((Component)val3).GetComponent() == (Object)null || (Object)(object)((Component)val3).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)val3).GetComponentInChildren(true) != (Object)null) { continue; } if (list.Count < 30) { list.Add(((Object)val3).name); } if ((Object)(object)val2 == (Object)null) { val2 = ((Component)val3).gameObject; } if (!((Object)(object)val == (Object)null) || hints == null) { continue; } string text = ((Object)val3).name.ToLowerInvariant(); foreach (string value in hints) { if (text.Contains(value)) { val = ((Component)val3).gameObject; break; } } } GameObject val4 = (((Object)(object)val != (Object)null) ? val : val2); Debug.Log((object)("[Valcoin][ArmorVfx] " + label + ": particle-only nodes in '" + ((Object)donor).name + "': " + ((list.Count > 0) ? string.Join(", ", list.ToArray()) : "(none)") + " -> picked " + (((Object)(object)val4 != (Object)null) ? ("'" + ((Object)val4).name + "'") : "NONE"))); return val4; } public static GameObject FindParticleChild(GameObject parent, string[] hints, bool anyChildOk, string label) { if ((Object)(object)parent == (Object)null) { return null; } GameObject val = null; GameObject val2 = null; Transform[] componentsInChildren = parent.GetComponentsInChildren(true); foreach (Transform val3 in componentsInChildren) { if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)(object)parent || (Object)(object)((Component)val3).GetComponentInChildren(true) == (Object)null) { continue; } if ((Object)(object)val2 == (Object)null) { val2 = ((Component)val3).gameObject; } string text = ((Object)val3).name.ToLowerInvariant(); if (hints != null) { foreach (string value in hints) { if (text.Contains(value)) { val = ((Component)val3).gameObject; break; } } } if ((Object)(object)val != (Object)null) { break; } } if ((Object)(object)val == (Object)null && anyChildOk) { val = val2; } if ((Object)(object)val != (Object)null) { Debug.Log((object)("[Valcoin][ArmorVfx] " + label + ": child hunt in '" + ((Object)parent).name + "' -> '" + ((Object)val).name + "'")); } else { List list = new List(); componentsInChildren = parent.GetComponentsInChildren(true); foreach (Transform val4 in componentsInChildren) { if ((Object)(object)val4 != (Object)null && (Object)(object)((Component)val4).gameObject != (Object)(object)parent && list.Count < 40) { list.Add(((Object)val4).name); } } Debug.Log((object)("[Valcoin][ArmorVfx] " + label + ": no child match in '" + ((Object)parent).name + "'. Children: " + string.Join(", ", list.ToArray()))); } return val; } public static Transform AttachPoint(Player p, string slot) { Reflect(); if ((Object)(object)p == (Object)null) { return null; } VisEquipment val = null; try { val = ((Component)p).GetComponentInChildren(); } catch { } if ((Object)(object)val == (Object)null) { return ((Component)p).transform; } try { switch (slot) { case "head": { object? obj2 = _fHelmetInst?.GetValue(val); GameObject val4 = (GameObject)((obj2 is GameObject) ? obj2 : null); if ((Object)(object)val4 != (Object)null) { return val4.transform; } object? obj3 = _fHelmetBone?.GetValue(val); Transform val5 = (Transform)((obj3 is Transform) ? obj3 : null); return ((Object)(object)val5 != (Object)null) ? val5 : ((Component)p).transform; } case "chest": { Transform val3 = FirstInstance(_fChestInsts?.GetValue(val)); if ((Object)(object)val3 != (Object)null) { return val3; } return BodyOr(val, p); } case "legs": { Transform val2 = FirstInstance(_fLegInsts?.GetValue(val)); if ((Object)(object)val2 != (Object)null) { return val2; } return BodyOr(val, p); } } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] AttachPoint: " + ex.Message)); } return ((Component)p).transform; } private static Transform BodyOr(VisEquipment ve, Player p) { object? obj = _fBodyModel?.GetValue(ve); SkinnedMeshRenderer val = (SkinnedMeshRenderer)((obj is SkinnedMeshRenderer) ? obj : null); if (!((Object)(object)val != (Object)null)) { return ((Component)p).transform; } return ((Component)val).transform; } private static Transform FirstInstance(object list) { if (list is IList list2) { foreach (object item in list2) { GameObject val = (GameObject)((item is GameObject) ? item : null); if (val != null && (Object)(object)val != (Object)null) { return val.transform; } } } return null; } public static string StatsText(Aura a) { List list = new List(); if (a.Slash > 0f) { list.Add($"+{a.Slash:0} slash"); } if (a.Pierce > 0f) { list.Add($"+{a.Pierce:0} pierce"); } if (a.Blunt > 0f) { list.Add($"+{a.Blunt:0} blunt"); } if (a.Fire > 0f) { list.Add($"+{a.Fire:0} fire"); } if (a.Frost > 0f) { list.Add($"+{a.Frost:0} frost"); } if (a.Spirit > 0f) { list.Add($"+{a.Spirit:0} spirit"); } return string.Join(", ", list.ToArray()); } } public class SE_FamiliarBond : StatusEffect { public float m_slash; public float m_pierce; public float m_blunt; public float m_fire; public float m_frost; public float m_spirit; public override void ModifyAttack(SkillType skill, ref HitData hitData) { hitData.m_damage.m_slash += m_slash; hitData.m_damage.m_pierce += m_pierce; hitData.m_damage.m_blunt += m_blunt; hitData.m_damage.m_fire += m_fire; hitData.m_damage.m_frost += m_frost; hitData.m_damage.m_spirit += m_spirit; } } [HarmonyPatch] internal static class ArmorVfxTooltipPatch { private static MethodBase _target; private static bool Prepare() { _target = AccessTools.Method(typeof(ItemData), "GetTooltip", new Type[5] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int) }, (Type[])null); if (_target == null) { Debug.LogWarning((object)"[Valcoin][ArmorVfx] GetTooltip not found — armor rename disabled (visual still works)."); } return _target != null; } private static MethodBase TargetMethod() { return _target; } private static void Postfix(ItemData item, ref string __result) { try { if (item?.m_customData != null && item.m_shared != null && item.m_customData.TryGetValue("vc_armor_vfx", out var value) && ArmorVfx.Registry.TryGetValue(value, out var value2)) { string text = ArmorVfx.LocalizeName(item.m_shared.m_name); __result = "" + text + " " + value2.Suffix + "\n" + __result; } } catch { } } } [HarmonyPatch] internal static class ArmorVfxUpgradePatch { internal sealed class Carry { public string Aura; public string Name; public int X; public int Y; public int NextQuality; public bool WasEquipped; } private static MethodBase _target; private static FieldInfo _fUpgradeItem; private static FieldInfo _fGridPos; private static FieldInfo _fGridX; private static FieldInfo _fGridY; private static bool Prepare() { _target = AccessTools.Method(typeof(InventoryGui), "DoCrafting", new Type[1] { typeof(Player) }, (Type[])null); _fUpgradeItem = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem"); _fGridPos = AccessTools.Field(typeof(ItemData), "m_gridPos"); Type type = _fGridPos?.FieldType; _fGridX = ((type != null) ? AccessTools.Field(type, "x") : null); _fGridY = ((type != null) ? AccessTools.Field(type, "y") : null); if (_target == null || _fUpgradeItem == null || _fGridX == null || _fGridY == null) { Debug.LogWarning((object)"[Valcoin][ArmorVfx] DoCrafting/grid fields not found — familiar upgrade-carry disabled."); } if (_target != null && _fUpgradeItem != null && _fGridX != null) { return _fGridY != null; } return false; } private static MethodBase TargetMethod() { return _target; } private static void Prefix(InventoryGui __instance, Player player, out Carry __state) { __state = null; try { object? value = _fUpgradeItem.GetValue(__instance); ItemData val = (ItemData)((value is ItemData) ? value : null); if (val?.m_customData != null && val.m_shared != null && val.m_customData.TryGetValue("vc_armor_vfx", out var value2) && ArmorVfx.Registry.ContainsKey(value2)) { object value3 = _fGridPos.GetValue(val); __state = new Carry { Aura = value2, Name = val.m_shared.m_name, X = (int)_fGridX.GetValue(value3), Y = (int)_fGridY.GetValue(value3), NextQuality = val.m_quality + 1, WasEquipped = ((Object)(object)player != (Object)null && ((Humanoid)player).IsItemEquiped(val)) }; } } catch { } } private static void Postfix(Player player, Carry __state) { if (__state == null || (Object)(object)player == (Object)null) { return; } try { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } ItemData val = inventory.GetItemAt(__state.X, __state.Y); if (!IsUpgraded(val, __state)) { val = null; foreach (ItemData allItem in inventory.GetAllItems()) { if (IsUpgraded(allItem, __state)) { val = allItem; break; } } } if (val != null) { if (val.m_customData == null) { val.m_customData = new Dictionary(); } val.m_customData["vc_armor_vfx"] = __state.Aura; Debug.Log((object)$"[Valcoin][ArmorVfx] Carried '{__state.Aura}' across upgrade to quality {__state.NextQuality}."); if (__state.WasEquipped && !((Humanoid)player).IsItemEquiped(val)) { ((Humanoid)player).EquipItem(val, false); } ArmorVfx.MirrorLocalToZdo(); } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] upgrade carry: " + ex.Message)); } } private static bool IsUpgraded(ItemData it, Carry st) { if (it != null && it.m_shared != null && it.m_shared.m_name == st.Name && it.m_quality == st.NextQuality) { if (it.m_customData != null) { return !it.m_customData.ContainsKey("vc_armor_vfx"); } return true; } return false; } } [HarmonyPatch] internal static class ArmorVfxUpgradePanelPatch { private static MethodBase _target; private static FieldInfo _fSelected; private static FieldInfo _fDesc; private static FieldInfo _fCraftType; private static PropertyInfo _pItemData; private static PropertyInfo _pText; private const string Marker = "\u200b"; private static bool Prepare() { _target = AccessTools.Method(typeof(InventoryGui), "UpdateRecipe", new Type[2] { typeof(Player), typeof(float) }, (Type[])null); _fSelected = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); _fDesc = AccessTools.Field(typeof(InventoryGui), "m_recipeDecription"); _fCraftType = AccessTools.Field(typeof(InventoryGui), "m_itemCraftType"); _pItemData = ((_fSelected != null) ? AccessTools.Property(_fSelected.FieldType, "ItemData") : null); _pText = ((_fDesc != null) ? AccessTools.Property(_fDesc.FieldType, "text") : null); int num; if (_target != null && _pItemData != null && _pText != null) { num = ((_fCraftType != null) ? 1 : 0); if (num != 0) { goto IL_0119; } } else { num = 0; } Debug.LogWarning((object)"[Valcoin][ArmorVfx] UpdateRecipe/labels not found — upgrade-panel familiar line disabled."); goto IL_0119; IL_0119: return (byte)num != 0; } private static MethodBase TargetMethod() { return _target; } private static void Postfix(InventoryGui __instance) { try { object value = _fSelected.GetValue(__instance); if (value == null) { return; } object? value2 = _pItemData.GetValue(value); ItemData val = (ItemData)((value2 is ItemData) ? value2 : null); if (val?.m_customData == null || val.m_shared == null || !val.m_customData.TryGetValue("vc_armor_vfx", out var value3) || !ArmorVfx.Registry.TryGetValue(value3, out var value4)) { return; } string text = ArmorVfx.LocalizeName(val.m_shared.m_name); string text2 = ArmorVfx.StatsText(value4); object value5 = _fDesc.GetValue(__instance); if (value5 != null) { string text3 = (_pText.GetValue(value5) as string) ?? ""; if (text3.IndexOf("\u200b", StringComparison.Ordinal) < 0) { string text4 = "\u200b" + text + " " + value4.Suffix + "\nFamiliar: " + value4.Display + "" + (string.IsNullOrEmpty(text2) ? "" : (" (" + text2 + ")")) + "\nKept when this piece is upgraded.\n\n"; _pText.SetValue(value5, text4 + text3); } } object value6 = _fCraftType.GetValue(__instance); if (value6 != null) { string text5 = (_pText.GetValue(value6) as string) ?? ""; if (text5.IndexOf("\u200b", StringComparison.Ordinal) < 0 && text5.IndexOf(text, StringComparison.Ordinal) >= 0) { _pText.SetValue(value6, "\u200b" + text5.Replace(text, text + " " + value4.Suffix + "")); } } } catch { } } } public class ArmorVfxManager : MonoBehaviour { private sealed class Attached { public GameObject Go; public Transform Parent; public string Aura; } private const float Interval = 0.75f; private float _next; private readonly Dictionary _live = new Dictionary(); private readonly HashSet _seen = new HashSet(); private static readonly HashSet KeepTypes = new HashSet { "Transform", "MeshFilter", "MeshRenderer", "SkinnedMeshRenderer", "ParticleSystem", "ParticleSystemRenderer", "Animator", "LODGroup", "Light" }; private static readonly string[] StripTypes = new string[7] { "ZNetView", "ZSyncTransform", "TimedDestruction", "Aoe", "Projectile", "ZSFX", "AudioSource" }; private const string BondName = "VcFamiliarBond"; private bool _slowFallAdded; private string _bondAura; private static int _bondHash; private static readonly HashSet PoofKeep = new HashSet { "Transform", "ParticleSystem", "ParticleSystemRenderer", "MeshFilter", "MeshRenderer", "Light" }; private static readonly string[] PoofCandidates = new string[3] { "vfx_spawn_small", "vfx_spawn", "vfx_ghost_death" }; private static GameObject _poofPrefab; private static bool _poofResolved; private static int BondHash { get { if (_bondHash == 0) { SE_FamiliarBond sE_FamiliarBond = ScriptableObject.CreateInstance(); ((Object)sE_FamiliarBond).name = "VcFamiliarBond"; _bondHash = ((StatusEffect)sE_FamiliarBond).NameHash(); Object.Destroy((Object)(object)sE_FamiliarBond); } return _bondHash; } } private void Update() { //IL_013e: Unknown result type (might be due to invalid IL or missing references) if (Time.time < _next) { return; } _next = Time.time + 0.75f; if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } try { ArmorVfx.MirrorLocalToZdo(); } catch { } try { UpdateFamiliarBuffs(); } catch { } _seen.Clear(); List list = null; try { list = Player.GetAllPlayers(); } catch { } if (list != null) { foreach (Player item in list) { RenderPlayer(item); } } if (_live.Count <= 0) { return; } List list2 = new List(); foreach (KeyValuePair item2 in _live) { if (!_seen.Contains(item2.Key)) { list2.Add(item2.Key); } } foreach (string item3 in list2) { GameObject go = _live[item3].Go; if ((Object)(object)go != (Object)null) { PlayPoof(go.transform.position); Object.Destroy((Object)(object)go); } _live.Remove(item3); } } private void RenderPlayer(Player p) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) ZNetView val = ArmorVfx.NView((Component)(object)p); if ((Object)(object)p == (Object)null || (Object)(object)val == (Object)null || !val.IsValid()) { return; } ZDO zDO = val.GetZDO(); if (zDO == null) { return; } int instanceID = ((Object)p).GetInstanceID(); string[] slots = ArmorVfx.Slots; foreach (string text in slots) { string text2 = ""; try { text2 = zDO.GetString(ArmorVfx.ZKey(text), ""); } catch { } if (string.IsNullOrEmpty(text2) || !ArmorVfx.Registry.TryGetValue(text2, out var value)) { continue; } string text3 = instanceID + ":" + text; _seen.Add(text3); Transform transform = ((Component)p).transform; if (_live.TryGetValue(text3, out var value2) && (value2.Aura != text2 || (Object)(object)value2.Parent != (Object)(object)transform || (Object)(object)value2.Go == (Object)null)) { if ((Object)(object)value2.Go != (Object)null) { PlayPoof(value2.Go.transform.position); } Object.Destroy((Object)(object)value2.Go); _live.Remove(text3); value2 = null; } else if (!_live.TryGetValue(text3, out value2)) { value2 = null; } if (value2 == null) { GameObject val2 = Spawn(value, transform); if (!((Object)(object)val2 == (Object)null)) { value2 = new Attached { Go = val2, Parent = transform, Aura = text2 }; _live[text3] = value2; PlayPoof(val2.transform.position); } } } } private GameObject Spawn(ArmorVfx.Aura def, Transform parent) { //IL_0059: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) GameObject val = ArmorVfx.ResolveSource(def); if ((Object)(object)val == (Object)null) { return null; } try { GameObject val2 = (def.WholeCreature ? SpawnCreatureVisual(val, parent) : Object.Instantiate(val, parent)); if ((Object)(object)val2 == (Object)null) { return null; } ((Object)val2).name = "vc_aura_" + def.Id; val2.transform.localPosition = ArmorVfx.CompanionOffset + new Vector3(0f, def.Raise, 0f); val2.transform.localRotation = Quaternion.identity; val2.transform.localScale = val2.transform.localScale * def.Scale; if (!def.WholeCreature) { StripToVisual(val2); ForceLoop(val2); } if (def.StripChildHints != null) { List list = new List(); Transform[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Transform val3 in componentsInChildren) { if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)(object)val2) { continue; } if ((Object)(object)((Component)val3).GetComponent() != (Object)null && list.Count < 30) { list.Add(((Object)val3).name); } string text = ((Object)val3).name.ToLowerInvariant(); string[] stripChildHints = def.StripChildHints; foreach (string value in stripChildHints) { if (text.Contains(value)) { Object.Destroy((Object)(object)((Component)val3).gameObject); break; } } } Debug.Log((object)("[Valcoin][ArmorVfx] " + def.Id + ": particle children: " + string.Join(", ", list.ToArray()))); } if (def.TameParticles) { ParticleSystem[] componentsInChildren2 = val2.GetComponentsInChildren(true); foreach (ParticleSystem obj in componentsInChildren2) { MainModule main = obj.main; if (((MainModule)(ref main)).startSize3D) { ((MainModule)(ref main)).startSizeXMultiplier = ((MainModule)(ref main)).startSizeXMultiplier * def.Scale; ((MainModule)(ref main)).startSizeYMultiplier = ((MainModule)(ref main)).startSizeYMultiplier * def.Scale; ((MainModule)(ref main)).startSizeZMultiplier = ((MainModule)(ref main)).startSizeZMultiplier * def.Scale; } else { ((MainModule)(ref main)).startSizeMultiplier = ((MainModule)(ref main)).startSizeMultiplier * def.Scale; } ((MainModule)(ref main)).startSpeedMultiplier = ((MainModule)(ref main)).startSpeedMultiplier * def.Scale; ((MainModule)(ref main)).gravityModifierMultiplier = ((MainModule)(ref main)).gravityModifierMultiplier * def.Scale; ShapeModule shape = obj.shape; if (((ShapeModule)(ref shape)).enabled) { ((ShapeModule)(ref shape)).radius = ((ShapeModule)(ref shape)).radius * def.Scale; ((ShapeModule)(ref shape)).scale = ((ShapeModule)(ref shape)).scale * def.Scale; } } } if (def.HasLight) { Light obj2 = val2.AddComponent(); obj2.type = (LightType)2; obj2.color = def.LightColor; obj2.intensity = 1.3f; obj2.range = 1.8f; obj2.shadows = (LightShadows)0; } GraftGlow(def, val2); val2.SetActive(true); if (def.WholeCreature) { TuneAnimators(val2); } return val2; } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] Spawn '" + def.Id + "' failed: " + ex.Message)); return null; } } private void GraftGlow(ArmorVfx.Aura def, GameObject go) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(def.GlowFromPrefab) || (Object)(object)go == (Object)null) { return; } try { GameObject val = ArmorVfx.ResolvePrefab(def.GlowFromPrefab); if ((Object)(object)val == (Object)null) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] " + def.Id + ": glow donor '" + def.GlowFromPrefab + "' not found — no glow grafted.")); return; } GameObject val2 = ArmorVfx.FindGlowChild(val, def.GlowChildHints, def.Id + " glow"); if ((Object)(object)val2 == (Object)null) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] " + def.Id + ": no particle-only node in '" + def.GlowFromPrefab + "' — no glow grafted.")); return; } GameObject val3 = Object.Instantiate(val2, go.transform); ((Object)val3).name = "vc_glow_" + def.Id; StripToVisual(val3); StripGeometry(val3); ForceLoop(val3); val3.transform.localPosition = Vector3.zero; val3.transform.localRotation = Quaternion.identity; float num = ((def.Scale > 0.0001f) ? (1f / def.Scale) : 1f); val3.transform.localScale = val3.transform.localScale * (def.GlowScale * num); val3.SetActive(true); Debug.Log((object)("[Valcoin][ArmorVfx] " + def.Id + ": grafted glow from '" + def.GlowFromPrefab + "' child '" + ((Object)val2).name + "'.")); } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] " + def.Id + ": glow graft failed: " + ex.Message)); } } private static void TuneAnimators(GameObject go) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 try { Animator[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { val.applyRootMotion = false; val.cullingMode = (AnimatorCullingMode)0; AnimatorControllerParameter[] parameters = val.parameters; foreach (AnimatorControllerParameter val2 in parameters) { if ((int)val2.type == 4) { if (val2.name == "flying") { val.SetBool(val2.nameHash, true); } else if (val2.name == "onGround") { val.SetBool(val2.nameHash, false); } } } } SkinnedMeshRenderer[] componentsInChildren2 = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].updateWhenOffscreen = true; } } catch { } } private GameObject SpawnCreatureVisual(GameObject creaturePrefab, Transform parent) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown GameObject val = new GameObject("vc_familiar_holder"); val.SetActive(false); try { GameObject val2 = Object.Instantiate(creaturePrefab, val.transform); StripAllExcept(val2, KeepTypes); Animator[] componentsInChildren = val2.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].applyRootMotion = false; } val2.transform.SetParent(parent, false); Object.Destroy((Object)(object)val); return val2; } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][ArmorVfx] Creature visual failed: " + ex.Message)); Object.Destroy((Object)(object)val); return null; } } private static void StripAllExcept(GameObject go, HashSet keep) { for (int i = 0; i < 16; i++) { bool flag = false; bool flag2 = false; Component[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null || keep.Contains(((object)val).GetType().Name)) { continue; } if (RequiredByAnother(val)) { flag = true; continue; } try { Object.DestroyImmediate((Object)(object)val); flag2 = true; } catch { } } if (!flag || !flag2) { break; } } } private static bool RequiredByAnother(Component c) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown Type type = ((object)c).GetType(); Component[] components = c.gameObject.GetComponents(); foreach (Component val in components) { if ((Object)(object)val == (Object)null || val == c) { continue; } object[] customAttributes = ((object)val).GetType().GetCustomAttributes(typeof(RequireComponent), inherit: true); for (int j = 0; j < customAttributes.Length; j++) { RequireComponent val2 = (RequireComponent)customAttributes[j]; if ((val2.m_Type0 != null && val2.m_Type0.IsAssignableFrom(type)) || (val2.m_Type1 != null && val2.m_Type1.IsAssignableFrom(type)) || (val2.m_Type2 != null && val2.m_Type2.IsAssignableFrom(type))) { return true; } } } return false; } private static void StripToVisual(GameObject go) { try { Component[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string name = ((object)val).GetType().Name; for (int j = 0; j < StripTypes.Length; j++) { if (name == StripTypes[j]) { Object.Destroy((Object)(object)val); break; } } } } catch { } } private static void StripGeometry(GameObject go) { try { MeshRenderer[] componentsInChildren = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.Destroy((Object)(object)componentsInChildren[i]); } MeshFilter[] componentsInChildren2 = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.Destroy((Object)(object)componentsInChildren2[i]); } SkinnedMeshRenderer[] componentsInChildren3 = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren3.Length; i++) { Object.Destroy((Object)(object)componentsInChildren3[i]); } Animator[] componentsInChildren4 = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren4.Length; i++) { Object.Destroy((Object)(object)componentsInChildren4[i]); } LODGroup[] componentsInChildren5 = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren5.Length; i++) { Object.Destroy((Object)(object)componentsInChildren5[i]); } } catch { } } private static void ForceLoop(GameObject go) { //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) try { ParticleSystem[] componentsInChildren = go.GetComponentsInChildren(true); foreach (ParticleSystem obj in componentsInChildren) { MainModule main = obj.main; ((MainModule)(ref main)).loop = true; ((MainModule)(ref main)).playOnAwake = true; obj.Play(); } } catch { } } private void UpdateFamiliarBuffs() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } SEMan sEMan = ((Character)localPlayer).GetSEMan(); if (sEMan == null) { return; } string text = ArmorVfx.EquippedAura((Humanoid)(object)localPlayer, "head"); bool flag = text != null; StatusEffect val = ArmorVfx.SlowFallEffect(); if ((Object)(object)val != (Object)null) { int num = val.NameHash(); if (flag) { if (!sEMan.HaveStatusEffect(num)) { sEMan.AddStatusEffect(val, true, 0, 0f); } _slowFallAdded = true; } else if (_slowFallAdded) { _slowFallAdded = false; if (!ArmorVfx.WearsSlowFallItem((Humanoid)(object)localPlayer) && sEMan.HaveStatusEffect(num)) { sEMan.RemoveStatusEffect(num, false); } } } bool flag2 = sEMan.HaveStatusEffect(BondHash); if (!flag) { if (flag2) { sEMan.RemoveStatusEffect(BondHash, false); } _bondAura = null; return; } if (flag2 && _bondAura != text) { sEMan.RemoveStatusEffect(BondHash, false); flag2 = false; } if (!flag2 && ArmorVfx.Registry.TryGetValue(text, out var value)) { SE_FamiliarBond sE_FamiliarBond = ScriptableObject.CreateInstance(); ((Object)sE_FamiliarBond).name = "VcFamiliarBond"; ((StatusEffect)sE_FamiliarBond).m_name = "Familiar Bond (" + value.Display + ")"; ((StatusEffect)sE_FamiliarBond).m_tooltip = "Your " + value.Display + " familiar sharpens your attacks: " + ArmorVfx.StatsText(value) + "."; ((StatusEffect)sE_FamiliarBond).m_ttl = 0f; sE_FamiliarBond.m_slash = value.Slash; sE_FamiliarBond.m_pierce = value.Pierce; sE_FamiliarBond.m_blunt = value.Blunt; sE_FamiliarBond.m_fire = value.Fire; sE_FamiliarBond.m_frost = value.Frost; sE_FamiliarBond.m_spirit = value.Spirit; sEMan.AddStatusEffect((StatusEffect)(object)sE_FamiliarBond, true, 0, 0f); } _bondAura = text; } private static GameObject PoofPrefab() { if (_poofResolved) { return _poofPrefab; } _poofResolved = true; string[] poofCandidates = PoofCandidates; for (int i = 0; i < poofCandidates.Length; i++) { GameObject val = ArmorVfx.ResolvePrefab(poofCandidates[i]); if ((Object)(object)val != (Object)null) { _poofPrefab = val; break; } } return _poofPrefab; } private void PlayPoof(Vector3 pos) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0059: 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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) GameObject val = PoofPrefab(); if ((Object)(object)val == (Object)null) { return; } try { GameObject val2 = new GameObject("vc_poof_holder"); val2.SetActive(false); GameObject val3 = Object.Instantiate(val, val2.transform); StripAllExcept(val3, PoofKeep); ((Object)val3).name = "vc_familiar_poof"; val3.transform.SetParent((Transform)null, false); val3.transform.position = pos; val3.transform.localScale = Vector3.one * 0.6f; ParticleSystem[] componentsInChildren = val3.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { MainModule main = componentsInChildren[i].main; ((MainModule)(ref main)).scalingMode = (ParticleSystemScalingMode)0; } val3.SetActive(true); Object.Destroy((Object)(object)val2); Object.Destroy((Object)(object)val3, 5f); } catch { } } private void OnDestroy() { foreach (Attached value in _live.Values) { if ((Object)(object)value?.Go != (Object)null) { Object.Destroy((Object)(object)value.Go); } } _live.Clear(); } } public static class BackendClient { public delegate void Callback(bool ok, T result, string error); public static IEnumerator Get(string path, Callback cb) { return Send("GET", path, null, cb); } public static IEnumerator Post(string path, object body, Callback cb) { return Send("POST", path, body, cb); } private static IEnumerator Send(string method, string path, object body, Callback cb) { if (!Config.Ready) { cb?.Invoke(ok: false, default(T), "backend not configured (valcoin_config.json missing backend_url/plugin_token)"); yield break; } string text = Config.BackendUrl.TrimEnd(new char[1] { '/' }) + path; UnityWebRequest req = new UnityWebRequest(text, method); try { req.timeout = 15; req.SetRequestHeader("Authorization", "Bearer " + Config.PluginToken); req.SetRequestHeader("Accept", "application/json"); req.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); if (body != null) { string s = JsonConvert.SerializeObject(body); byte[] bytes = Encoding.UTF8.GetBytes(s); req.uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes); req.SetRequestHeader("Content-Type", "application/json"); } yield return req.SendWebRequest(); if ((int)req.result != 1) { if (cb != null) { object arg = (int)req.responseCode; string error = req.error; DownloadHandler downloadHandler = req.downloadHandler; cb(ok: false, default(T), $"{arg} {error}: {((downloadHandler != null) ? downloadHandler.text : null)}"); } yield break; } T result; try { string text2 = req.downloadHandler.text; result = (string.IsNullOrEmpty(text2) ? default(T) : JsonConvert.DeserializeObject(text2)); } catch (Exception ex) { cb?.Invoke(ok: false, default(T), "json parse failed: " + ex.Message); yield break; } cb?.Invoke(ok: true, result, null); } finally { ((IDisposable)req)?.Dispose(); } } } public static class Catalog { public class Sku { public string Id; public string Name; public string Description; public int Price; public string Effect; public string Perk; public int Charges = 1; public int WeeklyChargeCap; public string Item; public int WeeklyCap; public string RequiresBoss; public string Category; public string CategoryDesc; public string PreviewImage; } private static readonly string CatalogPath = Path.Combine(Paths.ConfigPath, "valcoin_shop.yaml"); private static readonly Regex KvRe = new Regex("^\\s*([a-zA-Z_]+)\\s*:\\s*(.*)$", RegexOptions.Compiled); private static readonly Regex SkuRe = new Regex("^ ([a-z0-9_]+)\\s*:\\s*$", RegexOptions.Compiled); private static readonly Regex FieldRe = new Regex("^ ([a-zA-Z_]+)\\s*:\\s*(.*)$", RegexOptions.Compiled); public static Dictionary Items { get; private set; } = new Dictionary(); public static List Order { get; private set; } = new List(); public static void Load() { EnsureFile(); try { Parse(File.ReadAllLines(CatalogPath)); Debug.Log((object)$"[Valcoin] Shop catalog loaded: {Items.Count} SKU(s)."); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Failed to parse shop catalog: " + ex.Message)); Items = new Dictionary(); Order = new List(); } } private static void EnsureFile() { if (File.Exists(CatalogPath)) { return; } try { File.WriteAllText(CatalogPath, "# Valcoin shop catalog\n# -----------------------------------------------------------------------\n# Each SKU has:\n# name: what shows in the Shop tab (F8 panel / F4 Codex)\n# description: helper text\n# price: Valcoin cost\n# effect: grant_perk | add_charges | grant_item\n# perk: (perk effects) identifier the plugin understands\n# charges: (add_charges) how many uses each purchase grants\n# weekly_charge_cap: (add_charges) max charges of this kind per player per week (0 = unlimited)\n# item: (grant_item) comma list of \"prefab\" or \"prefab:qty\"\n# weekly_cap: (grant_item) max purchases per player per week (0 = unlimited)\n# requires_boss: (grant_item) global boss key gate, e.g. defeated_bonemass\n# preview_image: (optional) thumbnail shown in the Shop tab - an https URL, or\n# a path relative to BepInEx/config (e.g. shop_images/foo.png)\n# A full ecosystem-aware catalog is in examples/valcoin_shop.example.yaml.\n# Edit and restart the server to apply changes.\n\nshop:\n\n # ---------- Soulkeeper Charms (death insurance) ----------\n # Charges of one shared 'soulkeeper' pool. On death you keep your skills (no\n # skill drain) and a Valkyrie carries you back to your tombstone. Never helps\n # you win a fight - it only softens the death tax. `category_desc` is set once\n # on the first SKU of each group and drives the Shop tab's category blurb.\n soulkeeper_1:\n category: \"Soulkeeper Charms\"\n category_desc: \"Death insurance - keep your skills on death (no drain) and a Valkyrie carries you back to your tombstone, scattering nearby creatures on arrival. Limited to 10 charges per week. PvE-safe; never helps you win a fight.\"\n name: \"Soulkeeper Charm (x1)\"\n price: 300\n effect: add_charges\n perk: soulkeeper\n charges: 1\n weekly_charge_cap: 10\n\n soulkeeper_5:\n category: \"Soulkeeper Charms\"\n name: \"Soulkeeper Charm (x5)\"\n price: 1200\n effect: add_charges\n perk: soulkeeper\n charges: 5\n weekly_charge_cap: 10\n\n soulkeeper_10:\n category: \"Soulkeeper Charms\"\n name: \"Soulkeeper Charm (x10)\"\n description: \"Best value\"\n price: 1300\n effect: add_charges\n perk: soulkeeper\n charges: 10\n weekly_charge_cap: 10\n\n # ---------- Familiars (mini flying-creature companions) ----------\n # armor_vfx binds a miniature flying creature to your equipped helmet - it\n # hovers at your left shoulder, head height. Each grants feather fall plus\n # a tiny flat attack bonus (+2/+3 of the creature's damage type - flavor,\n # not power; weapons deal 50-150). `perk` selects the familiar; visuals and\n # stats live in the plugin's ArmorVfx registry. Priced by progression tier.\n familiar_bat:\n category: \"Familiars\"\n category_desc: \"A miniature flying creature hovers at your shoulder, bound to your equipped helmet (renames it to match). Grants feather fall and a small attack bonus. Other players see it too.\"\n name: \"Bat Familiar\"\n description: \"+2 slash\"\n price: 400\n effect: armor_vfx\n perk: bat\n\n familiar_ghost:\n category: \"Familiars\"\n name: \"Ghost Familiar\"\n description: \"+2 slash\"\n price: 500\n effect: armor_vfx\n perk: ghostlight\n\n familiar_deathsquito:\n category: \"Familiars\"\n name: \"Deathsquito Familiar\"\n description: \"+2 pierce\"\n price: 600\n effect: armor_vfx\n perk: deathsquito\n\n familiar_hatchling:\n category: \"Familiars\"\n name: \"Drake Hatchling Familiar\"\n description: \"+2 frost\"\n price: 700\n effect: armor_vfx\n perk: hatchling\n\n familiar_wraith:\n category: \"Familiars\"\n name: \"Wraith Familiar\"\n description: \"+2 slash\"\n price: 800\n effect: armor_vfx\n perk: wraith\n\n familiar_volture:\n category: \"Familiars\"\n name: \"Volture Familiar\"\n description: \"+3 pierce\"\n price: 900\n effect: armor_vfx\n perk: volture\n\n familiar_gjall:\n category: \"Familiars\"\n name: \"Gjall Familiar\"\n description: \"+2 blunt, +1 fire\"\n price: 1100\n effect: armor_vfx\n perk: gjall\n\n familiar_valkyrie:\n category: \"Familiars\"\n name: \"Fallen Valkyrie Familiar\"\n description: \"+2 spirit\"\n price: 1300\n effect: armor_vfx\n perk: fallen_valkyrie\n\n # ---------- Feasts (progression-gated food) ----------\n food_t1:\n category: \"Feasts\"\n category_desc: \"Top-tier cooked meals, 5 of each dish. Weekly-limited, and each unlocks once you've beaten its biome boss.\"\n name: \"Swamp Feast\"\n price: 120\n effect: grant_item\n item: \"Sausages:5,BloodPudding:5,SerpentStew:5\"\n weekly_cap: 4\n requires_boss: defeated_bonemass\n\n food_t2:\n category: \"Feasts\"\n name: \"Plains Feast\"\n price: 180\n effect: grant_item\n item: \"LoxPie:5,Bread:5,FishWraps:5\"\n weekly_cap: 3\n requires_boss: defeated_goblinking\n\n food_t3:\n category: \"Feasts\"\n name: \"Mistlands Feast\"\n price: 260\n effect: grant_item\n item: \"MisthareSupreme:5,MushroomOmelette:5,YggdrasilPorridge:5\"\n weekly_cap: 2\n requires_boss: defeated_queen\n\n food_t4:\n category: \"Feasts\"\n name: \"Ashlands Feast\"\n price: 350\n effect: grant_item\n item: \"MashedMeat:5,PiquantPie:5,MarinatedGreens:5\"\n weekly_cap: 2\n requires_boss: defeated_fader\n\n # ---------- Meads ----------\n meads_utility:\n category: \"Meads\"\n category_desc: \"Mead bundles, 5 of each. Weekly-limited; some unlock after their boss.\"\n name: \"Utility Meads\"\n price: 100\n effect: grant_item\n item: \"MeadTasty:5,MeadFrostResist:5,MeadPoisonResist:5\"\n weekly_cap: 3\n\n meads_vitality:\n category: \"Meads\"\n name: \"Vitality Meads\"\n price: 160\n effect: grant_item\n item: \"MeadHealthMedium:5,MeadStaminaMedium:5\"\n weekly_cap: 2\n requires_boss: defeated_bonemass\n\n meads_eitr:\n category: \"Meads\"\n name: \"Eitr Meads\"\n price: 160\n effect: grant_item\n item: \"MeadEitrMinor:5\"\n weekly_cap: 2\n requires_boss: defeated_queen\n\n # ---------- Supplies (materials & seeds) ----------\n farm_bundle:\n category: \"Supplies\"\n category_desc: \"Grind-heavy materials and seeds in bulk. Weekly-limited.\"\n name: \"Farmer's Crate\"\n price: 120\n effect: grant_item\n item: \"Barley:20,Flax:20,OnionSeeds:20,CarrotSeeds:20,TurnipSeeds:20\"\n weekly_cap: 2\n requires_boss: defeated_goblinking\n\n forage_bundle:\n category: \"Supplies\"\n name: \"Forager's Crate\"\n price: 100\n effect: grant_item\n item: \"Coal:50,Resin:50,Feathers:50,Thistle:20,Dandelion:20,Honey:20\"\n weekly_cap: 2\n"); Debug.LogWarning((object)("[Valcoin] Created shop catalog template at " + CatalogPath)); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Could not write catalog template: " + ex.Message)); } } private static void Parse(string[] lines) { Dictionary items = new Dictionary(); List order = new List(); bool flag = false; Sku sku = null; foreach (string text in lines) { if (string.IsNullOrWhiteSpace(text) || text.TrimStart(Array.Empty()).StartsWith("#")) { continue; } if (!flag) { if (Regex.IsMatch(text, "^shop\\s*:\\s*$")) { flag = true; } continue; } Match match = SkuRe.Match(text); if (match.Success) { if (sku != null) { Commit(sku, items, order); } sku = new Sku { Id = match.Groups[1].Value }; continue; } Match match2 = FieldRe.Match(text); if (sku != null && match2.Success) { string value = match2.Groups[1].Value; string text2 = StripQuotes(match2.Groups[2].Value.Trim()); switch (value) { case "name": sku.Name = text2; break; case "description": sku.Description = text2; break; case "price": int.TryParse(text2, out sku.Price); break; case "effect": sku.Effect = text2; break; case "perk": sku.Perk = text2; break; case "charges": int.TryParse(text2, out sku.Charges); break; case "item": sku.Item = text2; break; case "weekly_cap": int.TryParse(text2, out sku.WeeklyCap); break; case "weekly_charge_cap": int.TryParse(text2, out sku.WeeklyChargeCap); break; case "requires_boss": sku.RequiresBoss = text2; break; case "category": sku.Category = text2; break; case "category_desc": sku.CategoryDesc = text2; break; case "preview_image": sku.PreviewImage = text2; break; } } else if (text.Length > 0 && text[0] != ' ' && KvRe.IsMatch(text)) { break; } } if (sku != null) { Commit(sku, items, order); } Items = items; Order = order; } private static void Commit(Sku s, Dictionary items, List order) { if (string.IsNullOrEmpty(s.Id) || string.IsNullOrEmpty(s.Effect)) { return; } if (s.Effect == "grant_item") { if (string.IsNullOrEmpty(s.Item)) { return; } } else if (string.IsNullOrEmpty(s.Perk)) { return; } if (string.IsNullOrEmpty(s.Name)) { s.Name = s.Id; } items[s.Id] = s; order.Add(s); } public static string Serialize() { try { return JsonConvert.SerializeObject((object)Order); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Catalog serialize failed: " + ex.Message)); return null; } } public static void ApplyRemote(string json) { if (string.IsNullOrEmpty(json)) { return; } try { List list = JsonConvert.DeserializeObject>(json); if (list == null) { return; } Dictionary dictionary = new Dictionary(); foreach (Sku item in list) { if (!string.IsNullOrEmpty(item.Id)) { dictionary[item.Id] = item; } } Items = dictionary; Order = list; } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Catalog ApplyRemote failed: " + ex.Message)); } } private static string StripQuotes(string v) { if (v.Length >= 2 && v[0] == '"' && v[v.Length - 1] == '"') { return v.Substring(1, v.Length - 2); } return v; } } public class CatalogSync : MonoBehaviour { private const float IntervalSeconds = 30f; private Coroutine _loop; private void Start() { _loop = ((MonoBehaviour)this).StartCoroutine(Loop()); } private void OnDestroy() { if (_loop != null) { ((MonoBehaviour)this).StopCoroutine(_loop); } } private IEnumerator Loop() { while ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { yield return (object)new WaitForSeconds(2f); } while (true) { if (ZRoutedRpc.instance != null) { RpcLayer.BroadcastCatalog(Catalog.Serialize()); RpcLayer.BroadcastQuests(QuestCatalog.Serialize()); } yield return (object)new WaitForSeconds(30f); } } } public static class CoinManager { private class State { public Dictionary balances = new Dictionary(); public List recentGrants = new List(); } private static readonly string SaveDir = Path.Combine(Paths.ConfigPath, "valcoin_data"); private static readonly string SaveFile = Path.Combine(SaveDir, "coin_balances.json"); private const int RecentGrantCap = 5000; private static State _state = new State(); private static HashSet _seen = new HashSet(); public static void Load() { try { Directory.CreateDirectory(SaveDir); if (!File.Exists(SaveFile)) { return; } string text = File.ReadAllText(SaveFile); State state; try { state = JsonConvert.DeserializeObject(text); if (state == null || state.balances == null) { throw new Exception("not new shape"); } } catch { Dictionary balances = JsonConvert.DeserializeObject>(text) ?? new Dictionary(); state = new State { balances = balances }; } _state = state; State state2 = _state; if (state2.recentGrants == null) { state2.recentGrants = new List(); } _seen = new HashSet(_state.recentGrants); } catch (Exception ex) { Debug.LogError((object)("[CoinManager] Failed to load: " + ex.Message)); _state = new State(); _seen = new HashSet(); } } public static bool Save() { try { File.WriteAllText(SaveFile, JsonConvert.SerializeObject((object)_state, (Formatting)1)); return true; } catch (Exception ex) { Debug.LogError((object)("[CoinManager] Failed to save: " + ex.Message)); return false; } } public static int GetBalance(string steamId) { if (!_state.balances.TryGetValue(steamId, out var value)) { return 0; } return value; } public static bool TryGetKnownBalance(string steamId, out int balance) { return _state.balances.TryGetValue(steamId, out balance); } public static void AddCoins(string steamId, int amount) { _state.balances[steamId] = GetBalance(steamId) + amount; Save(); } public static void SetBalance(string steamId, int amount) { _state.balances[steamId] = Math.Max(0, amount); Save(); } public static bool TryApplyGrant(long grantId, string steamId, int amount) { if (_seen.Contains(grantId)) { return false; } _seen.Add(grantId); _state.recentGrants.Add(grantId); if (_state.recentGrants.Count > 5000) { int count = _state.recentGrants.Count - 5000; List range = _state.recentGrants.GetRange(0, count); _state.recentGrants.RemoveRange(0, count); foreach (long item in range) { _seen.Remove(item); } } _state.balances[steamId] = GetBalance(steamId) + amount; if (!Save()) { _state.balances[steamId] = GetBalance(steamId) - amount; _seen.Remove(grantId); _state.recentGrants.Remove(grantId); throw new IOException($"could not persist grant {grantId} for {steamId}"); } return true; } } public static class Config { public static string BackendUrl { get; private set; } public static string PluginToken { get; private set; } public static float PollIntervalSeconds { get; private set; } = 10f; public static string UiToggleKey { get; private set; } = "F8"; public static string CodexToggleKey { get; private set; } = "F4"; public static bool WelcomeEnabled { get; private set; } = true; public static string WelcomeMessage { get; private set; } public static bool ValkyrieCarryVisual { get; private set; } = true; public static bool Ready { get { if (!string.IsNullOrEmpty(BackendUrl) && !BackendUrl.Contains("your-app.fly.dev") && !string.IsNullOrEmpty(PluginToken)) { return !PluginToken.StartsWith("paste-the-"); } return false; } } public static void Load() { BackendUrl = Environment.GetEnvironmentVariable("VALCOIN_BACKEND_URL"); PluginToken = Environment.GetEnvironmentVariable("VALCOIN_PLUGIN_TOKEN"); try { string text = Path.Combine(Paths.ConfigPath, "valcoin_config.json"); if (File.Exists(text)) { JObject val = JObject.Parse(File.ReadAllText(text)); BackendUrl = (string.IsNullOrEmpty(BackendUrl) ? ((string)val["backend_url"]) : BackendUrl); PluginToken = (string.IsNullOrEmpty(PluginToken) ? ((string)val["plugin_token"]) : PluginToken); if (val["poll_interval_seconds"] != null) { PollIntervalSeconds = (float)val["poll_interval_seconds"]; } if (val["ui_toggle_key"] != null) { UiToggleKey = (string)val["ui_toggle_key"]; } if (val["codex_toggle_key"] != null) { CodexToggleKey = (string)val["codex_toggle_key"]; } if (val["welcome_message_enabled"] != null) { WelcomeEnabled = (bool)val["welcome_message_enabled"]; } if (val["welcome_message"] != null) { WelcomeMessage = (string)val["welcome_message"]; } if (val["valkyrie_carry_visual"] != null) { ValkyrieCarryVisual = (bool)val["valkyrie_carry_visual"]; } } else { File.WriteAllText(text, "{\n \"backend_url\": \"https://your-app.fly.dev\",\n \"plugin_token\": \"paste-the-PLUGIN_TOKEN-from-your-fly-secrets\",\n \"poll_interval_seconds\": 10,\n\n \"ui_toggle_key\": \"F8\",\n \"codex_toggle_key\": \"F4\",\n \"welcome_message_enabled\": true,\n \"welcome_message\": null,\n\n \"valkyrie_carry_visual\": true\n}\n"); Debug.LogWarning((object)("[Valcoin] Created template config at " + text + ". Fill in backend_url + plugin_token.")); } } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Failed to load config: " + ex.Message)); } if (Ready) { Debug.Log((object)("[Valcoin] Backend configured: " + BackendUrl)); } else if (!string.IsNullOrEmpty(BackendUrl) && BackendUrl.Contains("your-app.fly.dev")) { Debug.LogWarning((object)"[Valcoin] valcoin_config.json still has the PLACEHOLDER backend_url (your-app.fly.dev). Set backend_url + plugin_token to your real values and restart. Donation actions are disabled until then."); } else { Debug.LogWarning((object)"[Valcoin] Backend not configured; donation actions and grant polling are disabled."); } } } public class DonationPanel : MonoBehaviour { private enum Tab { Donate, Shop, Gift, Patrons, Admin } private class StateResp { public int balance; public TopEntry[] top_donors; public string[] owned_skus; public Dictionary weekly_usage; public string week_resets_in; public Dictionary charges; public float coins_per_usd; public int quest_daily_earned; public int quest_daily_cap; public string quest_resets_in; public int quest_streak; } private class TopEntry { public int rank; public string name; public int total_coins; } private const int PanelW = 640; private const int PanelH = 760; private Tab _tab; private bool _open; private bool _isAdmin; private bool _askedWhoAmI; private KeyCode _toggleKey = (KeyCode)285; private int _balance; private int _questEarned; private int _questCap; private int _questStreak; private string _questResetsIn = ""; private List _topDonors = new List(); private HashSet _ownedSkus = new HashSet(); private Dictionary _weeklyUsage = new Dictionary(); private string _weekResetsIn = ""; private Dictionary _charges = new Dictionary(); private float _coinsPerUsd; private string _donateCode; private string _donateUrl; private int _donateTtlMinutes; private string _donateStatus; private float _donateCooldownUntil; private float _donateWaitingSince = -1f; private float _copiedFlashUntil; private const float DonateCooldownSeconds = 30f; private const float DonateReplyTimeoutSeconds = 20f; private bool _showTerms; private Vector2 _termsScroll; private Catalog.Sku _confirmSku; private string _zoomImage; private string _zoomCaption; private string _pendingBuySku; private float _pendingBuyDeadline; private string _resultText; private bool _resultSuccess; private string _resultExtra; private readonly List _log = new List(); private const int LogCap = 12; private Vector2 _logScroll; private string _giftTo = ""; private string _giftAmount = ""; private string _adminTarget = ""; private string _adminAmount = ""; private GUIStyle _bg; private GUIStyle _hdr; private GUIStyle _sub; private GUIStyle _btn; private GUIStyle _btnActive; private GUIStyle _btnDim; private GUIStyle _btnPrimary; private GUIStyle _line; private GUIStyle _logLine; private GUIStyle _label; private GUIStyle _codeBox; private GUIStyle _linkBtn; private GUIStyle _pillOn; private GUIStyle _pillOff; private GUIStyle _owned; private GUIStyle _catHdr; private GUIStyle _dim; private GUIStyle _rateBox; private GUIStyle _rateSub; private bool _stylesReady; private float _lastStateFetch; private const float AutoRefreshSeconds = 20f; private bool _online; private bool _wasOpen; private const string AdminStatusPrefix = "__ADMIN__:"; private const string DonateOkPrefix = "__DONATE__:"; private const string DonateErrPrefix = "__DONATE_ERR__:"; private const string ArmorVfxPrefix = "__ARMORVFX__:"; private static Font _gameFont; private static bool _gameFontSearched; private static readonly Regex TierSuffix = new Regex("\\s*\\(x\\d+\\)\\s*$"); private Vector2 _shopScroll; private static readonly Regex CamelBoundary = new Regex("(?<=[a-z0-9])(?=[A-Z])", RegexOptions.Compiled); private static readonly string[] TermsText = new string[23] { "Please read these terms before donating. By making a donation you agree to all of the following.", "", "1. Voluntary support. Donations are entirely voluntary gifts to help cover server costs. They are not a purchase of goods or services.", "", "2. No real-world value. Valcoins, perks, and any in-game items are virtual and have no monetary value. They cannot be sold, traded for cash, or redeemed outside this server.", "", "3. Non-refundable. All donations are final and non-refundable, except where required by law. Initiating a chargeback may result in loss of Valcoins, perks, and access to the server.", "", "4. No pay-to-win. Perks are cosmetic or convenience only, and consumables are weekly-limited and earnable in normal play. Donating does not grant a competitive advantage.", "", "5. Subject to change. The server operators may adjust prices, perks, the Valcoin economy, or discontinue the donation system at any time without notice.", "", "6. No guarantee of service. Donating does not guarantee uninterrupted server availability, specific uptime, or that the server will continue to run for any period of time.", "", "7. Eligibility. You must be of legal age in your jurisdiction, or have permission from a parent or guardian, and use your own valid payment method.", "", "8. Conduct. Donations do not exempt any player from server rules. Perks may be revoked for rule violations without refund.", "", "9. Not affiliated. This is a community server and is not affiliated with, endorsed by, or sponsored by Iron Gate, Coffee Stain, or the payment providers.", "", "10. Contact. For questions about a donation, contact a server administrator. Any refunds are granted solely at the operators' discretion.", "", "Thank you for supporting the realm!" }; private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(Config.CodexToggleKey) && Enum.TryParse(Config.CodexToggleKey, ignoreCase: true, out KeyCode result)) { _toggleKey = result; } RpcLayer.OnPanelMessage = (Action)Delegate.Combine(RpcLayer.OnPanelMessage, new Action(OnServerMessage)); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void OnDestroy() { RpcLayer.OnPanelMessage = (Action)Delegate.Remove(RpcLayer.OnPanelMessage, new Action(OnServerMessage)); DonationUiState.PanelOpen = false; } private void Update() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(_toggleKey)) { Toggle(); } if (_open) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; DonationUiState.SetMouseCapture(value: false); RefreshStateSoon(); } else if (_wasOpen) { DonationUiState.SetMouseCapture(value: true); } if (_open != _wasOpen) { DonationUiState.PanelOpen = _open; _wasOpen = _open; } } private void Toggle() { _open = !_open; if (_open) { RefreshStateSoon(force: true); if (!_askedWhoAmI) { RpcLayer.SendAction("whoami"); _askedWhoAmI = true; } } } private void RefreshStateSoon(bool force = false) { if (!Config.Ready) { _online = false; return; } float num = (force ? 1f : 20f); if (!(Time.realtimeSinceStartup - _lastStateFetch < num)) { _lastStateFetch = Time.realtimeSinceStartup; ((MonoBehaviour)this).StartCoroutine(FetchState()); } } private IEnumerator FetchState() { string steam64 = ResolveLocalSteam64(); if (string.IsNullOrEmpty(steam64)) { _online = false; Debug.LogWarning((object)"[Valcoin] Panel offline: couldn't resolve local Steam ID yet."); yield break; } yield return BackendClient.Get("/api/state/" + steam64 + "?top=5", delegate(bool ok, StateResp r, string err) { _online = ok && r != null; if (!_online) { Debug.LogWarning((object)("[Valcoin] Panel offline: /api/state failed (" + (err ?? "no response") + ").")); } else { _balance = r.balance; _topDonors = ((r.top_donors != null) ? new List(r.top_donors) : new List()); _ownedSkus = ((r.owned_skus != null) ? new HashSet(r.owned_skus) : new HashSet()); _weeklyUsage = r.weekly_usage ?? new Dictionary(); _weekResetsIn = r.week_resets_in ?? ""; _charges = r.charges ?? new Dictionary(); _coinsPerUsd = r.coins_per_usd; _questEarned = r.quest_daily_earned; _questCap = r.quest_daily_cap; _questResetsIn = r.quest_resets_in ?? ""; _questStreak = r.quest_streak; _charges.TryGetValue("soulkeeper", out var value); SoulkeeperState.UpdateFromState(steam64, value); } }); } private string ResolveLocalSteam64() { return LocalIdentity.Steam64(); } private void OnServerMessage(string msg) { if (msg == null) { return; } if (msg.StartsWith("__ADMIN__:")) { _isAdmin = msg.Substring("__ADMIN__:".Length) == "true"; } else if (msg.StartsWith("__DONATE__:")) { string[] array = msg.Substring("__DONATE__:".Length).Split(new char[1] { '|' }, 3); _donateCode = ((array.Length != 0) ? array[0] : null); _donateUrl = ((array.Length > 1) ? array[1] : null); _donateTtlMinutes = ((array.Length > 2 && int.TryParse(array[2], out var result)) ? result : 0); _donateStatus = null; _donateWaitingSince = -1f; } else if (msg.StartsWith("__DONATE_ERR__:")) { _donateStatus = msg.Substring("__DONATE_ERR__:".Length); _donateCooldownUntil = 0f; _donateWaitingSince = -1f; } else if (msg.StartsWith("__ARMORVFX__:")) { string[] array2 = msg.Substring("__ARMORVFX__:".Length).Split(new char[1] { ':' }, 2); string msg2; if (array2.Length == 2) { ArmorVfx.ApplyToEquipped(array2[0], array2[1], out msg2); } else { msg2 = "Armor effect could not be applied."; } _log.Add(msg2); if (_log.Count > 12) { _log.RemoveAt(0); } if (_pendingBuySku != null) { _resultExtra = msg2; } } else { _log.Add(msg); if (_log.Count > 12) { _log.RemoveAt(0); } if (_pendingBuySku != null) { _pendingBuySku = null; _resultSuccess = msg.StartsWith("Purchased") || msg.Contains("was already processed"); _resultText = (string.IsNullOrEmpty(_resultExtra) ? msg : (msg + "\n\n" + _resultExtra)); _resultExtra = null; } RefreshStateSoon(); } } private void InitStyles() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_0073: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_00af: Expected O, but got Unknown //IL_00c9: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: 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_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Expected O, but got Unknown //IL_0295: 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_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Expected O, but got Unknown //IL_0349: 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_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03d2: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0400: Expected O, but got Unknown //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_0420: Expected O, but got Unknown //IL_042c: Unknown result type (might be due to invalid IL or missing references) //IL_0436: Expected O, but got Unknown //IL_0455: Unknown result type (might be due to invalid IL or missing references) //IL_046e: Unknown result type (might be due to invalid IL or missing references) //IL_049a: Unknown result type (might be due to invalid IL or missing references) //IL_04c3: Unknown result type (might be due to invalid IL or missing references) //IL_04dc: Unknown result type (might be due to invalid IL or missing references) //IL_04f9: Unknown result type (might be due to invalid IL or missing references) //IL_0524: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Expected O, but got Unknown //IL_054d: Unknown result type (might be due to invalid IL or missing references) //IL_0567: Unknown result type (might be due to invalid IL or missing references) //IL_056c: Unknown result type (might be due to invalid IL or missing references) //IL_0574: Unknown result type (might be due to invalid IL or missing references) //IL_0580: Expected O, but got Unknown //IL_059a: Unknown result type (might be due to invalid IL or missing references) //IL_05af: Unknown result type (might be due to invalid IL or missing references) //IL_05b4: Unknown result type (might be due to invalid IL or missing references) //IL_05bc: Unknown result type (might be due to invalid IL or missing references) //IL_05c8: Expected O, but got Unknown //IL_05e2: Unknown result type (might be due to invalid IL or missing references) //IL_05f7: Unknown result type (might be due to invalid IL or missing references) //IL_05fc: Unknown result type (might be due to invalid IL or missing references) //IL_0604: Unknown result type (might be due to invalid IL or missing references) //IL_060b: Unknown result type (might be due to invalid IL or missing references) //IL_0617: Expected O, but got Unknown //IL_0631: Unknown result type (might be due to invalid IL or missing references) //IL_0646: Unknown result type (might be due to invalid IL or missing references) //IL_064b: Unknown result type (might be due to invalid IL or missing references) //IL_0653: Unknown result type (might be due to invalid IL or missing references) //IL_065a: Unknown result type (might be due to invalid IL or missing references) //IL_0666: Expected O, but got Unknown //IL_0685: Unknown result type (might be due to invalid IL or missing references) //IL_06ae: Unknown result type (might be due to invalid IL or missing references) //IL_06c4: Unknown result type (might be due to invalid IL or missing references) //IL_06ce: Expected O, but got Unknown //IL_06d9: Unknown result type (might be due to invalid IL or missing references) //IL_06de: Unknown result type (might be due to invalid IL or missing references) //IL_06eb: Expected O, but got Unknown //IL_0705: Unknown result type (might be due to invalid IL or missing references) //IL_0729: Unknown result type (might be due to invalid IL or missing references) //IL_073e: Unknown result type (might be due to invalid IL or missing references) //IL_0743: Unknown result type (might be due to invalid IL or missing references) //IL_074b: Unknown result type (might be due to invalid IL or missing references) //IL_0752: Unknown result type (might be due to invalid IL or missing references) //IL_075e: Expected O, but got Unknown //IL_0778: Unknown result type (might be due to invalid IL or missing references) //IL_0789: Unknown result type (might be due to invalid IL or missing references) //IL_0793: Expected O, but got Unknown //IL_07ad: Unknown result type (might be due to invalid IL or missing references) //IL_07c2: Unknown result type (might be due to invalid IL or missing references) //IL_07c7: Unknown result type (might be due to invalid IL or missing references) //IL_07cf: Unknown result type (might be due to invalid IL or missing references) //IL_07db: Expected O, but got Unknown //IL_07f5: Unknown result type (might be due to invalid IL or missing references) //IL_080a: Unknown result type (might be due to invalid IL or missing references) //IL_080f: Unknown result type (might be due to invalid IL or missing references) //IL_0817: Unknown result type (might be due to invalid IL or missing references) //IL_0823: Expected O, but got Unknown //IL_083d: Unknown result type (might be due to invalid IL or missing references) //IL_0852: Unknown result type (might be due to invalid IL or missing references) //IL_0857: Unknown result type (might be due to invalid IL or missing references) //IL_085f: Unknown result type (might be due to invalid IL or missing references) //IL_0866: Unknown result type (might be due to invalid IL or missing references) //IL_086d: Unknown result type (might be due to invalid IL or missing references) //IL_0879: Expected O, but got Unknown //IL_0898: Unknown result type (might be due to invalid IL or missing references) //IL_08b1: Unknown result type (might be due to invalid IL or missing references) //IL_08dd: Unknown result type (might be due to invalid IL or missing references) //IL_08f1: Unknown result type (might be due to invalid IL or missing references) //IL_08fb: Expected O, but got Unknown //IL_0908: Unknown result type (might be due to invalid IL or missing references) //IL_0912: Expected O, but got Unknown //IL_091d: Unknown result type (might be due to invalid IL or missing references) //IL_0922: Unknown result type (might be due to invalid IL or missing references) //IL_092a: Unknown result type (might be due to invalid IL or missing references) //IL_0931: Unknown result type (might be due to invalid IL or missing references) //IL_093d: Expected O, but got Unknown //IL_0957: Unknown result type (might be due to invalid IL or missing references) _bg = new GUIStyle(GUI.skin.box); _bg.normal.background = BorderTex(new Color(0.09f, 0.08f, 0.06f, 0.985f), new Color(0.42f, 0.32f, 0.16f, 1f)); _bg.border = new RectOffset(3, 3, 3, 3); _bg.padding = new RectOffset(14, 14, 14, 14); _hdr = new GUIStyle(GUI.skin.label) { fontSize = 20, fontStyle = (FontStyle)1 }; _hdr.normal.textColor = new Color(0.87f, 0.72f, 0.42f); _sub = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)2, wordWrap = true }; _sub.normal.textColor = new Color(0.75f, 0.72f, 0.62f); _btn = new GUIStyle(GUI.skin.button) { fontSize = 15 }; _btn.border = new RectOffset(3, 3, 3, 3); _btn.padding = new RectOffset(10, 10, 7, 7); _btn.normal.background = BorderTex(new Color(0.17f, 0.14f, 0.1f, 1f), new Color(0.46f, 0.36f, 0.19f, 1f)); _btn.hover.background = BorderTex(new Color(0.26f, 0.21f, 0.13f, 1f), new Color(0.68f, 0.53f, 0.27f, 1f)); _btn.active.background = _btn.hover.background; _btn.normal.textColor = new Color(0.92f, 0.86f, 0.72f); _btn.hover.textColor = new Color(1f, 0.96f, 0.86f); _btnActive = new GUIStyle(_btn); _btnActive.normal.background = BorderTex(new Color(0.5f, 0.38f, 0.18f, 1f), new Color(0.72f, 0.57f, 0.29f, 1f)); _btnActive.hover.background = _btnActive.normal.background; _btnActive.normal.textColor = new Color(1f, 0.97f, 0.88f); _btnActive.hover.textColor = Color.white; _btnDim = new GUIStyle(_btn); _btnDim.normal.background = BorderTex(new Color(0.13f, 0.12f, 0.1f, 1f), new Color(0.3f, 0.26f, 0.18f, 1f)); _btnDim.hover.background = _btnDim.normal.background; _btnDim.normal.textColor = new Color(0.5f, 0.48f, 0.42f); _btnDim.hover.textColor = new Color(0.5f, 0.48f, 0.42f); _btnPrimary = new GUIStyle(GUI.skin.button) { fontSize = 16, fontStyle = (FontStyle)1 }; _btnPrimary.alignment = (TextAnchor)4; _btnPrimary.border = new RectOffset(3, 3, 3, 3); _btnPrimary.padding = new RectOffset(12, 12, 6, 6); _btnPrimary.normal.background = BorderTex(new Color(0.78f, 0.6f, 0.22f, 1f), new Color(0.5f, 0.36f, 0.12f, 1f)); _btnPrimary.normal.textColor = new Color(0.12f, 0.08f, 0.02f); _btnPrimary.hover.background = BorderTex(new Color(0.9f, 0.71f, 0.28f, 1f), new Color(0.6f, 0.44f, 0.16f, 1f)); _btnPrimary.hover.textColor = Color.black; _btnPrimary.active.background = _btnPrimary.normal.background; _line = new GUIStyle(); _line.normal.background = SolidTex(new Color(0.3f, 0.25f, 0.18f, 0.6f)); _logLine = new GUIStyle(GUI.skin.label) { fontSize = 14, wordWrap = true }; _logLine.normal.textColor = new Color(0.9f, 0.9f, 0.85f); _label = new GUIStyle(GUI.skin.label) { fontSize = 15, wordWrap = true }; _label.normal.textColor = new Color(0.88f, 0.85f, 0.78f); _owned = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1, alignment = (TextAnchor)5 }; _owned.normal.textColor = new Color(0.5f, 0.85f, 0.45f); _codeBox = new GUIStyle(GUI.skin.box) { fontSize = 22, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; _codeBox.normal.background = SolidTex(new Color(0.05f, 0.05f, 0.04f, 1f)); _codeBox.normal.textColor = new Color(1f, 0.86f, 0.45f); _codeBox.padding = new RectOffset(8, 8, 10, 10); _linkBtn = new GUIStyle(GUI.skin.label) { fontSize = 14 }; _linkBtn.normal.textColor = new Color(0.55f, 0.75f, 0.95f); _linkBtn.hover.textColor = new Color(0.75f, 0.88f, 1f); _pillOn = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1, alignment = (TextAnchor)5 }; _pillOn.normal.textColor = new Color(0.5f, 0.85f, 0.45f); _pillOff = new GUIStyle(_pillOn); _pillOff.normal.textColor = new Color(0.85f, 0.6f, 0.3f); _catHdr = new GUIStyle(GUI.skin.label) { fontSize = 17, fontStyle = (FontStyle)1 }; _catHdr.normal.textColor = new Color(0.85f, 0.68f, 0.34f); _dim = new GUIStyle(GUI.skin.label) { fontSize = 13, wordWrap = true }; _dim.normal.textColor = new Color(0.62f, 0.6f, 0.53f); _rateBox = new GUIStyle(GUI.skin.box) { fontSize = 24, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, wordWrap = false }; _rateBox.normal.background = BorderTex(new Color(0.16f, 0.13f, 0.07f, 1f), new Color(0.72f, 0.56f, 0.24f, 1f)); _rateBox.normal.textColor = new Color(1f, 0.86f, 0.45f); _rateBox.border = new RectOffset(3, 3, 3, 3); _rateBox.padding = new RectOffset(10, 10, 12, 6); _rateSub = new GUIStyle(GUI.skin.label) { fontSize = 13, alignment = (TextAnchor)4, wordWrap = true }; _rateSub.normal.textColor = new Color(0.75f, 0.72f, 0.62f); Font val = GameFont(); if ((Object)(object)val != (Object)null) { GUIStyle[] array = (GUIStyle[])(object)new GUIStyle[17] { _hdr, _sub, _btn, _btnActive, _btnDim, _btnPrimary, _logLine, _label, _codeBox, _linkBtn, _pillOn, _pillOff, _owned, _catHdr, _dim, _rateBox, _rateSub }; for (int i = 0; i < array.Length; i++) { array[i].font = val; } } _stylesReady = true; } private static Texture2D SolidTex(Color c) { //IL_0002: 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_000a: 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_0017: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1); val.SetPixel(0, 0, c); val.Apply(); return val; } private static Texture2D BorderTex(Color fill, Color border, int t = 2, int size = 16) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_0038: 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_0035: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(size, size, (TextureFormat)4, false); Color[] array = (Color[])(object)new Color[size * size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { array[i * size + j] = ((j < t || i < t || j >= size - t || i >= size - t) ? border : fill); } } val.SetPixels(array); val.Apply(); ((Texture)val).filterMode = (FilterMode)0; ((Texture)val).wrapMode = (TextureWrapMode)1; return val; } private static Font GameFont() { if (_gameFontSearched) { return _gameFont; } _gameFontSearched = true; try { Font[] array = Resources.FindObjectsOfTypeAll(); string[] array2 = new string[4] { "AveriaSerifLibre-Regular", "AveriaSerifLibre", "Averia", "Norse" }; foreach (string value in array2) { Font[] array3 = array; foreach (Font val in array3) { if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(((Object)val).name) && ((Object)val).name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { _gameFont = val; break; } } if ((Object)(object)_gameFont != (Object)null) { break; } } Debug.Log((object)("[Valcoin] UI font: " + (((Object)(object)_gameFont != (Object)null) ? ((Object)_gameFont).name : "default (Valheim font not found)"))); } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin] Font lookup failed: " + ex.Message)); } return _gameFont; } private void OnGUI() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_033b: Unknown result type (might be due to invalid IL or missing references) if (!_open) { return; } if (!_stylesReady) { InitStyles(); } if (Menu.IsVisible() || ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible()) || ((Object)(object)Minimap.instance != (Object)null && Minimap.IsOpen())) { _open = false; return; } float num = Mathf.Min(640, Screen.width - 40); float num2 = Mathf.Min(760, Screen.height - 40); Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2); GUI.Box(val, GUIContent.none, _bg); if (_pendingBuySku != null && Time.realtimeSinceStartup > _pendingBuyDeadline) { _pendingBuySku = null; _resultSuccess = false; _resultText = "No response from the server. Check your balance and the message log before retrying - the purchase may still have gone through."; } GUI.enabled = !_showTerms && _confirmSku == null && _resultText == null && _zoomImage == null; GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 14f, ((Rect)(ref val)).y + 14f, ((Rect)(ref val)).width - 28f, ((Rect)(ref val)).height - 28f)); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Valheim Donations", _hdr, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(_online ? "Live" : "Offline", _online ? _pillOn : _pillOff, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(70f), GUILayout.Height(22f) }); GUILayout.Space(6f); if (GUILayout.Button("X", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) })) { _open = false; } GUILayout.EndHorizontal(); GUILayout.Label($"Balance: {_balance} Valcoins", _label, Array.Empty()); DrawOwnedCharges(); DrawQuestProgress(); if (!_online) { GUILayout.Label(Config.Ready ? "Can't reach the donation service right now - you can still browse; it reconnects automatically." : "This client isn't configured yet (ask the operator) - you can still browse.", _sub, Array.Empty()); } DrawHr(); GUILayout.BeginHorizontal(Array.Empty()); TabButton("Donate", Tab.Donate); TabButton("Shop", Tab.Shop); TabButton("Gift", Tab.Gift); TabButton("Patrons", Tab.Patrons); if (_isAdmin) { TabButton("Admin", Tab.Admin); } GUILayout.EndHorizontal(); DrawHr(); switch (_tab) { case Tab.Donate: DrawDonate(); break; case Tab.Shop: DrawShop(); break; case Tab.Gift: DrawGift(); break; case Tab.Patrons: DrawPatrons(); break; case Tab.Admin: DrawAdmin(); break; } if (_tab != Tab.Donate && _log.Count > 0) { DrawHr(); DrawLog(); } GUILayout.EndArea(); GUI.enabled = true; if (_zoomImage != null) { DrawZoomModal(); } else if (_showTerms) { DrawTermsModal(val); } else if (_resultText != null) { DrawResultModal(); } else if (_confirmSku != null) { DrawConfirmModal(); } } private void DrawResultModal() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: 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_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) GUI.Box(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), GUIContent.none, _line); int num = Mathf.Min(460, Screen.width - 60); int num2 = Mathf.Min(260, Screen.height - 60); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)(Screen.width - num) / 2f, (float)(Screen.height - num2) / 2f, (float)num, (float)num2); GUI.Box(val, GUIContent.none, _bg); GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 18f, ((Rect)(ref val)).y + 18f, ((Rect)(ref val)).width - 36f, ((Rect)(ref val)).height - 36f)); Color contentColor = GUI.contentColor; GUI.contentColor = (_resultSuccess ? new Color(0.5f, 0.85f, 0.45f) : new Color(0.95f, 0.55f, 0.3f)); GUILayout.Label(_resultSuccess ? "Purchase Complete" : "Purchase Failed", _hdr, Array.Empty()); GUI.contentColor = contentColor; DrawHr(); GUILayout.Space(8f); GUILayout.Label(_resultText, _label, Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("OK", _btnPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { _resultText = null; } GUILayout.EndArea(); } private void DrawOwnedCharges() { bool flag = false; foreach (KeyValuePair charge in _charges) { if (charge.Value > 0) { if (!flag) { GUILayout.BeginHorizontal(Array.Empty()); flag = true; GUILayout.Label("Charges:", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); } GUILayout.Label($"{ChargeLabel(charge.Key)} x{charge.Value}", _pillOn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); GUILayout.Space(10f); } } if (flag) { GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } } private void DrawQuestProgress() { if (_questCap > 0) { string text = $"Daily quests: {_questEarned}/{_questCap}"; if (!string.IsNullOrEmpty(_questResetsIn)) { text = text + " · resets in " + _questResetsIn; } if (_questStreak > 0) { text += $" · {_questStreak}-day streak"; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(text, (_questEarned >= _questCap) ? _pillOn : _sub, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } } private string ChargeLabel(string kind) { foreach (Catalog.Sku value in Catalog.Items.Values) { if (value.Effect == "add_charges" && value.Perk == kind) { return TierSuffix.Replace(value.Name, "").Trim(); } } return kind; } private void TabButton(string label, Tab t) { if (GUILayout.Button(label, (_tab == t) ? _btnActive : _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { _tab = t; } } private void DrawHr() { GUILayout.Box(GUIContent.none, _line, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(1f), GUILayout.ExpandWidth(true) }); GUILayout.Space(4f); } private void DrawDonate() { GUILayout.Label("Support the server", _hdr, Array.Empty()); GUILayout.Label("Donating is always optional. Playing is free, and every perk is cosmetic or a weekly-limited supply - never raw power.", _sub, Array.Empty()); GUILayout.Space(8f); DrawRateCallout(); GUILayout.Label("How it works", _label, Array.Empty()); GUILayout.Label("1. Click \"Get my donation code\" below to generate your personal code.", _label, Array.Empty()); GUILayout.Label("2. Click \"Open donation portal\" - it opens in your web browser.", _label, Array.Empty()); GUILayout.Label("3. Pick a provider (Ko-fi, Patreon, or GCash/Maya).", _label, Array.Empty()); GUILayout.Label("4. On Ko-fi, paste your code into the message box - it can't be filled in for you. GCash/Maya carries the code automatically, and Patreon needs a one-time account link instead.", _label, Array.Empty()); GUILayout.Label("5. Your Valcoins are credited automatically within a few seconds.", _label, Array.Empty()); GUILayout.Space(10f); if (!_online) { GUILayout.Label("Donations aren't connected yet. Once the operator brings the service online, this button will hand you a code and a portal link.", _sub, Array.Empty()); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _donateCooldownUntil) { int num = Mathf.CeilToInt(_donateCooldownUntil - realtimeSinceStartup); GUILayout.Label($"Please wait {num}s before requesting another code", _btnDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) }); } else if (GUILayout.Button("Get my donation code", _btnPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { _donateCooldownUntil = realtimeSinceStartup + 30f; _donateStatus = "Requesting your code..."; _donateCode = null; _donateWaitingSince = realtimeSinceStartup; RpcLayer.SendAction("donate"); } if (_donateWaitingSince >= 0f && realtimeSinceStartup - _donateWaitingSince > 20f) { _donateWaitingSince = -1f; _donateCooldownUntil = 0f; _donateStatus = "The server didn't answer in time. Press the button again, and if it keeps failing tell an admin to check the server log for [Valcoin] errors — your Valcoins are safe either way."; } if (!string.IsNullOrEmpty(_donateStatus)) { GUILayout.Space(4f); GUILayout.Label(_donateStatus, _sub, Array.Empty()); } if (!string.IsNullOrEmpty(_donateCode)) { GUILayout.Space(8f); GUILayout.Label("Your donation code:", _label, Array.Empty()); GUILayout.Box(_donateCode, _codeBox, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(46f), GUILayout.ExpandWidth(true) }); GUILayout.Space(6f); if (GUILayout.Button("Copy code", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { GUIUtility.systemCopyBuffer = _donateCode; _copiedFlashUntil = realtimeSinceStartup + 2f; } if (!string.IsNullOrEmpty(_donateUrl)) { GUILayout.Space(4f); if (GUILayout.Button("Open donation portal", _btnPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { Application.OpenURL(_donateUrl); } } if (realtimeSinceStartup < _copiedFlashUntil) { GUILayout.Label("Copied to clipboard!", _sub, Array.Empty()); } if (_donateTtlMinutes > 0) { GUILayout.Label($"This code expires in about {_donateTtlMinutes} minutes.", _sub, Array.Empty()); } } GUILayout.Space(12f); DrawHr(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("Terms of Use", _linkBtn, Array.Empty())) { _showTerms = true; } GUILayout.EndHorizontal(); } private void DrawRateCallout() { if (_coinsPerUsd > 0f) { GUILayout.Box("$1 USD = " + FormatRate(_coinsPerUsd) + " Valcoins", _rateBox, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(52f), GUILayout.ExpandWidth(true) }); GUILayout.Label("Example: a $5 donation credits about " + FormatRate(_coinsPerUsd * 5f) + " Valcoins. Other currencies are converted at the same value.", _rateSub, Array.Empty()); } else if (_online) { GUILayout.Box("Exchange rate unavailable", _rateBox, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(52f), GUILayout.ExpandWidth(true) }); GUILayout.Label("The donation service didn't report a rate - ask the operator to update it.", _rateSub, Array.Empty()); } GUILayout.Space(8f); } private void DrawRateNote() { if (!(_coinsPerUsd <= 0f)) { GUILayout.Label("Exchange rate: $1 USD = " + FormatRate(_coinsPerUsd) + " Valcoins.", _sub, Array.Empty()); } } private static string FormatRate(float rate) { if (!Mathf.Approximately(rate, Mathf.Round(rate))) { return rate.ToString("0.0"); } return Mathf.RoundToInt(rate).ToString(); } private void DrawShop() { //IL_002a: 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_0043: Unknown result type (might be due to invalid IL or missing references) if (Catalog.Order.Count == 0) { GUILayout.Label("The shop is empty - the operator hasn't set up valcoin_shop.yaml yet.", _label, Array.Empty()); return; } DrawRateNote(); _shopScroll = GUILayout.BeginScrollView(_shopScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); List list = new List(); Dictionary> dictionary = new Dictionary>(); foreach (Catalog.Sku item in Catalog.Order) { string text = (string.IsNullOrEmpty(item.Category) ? "More" : item.Category); if (!dictionary.TryGetValue(text, out var value)) { value = (dictionary[text] = new List()); list.Add(text); } value.Add(item); } for (int i = 0; i < list.Count; i++) { string text2 = list[i]; List skus = dictionary[text2]; if (i > 0) { GUILayout.Space(6f); DrawHr(); GUILayout.Space(4f); } DrawCategory(text2, skus); } GUILayout.EndScrollView(); if (!_online) { GUILayout.Label("Purchasing activates once the donation service is online. Owned perks and weekly limits refresh when it reconnects.", _sub, Array.Empty()); } } private void DrawCategory(string category, List skus) { GUILayout.Label(category.ToUpperInvariant(), _catHdr, Array.Empty()); string text = null; foreach (Catalog.Sku sku in skus) { if (!string.IsNullOrEmpty(sku.CategoryDesc)) { text = sku.CategoryDesc; break; } } if (!string.IsNullOrEmpty(text)) { GUILayout.Label(text, _sub, Array.Empty()); } foreach (Catalog.Sku sku2 in skus) { if (sku2.Effect == "add_charges" && !string.IsNullOrEmpty(sku2.Perk)) { _charges.TryGetValue(sku2.Perk, out var value); GUILayout.Label($"You currently hold {value} charge(s).", _sub, Array.Empty()); break; } } GUILayout.Space(4f); foreach (Catalog.Sku sku3 in skus) { DrawSkuRow(sku3); } } private void DrawSkuRow(Catalog.Sku sku) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown bool flag = sku.Effect == "grant_perk" && OwnsSku(sku.Id); bool flag2 = sku.Effect == "grant_item" && !string.IsNullOrEmpty(sku.RequiresBoss) && !BossGateOk(sku.RequiresBoss); int weeklyCap = sku.WeeklyCap; int num = WeeklyUsed(sku.Id); int num2 = ((weeklyCap > 0) ? Mathf.Max(0, weeklyCap - num) : (-1)); bool flag3 = weeklyCap > 0 && num2 <= 0; GUILayout.BeginHorizontal(Array.Empty()); if (!string.IsNullOrEmpty(sku.PreviewImage)) { Rect rect = GUILayoutUtility.GetRect(72f, 72f, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(72f), GUILayout.Height(72f) }); Texture2D val = ImageCache.Get(sku.PreviewImage); if ((Object)(object)val != (Object)null) { GUI.DrawTexture(rect, (Texture)(object)val, (ScaleMode)2); if (GUI.Button(rect, new GUIContent("", "Click to enlarge"), GUIStyle.none)) { OpenZoom(sku); } } GUILayout.Space(8f); } string arg = (string.IsNullOrEmpty(sku.Description) ? "" : (" (" + sku.Description + ")")); GUILayout.Label($"{sku.Name} - {sku.Price}c{arg}", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (flag) { GUILayout.Label("Already Purchased", _owned, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) }); } else if (flag2) { DisabledButton("Locked", 110f); } else if (flag3) { DisabledButton("Limit reached", 130f); } else if (_online) { if (GUILayout.Button("Buy", _btn, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(90f), GUILayout.Height(30f) })) { _confirmSku = sku; } } else { DisabledButton("Buy", 90f); } GUILayout.EndHorizontal(); if (sku.Effect == "grant_item") { string text = BundleContents(sku.Item); if (!string.IsNullOrEmpty(text)) { GUILayout.Label(" " + text, _dim, Array.Empty()); } } else if (sku.Effect == "armor_vfx") { GUILayout.Label(" Hovers at your shoulder; renames your helmet \"... " + ArmorVfxSuffix(sku) + "\"", _dim, Array.Empty()); } if (flag2) { GUILayout.Label(" Unlocks after " + FriendlyBoss(sku.RequiresBoss), _sub, Array.Empty()); } else if (sku.Effect == "grant_item" && weeklyCap > 0) { if (flag3) { GUILayout.Label(" Weekly limit reached - resets in " + _weekResetsIn, _sub, Array.Empty()); } else { GUILayout.Label($" {num2} of {weeklyCap} left this week", _sub, Array.Empty()); } } GUILayout.Space(8f); } private static string BundleContents(string itemSpec) { if (string.IsNullOrEmpty(itemSpec)) { return ""; } List list = new List(); string[] array = itemSpec.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string input = text; string text2 = "1"; int num = text.LastIndexOf(':'); if (num > 0) { input = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); if (text3.Length > 0) { text2 = text3; } } string text4 = CamelBoundary.Replace(input, " "); list.Add((text2 == "1") ? text4 : (text4 + " x" + text2)); } return string.Join(", ", list.ToArray()); } private void DrawGift() { GUILayout.Label("Gift Valcoins", _hdr, Array.Empty()); GUILayout.Label("Send Valcoins to another player on the server.", _label, Array.Empty()); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("To:", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); _giftTo = GUILayout.TextField(_giftTo ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) }); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Amount:", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); _giftAmount = GUILayout.TextField(_giftAmount ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }); GUILayout.EndHorizontal(); GUILayout.Space(6f); if (_online) { if (GUILayout.Button("Send gift", _btn, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(28f), GUILayout.Width(140f) })) { if (string.IsNullOrWhiteSpace(_giftTo) || string.IsNullOrWhiteSpace(_giftAmount)) { PushLog("Fill in both fields."); } else { RpcLayer.SendAction("gift:" + _giftTo.Trim() + ":" + _giftAmount.Trim()); } } } else { GUILayout.Label("Send gift", _btnDim, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(28f), GUILayout.Width(140f) }); } } private void DrawPatrons() { GUILayout.Label("Top Patrons", _hdr, Array.Empty()); GUILayout.Space(4f); if (!_online) { GUILayout.Label("The patron leaderboard appears here once the donation service is online.", _label, Array.Empty()); return; } if (_topDonors.Count == 0) { GUILayout.Label("No patrons yet - be the first! Head to the Donate tab.", _label, Array.Empty()); } else { foreach (TopEntry topDonor in _topDonors) { GUILayout.Label(string.Format(" {0}. {1} - {2} coins", topDonor.rank, topDonor.name ?? "Anonymous", topDonor.total_coins), _label, Array.Empty()); } } GUILayout.Space(8f); if (GUILayout.Button("Refresh", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { _topDonors.Clear(); RefreshStateSoon(force: true); } } private void DrawAdmin() { GUILayout.Label("Manually adjust a player's Valcoin balance.", _label, Array.Empty()); GUILayout.Label("Give adds Valcoins to the player; Remove subtracts them (e.g. to correct a mistake or claw back an abuse).", _sub, Array.Empty()); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Player:", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); _adminTarget = GUILayout.TextField(_adminTarget ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) }); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Amount:", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); _adminAmount = GUILayout.TextField(_adminAmount ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }); GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Give", _btn, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(28f), GUILayout.Width(100f) })) { SendAdminAdjust(give: true); } if (GUILayout.Button("Remove", _btn, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(28f), GUILayout.Width(100f) })) { SendAdminAdjust(give: false); } GUILayout.EndHorizontal(); } private void SendAdminAdjust(bool give) { if (string.IsNullOrWhiteSpace(_adminTarget) || string.IsNullOrWhiteSpace(_adminAmount)) { PushLog("Fill in both fields."); } else { RpcLayer.SendAction((give ? "admin_give:" : "admin_remove:") + _adminTarget.Trim() + ":" + _adminAmount.Trim()); } } private void DrawLog() { //IL_0017: 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_0034: Unknown result type (might be due to invalid IL or missing references) GUILayout.Label("Messages", _label, Array.Empty()); _logScroll = GUILayout.BeginScrollView(_logScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(80f) }); for (int num = _log.Count - 1; num >= 0; num--) { GUILayout.Label("- " + _log[num], _logLine, Array.Empty()); } GUILayout.EndScrollView(); } private void PushLog(string msg) { _log.Add(msg); if (_log.Count > 12) { _log.RemoveAt(0); } } private void OpenZoom(Catalog.Sku sku) { _zoomImage = sku.PreviewImage; _zoomCaption = sku.Name; } private void DrawZoomModal() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 //IL_0119: 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_0061: Invalid comparison between Unknown and I4 //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0127: 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_01b2: Unknown result type (might be due to invalid IL or missing references) Texture2D val = ImageCache.Get(_zoomImage); if ((Object)(object)val == (Object)null) { _zoomImage = null; return; } GUI.Box(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), GUIContent.none, _line); if ((int)Event.current.type == 4 && (int)Event.current.keyCode == 27) { _zoomImage = null; Event.current.Use(); return; } float num = (float)Screen.width * 0.8f; float num2 = (float)Screen.height * 0.8f - 70f; float num3 = Mathf.Min(new float[3] { num / (float)((Texture)val).width, num2 / (float)((Texture)val).height, 1f }); float num4 = (float)((Texture)val).width * num3; float num5 = (float)((Texture)val).height * num3; float num6 = num4 + 36f; float num7 = num5 + 100f; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((float)Screen.width - num6) / 2f, ((float)Screen.height - num7) / 2f, num6, num7); if ((int)Event.current.type == 0 && !((Rect)(ref val2)).Contains(Event.current.mousePosition)) { _zoomImage = null; Event.current.Use(); return; } GUI.Box(val2, GUIContent.none, _bg); GUI.DrawTexture(new Rect(((Rect)(ref val2)).x + 18f, ((Rect)(ref val2)).y + 18f, num4, num5), (Texture)(object)val, (ScaleMode)2); if (!string.IsNullOrEmpty(_zoomCaption)) { GUI.Label(new Rect(((Rect)(ref val2)).x + 18f, ((Rect)(ref val2)).y + num5 + 22f, num4, 24f), _zoomCaption, _rateSub); } if (GUI.Button(new Rect(((Rect)(ref val2)).x + num6 / 2f - 60f, ((Rect)(ref val2)).y + num5 + 50f, 120f, 32f), "Close", _btn)) { _zoomImage = null; } } private void DrawConfirmModal() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Expected O, but got Unknown //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_03a3: Unknown result type (might be due to invalid IL or missing references) Catalog.Sku confirmSku = _confirmSku; if (confirmSku == null) { return; } GUI.Box(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), GUIContent.none, _line); bool flag = confirmSku.Effect == "add_charges"; bool flag2 = confirmSku.Effect == "armor_vfx"; string text = null; if (flag2) { string text2 = ArmorVfx.EquippedAura((Humanoid)(object)Player.m_localPlayer, "head"); if (text2 != null && ArmorVfx.Registry.TryGetValue(text2, out var value)) { text = ((text2 == confirmSku.Perk) ? ("Your equipped helmet already has the " + value.Display + " familiar bound to it.") : ("Warning: your equipped helmet already has the " + value.Display + " familiar bound to it. Buying this will overwrite it with " + ArmorVfxDisplay(confirmSku) + ".")); } } int num = ((!string.IsNullOrEmpty(confirmSku.PreviewImage)) ? 230 : 0); int num2 = Mathf.Min(flag2 ? 480 : 460, Screen.width - 60); int num3 = Mathf.Min((flag ? 300 : ((!flag2) ? 240 : ((text != null) ? 400 : 340))) + num, Screen.height - 60); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)(Screen.width - num2) / 2f, (float)(Screen.height - num3) / 2f, (float)num2, (float)num3); GUI.Box(val, GUIContent.none, _bg); GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 18f, ((Rect)(ref val)).y + 18f, ((Rect)(ref val)).width - 36f, ((Rect)(ref val)).height - 36f)); GUILayout.Label("Confirm Purchase", _hdr, Array.Empty()); DrawHr(); GUILayout.Space(8f); if (!string.IsNullOrEmpty(confirmSku.PreviewImage)) { Rect rect = GUILayoutUtility.GetRect(190f, 190f, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(190f), GUILayout.ExpandWidth(true) }); Texture2D val2 = ImageCache.Get(confirmSku.PreviewImage); if ((Object)(object)val2 != (Object)null) { GUI.DrawTexture(rect, (Texture)(object)val2, (ScaleMode)2); if (GUI.Button(rect, new GUIContent("", "Click to enlarge"), GUIStyle.none)) { OpenZoom(confirmSku); } GUILayout.Label("(click the image to enlarge)", _rateSub, Array.Empty()); } GUILayout.Space(6f); } GUILayout.Label($"Buy \"{confirmSku.Name}\" for {confirmSku.Price} Valcoins?", _label, Array.Empty()); GUILayout.Space(4f); GUILayout.Label($"Your balance: {_balance} Valcoins", _sub, Array.Empty()); if (flag) { GUILayout.Space(8f); GUILayout.Label("Note: charges are processed on the server - it may take a few seconds for your new charge count to appear.", _sub, Array.Empty()); } if (flag2) { GUILayout.Space(8f); GUILayout.Label("The familiar is bound to your equipped helmet and hovers at your shoulder.", _label, Array.Empty()); string text3 = ArmorVfxStats(confirmSku); GUILayout.Label("You must have a helmet equipped. It is renamed \"... " + ArmorVfxSuffix(confirmSku) + "\"." + ((text3 != "") ? (" Grants feather fall and " + text3 + ".") : ""), _sub, Array.Empty()); if (text != null) { GUILayout.Space(6f); Color color = GUI.color; GUI.color = new Color(1f, 0.6f, 0.4f); GUILayout.Label(text, _label, Array.Empty()); GUI.color = color; } } GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Yes, buy", _btnPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { RpcLayer.SendAction("buy:" + confirmSku.Id); _pendingBuySku = confirmSku.Id; _pendingBuyDeadline = Time.realtimeSinceStartup + 12f; _resultExtra = null; _confirmSku = null; } GUILayout.Space(10f); if (GUILayout.Button("Cancel", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { _confirmSku = null; } GUILayout.EndHorizontal(); GUILayout.EndArea(); } private static string ArmorVfxSuffix(Catalog.Sku sku) { if (!ArmorVfx.Registry.TryGetValue(sku.Perk ?? "", out var value)) { return "of ..."; } return value.Suffix; } private static string ArmorVfxDisplay(Catalog.Sku sku) { if (!ArmorVfx.Registry.TryGetValue(sku.Perk ?? "", out var value)) { return sku.Name; } return value.Display; } private static string ArmorVfxStats(Catalog.Sku sku) { if (!ArmorVfx.Registry.TryGetValue(sku.Perk ?? "", out var value)) { return ""; } return ArmorVfx.StatsText(value); } private void DrawTermsModal(Rect parent) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: 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) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) GUI.Box(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), GUIContent.none, _line); int num = Mathf.Min(560, Screen.width - 60); int num2 = Mathf.Min(460, Screen.height - 60); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)(Screen.width - num) / 2f, (float)(Screen.height - num2) / 2f, (float)num, (float)num2); GUI.Box(val, GUIContent.none, _bg); GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 16f, ((Rect)(ref val)).y + 16f, ((Rect)(ref val)).width - 32f, ((Rect)(ref val)).height - 32f)); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Terms of Use - Donations", _hdr, Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("X", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) })) { _showTerms = false; } GUILayout.EndHorizontal(); DrawHr(); _termsScroll = GUILayout.BeginScrollView(_termsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); string[] termsText = TermsText; foreach (string text in termsText) { if (text.Length == 0) { GUILayout.Space(6f); } else { GUILayout.Label(text, _label, Array.Empty()); } } GUILayout.EndScrollView(); GUILayout.Space(6f); if (GUILayout.Button("I understand", _btnPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { _showTerms = false; } GUILayout.EndArea(); } private bool OwnsSku(string skuId) { if (!string.IsNullOrEmpty(skuId)) { return _ownedSkus.Contains(skuId); } return false; } private int WeeklyUsed(string skuId) { if (string.IsNullOrEmpty(skuId) || !_weeklyUsage.TryGetValue(skuId, out var value)) { return 0; } return value; } private static bool BossGateOk(string bossKey) { if (string.IsNullOrEmpty(bossKey)) { return true; } try { if ((Object)(object)ZoneSystem.instance == (Object)null) { return true; } return ZoneSystem.instance.GetGlobalKey(bossKey); } catch { return true; } } private void DisabledButton(string label, float width) { GUILayout.Label(label, _btnDim, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(width), GUILayout.Height(30f) }); } private static string FriendlyBoss(string key) { object obj; switch (key) { case "defeated_eikthyr": return "Eikthyr"; case "defeated_gdking": return "The Elder"; case "defeated_bonemass": return "Bonemass"; case "defeated_dragon": return "Moder"; case "defeated_goblinking": return "Yagluth"; case "defeated_queen": return "The Queen"; case "defeated_fader": return "the Ashlands boss"; default: if (key.StartsWith("defeated_")) { obj = key.Substring("defeated_".Length); break; } goto case null; case null: obj = key ?? ""; break; } string text = (string)obj; string text2; if (text.Length <= 0) { text2 = key; if (text2 == null) { return ""; } } else { text2 = char.ToUpper(text[0]) + text.Substring(1); } return text2; } } public static class DonationUiState { public static bool PanelOpen; private static FieldInfo _mouseCapture; public static bool AnyOpen => PanelOpen; public static void SetMouseCapture(bool value) { GameCamera instance = GameCamera.instance; if (!((Object)(object)instance == (Object)null)) { if (_mouseCapture == null) { _mouseCapture = typeof(GameCamera).GetField("m_mouseCapture", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } _mouseCapture?.SetValue(instance, value); } } } [HarmonyPatch(typeof(Player), "TakeInput")] internal static class DonationPlayerTakeInputPatch { private static void Postfix(ref bool __result) { if (DonationUiState.AnyOpen) { __result = false; } } } [HarmonyPatch(typeof(PlayerController), "TakeInput", new Type[] { typeof(bool) })] internal static class DonationPlayerControllerTakeInputPatch { private static void Postfix(ref bool __result) { if (DonationUiState.AnyOpen) { __result = false; } } } internal static class ZInputRef { public static readonly Type Type = AccessTools.TypeByName("ZInput"); } [HarmonyPatch] internal static class DonationZInputButtonPatch { private static readonly HashSet BoolReads = new HashSet { "GetButton", "GetButtonDown", "GetButtonUp", "GetKey", "GetKeyDown", "GetKeyUp", "GetMouseButton", "GetMouseButtonDown", "GetMouseButtonUp" }; private static IEnumerable TargetMethods() { Type z = ZInputRef.Type; List patched = new List(); if (z != null) { foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(z)) { if (declaredMethod.IsStatic && !(declaredMethod.ReturnType != typeof(bool)) && BoolReads.Contains(declaredMethod.Name)) { patched.Add($"{declaredMethod.Name}/{declaredMethod.GetParameters().Length}"); yield return declaredMethod; } } } Debug.Log((object)("[Valcoin] ZInput input-block: patched " + patched.Count + " method(s): " + string.Join(", ", patched) + $" (ZInput resolved: {z != null}).")); } private static void Postfix(ref bool __result) { if (DonationUiState.AnyOpen) { __result = false; } } } [HarmonyPatch] internal static class DonationMinimapGuardPatch { private static IEnumerable TargetMethods() { MethodInfo[] methods = typeof(Minimap).GetMethods(AccessTools.all); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "SetMapMode") { yield return methodInfo; } } } private static bool Prefix() { return !DonationUiState.AnyOpen; } } [HarmonyPatch] internal static class DonationInventoryGuardPatch { private static IEnumerable TargetMethods() { MethodInfo[] methods = typeof(InventoryGui).GetMethods(AccessTools.all); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "Show") { yield return methodInfo; } } } private static bool Prefix() { return !DonationUiState.AnyOpen; } } [HarmonyPatch] internal static class DonationZInputScrollPatch { private static IEnumerable TargetMethods() { Type type = ZInputRef.Type; if (!(type == null)) { MethodInfo methodInfo = AccessTools.Method(type, "GetMouseScrollWheel", (Type[])null, (Type[])null); if (methodInfo != null) { yield return methodInfo; } } } private static void Postfix(ref float __result) { if (DonationUiState.AnyOpen) { __result = 0f; } } } public static class DonateFlow { private class ClaimResp { public string code; public string expires_at; public string donation_url; public int ttl_minutes; } public static void Run(string steam64, string senderName, Action reply) { if (string.IsNullOrEmpty(steam64)) { reply("__DONATE_ERR__:Couldn't resolve your Steam ID."); return; } if (!Config.Ready) { reply("__DONATE_ERR__:Donations aren't set up on this server yet."); return; } ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/claim", new { steam64 = steam64, name = senderName }, delegate(bool ok, ClaimResp r, string err) { if (!ok || r == null) { reply("__DONATE_ERR__:Couldn't reach the donation service. Please try again."); Debug.LogWarning((object)("[Valcoin] donate action failed: " + err)); } else { reply($"__DONATE__:{r.code}|{r.donation_url}|{r.ttl_minutes}"); } })); } } public static class GiftFlow { private class TransferResp { public string status; public int balance; public int transferred; } public static void Run(string fromSteam64, string fromName, string toName, int amount, Action reply) { if (string.IsNullOrEmpty(fromSteam64)) { reply("Couldn't resolve your Steam ID."); return; } if (string.IsNullOrEmpty(toName)) { reply("Specify a recipient."); return; } if (amount <= 0) { reply("Amount must be positive."); return; } if (!ResolveTargetByName(toName, out var steam)) { reply("Player \"" + toName + "\" not found or no Steam ID."); return; } if (steam == fromSteam64) { reply("You can't gift yourself."); return; } if (CoinManager.TryGetKnownBalance(fromSteam64, out var balance) && balance < amount) { reply($"Not enough Valcoins ({balance} / {amount})."); return; } string idempotency_key = $"gift-{Guid.NewGuid():N}"; ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/transfer", new { from_steam64 = fromSteam64, to_steam64 = steam, coins = amount, idempotency_key = idempotency_key, from_name = fromName, to_name = toName }, delegate(bool ok, TransferResp r, string err) { if (!ok || r == null) { reply("Gift failed. (" + (err ?? "unknown") + ")"); } else { CoinManager.SetBalance(fromSteam64, r.balance); reply($"Sent {amount} Valcoins to {toName}. Your balance: {r.balance}"); } })); } private static bool ResolveTargetByName(string name, out string steam64) { steam64 = null; if ((Object)(object)ZNet.instance == (Object)null) { return false; } foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (connectedPeer.m_playerName != null && connectedPeer.m_playerName.Equals(name, StringComparison.OrdinalIgnoreCase)) { steam64 = SteamIdResolver.FromPeer(connectedPeer); return !string.IsNullOrEmpty(steam64); } } return false; } } public static class TopDonorsFetcher { private class TopResp { public Entry[] donors; } private class Entry { public int rank; public string name; public int total_coins; } public static void Fetch(Action emit, int limit = 5) { if (!Config.Ready) { emit("⚠\ufe0f Leaderboard unavailable."); return; } ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Get($"/api/leaderboard/top?limit={limit}", delegate(bool ok, TopResp r, string err) { if (!ok || r?.donors == null) { emit("Couldn't fetch leaderboard. (" + (err ?? "unknown") + ")"); } else if (r.donors.Length == 0) { emit("No donors yet. Be the first - open the Donate tab!"); } else { emit("Top donors:"); Entry[] donors = r.donors; foreach (Entry entry in donors) { emit(string.Format(" {0}. {1} - {2} coins", entry.rank, entry.name ?? "Anonymous", entry.total_coins)); } } })); } } public class GrantPoller : MonoBehaviour { public class Grant { public long id; public string steam64; public int coins; public string source; public string note; public string created_at; } private class PendingResponse { public List grants; } private class AckRequest { public List ids; } private class AckResponse { public int acked; } private Coroutine _loop; private void Start() { _loop = ((MonoBehaviour)this).StartCoroutine(Loop()); } private void OnDestroy() { if (_loop != null) { ((MonoBehaviour)this).StopCoroutine(_loop); } } private IEnumerator Loop() { while ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { yield return (object)new WaitForSeconds(2f); } while (true) { yield return (object)new WaitForSeconds(Mathf.Max(2f, Config.PollIntervalSeconds)); if (Config.Ready) { yield return Tick(); } } } private IEnumerator Tick() { PendingResponse pending = null; string err = null; yield return BackendClient.Get("/api/grants/pending?limit=50", delegate(bool ok, PendingResponse r, string e) { if (ok) { pending = r; } else { err = e; } }); if (err != null) { Debug.LogWarning((object)("[Valcoin] poll failed: " + err)); } else { if (pending?.grants == null || pending.grants.Count == 0) { yield break; } List list = new List(pending.grants.Count); foreach (Grant grant in pending.grants) { try { bool num = CoinManager.TryApplyGrant(grant.id, grant.steam64, grant.coins); int balance = CoinManager.GetBalance(grant.steam64); if (num) { Player val = SteamIdResolver.OnlinePlayerFor(grant.steam64); if ((Object)(object)val != (Object)null) { ((Character)val).Message((MessageType)1, $"+{grant.coins} Valcoins! Balance: {balance}", 0, (Sprite)null); } else { Debug.Log((object)$"[Valcoin] +{grant.coins} to {grant.steam64} (offline). Balance: {balance}"); } } else { Debug.Log((object)$"[Valcoin] grant {grant.id} replay (already applied locally); will re-ack."); } list.Add(grant.id); } catch (Exception ex) { Debug.LogError((object)$"[Valcoin] failed to apply grant {grant.id}: {ex.Message}"); } } if (list.Count <= 0) { yield break; } yield return BackendClient.Post("/api/grants/ack", new AckRequest { ids = list }, delegate(bool ok, AckResponse r, string e) { if (!ok) { Debug.LogWarning((object)("[Valcoin] ack failed (will retry next tick): " + e)); } }); } } } public static class ImageCache { private enum Status { Loading, Ready, Failed } private class Entry { public Status Status; public Texture2D Texture; } private static readonly Dictionary _cache = new Dictionary(); public static Texture2D Get(string source) { if (string.IsNullOrEmpty(source)) { return null; } if (_cache.TryGetValue(source, out var value)) { if (value.Status != Status.Ready) { return null; } return value.Texture; } Entry entry = new Entry { Status = Status.Loading }; _cache[source] = entry; ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(Load(source, entry)); return null; } private static IEnumerator Load(string source, Entry entry) { string text = ToUrl(source); if (text == null) { entry.Status = Status.Failed; yield break; } UnityWebRequest req = UnityWebRequestTexture.GetTexture(text); try { req.timeout = 15; yield return req.SendWebRequest(); if ((int)req.result != 1) { Debug.LogWarning((object)("[Valcoin] Shop image load failed (" + source + "): " + req.error)); entry.Status = Status.Failed; yield break; } Texture2D content = DownloadHandlerTexture.GetContent(req); if ((Object)(object)content == (Object)null) { entry.Status = Status.Failed; yield break; } ((Texture)content).wrapMode = (TextureWrapMode)1; entry.Texture = content; entry.Status = Status.Ready; } finally { ((IDisposable)req)?.Dispose(); } } private static string ToUrl(string source) { try { if (source.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || source.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || source.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) { return source; } string text = (Path.IsPathRooted(source) ? source : Path.Combine(Paths.ConfigPath, source)); if (!File.Exists(text)) { Debug.LogWarning((object)("[Valcoin] Shop image not found: " + text)); return null; } return new Uri(text).AbsoluteUri; } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin] Shop image path error (" + source + "): " + ex.Message)); return null; } } } public static class LocalIdentity { private static string _cached; public static string Steam64() { if (!string.IsNullOrEmpty(_cached)) { return _cached; } try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type; try { type = assembly.GetType("Steamworks.SteamUser"); } catch { continue; } if (!(type == null)) { object obj2 = type.GetMethod("GetSteamID", BindingFlags.Static | BindingFlags.Public)?.Invoke(null, null); string text = obj2?.GetType().GetField("m_SteamID")?.GetValue(obj2)?.ToString(); if (!string.IsNullOrEmpty(text) && text.Length == 17 && text.StartsWith("7656119")) { _cached = text; Debug.Log((object)("[Valcoin] Local Steam64 resolved via Steamworks: " + text)); return text; } } } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin] Steamworks id lookup failed: " + ex.Message)); } try { object obj3 = Type.GetType("ZSteamMatchmaking, assembly_valheim")?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); string text2 = obj3?.GetType().GetMethod("GetSteamID")?.Invoke(obj3, null)?.ToString(); if (!string.IsNullOrEmpty(text2) && text2.Length == 17 && text2.StartsWith("7656119")) { _cached = text2; Debug.Log((object)("[Valcoin] Local Steam64 resolved via ZSteamMatchmaking: " + text2)); return text2; } } catch { } return null; } } public static class PerkManager { public class Pos { public float x; public float y; public float z; } public class PlayerPerks { public HashSet perks = new HashSet(); public Dictionary charges = new Dictionary(); public string title; public Pos home; public string homeCooldownUntilUtc; } private class State { public Dictionary players = new Dictionary(); } private static readonly string SaveDir = Path.Combine(Paths.ConfigPath, "valcoin_data"); private static readonly string SaveFile = Path.Combine(SaveDir, "perks.json"); private static State _state = new State(); public static void Load() { try { Directory.CreateDirectory(SaveDir); if (File.Exists(SaveFile)) { _state = JsonConvert.DeserializeObject(File.ReadAllText(SaveFile)) ?? new State(); if (_state.players == null) { _state.players = new Dictionary(); } } } catch (Exception ex) { Debug.LogError((object)("[PerkManager] Failed to load: " + ex.Message)); _state = new State(); } } public static void Save() { try { File.WriteAllText(SaveFile, JsonConvert.SerializeObject((object)_state, (Formatting)1)); } catch (Exception ex) { Debug.LogError((object)("[PerkManager] Failed to save: " + ex.Message)); } } public static PlayerPerks Get(string steam64) { if (string.IsNullOrEmpty(steam64)) { return new PlayerPerks(); } if (!_state.players.TryGetValue(steam64, out var value)) { value = new PlayerPerks(); _state.players[steam64] = value; } return value; } public static bool Has(string steam64, string perk) { if (string.IsNullOrEmpty(steam64)) { return false; } if (_state.players.TryGetValue(steam64, out var value)) { return value.perks.Contains(perk); } return false; } public static void Grant(string steam64, string perk) { Get(steam64).perks.Add(perk); Save(); } public static void AddCharges(string steam64, string perk, int n) { PlayerPerks playerPerks = Get(steam64); playerPerks.charges.TryGetValue(perk, out var value); playerPerks.charges[perk] = value + n; Save(); } public static int Charges(string steam64, string perk) { if (string.IsNullOrEmpty(steam64)) { return 0; } if (!_state.players.TryGetValue(steam64, out var value) || !value.charges.TryGetValue(perk, out var value2)) { return 0; } return value2; } public static bool ConsumeCharge(string steam64, string perk) { PlayerPerks playerPerks = Get(steam64); if (!playerPerks.charges.TryGetValue(perk, out var value) || value <= 0) { return false; } playerPerks.charges[perk] = value - 1; Save(); return true; } public static void SetTitle(string steam64, string title) { Get(steam64).title = title; Save(); } public static string Title(string steam64) { if (!_state.players.TryGetValue(steam64, out var value)) { return null; } return value.title; } public static void SetHome(string steam64, Vector3 pos) { //IL_000c: 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_0024: Unknown result type (might be due to invalid IL or missing references) Get(steam64).home = new Pos { x = pos.x, y = pos.y, z = pos.z }; Save(); } public static Vector3? Home(string steam64) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!_state.players.TryGetValue(steam64, out var value) || value.home == null) { return null; } return new Vector3(value.home.x, value.home.y, value.home.z); } public static int HomeCooldownRemaining(string steam64) { PlayerPerks playerPerks = Get(steam64); if (string.IsNullOrEmpty(playerPerks.homeCooldownUntilUtc)) { return 0; } if (!DateTime.TryParse(playerPerks.homeCooldownUntilUtc, null, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result)) { return 0; } int val = (int)(result - DateTime.UtcNow).TotalSeconds; return Math.Max(0, val); } public static void StartHomeCooldown(string steam64, int seconds) { Get(steam64).homeCooldownUntilUtc = DateTime.UtcNow.AddSeconds(seconds).ToString("o"); Save(); } } [BepInPlugin("com.taeguk.valheimdonations", "Valheim Donations", "5.20.0")] public class Plugin : BaseUnityPlugin { public static HashSet AdminSteamIDs = new HashSet(); private Harmony _harmony; private static readonly string AdminConfigPath = Path.Combine(Paths.ConfigPath, "valcoin_admins.yaml"); private void Awake() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_004e: 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_005f: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Valheim Donations] Plugin loaded"); Config.Load(); EnsureAdminFile(); LoadAdmins(); CoinManager.Load(); PerkManager.Load(); Catalog.Load(); QuestCatalog.Load(); GameObject val = new GameObject("ValcoinGrantPoller"); val.AddComponent(); Object.DontDestroyOnLoad((Object)val); GameObject val2 = new GameObject("ValcoinCatalogSync"); val2.AddComponent(); Object.DontDestroyOnLoad((Object)val2); _harmony = new Harmony("com.taeguk.valheimdonations"); _harmony.PatchAll(); ((MonoBehaviour)this).StartCoroutine(InitRpcsWhenReady()); SpawnClientUiIfNotServer(); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Startup complete. Admins: {AdminSteamIDs.Count}, Backend ready: {Config.Ready}"); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private IEnumerator InitRpcsWhenReady() { while ((Object)(object)ZNet.instance == (Object)null) { yield return null; } if (ZNet.instance.IsServer()) { yield return RpcLayer.RegisterWhenReady(serverSide: true); } if (!ZNet.instance.IsDedicated()) { yield return RpcLayer.RegisterWhenReady(serverSide: false); } } private void SpawnClientUiIfNotServer() { ((MonoBehaviour)this).StartCoroutine(SpawnUiWhenZnetReady()); } private IEnumerator SpawnUiWhenZnetReady() { while ((Object)(object)ZNet.instance == (Object)null) { yield return null; } if (!ZNet.instance.IsServer() || !ZNet.instance.IsDedicated()) { GameObject val = new GameObject("ValcoinDonationPanel"); val.AddComponent(); Object.DontDestroyOnLoad((Object)val); GameObject val2 = new GameObject("ValcoinSoulkeeperPoller"); val2.AddComponent(); Object.DontDestroyOnLoad((Object)val2); GameObject val3 = new GameObject("ValcoinArmorVfxManager"); val3.AddComponent(); Object.DontDestroyOnLoad((Object)val3); GameObject val4 = new GameObject("ValcoinQuestWatcher"); val4.AddComponent(); Object.DontDestroyOnLoad((Object)val4); } } private static void EnsureAdminFile() { try { Directory.CreateDirectory(Paths.ConfigPath); if (!File.Exists(AdminConfigPath)) { File.WriteAllText(AdminConfigPath, "# Valcoin Admins (Steam64 IDs)\r\n# ------------------------------------------------------------\r\n# Add Steam64 IDs here to grant admin permission for the Admin tab in the\r\n# F8 quick panel (give/remove a player's Valcoin balance).\r\n#\r\n# Find your Steam64 at https://steamid.io\r\n# Restart the server after changes.\r\nadmins:\r\n - 76561198012345678 # <-- replace\r\n"); Debug.LogWarning((object)("[Valcoin] Created admin file template at: " + AdminConfigPath)); } } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Failed to create admin YAML: " + ex.Message)); } } private static void LoadAdmins() { try { HashSet hashSet = new HashSet(); if (!File.Exists(AdminConfigPath)) { AdminSteamIDs = hashSet; return; } bool flag = false; Regex regex = new Regex("^\\s*-\\s*(\\d{17})\\b", RegexOptions.Compiled); string[] array = File.ReadAllLines(AdminConfigPath); for (int i = 0; i < array.Length; i++) { string text = array[i].TrimEnd(Array.Empty()); if (text.TrimStart(Array.Empty()).StartsWith("#")) { continue; } if (!flag) { if (Regex.IsMatch(text, "^\\s*admins\\s*:\\s*$")) { flag = true; } continue; } Match match = regex.Match(text); if (match.Success) { hashSet.Add(match.Groups[1].Value); } else if (Regex.IsMatch(text, "^\\s*\\w+\\s*:\\s*$")) { break; } } AdminSteamIDs = hashSet; Debug.Log((object)$"[Valcoin] Loaded {hashSet.Count} admin Steam64 ID(s)."); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Failed to load admin YAML: " + ex.Message)); AdminSteamIDs = new HashSet(); } } } public static class QuestCatalog { public class Quest { public string Id; public string Name; public int Coins; public string Period = "daily"; public bool Capped = true; } public const string KeyPrefix = "VC.Q."; private static readonly string QuestPath = Path.Combine(Paths.ConfigPath, "valcoin_quests.yaml"); private static readonly Regex QuestRe = new Regex("^\\s{2}([A-Za-z0-9_]+)\\s*:\\s*$", RegexOptions.Compiled); private static readonly Regex FieldRe = new Regex("^\\s{4}([a-z_]+)\\s*:\\s*(.+?)\\s*$", RegexOptions.Compiled); public static Dictionary Items { get; private set; } = new Dictionary(); public static List Order { get; private set; } = new List(); public static string KeyFor(string questId) { return "VC.Q." + questId; } public static Quest Get(string questId) { if (string.IsNullOrEmpty(questId)) { return null; } if (!Items.TryGetValue(questId, out var value)) { return null; } return value; } public static void Load() { EnsureFile(); try { Parse(File.ReadAllLines(QuestPath)); Debug.Log((object)$"[Valcoin] Quest catalog loaded: {Items.Count} quest(s)."); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Failed to parse quest catalog: " + ex.Message)); Items = new Dictionary(); Order = new List(); } } private static void EnsureFile() { if (File.Exists(QuestPath)) { return; } try { File.WriteAllText(QuestPath, "# Valcoin quest rewards\r\n# -----------------------------------------------------------------------\r\n# Maps a ServerGuide quest to its Valcoin payout. The quest itself lives in\r\n# ServerGuide's own config (guidance.valcoin-quests.yaml); all it does to earn\r\n# coins is set the player key \"VC.Q.\" with a set_player_key reward.\r\n#\r\n# coins: payout. Daily quests are clamped by the backend's per-day cap, so\r\n# the pool below deliberately sums to more than a player can earn.\r\n# period: daily = once per UTC day · once = a single time per character\r\n# capped: false = exempt from the daily cap. Use it for EVENT prizes\r\n# (tournament purses, bounty rewards) — a 100-coin prize squeezed\r\n# through an 8/day allowance pays 8 at best. Exempt payouts are paid\r\n# in full and don't eat anyone's daily allowance. Defaults to true.\r\n# Exempt is not unlimited: a daily quest still pays at most once per\r\n# UTC day, which is what bounds it instead of the coin cap.\r\n#\r\n# The backend is the only authority on whether a report actually pays — this\r\n# file just prices the quests. Edit and restart the server to apply changes.\r\nquests:\r\n\r\n # ---------- One-time onboarding ----------\r\n vc_welcome:\r\n name: \"The Patron's Welcome\"\r\n coins: 30\r\n period: once\r\n\r\n # ---------- Dailies ----------\r\n daily_horn:\r\n name: \"Answer the Horn\"\r\n coins: 2\r\n period: daily\r\n\r\n daily_hunt:\r\n name: \"Thin the Wilds\"\r\n coins: 3\r\n period: daily\r\n\r\n daily_tame:\r\n name: \"Tend the Beasts\"\r\n coins: 2\r\n period: daily\r\n\r\n daily_lord:\r\n name: \"Fell a Lord\"\r\n coins: 8\r\n period: daily\r\n\r\n daily_bond:\r\n name: \"Forge a Bond\"\r\n coins: 5\r\n period: daily\r\n\r\n # ---------- Event prizes (uncapped) ----------\r\n # Paid by sibling mods for winning something, not for logging in, so they sit\r\n # outside the daily allowance. Delete any you don't run.\r\n\r\n # Lost Scrolls II — Valcoin tournament champion's purse.\r\n ls_tournament_prize:\r\n name: \"Tournament Champion\"\r\n coins: 100\r\n period: daily\r\n capped: false\r\n\r\n # Lost Scrolls II — bounty hunting, one entry per tier.\r\n ls_bounty_t1:\r\n name: \"Bounty Answered (Marked)\"\r\n coins: 5\r\n period: daily\r\n capped: false\r\n\r\n ls_bounty_t2:\r\n name: \"Bounty Answered (Hunted)\"\r\n coins: 10\r\n period: daily\r\n capped: false\r\n\r\n ls_bounty_t3:\r\n name: \"Bounty Answered (Wanted)\"\r\n coins: 15\r\n period: daily\r\n capped: false\r\n\r\n ls_bounty_t4:\r\n name: \"Bounty Answered (Dread)\"\r\n coins: 25\r\n period: daily\r\n capped: false\r\n\r\n ls_bounty_t5:\r\n name: \"Bounty Answered (Accursed)\"\r\n coins: 40\r\n period: daily\r\n capped: false\r\n"); Debug.Log((object)("[Valcoin] Created quest catalog template at " + QuestPath + ".")); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Failed to create quest catalog: " + ex.Message)); } } private static void Parse(string[] lines) { Dictionary items = new Dictionary(); List order = new List(); bool flag = false; Quest quest = null; foreach (string text in lines) { if (string.IsNullOrWhiteSpace(text) || text.TrimStart(Array.Empty()).StartsWith("#")) { continue; } if (!flag) { if (Regex.IsMatch(text, "^quests\\s*:\\s*$")) { flag = true; } continue; } Match match = QuestRe.Match(text); if (match.Success) { Commit(quest, items, order); quest = new Quest { Id = match.Groups[1].Value }; continue; } Match match2 = FieldRe.Match(text); if (quest == null || !match2.Success) { continue; } string text2 = StripQuotes(match2.Groups[2].Value); switch (match2.Groups[1].Value) { case "name": quest.Name = text2; break; case "period": quest.Period = text2.ToLowerInvariant(); break; case "coins": { if (int.TryParse(text2, out var result)) { quest.Coins = result; } break; } case "capped": quest.Capped = !string.Equals(text2.Trim(), "false", StringComparison.OrdinalIgnoreCase); break; } } Commit(quest, items, order); Items = items; Order = order; } private static void Commit(Quest q, Dictionary items, List order) { if (q == null || string.IsNullOrEmpty(q.Id)) { return; } if (q.Coins <= 0) { Debug.LogWarning((object)("[Valcoin] Quest '" + q.Id + "' has no positive coins value; skipping.")); return; } if (q.Period != "once" && q.Period != "daily") { Debug.LogWarning((object)("[Valcoin] Quest '" + q.Id + "' has unknown period '" + q.Period + "'; defaulting to daily.")); q.Period = "daily"; } if (string.IsNullOrEmpty(q.Name)) { q.Name = q.Id; } items[q.Id] = q; order.Add(q); } public static string Serialize() { try { return JsonConvert.SerializeObject((object)Order); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Quest catalog serialize failed: " + ex.Message)); return null; } } public static void ApplyRemote(string json) { if (string.IsNullOrEmpty(json)) { return; } try { List list = JsonConvert.DeserializeObject>(json); if (list == null) { return; } Dictionary dictionary = new Dictionary(); foreach (Quest item in list) { if (!string.IsNullOrEmpty(item.Id)) { dictionary[item.Id] = item; } } Items = dictionary; Order = list; } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Quest catalog ApplyRemote failed: " + ex.Message)); } } private static string StripQuotes(string v) { if (v.Length >= 2 && v[0] == '"' && v[v.Length - 1] == '"') { return v.Substring(1, v.Length - 2); } return v; } } public static class QuestFlow { private class ClaimResp { public string status; public int coins_awarded; public bool capped; public int daily_earned; public int daily_cap; public string resets_in; public int streak; public int streak_bonus; public int balance; } private static readonly Dictionary _capToastDay = new Dictionary(); public static void Run(long senderPeerID, string steam64, string playerName, string questId, Action reply) { if (string.IsNullOrEmpty(steam64)) { Debug.LogWarning((object)("[Valcoin] quest '" + questId + "' reported by a peer with no resolvable Steam ID; ignoring.")); return; } if (!Config.Ready) { Debug.LogWarning((object)("[Valcoin] quest '" + questId + "' reported by " + playerName + " but the backend isn't configured; not acknowledging (client will retry).")); return; } QuestCatalog.Quest quest = QuestCatalog.Get(questId); if (quest == null) { Debug.LogWarning((object)("[Valcoin] Unknown quest '" + questId + "' reported by " + playerName + " — no price in valcoin_quests.yaml. Not acknowledging (client will retry).")); return; } ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/quests/claim", new { steam64 = steam64, quest_id = quest.Id, coins = quest.Coins, period = quest.Period, name = playerName, capped = quest.Capped }, delegate(bool ok, ClaimResp r, string err) { if (!ok || r == null) { Debug.LogWarning((object)("[Valcoin] quest claim failed for " + playerName + " (" + quest.Id + "): " + (err ?? "unknown") + " — not acknowledging (client will retry).")); } else { RpcLayer.SendQuestAck(senderPeerID, quest.Id); Announce(steam64, playerName, quest, r, reply); } })); } private static void Announce(string steam64, string playerName, QuestCatalog.Quest quest, ClaimResp r, Action reply) { string text = $"{r.daily_earned}/{r.daily_cap} today"; switch (r.status) { case "credited": reply($"{quest.Name} — +{r.coins_awarded} Valcoins ({text})"); if (r.capped) { Toast(steam64, $"Daily cap reached ({r.daily_earned}/{r.daily_cap}) — resets in {r.resets_in}"); } if (r.streak_bonus > 0) { reply($"{r.streak}-day streak — +{r.streak_bonus} Valcoins"); Toast(steam64, $"{r.streak}-day streak! +{r.streak_bonus} Valcoins"); } Debug.Log((object)$"[Valcoin] quest '{quest.Id}' paid {r.coins_awarded} to {playerName} ({text})."); break; case "already_claimed": reply(quest.Name + " — already claimed today. Resets in " + r.resets_in + "."); break; case "cap_reached": reply($"{quest.Name} — daily cap reached ({r.daily_earned}/{r.daily_cap}). Resets in {r.resets_in}."); if (ShouldToastCap(steam64)) { Toast(steam64, $"Daily Valcoin cap reached ({r.daily_earned}/{r.daily_cap}) — resets in {r.resets_in}"); } break; } } private static bool ShouldToastCap(string steam64) { string text = DateTime.UtcNow.ToString("yyyy-MM-dd"); if (_capToastDay.TryGetValue(steam64, out var value) && value == text) { return false; } _capToastDay[steam64] = text; return true; } private static void Toast(string steam64, string message) { try { Player obj = SteamIdResolver.OnlinePlayerFor(steam64); if (obj != null) { ((Character)obj).Message((MessageType)1, message, 0, (Sprite)null); } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin] quest toast failed: " + ex.Message)); } } } public class QuestWatcher : MonoBehaviour { private const float IntervalSeconds = 5f; private const float RetrySeconds = 60f; private static readonly Dictionary _reportedAt = new Dictionary(); private Coroutine _loop; private void Start() { _loop = ((MonoBehaviour)this).StartCoroutine(Loop()); } private void OnDestroy() { if (_loop != null) { ((MonoBehaviour)this).StopCoroutine(_loop); } _reportedAt.Clear(); } public static void OnAck(string questId) { if (string.IsNullOrEmpty(questId)) { return; } _reportedAt.Remove(questId); Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { string text = QuestCatalog.KeyFor(questId); if (((Humanoid)localPlayer).HaveUniqueKey(text)) { ((Humanoid)localPlayer).RemoveUniqueKey(text); Debug.Log((object)("[Valcoin] Quest '" + questId + "' acknowledged by server; key cleared.")); } } } private IEnumerator Loop() { while (true) { yield return (object)new WaitForSeconds(5f); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || QuestCatalog.Order.Count == 0) { continue; } float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (QuestCatalog.Quest item in QuestCatalog.Order) { if (((Humanoid)localPlayer).HaveUniqueKey(QuestCatalog.KeyFor(item.Id)) && (!_reportedAt.TryGetValue(item.Id, out var value) || !(realtimeSinceStartup - value < 60f))) { bool num = _reportedAt.ContainsKey(item.Id); _reportedAt[item.Id] = realtimeSinceStartup; RpcLayer.SendAction("quest:" + item.Id); Debug.Log((object)(num ? ("[Valcoin] Quest '" + item.Id + "' still unacknowledged; re-reporting.") : ("[Valcoin] Quest '" + item.Id + "' completed; reported to server."))); } } } } } public static class RpcLayer { public const string ActionRpc = "vc_action"; public const string PanelRpc = "vc_panel"; public const string CatalogRpc = "vc_catalog"; public const string QuestsRpc = "vc_quests"; public const string QuestAckRpc = "vc_questack"; private static bool _registeredServer; private static bool _registeredClient; public static Action OnPanelMessage; public static IEnumerator RegisterWhenReady(bool serverSide) { while (ZRoutedRpc.instance == null) { yield return null; } if (serverSide && !_registeredServer) { ZRoutedRpc.instance.Register("vc_action", (Action)HandleActionOnServer); _registeredServer = true; Debug.Log((object)"[Valcoin] RPC registered (server): vc_action"); } if (!serverSide && !_registeredClient) { ZRoutedRpc.instance.Register("vc_panel", (Action)HandlePanelOnClient); ZRoutedRpc.instance.Register("vc_catalog", (Action)HandleCatalogOnClient); ZRoutedRpc.instance.Register("vc_quests", (Action)HandleQuestsOnClient); ZRoutedRpc.instance.Register("vc_questack", (Action)HandleQuestAckOnClient); _registeredClient = true; Debug.Log((object)"[Valcoin] RPC registered (client): vc_panel, vc_catalog, vc_quests, vc_questack"); } } public static void SendAction(string action) { if (ZRoutedRpc.instance == null) { return; } try { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "vc_action", new object[1] { action }); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] SendAction failed: " + ex.Message)); } } private static void HandleActionOnServer(long senderPeerID, string action) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } try { UiActionRouter.Execute(senderPeerID, action); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] HandleActionOnServer error: " + ex)); } } public static void PushPanelMessage(long peerID, string msg) { if (ZRoutedRpc.instance == null) { return; } try { ZRoutedRpc.instance.InvokeRoutedRPC(peerID, "vc_panel", new object[1] { msg }); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] PushPanelMessage failed: " + ex.Message)); } } private static void HandlePanelOnClient(long _from, string msg) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return; } try { OnPanelMessage?.Invoke(msg); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] OnPanelMessage handler failed: " + ex)); } } public static void BroadcastCatalog(string json) { if (ZRoutedRpc.instance == null || string.IsNullOrEmpty(json)) { return; } try { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "vc_catalog", new object[1] { json }); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] BroadcastCatalog failed: " + ex.Message)); } } private static void HandleCatalogOnClient(long _from, string json) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return; } try { Catalog.ApplyRemote(json); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Catalog apply failed: " + ex)); } } public static void BroadcastQuests(string json) { if (ZRoutedRpc.instance == null || string.IsNullOrEmpty(json)) { return; } try { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "vc_quests", new object[1] { json }); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] BroadcastQuests failed: " + ex.Message)); } } private static void HandleQuestsOnClient(long _from, string json) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return; } try { QuestCatalog.ApplyRemote(json); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Quest catalog apply failed: " + ex)); } } public static void SendQuestAck(long peerID, string questId) { if (ZRoutedRpc.instance == null || string.IsNullOrEmpty(questId)) { return; } try { ZRoutedRpc.instance.InvokeRoutedRPC(peerID, "vc_questack", new object[1] { questId }); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] SendQuestAck failed: " + ex.Message)); } } private static void HandleQuestAckOnClient(long _from, string questId) { try { QuestWatcher.OnAck(questId); } catch (Exception ex) { Debug.LogError((object)("[Valcoin] Quest ack handler failed: " + ex)); } } } public static class ShopHandler { public delegate void TellFn(string msg); private class SpendResp { public string status; public int balance; public int spent; } public static void Buy(string steam64, string skuId, TellFn tell, Action onSuccess = null, string extra = null) { if (string.IsNullOrEmpty(steam64)) { tell("Couldn't resolve your Steam ID."); return; } if (!Config.Ready) { tell("Shop is offline (backend not configured)."); return; } if (!Catalog.Items.TryGetValue(skuId, out var sku)) { tell("Unknown SKU: " + skuId + ". Check the Shop tab for the list."); return; } if (CoinManager.TryGetKnownBalance(steam64, out var balance) && balance < sku.Price) { tell($"Not enough Valcoins ({balance} / {sku.Price})."); return; } if (sku.Effect == "grant_perk" && PerkManager.Has(steam64, sku.Perk)) { tell("You already own \"" + sku.Name + "\"."); return; } if (sku.Effect == "armor_vfx" && ArmorVfx.SlotFor(sku.Perk) == null) { tell("\"" + sku.Name + "\" is misconfigured (unknown effect). Tell an admin."); return; } if (sku.Effect == "grant_item") { if (!string.IsNullOrEmpty(sku.RequiresBoss) && !BossGateSatisfied(sku.RequiresBoss)) { tell("\"" + sku.Name + "\" unlocks after a later boss. Keep progressing!"); return; } if (SteamIdResolver.ZdoFor(steam64) == null) { tell("Couldn't find your character to deliver items. Spawn in, then try again."); return; } } string idempotency_key = $"buy-{skuId}-{Guid.NewGuid():N}"; var body = new { steam64 = steam64, sku = skuId, coins = sku.Price, idempotency_key = idempotency_key, weekly_cap = ((sku.Effect == "grant_item") ? sku.WeeklyCap : 0), grant_charges = ((sku.Effect == "add_charges") ? new int?(sku.Charges) : ((int?)null)), charge_kind = ((sku.Effect == "add_charges") ? sku.Perk : null), weekly_charge_cap = ((sku.Effect == "add_charges" && sku.WeeklyChargeCap > 0) ? new int?(sku.WeeklyChargeCap) : ((int?)null)) }; ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/spend", body, delegate(bool ok, SpendResp r, string err) { if (!ok || r == null) { if (err != null && err.Contains("429")) { tell(("Weekly limit reached for \"" + sku.Name + "\". " + ExtractDetail(err)).TrimEnd(Array.Empty())); } else if (err != null && err.Contains("402")) { tell("The server says you don't have enough Valcoins. Check your balance at the top of the panel."); } else { tell("Purchase failed. (" + (err ?? "unknown") + ")"); } } else { CoinManager.SetBalance(steam64, r.balance); if (r.status == "duplicate") { tell($"\"{sku.Name}\" was already processed. Balance: {r.balance}."); onSuccess?.Invoke(); } else { ApplyEffect(steam64, sku, tell, extra); onSuccess?.Invoke(); } } })); } private static void ApplyEffect(string steam64, Catalog.Sku sku, TellFn tell, string extra = null) { switch (sku.Effect) { case "armor_vfx": { string text = ArmorVfx.SlotFor(sku.Perk); tell("__ARMORVFX__:" + sku.Perk + ":" + text); tell("Purchased \"" + sku.Name + "\" - applying to your " + text + " armor..."); break; } case "grant_perk": PerkManager.Grant(steam64, sku.Perk); tell("Purchased \"" + sku.Name + "\" - perk \"" + sku.Perk + "\" unlocked!"); break; case "add_charges": tell($"Purchased \"{sku.Name}\" - +{sku.Charges} charge(s). " + "It may take a few seconds for your charge count to update."); break; case "grant_item": { int num = GrantItems(steam64, sku.Item); if (num > 0) { tell($"Purchased \"{sku.Name}\" - {num} item stack(s) dropped at your feet."); } else { tell("\"" + sku.Name + "\" was charged but no items could be spawned (bad prefab id?). Tell an admin."); } break; } default: Debug.LogWarning((object)("[Valcoin] Unknown effect type \"" + sku.Effect + "\" for SKU " + sku.Id)); tell("\"" + sku.Name + "\" was charged but the effect couldn't be applied. Tell an admin."); break; } } private static int GrantItems(string steam64, string itemSpec) { //IL_003a: 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_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(itemSpec)) { return 0; } if ((Object)(object)ZNetScene.instance == (Object)null) { Debug.LogWarning((object)"[Valcoin] grant_item: no ZNetScene."); return 0; } ZDO val = SteamIdResolver.ZdoFor(steam64); if (val == null) { Debug.LogWarning((object)"[Valcoin] grant_item: no ZDO for buyer."); return 0; } Vector3 position = val.GetPosition(); int num = 0; string[] array = itemSpec.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string text2 = text; int result = 1; int num2 = text.LastIndexOf(':'); if (num2 > 0) { text2 = text.Substring(0, num2).Trim(); if (!int.TryParse(text.Substring(num2 + 1), out result) || result < 1) { result = 1; } } GameObject prefab = ZNetScene.instance.GetPrefab(text2); if ((Object)(object)prefab == (Object)null) { Debug.LogWarning((object)("[Valcoin] grant_item: unknown prefab \"" + text2 + "\" — skipped.")); continue; } ItemDrop component = prefab.GetComponent(); int num3 = ((!((Object)(object)component != (Object)null) || component.m_itemData?.m_shared == null) ? 1 : Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize)); int num4 = result; while (num4 > 0) { int num5 = Mathf.Min(num4, num3); Vector3 val2 = position + new Vector3(Random.Range(-1f, 1f), 1.5f, Random.Range(-1f, 1f)); try { ItemDrop component2 = Object.Instantiate(prefab, val2, Quaternion.identity).GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.m_itemData.m_stack = num5; } num++; } catch (Exception ex) { Debug.LogError((object)("[Valcoin] grant_item: failed to spawn " + text2 + ": " + ex.Message)); } num4 -= num5; } } return num; } private static bool BossGateSatisfied(string bossKey) { if (string.IsNullOrEmpty(bossKey)) { return true; } try { if ((Object)(object)ZoneSystem.instance == (Object)null) { return true; } return ZoneSystem.instance.GetGlobalKey(bossKey); } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin] boss-gate check failed for \"" + bossKey + "\": " + ex.Message)); return true; } } private static string ExtractDetail(string err) { if (string.IsNullOrEmpty(err)) { return ""; } int num = err.IndexOf("\"detail\":\"", StringComparison.Ordinal); if (num < 0) { return ""; } num += "\"detail\":\"".Length; int num2 = err.IndexOf('"', num); if (num2 <= num) { return ""; } return err.Substring(num, num2 - num); } } public static class SoulkeeperState { private class ConsumeResp { public bool consumed; public int remaining; } public const string Kind = "soulkeeper"; public static string LocalSteam64; public static int LocalCharges; private static bool _wardThisDeath; public static void UpdateFromState(string steam64, int charges) { if (!string.IsNullOrEmpty(steam64)) { LocalSteam64 = steam64; } LocalCharges = charges; } public static bool TryWardDeath() { if (LocalCharges <= 0 || string.IsNullOrEmpty(LocalSteam64) || !Config.Ready) { return false; } LocalCharges--; _wardThisDeath = true; if ((Object)(object)SharedCoroutineRunner.Instance != (Object)null) { ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(ConsumeOnBackend()); } Debug.Log((object)"[Valcoin] Soulkeeper: death warded — skills preserved."); return true; } public static bool ConsumeWardFlag() { if (!_wardThisDeath) { return false; } _wardThisDeath = false; return true; } private static IEnumerator ConsumeOnBackend() { var body = new { steam64 = LocalSteam64, kind = "soulkeeper" }; yield return BackendClient.Post("/api/charges/consume", body, delegate(bool ok, ConsumeResp r, string err) { if (!ok || r == null) { Debug.LogWarning((object)("[Valcoin] Soulkeeper consume failed (reconciles later): " + (err ?? "no response"))); } else { LocalCharges = r.remaining; } }); } } public class SoulkeeperPoller : MonoBehaviour { private class ChargesResp { public Dictionary charges; } private const float IntervalSeconds = 45f; private void Start() { ((MonoBehaviour)this).StartCoroutine(Loop()); } private IEnumerator Loop() { while (true) { if (Config.Ready && (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())) { string steam64 = LocalIdentity.Steam64(); if (!string.IsNullOrEmpty(steam64)) { yield return BackendClient.Get("/api/state/" + steam64, delegate(bool ok, ChargesResp r, string err) { if (ok && r != null) { int value = 0; if (r.charges != null) { r.charges.TryGetValue("soulkeeper", out value); } SoulkeeperState.UpdateFromState(steam64, value); } }); } } yield return (object)new WaitForSeconds(45f); } } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class SoulkeeperOnDeathPatch { private static void Prefix(Player __instance) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && SoulkeeperState.TryWardDeath()) { ((Character)__instance).Message((MessageType)2, "Soulkeeper Charm — your skills are preserved", 0, (Sprite)null); ValkyrieCarry.ArmCarry(((Component)__instance).transform.position); } } private static void Postfix() { SoulkeeperState.ConsumeWardFlag(); } } [HarmonyPatch(typeof(Skills), "LowerAllSkills")] internal static class SoulkeeperSkillLossPatch { private static bool Prefix() { return !SoulkeeperState.ConsumeWardFlag(); } } public static class SteamIdResolver { private static readonly Regex Steam64Re = new Regex("^7656119\\d{10}$", RegexOptions.Compiled); private static readonly Regex PlayFabIdRe = new Regex("^[A-Za-z0-9]{8,32}$", RegexOptions.Compiled); private const string PlayFabPrefix = "PlayFab_"; public static string FromPeer(ZNetPeer peer) { if (peer == null) { return null; } try { ZRpc rpc = peer.m_rpc; ISocket val = ((rpc != null) ? rpc.GetSocket() : null); if (val == null) { return null; } string hostName = val.GetHostName(); if (!string.IsNullOrEmpty(hostName) && Steam64Re.IsMatch(hostName)) { return hostName; } if ((((object)val).GetType().Name ?? "").IndexOf("PlayFab", StringComparison.OrdinalIgnoreCase) >= 0 && !string.IsNullOrEmpty(hostName) && PlayFabIdRe.IsMatch(hostName)) { return "PlayFab_" + hostName; } } catch { } return null; } public static string FromNetworkUserId(string nuid) { if (string.IsNullOrEmpty(nuid)) { return null; } if (nuid.StartsWith("Steam_")) { string text = nuid.Substring("Steam_".Length); if (!Steam64Re.IsMatch(text)) { return null; } return text; } if (nuid.StartsWith("Pla_") || nuid.StartsWith("PlayFab_")) { string text2 = (nuid.StartsWith("Pla_") ? nuid.Substring("Pla_".Length) : nuid.Substring("PlayFab_".Length)); if (!PlayFabIdRe.IsMatch(text2)) { return null; } return "PlayFab_" + text2; } if (!Steam64Re.IsMatch(nuid)) { return null; } return nuid; } public static string FromPeerId(long peerId) { if (peerId == 0L || (Object)(object)ZNet.instance == (Object)null) { return null; } return FromPeer(ZNet.instance.GetPeer(peerId)); } public static ZNetPeer PeerFor(string steam64) { if (string.IsNullOrEmpty(steam64) || (Object)(object)ZNet.instance == (Object)null) { return null; } return ((IEnumerable)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func)((ZNetPeer p) => FromPeer(p) == steam64)); } public static ZDO ZdoFor(string steam64) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) ZNetPeer val = PeerFor(steam64); if (val == null || ZDOMan.instance == null) { return null; } return ZDOMan.instance.GetZDO(val.m_characterID); } public static Player OnlinePlayerFor(string steam64) { if (string.IsNullOrEmpty(steam64) || (Object)(object)ZNet.instance == (Object)null) { return null; } ZNetPeer val = ((IEnumerable)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func)((ZNetPeer p) => FromPeer(p) == steam64)); if (val == null) { return null; } string name = val.m_playerName; return ((IEnumerable)Player.GetAllPlayers()).FirstOrDefault((Func)((Player p) => (Object)(object)p != (Object)null && p.GetPlayerName().Equals(name, StringComparison.OrdinalIgnoreCase))); } } public static class UiActionRouter { public static void Execute(long senderPeerID, string action) { if (string.IsNullOrEmpty(action)) { return; } ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(senderPeerID) : null); if (val == null) { return; } string text = SteamIdResolver.FromPeer(val); string playerName = val.m_playerName; int num = action.IndexOf(':'); string text2; string text3; if (num < 0) { text2 = action; text3 = ""; } else { text2 = action.Substring(0, num); text3 = action.Substring(num + 1); } switch (text2) { case "donate": DonateFlow.Run(text, playerName, Reply); break; case "quest": QuestFlow.Run(senderPeerID, text, playerName, text3.Trim(), Reply); break; case "buy": DoBuy(text, text3, Reply); break; case "gift": DoGift(text, playerName, text3, Reply); break; case "topdonors": TopDonorsFetcher.Fetch(delegate(string reply) { Reply(reply); }); break; case "whoami": Reply("__ADMIN__:" + IsAdmin(text).ToString().ToLowerInvariant()); break; case "admin_give": DoAdminAdjust(text, text3, Reply, give: true); break; case "admin_remove": DoAdminAdjust(text, text3, Reply, give: false); break; default: Reply("⚠\ufe0f Unknown UI action: " + text2); break; } void Reply(string msg) { RpcLayer.PushPanelMessage(senderPeerID, msg); } } private static void DoBuy(string steam64, string rest, Action reply) { string[] array = rest.Split(new char[1] { ':' }, 2); string skuId = array[0].Trim().ToLowerInvariant(); string extra = ((array.Length > 1) ? array[1].Trim().ToLowerInvariant() : null); ShopHandler.Buy(steam64, skuId, delegate(string m) { reply(m); }, null, extra); } private static bool IsAdmin(string steam64) { if (!string.IsNullOrEmpty(steam64)) { return Plugin.AdminSteamIDs.Contains(steam64); } return false; } private static void DoAdminAdjust(string callerSteam64, string rest, Action reply, bool give) { if (!IsAdmin(callerSteam64)) { reply("You are not authorized."); return; } string[] array = rest.Split(new char[1] { ':' }, 2); if (array.Length != 2 || !int.TryParse(array[1].Trim(), out var result) || result <= 0) { reply("Bad amount."); return; } string text = array[0].Trim(); if (!ResolveTargetByName(text, out var steam, out var player)) { reply("Player \"" + text + "\" not found or no Steam ID."); return; } if (give) { CoinManager.AddCoins(steam, result); reply($"Gave {result} Valcoins to {text}."); if (player != null) { ((Character)player).Message((MessageType)1, $"+{result} Valcoins from admin!", 0, (Sprite)null); } return; } int num = Math.Max(0, CoinManager.GetBalance(steam) - result); CoinManager.SetBalance(steam, num); reply($"Removed {result} from {text} (new balance: {num})."); if (player != null) { ((Character)player).Message((MessageType)1, $"{result} Valcoins removed by admin.", 0, (Sprite)null); } } private static bool ResolveTargetByName(string name, out string steam64, out Player player) { steam64 = null; player = null; if ((Object)(object)ZNet.instance == (Object)null) { return false; } ZNetPeer val = ((IEnumerable)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func)((ZNetPeer p) => p.m_playerName != null && p.m_playerName.Equals(name, StringComparison.OrdinalIgnoreCase))); if (val == null) { return false; } steam64 = SteamIdResolver.FromPeer(val); if (string.IsNullOrEmpty(steam64)) { return false; } player = ((IEnumerable)Player.GetAllPlayers()).FirstOrDefault((Func)((Player pp) => pp.GetPlayerName().Equals(name, StringComparison.OrdinalIgnoreCase))); return true; } private static void DoGift(string fromSteam64, string fromName, string rest, Action reply) { string[] array = rest.Split(new char[1] { ':' }, 2); int result; if (array.Length != 2) { reply("Bad gift format."); } else if (!int.TryParse(array[1].Trim(), out result) || result <= 0) { reply("Amount must be a positive number."); } else { GiftFlow.Run(fromSteam64, fromName, array[0].Trim(), result, reply); } } } public class SharedCoroutineRunner : MonoBehaviour { private static SharedCoroutineRunner _instance; public static SharedCoroutineRunner Instance { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("ValcoinCoroutineRunner"); Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent(); } return _instance; } } } public static class ValcoinWallet { private class SpendResp { public string status; public int balance; public int spent; } private class GrantResp { public string status; public long grant_id; public int coins; } public const string SkuPrefix = "eco_"; public static bool Ready { get { if (IsServer) { return Config.Ready; } return false; } } public static string UnavailableReason { get { if (!IsServer) { return "Valcoin wagers are settled on the server."; } if (!Config.Ready) { return "The donation backend is not configured on this server."; } return null; } } private static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static int BalanceOf(string playerName) { if (!IsServer) { return -1; } string text = Resolve(playerName); if (string.IsNullOrEmpty(text)) { return -1; } if (!CoinManager.TryGetKnownBalance(text, out var balance)) { return -1; } return balance; } public static void Charge(string playerName, string sku, int coins, string reason, Action done) { if (!Guard(coins, done, out var id, playerName, out var safeSku, sku)) { return; } if (CoinManager.TryGetKnownBalance(id, out var balance) && balance < coins) { done?.Invoke(arg1: false, $"Not enough Valcoins ({balance} / {coins})."); return; } string idempotency_key = $"eco-{safeSku}-{Guid.NewGuid():N}"; ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/spend", new { steam64 = id, sku = safeSku, coins = coins, idempotency_key = idempotency_key, metadata = new { source = "ecosystem", reason = (reason ?? ""), player = (playerName ?? "") } }, delegate(bool ok, SpendResp r, string err) { if (!ok || r == null) { if (err != null && err.Contains("402")) { done?.Invoke(arg1: false, "You do not have enough Valcoins."); } else { done?.Invoke(arg1: false, "Valcoin charge failed. (" + (err ?? "unknown") + ")"); } } else { CoinManager.SetBalance(id, r.balance); CoinManager.Save(); Debug.Log((object)$"[Valcoin] ecosystem charge: {coins} from {playerName} ({safeSku}); balance {r.balance}."); done?.Invoke(arg1: true, $"-{coins} Valcoins ({reason ?? safeSku}). Balance: {r.balance}"); } })); } public static void Credit(string playerName, string sku, int coins, string reason, Action done) { if (!Guard(coins, done, out var id, playerName, out var safeSku, sku)) { return; } ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/admin/grant", new { steam64 = id, coins = coins, note = safeSku + ": " + (reason ?? "ecosystem payout") + " (" + playerName + ")" }, delegate(bool ok, GrantResp r, string err) { if (!ok || r == null) { Debug.LogWarning((object)string.Format("[Valcoin] ecosystem credit FAILED for {0} ({1}, {2}): {3}", playerName, safeSku, coins, err ?? "unknown")); done?.Invoke(arg1: false, "Valcoin payout failed. (" + (err ?? "unknown") + ")"); } else { Debug.Log((object)$"[Valcoin] ecosystem credit: {coins} to {playerName} ({safeSku})."); done?.Invoke(arg1: true, $"+{coins} Valcoins ({reason ?? safeSku})"); } })); } private static bool Guard(int coins, Action done, out string id, string playerName, out string safeSku, string sku) { id = null; safeSku = null; if (!IsServer) { done?.Invoke(arg1: false, "Valcoin wagers are settled on the server."); return false; } if (!Config.Ready) { done?.Invoke(arg1: false, "The donation backend is not configured on this server."); return false; } if (coins <= 0) { done?.Invoke(arg1: false, "Amount must be positive."); return false; } safeSku = SanitizeSku(sku); if (safeSku == null) { done?.Invoke(arg1: false, "Invalid wager id."); return false; } id = Resolve(playerName); if (string.IsNullOrEmpty(id)) { done?.Invoke(arg1: false, "Couldn't resolve " + (playerName ?? "that player") + "'s account — are they still connected?"); return false; } return true; } private static string SanitizeSku(string sku) { if (string.IsNullOrWhiteSpace(sku)) { return null; } string text = sku.Trim().ToLowerInvariant(); if (!text.StartsWith("eco_")) { text = "eco_" + text; } string text2 = new string(text.Where((char c) => (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_').ToArray()); if (text2.Length < 2) { return null; } if (text2.Length <= 32) { return text2; } return text2.Substring(0, 32); } private static string Resolve(string playerName) { if (string.IsNullOrEmpty(playerName) || (Object)(object)ZNet.instance == (Object)null) { return null; } ZNetPeer val = ((IEnumerable)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func)((ZNetPeer p) => p.m_playerName != null && p.m_playerName.Equals(playerName, StringComparison.OrdinalIgnoreCase))); if (val != null) { return SteamIdResolver.FromPeer(val); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && string.Equals(localPlayer.GetPlayerName(), playerName, StringComparison.OrdinalIgnoreCase)) { return LocalIdentity.Steam64(); } return null; } } public static class ValkyrieCarry { private const float PickupDelaySeconds = 20f; private const float FadeSeconds = 1.2f; private static bool _armed; private static Vector3 _targetPos; private static bool _reflectResolved; private static MethodInfo _spawnValkyrie; private static MethodInfo _syncPlayer; private static FieldInfo _fTargetPoint; private static FieldInfo _fDescentStart; private static FieldInfo _fFlyAwayPoint; private static FieldInfo _fDescent; private static FieldInfo _fDroppedPlayer; private static FieldInfo _fAutoPickup; private const float RepelRadius = 12f; private const float RepelForce = 90f; public static bool FlightActive { get; private set; } private static bool VisualAvailable { get { if (Config.ValkyrieCarryVisual && _spawnValkyrie != null && _fTargetPoint != null && _fDescent != null && _fFlyAwayPoint != null) { return _fDroppedPlayer != null; } return false; } } public static void ArmCarry(Vector3 deathPos) { //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_0011: Unknown result type (might be due to invalid IL or missing references) _armed = true; _targetPos = deathPos; Debug.Log((object)$"[Valcoin][Carry] Armed — will carry to {deathPos} on respawn."); } internal static bool ConsumeArmed(out 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) pos = _targetPos; if (!_armed) { return false; } _armed = false; return true; } private static void ResolveReflection() { if (!_reflectResolved) { _reflectResolved = true; _spawnValkyrie = AccessTools.Method(typeof(Player), "SpawnValkyrie", (Type[])null, (Type[])null); _syncPlayer = AccessTools.Method(typeof(Valkyrie), "SyncPlayer", (Type[])null, (Type[])null); _fTargetPoint = AccessTools.Field(typeof(Valkyrie), "m_targetPoint"); _fDescentStart = AccessTools.Field(typeof(Valkyrie), "m_descentStart"); _fFlyAwayPoint = AccessTools.Field(typeof(Valkyrie), "m_flyAwayPoint"); _fDescent = AccessTools.Field(typeof(Valkyrie), "m_descent"); _fDroppedPlayer = AccessTools.Field(typeof(Valkyrie), "m_droppedPlayer"); _fAutoPickup = AccessTools.Field(typeof(Player), "m_enableAutoPickup"); bool flag = _spawnValkyrie != null && _fTargetPoint != null && _fDescent != null && _fFlyAwayPoint != null && _fDroppedPlayer != null; Debug.Log((object)($"[Valcoin][Carry] Reflection bind ok={flag} (spawn={_spawnValkyrie != null}, " + $"sync={_syncPlayer != null}, autoPickup={_fAutoPickup != null})")); } } internal static IEnumerator DoCarry(Player player, Vector3 gravePos) { //IL_000e: 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) ResolveReflection(); for (int i = 0; i < 5; i++) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { break; } yield return null; } if (Lost(player)) { yield break; } yield return (object)new WaitForSeconds(0.5f); if (Lost(player)) { yield break; } ((Character)player).Message((MessageType)2, $"Soulkeeper Charm — 1 charge consumed.\nA Valkyrie will carry you to your tombstone in {20f:0} seconds.", 0, (Sprite)null); yield return (object)new WaitForSeconds(15f); if (Lost(player)) { yield break; } ((Character)player).Message((MessageType)2, "The Valkyrie descends...", 0, (Sprite)null); yield return (object)new WaitForSeconds(5f); if (!Lost(player)) { if (VisualAvailable) { yield return CarryFlight(player, gravePos); yield break; } FallbackTeleport(player, gravePos); yield return RepelPulses(gravePos); } } private static bool Lost(Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { return ((Character)player).IsDead(); } return true; } private static IEnumerator CarryFlight(Player player, Vector3 gravePos) { //IL_000e: 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) FlightActive = true; try { if (Menu.IsVisible()) { Menu instance = Menu.instance; if (instance != null) { instance.Hide(); } } } catch { } yield return CarryFadeOverlay.FadeTo(1f, 1.2f); if (Lost(player)) { FlightActive = false; yield return CarryFadeOverlay.FadeTo(0f, 0.3f); yield break; } bool prevAutoPickup = SetAutoPickup(enabled: false); Valkyrie valk = null; try { player.SetIntro(true); _spawnValkyrie.Invoke(player, null); valk = Valkyrie.m_instance; if ((Object)(object)valk != (Object)null) { Vector3 val = ((Component)player).transform.position + Vector3.up * valk.m_dropHeight; Vector3 val2 = gravePos + Vector3.up * valk.m_dropHeight; Vector3 val3 = val2 - val; val3.y = 0f; float magnitude = ((Vector3)(ref val3)).magnitude; val3 = ((magnitude > 0.01f) ? (val3 / magnitude) : Vector3.forward); ((Component)valk).transform.position = val; ((Component)valk).transform.rotation = Quaternion.LookRotation(val3); _fTargetPoint.SetValue(valk, val2); _fDescentStart.SetValue(valk, val); _fDescent.SetValue(valk, true); _fDroppedPlayer.SetValue(valk, false); Vector3 val4 = val2 + val3 * 200f; val4.y = valk.m_startAltitude; _fFlyAwayPoint.SetValue(valk, val4); valk.m_speed = Mathf.Clamp(magnitude / 35f, 12f, 60f); valk.m_turnRate = 20f; try { _syncPlayer?.Invoke(valk, new object[1] { true }); } catch { } Debug.Log((object)$"[Valcoin][Carry] Flight configured: dist={magnitude:0}m speed={valk.m_speed:0.0} → {val2}"); } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][Carry] Valkyrie setup threw: " + ex.Message)); } if ((Object)(object)valk == (Object)null) { try { player.SetIntro(false); } catch { } SetAutoPickup(prevAutoPickup); FlightActive = false; yield return CarryFadeOverlay.FadeTo(0f, 0.3f); FallbackTeleport(player, gravePos); yield break; } yield return CarryFadeOverlay.FadeTo(0f, 1.2f); float num = Vector3.Distance(((Component)player).transform.position, gravePos) / Mathf.Max(valk.m_speed, 1f); float hardCap = num * 2f + 45f; float elapsed = 0f; bool landed = false; while (elapsed < hardCap && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { if (!((Character)player).InIntro()) { landed = true; break; } elapsed += 0.5f; yield return (object)new WaitForSeconds(0.5f); } FlightActive = false; if (landed) { Debug.Log((object)$"[Valcoin][Carry] Valkyrie delivered player (t={elapsed:0.0}s)."); } else if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer) { Debug.LogWarning((object)"[Valcoin][Carry] Flight watchdog hit hard cap — forcing landing."); try { player.SetIntro(false); } catch { } FallbackTeleport(player, gravePos); } yield return RepelPulses(gravePos); SetAutoPickup(prevAutoPickup); } private static IEnumerator RepelPulses(Vector3 gravePos) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) for (int pulse = 0; pulse < 3; pulse++) { RepelPulse(gravePos, 12f, 90f); yield return (object)new WaitForSeconds(1.5f); } } private static void RepelPulse(Vector3 center, float radius, float force) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0077: 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_009f: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0092: 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) try { int num = 0; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.IsPlayer() && !allCharacter.IsTamed() && !allCharacter.IsBoss() && !allCharacter.IsDead() && !(Vector3.Distance(((Component)allCharacter).transform.position, center) > radius)) { Vector3 val = ((Component)allCharacter).transform.position - center; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = Random.insideUnitSphere; } HitData val2 = new HitData(); val2.m_dir = ((Vector3)(ref val)).normalized; val2.m_pushForce = force; val2.m_staggerMultiplier = 100f; val2.m_point = ((Component)allCharacter).transform.position; allCharacter.Damage(val2); num++; } } if (num > 0) { Debug.Log((object)$"[Valcoin][Carry] Repel pulse pushed {num} creature(s) from the tomb."); } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][Carry] Repel failed: " + ex.Message)); } } private static bool SetAutoPickup(bool enabled) { try { if (_fAutoPickup == null) { return true; } bool result = (bool)_fAutoPickup.GetValue(null); _fAutoPickup.SetValue(null, enabled); return result; } catch { return true; } } private static void FallbackTeleport(Player player, Vector3 gravePos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: 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_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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) Vector3 val = gravePos + Vector3.up * 0.5f; bool flag = false; try { flag = ((Character)player).TeleportTo(val, ((Component)player).transform.rotation, true); } catch (Exception ex) { Debug.LogError((object)("[Valcoin][Carry] TeleportTo threw: " + ex.Message)); } if (!flag) { try { ((Component)player).transform.position = val; flag = true; } catch (Exception ex2) { Debug.LogError((object)("[Valcoin][Carry] Hard reposition failed: " + ex2.Message)); } } Debug.Log((object)$"[Valcoin][Carry] Fallback teleport issued={flag} → {val}"); } } public class CarryFadeOverlay : MonoBehaviour { private static CarryFadeOverlay _instance; private float _alpha; private static CarryFadeOverlay Ensure() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("ValcoinCarryFade"); _instance = val.AddComponent(); Object.DontDestroyOnLoad((Object)val); } return _instance; } public static IEnumerator FadeTo(float target, float seconds) { CarryFadeOverlay o = Ensure(); float from = o._alpha; float t = 0f; while (t < seconds) { t += Time.deltaTime; o._alpha = Mathf.Lerp(from, target, Mathf.Clamp01(t / seconds)); yield return null; } o._alpha = target; } private void OnGUI() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) if (!(_alpha <= 0.001f)) { GUI.depth = -10000; Color color = GUI.color; GUI.color = new Color(0f, 0f, 0f, _alpha); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture); GUI.color = color; } } } [HarmonyPatch(typeof(Menu), "Show")] internal static class ValkyrieCarryMenuPatch { private static bool Prefix() { return !ValkyrieCarry.FlightActive; } } [HarmonyPatch(typeof(Player), "OnSpawned", new Type[] { typeof(bool) })] internal static class ValkyrieCarryOnSpawnedPatch { private static void Postfix(Player __instance) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && ValkyrieCarry.ConsumeArmed(out var pos) && !((Object)(object)SharedCoroutineRunner.Instance == (Object)null)) { ((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(ValkyrieCarry.DoCarry(__instance, pos)); } } catch (Exception ex) { Debug.LogWarning((object)("[Valcoin][Carry] OnSpawned postfix error: " + ex.Message)); } } } public class WelcomeBanner : MonoBehaviour { private const float DelaySeconds = 5f; private static bool _shownThisSession; public static void ResetForNewSession() { _shownThisSession = false; } public static void Show() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown if (!_shownThisSession && Config.WelcomeEnabled) { GameObject val = new GameObject("ValcoinWelcome"); val.AddComponent(); Object.DontDestroyOnLoad((Object)val); } } private IEnumerator Start() { while ((Object)(object)Player.m_localPlayer == (Object)null) { yield return null; } yield return (object)new WaitForSeconds(5f); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { string text = (string.IsNullOrEmpty(Config.WelcomeMessage) ? ("Press " + (Config.CodexToggleKey ?? "F4") + " to support the server") : Config.WelcomeMessage); ((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null); _shownThisSession = true; Object.Destroy((Object)(object)((Component)this).gameObject); } } } [HarmonyPatch(typeof(Player), "OnSpawned")] public static class WelcomeOnSpawn { private static void Postfix(Player __instance) { if (!((Object)(object)ZNet.instance == (Object)null) && (!ZNet.instance.IsServer() || !ZNet.instance.IsDedicated()) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { WelcomeBanner.Show(); } } }