Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Valheim Donations v5.20.0
ValheimDonationSystem.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using 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<string, Aura> Registry = new Dictionary<string, Aura> { ["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<string, GameObject> _prefabCache = new Dictionary<string, GameObject>(); private static readonly Dictionary<string, GameObject> _sourceCache = new Dictionary<string, GameObject>(); 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<ZNetView>(); } 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<ItemDrop>()?.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<string, string>(); } 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<GameObject>(); 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<string> list = new List<string>(); Transform[] componentsInChildren = donor.GetComponentsInChildren<Transform>(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<ParticleSystem>() == (Object)null || (Object)(object)((Component)val3).GetComponentInChildren<MeshRenderer>(true) != (Object)null || (Object)(object)((Component)val3).GetComponentInChildren<SkinnedMeshRenderer>(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<Transform>(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<ParticleSystem>(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<string> list = new List<string>(); componentsInChildren = parent.GetComponentsInChildren<Transform>(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<VisEquipment>(); } 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<string> list = new List<string>(); 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 = "<color=#E8C877>" + text + " " + value2.Suffix + "</color>\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<string, string>(); } 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<color=#E8C877>" + text + " " + value4.Suffix + "</color>\n<color=#9BE8B4>Familiar: " + value4.Display + "</color>" + (string.IsNullOrEmpty(text2) ? "" : (" <color=#9BE8B4>(" + text2 + ")</color>")) + "\n<color=#8C8C8C>Kept when this piece is upgraded.</color>\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 + " <color=#E8C877>" + value4.Suffix + "</color>")); } } } 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<string, Attached> _live = new Dictionary<string, Attached>(); private readonly HashSet<string> _seen = new HashSet<string>(); private static readonly HashSet<string> KeepTypes = new HashSet<string> { "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<string> PoofKeep = new HashSet<string> { "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<SE_FamiliarBond>(); ((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<Player> list = null; try { list = Player.GetAllPlayers(); } catch { } if (list != null) { foreach (Player item in list) { RenderPlayer(item); } } if (_live.Count <= 0) { return; } List<string> list2 = new List<string>(); foreach (KeyValuePair<string, Attached> 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<GameObject>(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<string> list = new List<string>(); Transform[] componentsInChildren = val2.GetComponentsInChildren<Transform>(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<ParticleSystem>() != (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<ParticleSystem>(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<Light>(); 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<GameObject>(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<Animator>(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<SkinnedMeshRenderer>(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<GameObject>(creaturePrefab, val.transform); StripAllExcept(val2, KeepTypes); Animator[] componentsInChildren = val2.GetComponentsInChildren<Animator>(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<string> keep) { for (int i = 0; i < 16; i++) { bool flag = false; bool flag2 = false; Component[] componentsInChildren = go.GetComponentsInChildren<Component>(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<Component>(); 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<Component>(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<MeshRenderer>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.Destroy((Object)(object)componentsInChildren[i]); } MeshFilter[] componentsInChildren2 = go.GetComponentsInChildren<MeshFilter>(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.Destroy((Object)(object)componentsInChildren2[i]); } SkinnedMeshRenderer[] componentsInChildren3 = go.GetComponentsInChildren<SkinnedMeshRenderer>(true); for (int i = 0; i < componentsInChildren3.Length; i++) { Object.Destroy((Object)(object)componentsInChildren3[i]); } Animator[] componentsInChildren4 = go.GetComponentsInChildren<Animator>(true); for (int i = 0; i < componentsInChildren4.Length; i++) { Object.Destroy((Object)(object)componentsInChildren4[i]); } LODGroup[] componentsInChildren5 = go.GetComponentsInChildren<LODGroup>(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<ParticleSystem>(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<SE_FamiliarBond>(); ((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<GameObject>(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<ParticleSystem>(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<T>(bool ok, T result, string error); public static IEnumerator Get<T>(string path, Callback<T> cb) { return Send("GET", path, null, cb); } public static IEnumerator Post<T>(string path, object body, Callback<T> cb) { return Send("POST", path, body, cb); } private static IEnumerator Send<T>(string method, string path, object body, Callback<T> 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<T>(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<string, Sku> Items { get; private set; } = new Dictionary<string, Sku>(); public static List<Sku> Order { get; private set; } = new List<Sku>(); 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<string, Sku>(); Order = new List<Sku>(); } } 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<string, Sku> items = new Dictionary<string, Sku>(); List<Sku> order = new List<Sku>(); bool flag = false; Sku sku = null; foreach (string text in lines) { if (string.IsNullOrWhiteSpace(text) || text.TrimStart(Array.Empty<char>()).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<string, Sku> items, List<Sku> 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<Sku> list = JsonConvert.DeserializeObject<List<Sku>>(json); if (list == null) { return; } Dictionary<string, Sku> dictionary = new Dictionary<string, Sku>(); 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<string, int> balances = new Dictionary<string, int>(); public List<long> recentGrants = new List<long>(); } 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<long> _seen = new HashSet<long>(); public static void Load() { try { Directory.CreateDirectory(SaveDir); if (!File.Exists(SaveFile)) { return; } string text = File.ReadAllText(SaveFile); State state; try { state = JsonConvert.DeserializeObject<State>(text); if (state == null || state.balances == null) { throw new Exception("not new shape"); } } catch { Dictionary<string, int> balances = JsonConvert.DeserializeObject<Dictionary<string, int>>(text) ?? new Dictionary<string, int>(); state = new State { balances = balances }; } _state = state; State state2 = _state; if (state2.recentGrants == null) { state2.recentGrants = new List<long>(); } _seen = new HashSet<long>(_state.recentGrants); } catch (Exception ex) { Debug.LogError((object)("[CoinManager] Failed to load: " + ex.Message)); _state = new State(); _seen = new HashSet<long>(); } } 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<long> 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<string, int> weekly_usage; public string week_resets_in; public Dictionary<string, int> 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<TopEntry> _topDonors = new List<TopEntry>(); private HashSet<string> _ownedSkus = new HashSet<string>(); private Dictionary<string, int> _weeklyUsage = new Dictionary<string, int>(); private string _weekResetsIn = ""; private Dictionary<string, int> _charges = new Dictionary<string, int>(); 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<string> _log = new List<string>(); 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<KeyCode>(Config.CodexToggleKey, ignoreCase: true, out KeyCode result)) { _toggleKey = result; } RpcLayer.OnPanelMessage = (Action<string>)Delegate.Combine(RpcLayer.OnPanelMessage, new Action<string>(OnServerMessage)); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void OnDestroy() { RpcLayer.OnPanelMessage = (Action<string>)Delegate.Remove(RpcLayer.OnPanelMessage, new Action<string>(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<TopEntry>(r.top_donors) : new List<TopEntry>()); _ownedSkus = ((r.owned_skus != null) ? new HashSet<string>(r.owned_skus) : new HashSet<string>()); _weeklyUsage = r.weekly_usage ?? new Dictionary<string, int>(); _weekResetsIn = r.week_resets_in ?? ""; _charges = r.charges ?? new Dictionary<string, int>(); _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<Font>(); 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<GUILayoutOption>()); GUILayout.Label("Valheim Donations", _hdr, Array.Empty<GUILayoutOption>()); 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<GUILayoutOption>()); 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<GUILayoutOption>()); } DrawHr(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); 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<GUILayoutOption>()); GUI.contentColor = contentColor; DrawHr(); GUILayout.Space(8f); GUILayout.Label(_resultText, _label, Array.Empty<GUILayoutOption>()); 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<string, int> charge in _charges) { if (charge.Value > 0) { if (!flag) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); 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<GUILayoutOption>()); 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<GUILayoutOption>()); 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<GUILayoutOption>()); GUILayout.Space(8f); DrawRateCallout(); GUILayout.Label("How it works", _label, Array.Empty<GUILayoutOption>())