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 Bragi v0.2.0
plugins\Bragi\Bragi.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Splatform; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace Bragi { public static class BardBuff { private static Coroutine? _buffLoop; private const float RESTED_EXTENSION_PER_PULSE = 10f; private const float RESTED_MAX_EXTENSION = 1200f; public static void StartBuff() { if (BragiConfig.BuffEnabled.Value) { Player localPlayer = Player.m_localPlayer; _buffLoop = ((localPlayer != null) ? ((MonoBehaviour)localPlayer).StartCoroutine(BuffLoop()) : null); } } public static void EndBuff() { if (_buffLoop != null && (Object)(object)Player.m_localPlayer != (Object)null) { ((MonoBehaviour)Player.m_localPlayer).StopCoroutine(_buffLoop); } _buffLoop = null; } private static IEnumerator BuffLoop() { while (true) { ApplyToNearbyPlayers(); yield return (object)new WaitForSeconds(5f); } } private static void ApplyToNearbyPlayers() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } float value = BragiConfig.BuffRadius.Value; foreach (Player allPlayer in Player.GetAllPlayers()) { if (!(Vector3.Distance(((Component)allPlayer).transform.position, ((Component)localPlayer).transform.position) > value)) { ExtendRestBuff(allPlayer); } } } private static void ExtendRestBuff(Player player) { SEMan sEMan = ((Character)player).GetSEMan(); if (sEMan == null) { return; } int hashCode = "Rested".GetHashCode(); if (sEMan.HaveStatusEffect(hashCode)) { StatusEffect statusEffect = sEMan.GetStatusEffect(hashCode); if ((Object)(object)statusEffect != (Object)null) { float remaningTime = statusEffect.GetRemaningTime(); float ttl = Mathf.Min(1200f, remaningTime + 10f + BragiConfig.StaminaRegenBonus.Value * 60f); statusEffect.m_ttl = ttl; statusEffect.ResetTime(); } } else { ObjectDB instance = ObjectDB.instance; StatusEffect val = ((instance != null) ? instance.GetStatusEffect(hashCode) : null); if ((Object)(object)val != (Object)null) { sEMan.AddStatusEffect(val, false, 0, 0f, (short)(-1)); ((Character)player).Message((MessageType)1, "$se_rested_start", 0, (Sprite)null, false); } } } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class PlayerSpawnPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { if (((Character)__instance).IsOwner()) { if ((Object)(object)((Component)__instance).GetComponent<SongPlayer>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<SongPlayer>(); } if ((Object)(object)((Component)__instance).GetComponent<SongSelectUI>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<SongSelectUI>(); } BragiPlugin.Log.LogInfo((object)"\ud83c\udfb5 Bragi components attached to local player."); } } } public static class BragiConfig { public static ConfigEntry<float> MasterVolume; public static ConfigEntry<float> MusicRange; public static ConfigEntry<bool> DuckGameMusic; public static ConfigEntry<bool> BuffEnabled; public static ConfigEntry<float> BuffRadius; public static ConfigEntry<float> BuffDuration; public static ConfigEntry<float> StaminaRegenBonus; public static ConfigEntry<string> OpenMenuKey; public static void Init(ConfigFile cfg) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Expected O, but got Unknown MasterVolume = cfg.Bind<float>("Audio", "MasterVolume", 1f, new ConfigDescription("Global volume multiplier for instrument audio (0.0 – 1.0).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); MusicRange = cfg.Bind<float>("Audio", "MusicRange", 30f, new ConfigDescription("Radius in metres within which other players hear your music.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f), Array.Empty<object>())); DuckGameMusic = cfg.Bind<bool>("Audio", "DuckGameMusic", true, "Reduce Valheim's background ambient music volume while instruments are playing, so it doesn't clash with the performed song."); BuffEnabled = cfg.Bind<bool>("BardBuff", "Enabled", true, "Enable the bard buff: playing music refreshes the vanilla Rested status effect for nearby players."); BuffRadius = cfg.Bind<float>("BardBuff", "Radius", 15f, new ConfigDescription("Radius in metres of the bard Rested-extension buff.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 50f), Array.Empty<object>())); BuffDuration = cfg.Bind<float>("BardBuff", "Duration", 60f, new ConfigDescription("How long (seconds) the Rested buff persists naturally after being granted.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 300f), Array.Empty<object>())); StaminaRegenBonus = cfg.Bind<float>("BardBuff", "StaminaRegenBonus", 0.15f, new ConfigDescription("Extra stamina regen percentage on top of the vanilla Rested buff (0.15 = +15%).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); OpenMenuKey = cfg.Bind<string>("UI", "OpenMenuKey", "G", "Keyboard key to open the song selection menu while holding an instrument."); } } public static class ForagerConfig { public static ConfigEntry<float> ScanRadius; public static ConfigEntry<float> PulseInterval; public static ConfigEntry<bool> DetectBerries; public static ConfigEntry<bool> DetectMushrooms; public static ConfigEntry<int> MaxPins; public static void Init(ConfigFile cfg) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown ScanRadius = cfg.Bind<float>("Forager", "ScanRadius", 40f, new ConfigDescription("Radius in metres that the Forager's Nose scans for pickables.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 200f), Array.Empty<object>())); PulseInterval = cfg.Bind<float>("Forager", "PulseInterval", 3f, new ConfigDescription("Seconds between each scan pulse (lower = more frequent, heavier on CPU).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); DetectBerries = cfg.Bind<bool>("Forager", "DetectBerries", true, "Detect blueberry, raspberry and cloudberry bushes."); DetectMushrooms = cfg.Bind<bool>("Forager", "DetectMushrooms", true, "Detect wild mushrooms (red, yellow, blue, jotun puffs, magecap)."); MaxPins = cfg.Bind<int>("Forager", "MaxPins", 20, new ConfigDescription("Maximum number of minimap pins shown by the radar at once.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); } } public static class ForagerItem { public const string ItemPrefabName = "ForagersNose"; public static void Register() { PrefabManager.OnVanillaPrefabsAvailable += CreateItem; BragiPlugin.Log.LogInfo((object)"Forager's Nose queued for registration."); } private static void CreateItem() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown PrefabManager.OnVanillaPrefabsAvailable -= CreateItem; ItemConfig val = new ItemConfig(); val.Name = "$item_foragersnose"; val.Description = "$item_foragersnose_desc"; val.CraftingStation = CraftingStations.Workbench; val.Requirements = (RequirementConfig[])(object)new RequirementConfig[4] { new RequirementConfig("Raspberry", 10, 0, true), new RequirementConfig("Blueberries", 10, 0, true), new RequirementConfig("Mushroom", 5, 0, true), new RequirementConfig("Dandelion", 2, 0, true) }; ItemConfig val2 = val; CustomItem val3 = new CustomItem("ForagersNose", "Wishbone", val2); TweakSharedData(val3); ItemManager.Instance.AddItem(val3); BragiPlugin.Log.LogInfo((object)"Forager's Nose item registered."); } private static void TweakSharedData(CustomItem item) { GameObject itemPrefab = item.ItemPrefab; SharedData val = ((itemPrefab == null) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared); if (val == null) { BragiPlugin.Log.LogWarning((object)"Could not tweak Forager's Nose shared data — ItemDrop missing."); return; } val.m_name = "$item_foragersnose"; val.m_description = "$item_foragersnose_desc"; val.m_equipStatusEffect = null; val.m_setStatusEffect = null; BragiPlugin.Log.LogInfo((object)"Forager's Nose shared data tweaked."); } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class ForagerSpawnPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { if (((Character)__instance).IsOwner() && (Object)(object)((Component)__instance).GetComponent<ForagerRadar>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<ForagerRadar>(); BragiPlugin.Log.LogInfo((object)"ForagerRadar attached to local player."); } } } public class ForagerRadar : MonoBehaviour { private static readonly HashSet<string> BerryPrefabs = new HashSet<string> { "BlueberryBush", "RaspberryBush", "CloudberryBush" }; private static readonly HashSet<string> MushroomPrefabs = new HashSet<string> { "Pickable_Mushroom", "Pickable_Mushroom_blue", "Pickable_Mushroom_yellow", "Pickable_Mushroom_JotunPuffs", "Pickable_Mushroom_Magecap" }; private static readonly Dictionary<string, string?> FoodCategoryCache = new Dictionary<string, string>(); private static readonly int s_pickedKey = "picked".GetHashCode(); private const PinType PIN_TYPE = (PinType)8; private readonly Dictionary<int, PinData> _pins = new Dictionary<int, PinData>(); private Coroutine? _scanLoop; private static AudioClip? _pingSfx; private readonly Collider[] _colliderBuffer = (Collider[])(object)new Collider[256]; private bool _sfxAttempted; private void OnEnable() { _scanLoop = ((MonoBehaviour)this).StartCoroutine(ScanLoop()); BragiPlugin.Log.LogInfo((object)"ForagerRadar started."); } private void OnDisable() { if (_scanLoop != null) { ((MonoBehaviour)this).StopCoroutine(_scanLoop); } _scanLoop = null; ClearAllPins(); BragiPlugin.Log.LogInfo((object)"ForagerRadar stopped."); } private IEnumerator ScanLoop() { while (true) { yield return (object)new WaitForSeconds(ForagerConfig.PulseInterval.Value); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { continue; } if (!IsForagersNoseEquipped(localPlayer)) { if (_pins.Count > 0) { ClearAllPins(); } continue; } List<(GameObject, string)> list = FindNearbyPickables(((Component)localPlayer).transform.position); BragiPlugin.Log.LogDebug((object)$"Radar scan: found {list.Count} harvestable food resource(s) within {ForagerConfig.ScanRadius.Value}m."); RefreshPins(list); if (list.Count > 0) { PlayPingSfx(localPlayer, list); } } } private static bool IsForagersNoseEquipped(Player player) { Inventory inventory = ((Humanoid)player).GetInventory(); List<ItemData> list = ((inventory != null) ? inventory.GetEquippedItems() : null); if (list == null) { return false; } foreach (ItemData item in list) { if (item?.m_shared?.m_name == "$item_foragersnose") { return true; } } return false; } private List<(GameObject go, string category)> FindNearbyPickables(Vector3 origin) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) List<(GameObject, string)> list = new List<(GameObject, string)>(); float value = ForagerConfig.ScanRadius.Value; int value2 = ForagerConfig.MaxPins.Value; int num = Physics.OverlapSphereNonAlloc(origin, value, _colliderBuffer, -1, (QueryTriggerInteraction)2); HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < num; i++) { if (list.Count >= value2) { break; } Collider val = _colliderBuffer[i]; if ((Object)(object)val == (Object)null) { continue; } ZNetView componentInParent = ((Component)val).gameObject.GetComponentInParent<ZNetView>(); if ((Object)(object)componentInParent == (Object)null) { continue; } GameObject gameObject = ((Component)componentInParent).gameObject; int instanceID = ((Object)gameObject).GetInstanceID(); if (hashSet.Contains(instanceID)) { continue; } string name = ((Object)gameObject).name; int num2 = name.IndexOf('('); string prefabName = ((num2 >= 0) ? name.Substring(0, num2).TrimEnd(Array.Empty<char>()) : name); string category = GetCategory(gameObject, prefabName); if (category != null) { Pickable componentInChildren = gameObject.GetComponentInChildren<Pickable>(); if ((!((Object)(object)componentInChildren != (Object)null) || (!componentInChildren.GetPicked() && componentInChildren.CanBePicked())) && (!componentInParent.IsValid() || !componentInParent.GetZDO().GetBool(s_pickedKey, false))) { hashSet.Add(instanceID); list.Add((gameObject, category)); } } } return list; } private string? GetCategory(GameObject rootGo, string prefabName) { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Invalid comparison between Unknown and I4 if (!FoodCategoryCache.TryGetValue(prefabName, out string value)) { if (BerryPrefabs.Contains(prefabName)) { value = "$forager_berries"; } else if (MushroomPrefabs.Contains(prefabName)) { value = "$forager_mushrooms"; } else { Pickable componentInChildren = rootGo.GetComponentInChildren<Pickable>(); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.m_itemPrefab != (Object)null) { SharedData val = componentInChildren.m_itemPrefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared; if (val != null && (val.m_food > 0f || val.m_foodStamina > 0f || val.m_foodEitr > 0f || ((int)val.m_itemType == 2 && !val.m_isDrink))) { value = (prefabName.ToLowerInvariant().Contains("mushroom") ? "$forager_mushrooms" : "$forager_berries"); } } } FoodCategoryCache[prefabName] = value; } if (value == null) { return null; } if (value == "$forager_berries" && !ForagerConfig.DetectBerries.Value) { return null; } if (value == "$forager_mushrooms" && !ForagerConfig.DetectMushrooms.Value) { return null; } return value; } private void RefreshPins(List<(GameObject go, string category)> current) { //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Minimap.instance == (Object)null) { return; } HashSet<int> hashSet = new HashSet<int>(); foreach (var item4 in current) { GameObject item = item4.go; hashSet.Add(((Object)item).GetInstanceID()); } List<int> list = new List<int>(); foreach (int key in _pins.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (int item5 in list) { Minimap.instance.RemovePin(_pins[item5]); _pins.Remove(item5); } foreach (var item6 in current) { GameObject item2 = item6.go; string item3 = item6.category; int instanceID = ((Object)item2).GetInstanceID(); if (!_pins.ContainsKey(instanceID)) { PinData value = Minimap.instance.AddPin(item2.transform.position, (PinType)8, item3, false, false, 0L, default(PlatformUserID)); _pins[instanceID] = value; BragiPlugin.Log.LogDebug((object)$"Pin added at {item2.transform.position} [{item3}]"); } } } private void ClearAllPins() { if ((Object)(object)Minimap.instance == (Object)null) { _pins.Clear(); return; } foreach (PinData value in _pins.Values) { Minimap.instance.RemovePin(value); } _pins.Clear(); } private void PlayPingSfx(Player player, List<(GameObject go, string category)> results) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) float num = float.MaxValue; foreach (var result in results) { GameObject item = result.go; float num2 = Vector3.Distance(((Component)player).transform.position, item.transform.position); if (num2 < num) { num = num2; } } float value = ForagerConfig.ScanRadius.Value; float num3 = 1f - Mathf.Clamp01(num / value); if ((Object)(object)_pingSfx == (Object)null && !_sfxAttempted) { _sfxAttempted = true; _pingSfx = LoadWishbonePingClip(); BragiPlugin.Log.LogInfo((object)(((Object)(object)_pingSfx != (Object)null) ? "Wishbone SFX loaded successfully." : "Wishbone SFX not found — continuing without audio.")); } if (!((Object)(object)_pingSfx == (Object)null)) { float volume = Mathf.Lerp(0.15f, 0.7f, num3); float pitch = Mathf.Lerp(0.8f, 1.4f, num3); PlaySfxAtPoint(_pingSfx, ((Component)player).transform.position, volume, pitch); } } private static void PlaySfxAtPoint(AudioClip clip, Vector3 position, float volume, float pitch) { //IL_0005: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown GameObject val = new GameObject("_ForagerPing"); val.transform.position = position; AudioSource obj = val.AddComponent<AudioSource>(); obj.clip = clip; obj.volume = volume; obj.pitch = pitch; obj.spatialBlend = 1f; obj.maxDistance = 25f; obj.rolloffMode = (AudioRolloffMode)1; obj.Play(); Object.Destroy((Object)val, clip.length + 0.5f); } private static AudioClip? LoadWishbonePingClip() { if ((Object)(object)ObjectDB.instance == (Object)null) { BragiPlugin.Log.LogInfo((object)"LoadWishbonePingClip: ObjectDB not ready."); return null; } SE_Finder val = null; foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects) { SE_Finder val2 = (SE_Finder)(object)((statusEffect is SE_Finder) ? statusEffect : null); if (val2 != null && ((Object)statusEffect).name == "Wishbone") { val = val2; break; } } if ((Object)(object)val == (Object)null) { BragiPlugin.Log.LogInfo((object)"LoadWishbonePingClip: SE_Finder 'Wishbone' not found in ObjectDB."); return null; } EffectData[] array = val.m_pingEffectNear?.m_effectPrefabs; if (array == null) { return null; } EffectData[] array2 = array; foreach (EffectData val3 in array2) { if (!((Object)(object)val3.m_prefab == (Object)null)) { AudioSource componentInChildren = val3.m_prefab.GetComponentInChildren<AudioSource>(); if ((Object)(object)((componentInChildren != null) ? componentInChildren.clip : null) != (Object)null) { return componentInChildren.clip; } } } return null; } } public static class InstrumentDefinitions { public static readonly Dictionary<string, InstrumentType> ItemNameToType = new Dictionary<string, InstrumentType> { { "BragiLyre", InstrumentType.Lyre }, { "BragiBoneFlute", InstrumentType.BoneFlute }, { "BragiJawHarp", InstrumentType.JawHarp } }; public static bool IsInstrument(string itemName) { return ItemNameToType.ContainsKey(itemName); } public static InstrumentType? GetType(string itemName) { if (!ItemNameToType.TryGetValue(itemName, out var value)) { return null; } return value; } } public enum InstrumentType { Lyre, BoneFlute, JawHarp } public static class InstrumentRegistry { public static void Register() { PrefabManager.OnVanillaPrefabsAvailable += AddInstruments; } private static void AddInstruments() { RegisterLyre(); RegisterBoneFlute(); RegisterJawHarp(); BragiPlugin.Log.LogInfo((object)"\ud83c\udfb5 Bragi instruments registered."); PrefabManager.OnVanillaPrefabsAvailable -= AddInstruments; } private static void RegisterLyre() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown ItemConfig val = new ItemConfig(); val.Name = "$item_bragi_lyre"; val.Description = "$item_bragi_lyre_desc"; val.CraftingStation = CraftingStations.Workbench; val.MinStationLevel = 1; val.Requirements = (RequirementConfig[])(object)new RequirementConfig[3] { new RequirementConfig { Item = "FineWood", Amount = 8 }, new RequirementConfig { Item = "DeerHide", Amount = 2 }, new RequirementConfig { Item = "Resin", Amount = 4 } }; ItemConfig val2 = val; ItemManager.Instance.AddItem(new CustomItem("BragiLyre", "Club", val2)); } private static void RegisterBoneFlute() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown ItemConfig val = new ItemConfig(); val.Name = "$item_bragi_boneflute"; val.Description = "$item_bragi_boneflute_desc"; val.CraftingStation = CraftingStations.Workbench; val.MinStationLevel = 1; val.Requirements = (RequirementConfig[])(object)new RequirementConfig[2] { new RequirementConfig { Item = "BoneFragments", Amount = 4 }, new RequirementConfig { Item = "Feathers", Amount = 2 } }; ItemConfig val2 = val; ItemManager.Instance.AddItem(new CustomItem("BragiBoneFlute", "Club", val2)); } private static void RegisterJawHarp() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown ItemConfig val = new ItemConfig(); val.Name = "$item_bragi_jawharp"; val.Description = "$item_bragi_jawharp_desc"; val.CraftingStation = CraftingStations.Forge; val.MinStationLevel = 1; val.Requirements = (RequirementConfig[])(object)new RequirementConfig[2] { new RequirementConfig { Item = "Iron", Amount = 2 }, new RequirementConfig { Item = "LeatherScraps", Amount = 1 } }; ItemConfig val2 = val; ItemManager.Instance.AddItem(new CustomItem("BragiJawHarp", "Club", val2)); } } [Serializable] public class SongData { [JsonProperty("id")] public string Id { get; set; } = ""; [JsonProperty("name")] public string Name { get; set; } = "Unknown Song"; [JsonProperty("author")] public string Author { get; set; } = "Traditional"; [JsonProperty("description")] public string Description { get; set; } = ""; [JsonProperty("audioFile")] public string AudioFile { get; set; } = ""; [JsonProperty("tracks")] public Dictionary<string, string> Tracks { get; set; } = new Dictionary<string, string>(); [JsonProperty("duration")] public float Duration { get; set; } = 60f; [JsonProperty("instruments")] public List<string> Instruments { get; set; } = new List<string>(); [JsonProperty("mood")] public string Mood { get; set; } = ""; [JsonIgnore] public Dictionary<string, AudioClip> StemClips { get; } = new Dictionary<string, AudioClip>(); [JsonIgnore] public string SourcePath { get; set; } = ""; public bool SupportsInstrument(InstrumentType type) { if (Instruments == null || Instruments.Count == 0) { return true; } return Instruments.Contains(type.ToString()); } public string GetAudioFileForInstrument(InstrumentType? type) { if (type.HasValue && Tracks != null && Tracks.TryGetValue(type.Value.ToString(), out string value)) { return value; } return AudioFile; } public bool HasStemFor(InstrumentType type) { if (Tracks != null) { return Tracks.ContainsKey(type.ToString()); } return false; } public override string ToString() { return $"[{Id}] {Name} by {Author} ({Duration:0}s)"; } } public static class SongLibrary { private static readonly List<SongData> _songs = new List<SongData>(); public static IReadOnlyList<SongData> AllSongs => _songs; public static string SongsDirectory => Path.Combine(Paths.ConfigPath, "Bragi", "songs"); public static void Init() { Directory.CreateDirectory(SongsDirectory); ExtractDefaultSongs(); LoadSongs(); } private static void ExtractDefaultSongs() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); foreach (string item in from n in executingAssembly.GetManifestResourceNames() where n.StartsWith("Bragi.songs.") && n.EndsWith(".json") select n) { string text = item.Replace("Bragi.songs.", ""); string path = Path.Combine(SongsDirectory, text); if (File.Exists(path)) { continue; } using Stream stream = executingAssembly.GetManifestResourceStream(item); if (stream != null) { using StreamReader streamReader = new StreamReader(stream); File.WriteAllText(path, streamReader.ReadToEnd()); BragiPlugin.Log.LogInfo((object)("\ud83d\udcc4 Extracted default song: " + text)); } } } private static void LoadSongs() { _songs.Clear(); string[] files = Directory.GetFiles(SongsDirectory, "*.json"); foreach (string text in files) { try { SongData songData = JsonConvert.DeserializeObject<SongData>(File.ReadAllText(text)); if (songData != null) { songData.SourcePath = text; _songs.Add(songData); BragiPlugin.Log.LogInfo((object)$"\ud83c\udfb5 Loaded song: {songData}"); } } catch (Exception ex) { BragiPlugin.Log.LogError((object)("Failed to load song JSON " + text + ": " + ex.Message)); } } BragiPlugin.Log.LogInfo((object)$"\ud83c\udfb5 SongLibrary: {_songs.Count} songs loaded."); } public static IEnumerator LoadClip(SongData song, InstrumentType? instrument, Action<AudioClip?> onLoaded) { string audioFileName = song.GetAudioFileForInstrument(instrument); if (string.IsNullOrEmpty(audioFileName)) { BragiPlugin.Log.LogWarning((object)("No audio file defined for song '" + song.Id + "'.")); onLoaded(null); yield break; } if (song.StemClips.TryGetValue(audioFileName, out AudioClip value)) { onLoaded(value); yield break; } string path = Path.GetDirectoryName(song.SourcePath) ?? SongsDirectory; string audioPath = Path.Combine(path, audioFileName); if (!File.Exists(audioPath)) { BragiPlugin.Log.LogWarning((object)("Audio file not found: " + audioPath)); onLoaded(null); yield break; } AudioType val = (AudioType)(audioPath.EndsWith(".ogg", StringComparison.OrdinalIgnoreCase) ? 14 : 20); UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip("file:///" + audioPath.Replace('\\', '/'), val); try { yield return request.SendWebRequest(); if ((int)request.result != 1) { BragiPlugin.Log.LogError((object)("Failed to load audio '" + audioPath + "': " + request.error)); onLoaded(null); yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(request); ((Object)content).name = song.Id + "_" + audioFileName; song.StemClips[audioFileName] = content; onLoaded(content); } finally { ((IDisposable)request)?.Dispose(); } } public static List<SongData> GetSongsForInstrument(InstrumentType type) { return _songs.Where((SongData s) => s.SupportsInstrument(type)).ToList(); } public static SongData? GetById(string id) { return _songs.FirstOrDefault((SongData s) => s.Id == id); } } public class SongPlayer : MonoBehaviour { private AudioSource? _audioSource; private Coroutine? _loadCoroutine; public static SongPlayer? Instance { get; private set; } public bool IsPlaying { get; private set; } public SongData? CurrentSong { get; private set; } private void Awake() { Instance = this; _audioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); Configure3DAudio(_audioSource); } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } StopPlaying(); } public void StartNewSession(SongData song) { if (IsPlaying) { StopPlaying(); } InstrumentType? equippedInstrumentType = GetEquippedInstrumentType(); CurrentSong = song; _loadCoroutine = ((MonoBehaviour)this).StartCoroutine(LoadAndPlay(song, equippedInstrumentType, 0f, isNewSession: true)); } public void JoinActiveSession() { MusicSync.EnsembleSession activeSession = MusicSync.ActiveSession; if (activeSession == null) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "⚠ No active ensemble session nearby.", 0, (Sprite)null, false); } return; } if (IsPlaying) { StopPlaying(); } InstrumentType? equippedInstrumentType = GetEquippedInstrumentType(); float seekSeconds = (((Object)(object)ZNet.instance != (Object)null) ? ((float)(ZNet.instance.GetTimeSeconds() - activeSession.StartNetworkTime)) : 0f); CurrentSong = activeSession.Song; _loadCoroutine = ((MonoBehaviour)this).StartCoroutine(LoadAndPlay(activeSession.Song, equippedInstrumentType, seekSeconds, isNewSession: false)); } public void StopPlaying() { if (_loadCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_loadCoroutine); _loadCoroutine = null; } if ((Object)(object)_audioSource != (Object)null) { _audioSource.Stop(); } if (IsPlaying) { IsPlaying = false; MusicSync.SendStopSession(); AudioRouting.RestoreGameMusic(); BardBuff.EndBuff(); BragiPlugin.Log.LogInfo((object)"\ud83c\udfb5 Stopped playing."); } CurrentSong = null; } private IEnumerator LoadAndPlay(SongData song, InstrumentType? instrType, float seekSeconds, bool isNewSession) { BragiPlugin.Log.LogInfo((object)("\ud83c\udfb5 Loading song: " + song.Name + (instrType.HasValue ? $" [{instrType.Value} stem]" : " [full mix]"))); AudioClip clip = null; yield return SongLibrary.LoadClip(song, instrType, delegate(AudioClip? c) { clip = c; }); if ((Object)(object)clip == (Object)null) { BragiPlugin.Log.LogError((object)("Could not load audio for '" + song.Name + "'. Aborting.")); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "⚠ Could not load audio file.", 0, (Sprite)null, false); } yield break; } _audioSource.clip = clip; _audioSource.volume = BragiConfig.MasterVolume.Value; _audioSource.maxDistance = BragiConfig.MusicRange.Value; AudioRouting.ApplyMixerGroup(_audioSource); float num = Mathf.Repeat(seekSeconds, clip.length); _audioSource.time = num; _audioSource.Play(); IsPlaying = true; AudioRouting.DuckGameMusic(); if (isNewSession) { MusicSync.SendStartSession(song.Id, instrType?.ToString() ?? ""); } else { MusicSync.SendJoinSession(song.Id, instrType?.ToString() ?? ""); } if (BragiConfig.BuffEnabled.Value) { BardBuff.StartBuff(); } BragiPlugin.Log.LogInfo((object)("\ud83c\udfb5 Now playing: " + song.Name + ((num > 0f) ? $" (offset {num:0.0}s)" : ""))); float num2 = clip.length - num; yield return (object)new WaitForSeconds(num2); StopPlaying(); } private static InstrumentType? GetEquippedInstrumentType() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } ItemData val = ((Humanoid)localPlayer).GetInventory().GetEquippedItems().Find(delegate(ItemData i) { GameObject dropPrefab2 = i.m_dropPrefab; return InstrumentDefinitions.IsInstrument(((dropPrefab2 != null) ? ((Object)dropPrefab2).name : null) ?? ""); }); if (val == null) { return null; } GameObject dropPrefab = val.m_dropPrefab; return InstrumentDefinitions.GetType(((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? ""); } private static void Configure3DAudio(AudioSource src) { src.spatialBlend = 1f; src.rolloffMode = (AudioRolloffMode)1; src.minDistance = 2f; src.maxDistance = BragiConfig.MusicRange.Value; src.dopplerLevel = 0f; src.spread = 60f; src.loop = false; src.playOnAwake = false; } } public static class MusicSync { public class EnsembleSession { public SongData Song; public double StartNetworkTime; public long HostPeerId; } internal const string RPC_START_SESSION = "Bragi_StartSession"; internal const string RPC_JOIN_SESSION = "Bragi_JoinSession"; internal const string RPC_STOP_SESSION = "Bragi_StopSession"; public static EnsembleSession? ActiveSession { get; private set; } public static void Init() { BragiPlugin.Log.LogInfo((object)"\ud83c\udfb5 MusicSync ready (RPCs will register on ZNet.Awake)."); } public static void SendStartSession(string songId, string instrumentType) { if (ZRoutedRpc.instance != null) { double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Bragi_StartSession", new object[3] { songId, num, instrumentType }); } } public static void SendJoinSession(string songId, string instrumentType) { if (ZRoutedRpc.instance != null) { double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Bragi_JoinSession", new object[3] { songId, num, instrumentType }); } } public static void SendStopSession() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Bragi_StopSession", Array.Empty<object>()); if (ActiveSession?.HostPeerId == ZDOMan.GetSessionID()) { ActiveSession = null; } } } internal static void OnRemoteStartSession(long senderPeerId, string songId, double startNetTime, string instrumentType) { if (senderPeerId == ZDOMan.GetSessionID()) { return; } SongData byId = SongLibrary.GetById(songId); if (byId == null) { BragiPlugin.Log.LogWarning((object)("Received StartSession for unknown song: " + songId)); return; } ActiveSession = new EnsembleSession { Song = byId, StartNetworkTime = startNetTime, HostPeerId = senderPeerId }; Player playerByPeerId = GetPlayerByPeerId(senderPeerId); if (!((Object)(object)playerByPeerId == (Object)null)) { double elapsedSeconds = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : startNetTime) - startNetTime; RemoteAudioSource.PlayAt(playerByPeerId, byId, instrumentType, startNetTime, elapsedSeconds); BragiPlugin.Log.LogInfo((object)("\ud83c\udfb5 Remote session started: " + byId.Name + " by " + playerByPeerId.GetPlayerName())); } } internal static void OnRemoteJoinSession(long senderPeerId, string songId, double joinNetTime, string instrumentType) { if (senderPeerId == ZDOMan.GetSessionID()) { return; } SongData byId = SongLibrary.GetById(songId); if (byId != null) { if (ActiveSession == null) { ActiveSession = new EnsembleSession { Song = byId, StartNetworkTime = joinNetTime, HostPeerId = senderPeerId }; } Player playerByPeerId = GetPlayerByPeerId(senderPeerId); if (!((Object)(object)playerByPeerId == (Object)null)) { double elapsedSeconds = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : joinNetTime) - ActiveSession.StartNetworkTime; RemoteAudioSource.PlayAt(playerByPeerId, byId, instrumentType, ActiveSession.StartNetworkTime, elapsedSeconds); BragiPlugin.Log.LogInfo((object)("\ud83c\udfb5 Remote player joined session: " + playerByPeerId.GetPlayerName() + " (" + instrumentType + ")")); } } } internal static void OnRemoteStopSession(long senderPeerId) { if (senderPeerId != ZDOMan.GetSessionID()) { Player playerByPeerId = GetPlayerByPeerId(senderPeerId); if ((Object)(object)playerByPeerId != (Object)null) { RemoteAudioSource.StopAt(playerByPeerId); } EnsembleSession? activeSession = ActiveSession; if (activeSession != null && activeSession.HostPeerId == senderPeerId) { ActiveSession = null; BragiPlugin.Log.LogInfo((object)"\ud83c\udfb5 Remote ensemble session ended."); } } } private static Player? GetPlayerByPeerId(long peerId) { foreach (Player allPlayer in Player.GetAllPlayers()) { ZNetView component = ((Component)allPlayer).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { ZDO zDO = component.GetZDO(); if (zDO != null && zDO.GetOwner() == peerId) { return allPlayer; } } } return null; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakePatch { [HarmonyPostfix] private static void Postfix() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<string, double, string>("Bragi_StartSession", (Action<long, string, double, string>)MusicSync.OnRemoteStartSession); ZRoutedRpc.instance.Register<string, double, string>("Bragi_JoinSession", (Action<long, string, double, string>)MusicSync.OnRemoteJoinSession); ZRoutedRpc.instance.Register("Bragi_StopSession", (Action<long>)MusicSync.OnRemoteStopSession); BragiPlugin.Log.LogInfo((object)"\ud83c\udfb5 MusicSync RPCs registered."); } } } public class RemoteAudioSource : MonoBehaviour { private AudioSource? _source; private static readonly Dictionary<Player, RemoteAudioSource> _active = new Dictionary<Player, RemoteAudioSource>(); public static void PlayAt(Player player, SongData song, string instrumentType, double sessionStartTime, double elapsedSeconds) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) StopAt(player); GameObject val = new GameObject("BragiRemoteAudio"); val.transform.SetParent(((Component)player).transform, false); val.transform.localPosition = new Vector3(0f, 1.5f, 0f); RemoteAudioSource remoteAudioSource = val.AddComponent<RemoteAudioSource>(); ((MonoBehaviour)remoteAudioSource).StartCoroutine(remoteAudioSource.LoadAndPlay(song, instrumentType, (float)elapsedSeconds)); _active[player] = remoteAudioSource; } public static void StopAt(Player player) { if (_active.TryGetValue(player, out RemoteAudioSource value)) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)((Component)value).gameObject); } _active.Remove(player); } } private IEnumerator LoadAndPlay(SongData song, string instrumentType, float seekSeconds) { InstrumentType result; InstrumentType? instrument = (Enum.TryParse<InstrumentType>(instrumentType, out result) ? new InstrumentType?(result) : ((InstrumentType?)null)); AudioClip clip = null; yield return SongLibrary.LoadClip(song, instrument, delegate(AudioClip? c) { clip = c; }); if (!((Object)(object)clip == (Object)null) && !((Object)(object)this == (Object)null)) { _source = ((Component)this).gameObject.AddComponent<AudioSource>(); _source.clip = clip; _source.volume = BragiConfig.MasterVolume.Value; _source.spatialBlend = 1f; _source.rolloffMode = (AudioRolloffMode)1; _source.minDistance = 2f; _source.maxDistance = BragiConfig.MusicRange.Value; _source.dopplerLevel = 0f; _source.loop = false; AudioRouting.ApplyMixerGroup(_source); float num = Mathf.Repeat(seekSeconds, clip.length); _source.time = num; _source.Play(); float num2 = clip.length - num; yield return (object)new WaitForSeconds(num2); if ((Object)(object)this != (Object)null) { Object.Destroy((Object)(object)((Component)this).gameObject); } } } } public static class AudioRouting { private static AudioMixerGroup? _cachedMixerGroup; public static void ApplyMixerGroup(AudioSource source) { if ((Object)(object)_cachedMixerGroup == (Object)null) { _cachedMixerGroup = ResolveMixerGroup(); } if ((Object)(object)_cachedMixerGroup != (Object)null) { source.outputAudioMixerGroup = _cachedMixerGroup; } } private static AudioMixerGroup? ResolveMixerGroup() { try { if ((Object)(object)AudioMan.instance != (Object)null) { return AudioMan.instance.m_ambientMixer; } } catch { } return null; } public static void DuckGameMusic() { if (!BragiConfig.DuckGameMusic.Value) { return; } try { AudioMan instance = AudioMan.instance; if (instance != null) { AudioMixer masterMixer = instance.m_masterMixer; if (masterMixer != null) { masterMixer.SetFloat("MusicVolume", -80f); } } } catch { } } public static void RestoreGameMusic() { try { AudioMan instance = AudioMan.instance; if (instance != null) { AudioMixer masterMixer = instance.m_masterMixer; if (masterMixer != null) { masterMixer.SetFloat("MusicVolume", 0f); } } } catch { } } } [BepInPlugin("com.bragi.valheim", "Bragi", "0.2.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class BragiPlugin : BaseUnityPlugin { public const string PluginGUID = "com.bragi.valheim"; public const string PluginName = "Bragi"; public const string PluginVersion = "0.2.0"; internal static ManualLogSource Log; private static Harmony _harmony; private void Awake() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"\ud83c\udfb5 Bragi 0.2.0 loading..."); BragiConfig.Init(((BaseUnityPlugin)this).Config); ForagerConfig.Init(((BaseUnityPlugin)this).Config); _harmony = new Harmony("com.bragi.valheim"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); InstrumentRegistry.Register(); ForagerItem.Register(); SongLibrary.Init(); MusicSync.Init(); RegisterLocalization(); Log.LogInfo((object)"\ud83c\udfb5 Bragi loaded successfully!"); } private static void RegisterLocalization() { CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Bragi.Localization.English.json"); if (stream != null) { using (StreamReader streamReader = new StreamReader(stream)) { localization.AddJsonFile("English", streamReader.ReadToEnd()); Log.LogInfo((object)"\ud83c\udfb5 Localization loaded from embedded resource."); return; } } string path = Path.Combine(Paths.ConfigPath, "Bragi", "Localization", "English.json"); if (File.Exists(path)) { localization.AddJsonFile("English", File.ReadAllText(path)); Log.LogInfo((object)"\ud83c\udfb5 Localization loaded from config folder."); } else { Log.LogWarning((object)"⚠ Localization file not found — item names may appear as raw tokens."); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } public class SongSelectUI : MonoBehaviour { private List<SongData> _availableSongs = new List<SongData>(); private const int PanelW = 460; private const int PanelH = 560; private Rect _panelRect; private Vector2 _scrollPos; private int _selectedIndex = -1; private KeyCode _cachedMenuKey = (KeyCode)103; private string _cachedMenuKeyStr = "G"; public static SongSelectUI? Instance { get; private set; } public static bool IsOpen { get; private set; } private void Awake() { Instance = this; CenterPanel(); } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } ForceClose(); } private void CenterPanel() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) _panelRect = new Rect((float)(Screen.width - 460) / 2f, (float)(Screen.height - 560) / 2f, 460f, 560f); } private KeyCode GetMenuKey() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) string value = BragiConfig.OpenMenuKey.Value; if (value != _cachedMenuKeyStr) { _cachedMenuKeyStr = value; _cachedMenuKey = (KeyCode)((!Enum.TryParse<KeyCode>(value, ignoreCase: true, out KeyCode result)) ? 103 : ((int)result)); } return _cachedMenuKey; } private void Update() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(GetMenuKey()) && IsInstrumentEquipped()) { Toggle(); } if (IsOpen && Input.GetKeyDown((KeyCode)27)) { Close(); } if (IsOpen && _selectedIndex >= 0 && Input.GetKeyDown((KeyCode)13)) { PlaySelected(); } } private void OnGUI() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0079: Unknown result type (might be due to invalid IL or missing references) if (IsOpen) { if ((int)Event.current.type == 8) { CenterPanel(); } GUI.color = new Color(0f, 0f, 0f, 0.55f); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; GUI.Box(_panelRect, ""); GUILayout.BeginArea(_panelRect); DrawHeader(); DrawEnsembleBanner(); DrawSongList(); DrawFooter(); GUILayout.EndArea(); } } private void DrawHeader() { GUILayout.Space(10f); GUILayout.Label("♪ Choose a Song ♪", LargeLabel(), Array.Empty<GUILayoutOption>()); GUILayout.Label(GetInstrumentDisplayName(), SmallLabel(), Array.Empty<GUILayoutOption>()); GUILayout.Space(6f); DrawHorizontalLine(); GUILayout.Space(4f); } private void DrawEnsembleBanner() { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) MusicSync.EnsembleSession activeSession = MusicSync.ActiveSession; if (activeSession == null) { return; } string text = "someone"; foreach (Player allPlayer in Player.GetAllPlayers()) { ZNetView component = ((Component)allPlayer).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { ZDO zDO = component.GetZDO(); if (((zDO != null) ? new long?(zDO.GetOwner()) : ((long?)null)) == activeSession.HostPeerId) { text = allPlayer.GetPlayerName(); break; } } } GUILayout.Space(4f); GUI.backgroundColor = new Color(0.15f, 0.45f, 0.15f, 0.95f); if (GUILayout.Button("\ud83c\udfb5 Join " + text + "'s \"" + activeSession.Song.Name + "\" →", JoinBannerStyle(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) })) { JoinSession(); } GUI.backgroundColor = Color.white; GUILayout.Space(6f); DrawHorizontalLine(); GUILayout.Space(4f); } private void DrawSongList() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) _scrollPos = GUILayout.BeginScrollView(_scrollPos, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(444f), GUILayout.Height(400f) }); for (int i = 0; i < _availableSongs.Count; i++) { SongData songData = _availableSongs[i]; GUI.backgroundColor = ((i == _selectedIndex) ? new Color(0.65f, 0.48f, 0.1f, 0.95f) : new Color(0.18f, 0.14f, 0.07f, 0.85f)); if (GUILayout.Button("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(54f) })) { _selectedIndex = i; } Rect lastRect = GUILayoutUtility.GetLastRect(); GUI.Label(new Rect(((Rect)(ref lastRect)).x + 10f, ((Rect)(ref lastRect)).y + 5f, ((Rect)(ref lastRect)).width - 100f, 22f), songData.Name, SongNameStyle()); GUI.Label(new Rect(((Rect)(ref lastRect)).x + 10f, ((Rect)(ref lastRect)).y + 28f, ((Rect)(ref lastRect)).width - 100f, 17f), songData.Author + " · " + songData.Mood, SubtitleStyle()); GUI.Label(new Rect(((Rect)(ref lastRect)).xMax - 90f, ((Rect)(ref lastRect)).y + 18f, 80f, 18f), FormatDuration(songData.Duration), DurationStyle()); DrawStemDots(songData, lastRect); GUI.backgroundColor = Color.white; GUILayout.Space(2f); } if (_availableSongs.Count == 0) { GUILayout.Label("No songs found.\nAdd .json + .ogg files to:\nBepInEx/config/Bragi/songs/", SmallLabel(), Array.Empty<GUILayoutOption>()); } GUILayout.EndScrollView(); } private static void DrawStemDots(SongData song, Rect btnRect) { //IL_009f: 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_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) if (song.Tracks == null || song.Tracks.Count == 0) { return; } float num = ((Rect)(ref btnRect)).x + 10f; float num2 = ((Rect)(ref btnRect)).yMax - 12f; foreach (InstrumentType value in Enum.GetValues(typeof(InstrumentType))) { if (song.HasStemFor(value)) { GUI.color = (Color)(value switch { InstrumentType.BoneFlute => new Color(0.6f, 0.9f, 0.6f), InstrumentType.Lyre => new Color(0.9f, 0.85f, 0.4f), InstrumentType.JawHarp => new Color(0.7f, 0.5f, 0.9f), _ => Color.white, }); GUI.DrawTexture(new Rect(num, num2, 8f, 8f), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; num += 12f; } } } private void DrawFooter() { DrawHorizontalLine(); GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUI.enabled = _selectedIndex >= 0; if (GUILayout.Button("▶ Play", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(34f), GUILayout.Width(130f) })) { PlaySelected(); } GUI.enabled = true; SongPlayer? instance = SongPlayer.Instance; if (instance != null && instance.IsPlaying && GUILayout.Button("■ Stop", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(34f), GUILayout.Width(90f) })) { SongPlayer.Instance.StopPlaying(); } GUILayout.FlexibleSpace(); if (GUILayout.Button("✕", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(34f), GUILayout.Width(34f) })) { Close(); } GUILayout.EndHorizontal(); GUILayout.Space(8f); } public void Open(InstrumentType instrumentType) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) _availableSongs = SongLibrary.GetSongsForInstrument(instrumentType); _selectedIndex = ((_availableSongs.Count <= 0) ? (-1) : 0); _scrollPos = Vector2.zero; IsOpen = true; Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } public void Close() { IsOpen = false; Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; } private void ForceClose() { if (IsOpen) { Close(); } } public void Toggle() { if (IsOpen) { Close(); return; } InstrumentType? equippedInstrumentType = GetEquippedInstrumentType(); if (equippedInstrumentType.HasValue) { Open(equippedInstrumentType.Value); } } private void PlaySelected() { if (_selectedIndex >= 0 && _selectedIndex < _availableSongs.Count) { SongData song = _availableSongs[_selectedIndex]; SongPlayer instance = SongPlayer.Instance; if (!((Object)(object)instance == (Object)null)) { instance.StartNewSession(song); Close(); } } } private void JoinSession() { SongPlayer instance = SongPlayer.Instance; if (!((Object)(object)instance == (Object)null)) { instance.JoinActiveSession(); Close(); } } private static bool IsInstrumentEquipped() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } return ((Humanoid)localPlayer).GetInventory().GetEquippedItems().Exists(delegate(ItemData i) { GameObject dropPrefab = i.m_dropPrefab; return InstrumentDefinitions.IsInstrument(((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? ""); }); } private static InstrumentType? GetEquippedInstrumentType() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } ItemData val = ((Humanoid)localPlayer).GetInventory().GetEquippedItems().Find(delegate(ItemData i) { GameObject dropPrefab2 = i.m_dropPrefab; return InstrumentDefinitions.IsInstrument(((dropPrefab2 != null) ? ((Object)dropPrefab2).name : null) ?? ""); }); if (val == null) { return null; } GameObject dropPrefab = val.m_dropPrefab; return InstrumentDefinitions.GetType(((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? ""); } private static string GetInstrumentDisplayName() { return GetEquippedInstrumentType() switch { InstrumentType.Lyre => "Lyre", InstrumentType.BoneFlute => "Bone Flute", InstrumentType.JawHarp => "Jaw Harp", _ => "Instrument", }; } private static string FormatDuration(float seconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); if (timeSpan.Minutes <= 0) { return $"0:{timeSpan.Seconds:00}"; } return $"{timeSpan.Minutes}:{timeSpan.Seconds:00}"; } private static void DrawHorizontalLine() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) Rect rect = GUILayoutUtility.GetRect(1f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUI.color = new Color(0.6f, 0.45f, 0.1f); GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; } private static GUIStyle LargeLabel() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 18, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; val.normal.textColor = new Color(0.95f, 0.8f, 0.4f); return val; } private static GUIStyle SmallLabel() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 11, alignment = (TextAnchor)4 }; val.normal.textColor = new Color(0.75f, 0.68f, 0.55f); return val; } private static GUIStyle JoinBannerStyle() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.button) { fontSize = 13, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; val.normal.textColor = new Color(0.85f, 1f, 0.85f); val.hover.textColor = Color.white; return val; } private static GUIStyle SongNameStyle() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 13, fontStyle = (FontStyle)1 }; val.normal.textColor = new Color(0.95f, 0.9f, 0.8f); return val; } private static GUIStyle SubtitleStyle() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 10 }; val.normal.textColor = new Color(0.7f, 0.65f, 0.5f); return val; } private static GUIStyle DurationStyle() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 11, alignment = (TextAnchor)5 }; val.normal.textColor = new Color(0.6f, 0.8f, 0.6f); return val; } } [HarmonyPatch(typeof(PlayerController), "InInventoryEtc")] internal static class BlockInputWhileUIOpenPatch { [HarmonyPostfix] private static void Postfix(ref bool __result) { if (SongSelectUI.IsOpen) { __result = true; } } } }