using 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.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using Newtonsoft.Json; using Photon.Pun; using Photon.Realtime; using REPOLib; using SkillFern.Custom; using SkillFern.Networking; using SkillFern.UI; using SkillFern.Utilities; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("SkillFern")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SkillFern")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("2317c45d-9d42-4c50-aeed-5f05c5bf5865")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace SkillFern { [BepInPlugin("com.dajadeninja.repo.skillfern", "Skill Fern", "0.0.0")] public class Plugin : BaseUnityPlugin { public static Plugin instance; private Harmony harmony; private void Awake() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown instance = this; harmony = new Harmony("com.dajadeninja.repo.skillfern"); ((BaseUnityPlugin)instance).Logger.LogInfo((object)"Setting up configuration. . ."); ConfigHelper.Initialize(((BaseUnityPlugin)this).Config); LogInfo("Loaded successfully! Loading patches. . ."); harmony.PatchAll(); LogInfo("Patches loaded"); AssetHelper.LoadBundles(); LogInfo("Newest 9"); } public static void LogInfo(string msg) { if (ConfigHelper.EnableDebug()) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)("Skill Fern: " + msg)); } } public static void LogError(string msg) { if (ConfigHelper.EnableDebug()) { ((BaseUnityPlugin)instance).Logger.LogError((object)("Skill Fern: " + msg)); } } } } namespace SkillFern.Utilities { public class AssetHelper { public static AssetBundle bundle; public static bool loaded; public static void LoadBundles() { BundleLoader.LoadBundle(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "SkillFern.bundle"), (Func)OnBundleLoaded, true); } private static IEnumerator OnBundleLoaded(AssetBundle bundle) { Plugin.LogInfo("Assets successfully loaded!"); AssetHelper.bundle = bundle; loaded = true; yield break; } public static IEnumerator RegisterMenus(MenuManager menuManager) { yield return (object)new WaitUntil((Func)(() => loaded)); Plugin.LogInfo("Registering menu pages..."); menuManager.menuPages.Add(new MenuPages { menuPageIndex = (MenuPageIndex)MenuPageSkills.CUSTOM_PAGE_INDEX, menuPage = GetPrefab(MenuPageSkills.PREFAB_NAME) }); Plugin.LogInfo("Menu pages registered!"); } public static GameObject GetPrefab(string prefab) { return bundle.LoadAsset(prefab); } } public class ConfigHelper { private static ConfigEntry disableShopUpgrades; private static ConfigEntry fairDistribution; private static ConfigEntry startingSkillPoints; private static ConfigEntry baseSkillPointsEarned; private static ConfigEntry moonSkillPoints; private static ConfigEntry enableDebug; public static bool ShopUpgradesDisabled() { return disableShopUpgrades.Value; } public static bool FairDistribution() { return false; } public static int StartingSkillPoints() { return startingSkillPoints.Value; } public static int BaseSkillPointsEarned() { return baseSkillPointsEarned.Value; } public static int MoonSkillPoints() { return moonSkillPoints.Value; } public static bool EnableDebug() { return enableDebug == null || enableDebug.Value; } public static void Initialize(ConfigFile config) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown startingSkillPoints = config.Bind("General", "StartingSkillPoints", 0, new ConfigDescription("The number of skill points to start with (default 0)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); baseSkillPointsEarned = config.Bind("General", "BasePointsEarned", 2, new ConfigDescription("The base number of skill points gained each level (default 2)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); moonSkillPoints = config.Bind("General", "PointsPerMoon", 1, new ConfigDescription("How many additional skill points to earn per moon phase (default 1)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); disableShopUpgrades = config.Bind("Miscellaneous", "DisableUpgrades", true, "Whether to disable the vanilla shop upgrades (on by default)"); enableDebug = config.Bind("Debug", "EnableDebug", false, "Whether to spam the log with my debug info"); } } public class DebugHelper { public static void TestShop() { RunManager.instance.ChangeLevel(true, false, (ChangeLevelType)5); } public static void TestAllUpgrades() { Plugin.LogInfo("Testing all upgrades:"); string[] sKILL_NAMES = SkillData.SKILL_NAMES; foreach (string text in sKILL_NAMES) { Plugin.LogInfo("-- Trying " + text); SkillDataManager.instance.UpdateSkill(PlayerHelper.GetLocalSteamID(), text, 2); } } public static void AwardPoints(int points) { SkillNetworkSync.UpdateSkillPoints(PlayerHelper.GetLocalSteamID(), points); } } public static class PlayerHelper { private const int STARTING_ENERGY = 40; private const int ENERGY_INCREMENT = 10; private const int STARTING_HEALTH = 100; public const int HEALTH_INCREMENT = 20; private const float STARTING_SPEED = 5f; private const float STARTING_STRENGTH = 1f; private const float STRENGTH_INCREMENT = 0.2f; private const float STARTING_RANGE = 2.5f; private const float THROW_INCREMENT = 0.3f; public static bool IsLocalSteamID(string steamID) { return (Object)(object)SemiFunc.PlayerAvatarGetFromSteamID(steamID) == (Object)(object)SemiFunc.PlayerAvatarLocal(); } public static bool IsHost() { return PhotonNetwork.IsMasterClient; } public static List GetAllPlayerSteamIDs() { List list = new List(); foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { list.Add((string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(item)); } return list; } public static PlayerController GetLocalPlayerController() { return PlayerController.instance; } public static PlayerAvatar GetLocalPlayerAvatar() { return PlayerAvatar.instance; } public static string GetLocalSteamID() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) return SteamClient.SteamId.Value.ToString(); } public static void UpdatePlayerEnergy(int newLevel) { PlayerController localPlayerController = GetLocalPlayerController(); AccessTools.Field(typeof(PlayerController), "EnergyStart").SetValue(localPlayerController, (float)(40 + newLevel * 10)); AccessTools.Field(typeof(PlayerController), "EnergyCurrent").SetValue(localPlayerController, localPlayerController.EnergyStart); } public static void UpdatePlayerHealth(int newLevel, int oldValue) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); int num = (StatsManager.instance.playerUpgradeHealth.ContainsKey(GetLocalSteamID()) ? (newLevel - oldValue) : newLevel); StatsManager.instance.playerUpgradeHealth[GetLocalSteamID()] = newLevel; AccessTools.Field(typeof(PlayerHealth), "maxHealth").SetValue(localPlayerAvatar.playerHealth, 100 + newLevel * 20); int num2 = (int)AccessTools.Field(typeof(PlayerHealth), "maxHealth").GetValue(localPlayerAvatar.playerHealth); int num3 = (int)AccessTools.Field(typeof(PlayerHealth), "health").GetValue(localPlayerAvatar.playerHealth); if (num != 0) { CameraGlitch.Instance.PlayShortHeal(); AccessTools.Field(typeof(PlayerHealth), "health").SetValue(localPlayerAvatar.playerHealth, num3 + num * 20); if (GameManager.Multiplayer()) { localPlayerAvatar.photonView.RPC("UpdateHealthRPC", (RpcTarget)1, new object[4] { num2, num3 + num * 20, true, false }); } } } public static void UpdatePlayerExtraJump(int newLevel) { PlayerController localPlayerController = GetLocalPlayerController(); AccessTools.Field(typeof(PlayerController), "JumpExtra").SetValue(localPlayerController, newLevel); } public static void UpdatePlayerLaunch(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); object value = AccessTools.Field(typeof(PlayerAvatar), "tumble").GetValue(localPlayerAvatar); if (value != null) { AccessTools.Field(typeof(PlayerTumble), "tumbleLaunch").SetValue(value, newLevel); } } public static void UpdatePlayerTumbleClimb(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PlayerAvatar), "upgradeTumbleClimb").SetValue(localPlayerAvatar, (float)newLevel); } public static void UpdatePlayerDeathHeadBattery(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PlayerAvatar), "upgradeDeathHeadBattery").SetValue(localPlayerAvatar, (float)newLevel); } public static void UpdatePlayerMapPlayerCount(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PlayerAvatar), "upgradeMapPlayerCount").SetValue(localPlayerAvatar, newLevel); } public static void UpdatePlayerSpeed(int newLevel) { PlayerController localPlayerController = GetLocalPlayerController(); AccessTools.Field(typeof(PlayerController), "SprintSpeed").SetValue(localPlayerController, 5f + (float)newLevel); AccessTools.Field(typeof(PlayerController), "SprintSpeedUpgrades").SetValue(localPlayerController, (float)newLevel); AccessTools.Field(typeof(PlayerController), "playerOriginalSprintSpeed").SetValue(localPlayerController, 5f + (float)newLevel); } public static void UpdatePlayerStrength(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PhysGrabber), "grabStrength").SetValue(AccessTools.Field(typeof(PlayerAvatar), "physGrabber").GetValue(localPlayerAvatar), 1f + (float)newLevel * 0.2f); } public static void UpdatePlayerRange(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PhysGrabber), "grabRange").SetValue(AccessTools.Field(typeof(PlayerAvatar), "physGrabber").GetValue(localPlayerAvatar), 2.5f + (float)newLevel); } public static void UpdatePlayerThrow(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PhysGrabber), "throwStrength").SetValue(AccessTools.Field(typeof(PlayerAvatar), "physGrabber").GetValue(localPlayerAvatar), (float)newLevel * 0.3f); } public static void UpdatePlayerCrouchRest(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PlayerAvatar), "upgradeCrouchRest").SetValue(localPlayerAvatar, (float)newLevel); } public static void UpdatePlayerTumbleWings(int newLevel) { PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); AccessTools.Field(typeof(PlayerAvatar), "upgradeTumbleWings").SetValue(localPlayerAvatar, (float)newLevel); } } } namespace SkillFern.Networking { public static class SkillNetworkSync { private enum EVENT_TYPE { SKILL_UPDATE, NODE_PURCHASE, POINTS_UPDATE, SKILL_SYNC, POINTS_SYNC, CAREER_SYNC, CONFIG_SYNC } private const byte SKILL_DATA_CHANNEL = 187; private static bool isInitialized = false; public static int saveCountdown = -1; public static void Initialize() { if (!isInitialized && PhotonNetwork.NetworkingClient != null) { isInitialized = true; saveCountdown = -1; PhotonNetwork.NetworkingClient.EventReceived += OnEventReceived; Plugin.LogInfo("Network initialized"); } } public static void OnEventReceived(EventData data) { if (data.Code != 187) { return; } object[] array = (object[])data.CustomData; Plugin.LogInfo("Payload: " + array.ToString()); string text = (string)array[1]; switch ((EVENT_TYPE)array[0]) { case EVENT_TYPE.CONFIG_SYNC: { int moonSkillPoints = (int)array[2]; int baseSkillPoints = (int)array[3]; SkillDataManager.instance.moonSkillPoints = moonSkillPoints; SkillDataManager.instance.baseSkillPoints = baseSkillPoints; break; } case EVENT_TYPE.CAREER_SYNC: { int correctPoints = (int)array[2]; int ownedPoints = (int)array[3]; if (text == PlayerHelper.GetLocalSteamID()) { SkillDataManager.instance.ReconcileCareer(correctPoints, ownedPoints); } break; } case EVENT_TYPE.NODE_PURCHASE: { string nodeID = (string)array[2]; SkillDataManager.instance.PurchaseNode(text, nodeID); break; } case EVENT_TYPE.SKILL_UPDATE: case EVENT_TYPE.SKILL_SYNC: { string skillName = (string)array[2]; int newLevel = (int)array[3]; SkillDataManager.instance.UpdateSkill(text, skillName, newLevel, (EVENT_TYPE)array[0] == EVENT_TYPE.SKILL_SYNC); break; } case EVENT_TYPE.POINTS_UPDATE: case EVENT_TYPE.POINTS_SYNC: { int amount = (int)array[2]; SkillDataManager.instance.UpdateSkillPoints(text, amount, (EVENT_TYPE)array[0] == EVENT_TYPE.POINTS_SYNC); if (saveCountdown > 0 && PlayerHelper.IsHost()) { saveCountdown--; if (saveCountdown == 0) { saveCountdown = -1; Plugin.LogInfo("Forcing save after skill point updates. . ."); SaveManager.Save(SaveManager.lastFile); } } break; } } } public static void UpdateSkillPoints(string steamID, int amount, bool sync = false) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) Plugin.LogInfo(" Granting " + amount + " points to " + steamID); if (!GameManager.Multiplayer()) { SkillDataManager.instance.UpdateSkillPoints(steamID, amount); return; } object[] array = new object[3] { sync ? EVENT_TYPE.POINTS_SYNC : EVENT_TYPE.POINTS_UPDATE, steamID, amount }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)187, (object)array, val, SendOptions.SendReliable); } public static void UpdateSkill(string steamID, string skillName, int newLevel, bool sync = false) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (!GameManager.Multiplayer()) { SkillDataManager.instance.UpdateSkill(steamID, skillName, newLevel); return; } object[] array = new object[4] { sync ? EVENT_TYPE.SKILL_SYNC : EVENT_TYPE.SKILL_UPDATE, steamID, skillName, newLevel }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)187, (object)array, val, SendOptions.SendReliable); } public static void UpdateSkill(string steamID, SkillData.SKILL_TYPE skillType, int newLevel) { UpdateSkill(steamID, SkillData.SKILL_NAMES[(int)skillType], newLevel); } public static void PurchaseNode(string steamID, string nodeID) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (!GameManager.Multiplayer()) { SkillDataManager.instance.PurchaseNode(steamID, nodeID); return; } Plugin.LogInfo("Purchasing node " + nodeID + " for " + steamID + " on network."); object[] array = new object[3] { EVENT_TYPE.NODE_PURCHASE, steamID, nodeID }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)187, (object)array, val, SendOptions.SendReliable); } public static void AwardSkillPointsForLevel() { saveCountdown = 0; foreach (string allPlayerSteamID in PlayerHelper.GetAllPlayerSteamIDs()) { saveCountdown++; int amount = SkillDataManager.CalculatePointsEarned(); UpdateSkillPoints(allPlayerSteamID, amount); } } public static void SyncAll() { //IL_0368: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Expected O, but got Unknown //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) Plugin.LogInfo("Syncing all skill data. . ."); List list = new List(); List allPlayerSteamIDs = PlayerHelper.GetAllPlayerSteamIDs(); foreach (SkillData skillData in SkillDataManager.instance.skillDatas) { if (!allPlayerSteamIDs.Contains(skillData.steamID)) { Plugin.LogInfo("Skipping " + skillData.steamID + " who is not in this game."); continue; } Plugin.LogInfo("Syncing data for " + skillData.steamID); Plugin.LogInfo("Cycling skill datas"); string[] sKILL_NAMES = SkillData.SKILL_NAMES; foreach (string text in sKILL_NAMES) { UpdateSkill(skillData.steamID, text, (int)typeof(SkillData).GetField(text).GetValue(skillData), sync: true); } Plugin.LogInfo("Cycling owned nodes"); foreach (string ownedNode in skillData.ownedNodes) { PurchaseNode(skillData.steamID, ownedNode); } Plugin.LogInfo("Syncing skill points"); UpdateSkillPoints(skillData.steamID, skillData.skillPoints, sync: true); if (ConfigHelper.FairDistribution()) { Plugin.LogInfo("Reconciling career"); object[] array = new object[4] { EVENT_TYPE.CAREER_SYNC, skillData.steamID, SkillDataManager.instance.careerPoints, skillData.pointsGained }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)187, (object)array, val, SendOptions.SendReliable); } list.Add(skillData.steamID); } int num = allPlayerSteamIDs.Count - list.Count; if (num > 0) { Plugin.LogInfo("Creating new skill data for " + num + " new players."); } foreach (string item in allPlayerSteamIDs) { if (!list.Contains(item)) { if (ConfigHelper.FairDistribution()) { Plugin.LogInfo("Starting " + item + " with " + SkillDataManager.instance.careerPoints + " points."); UpdateSkillPoints(item, SkillDataManager.instance.careerPoints, sync: true); } else { Plugin.LogInfo("Starting " + item + " with " + ConfigHelper.StartingSkillPoints() + " points."); UpdateSkillPoints(item, ConfigHelper.StartingSkillPoints(), sync: true); } } } Plugin.LogInfo("Skill data synced!"); Plugin.LogInfo("Syncing moon phase data. . ."); object[] array2 = new object[4] { EVENT_TYPE.CONFIG_SYNC, PlayerHelper.GetLocalSteamID(), ConfigHelper.MoonSkillPoints(), ConfigHelper.BaseSkillPointsEarned() }; RaiseEventOptions val2 = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)187, (object)array2, val2, SendOptions.SendReliable); } } } namespace SkillFern.Custom { public static class SaveManager { public static string lastFile; public static void Save(string fileName) { lastFile = fileName; string text = Application.persistentDataPath + "/saves/" + fileName; string text2 = text + "/" + fileName + "_SkillFern.json"; string contents = JsonConvert.SerializeObject((object)SkillDataManager.instance, (Formatting)1); File.WriteAllText(text2, contents); Plugin.LogInfo("Saved data to " + text2); } public static void Load(string fileName) { lastFile = fileName; string text = Application.persistentDataPath + "/saves/" + fileName; string text2 = text + "/" + fileName + "_SkillFern.json"; if (File.Exists(text2)) { string text3 = File.ReadAllText(text2); SkillDataManager.instance = JsonConvert.DeserializeObject(text3); Plugin.LogInfo("Loaded save data from " + text2); } else { Plugin.LogInfo("SAVE DOESN T EXIST"); SkillDataManager.instance = new SkillDataManager(); Plugin.LogInfo("Created new save data at " + text2); } } } [Serializable] public class SkillDataManager { public static SkillDataManager instance; public int careerPoints; public int moonSkillPoints; public int baseSkillPoints; [JsonProperty("skillDatas")] public List skillDatas { get; set; } [JsonConstructor] public SkillDataManager() { Plugin.LogInfo("NEW SKILLDATAMANAGER CREATED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); skillDatas = new List(); careerPoints = ConfigHelper.StartingSkillPoints(); moonSkillPoints = ConfigHelper.MoonSkillPoints(); baseSkillPoints = ConfigHelper.BaseSkillPointsEarned(); } public static void Initialize() { if (instance == null) { instance = new SkillDataManager(); } } public static int CalculatePointsEarned(int offset = 0) { return instance.baseSkillPoints + RunManager.instance.CalculateMoonLevel(SemiFunc.RunGetLevelsCompleted() - 1 + offset) * instance.moonSkillPoints; } public int GetLocalSkillPoints() { return GetPlayerData(PlayerHelper.GetLocalSteamID()).skillPoints; } public SkillData GetLocalPlayerData() { return GetPlayerData(PlayerHelper.GetLocalSteamID()); } public SkillData GetPlayerData(string steamID) { SkillData skillData = skillDatas.Find((SkillData data) => data.steamID == steamID); if (skillData == null) { skillData = new SkillData(steamID); skillDatas.Add(skillData); } return skillData; } public void UpdateSkillPoints(string steamID, int amount, bool syncing = false) { if (skillDatas == null) { Plugin.LogInfo("Skill Data does not exist! Creating. . ."); skillDatas = new List(); } SkillData skillData = skillDatas.Find((SkillData data) => data.steamID == steamID); if (skillData == null) { skillData = new SkillData(steamID); skillDatas.Add(skillData); } if (syncing) { if (skillData.pointsGained == 0) { skillData.pointsGained = amount; } skillData.skillPoints = amount; } else { if (amount > 0) { if (steamID == PlayerHelper.GetLocalSteamID()) { careerPoints += amount; } skillData.pointsGained += amount; } skillData.skillPoints += amount; } Plugin.LogInfo(steamID + " now has " + skillData.skillPoints + " points"); } public void UpdateSkill(string steamID, string skillName, int newLevel, bool syncing = false) { if (skillDatas == null) { Plugin.LogInfo("Skill Data does not exist! Creating. . ."); skillDatas = new List(); } SkillData skillData = skillDatas.Find((SkillData data) => data.steamID == steamID); if (skillData == null) { skillData = new SkillData(steamID); skillDatas.Add(skillData); } int num = ((!syncing) ? ((int)typeof(SkillData).GetField(skillName).GetValue(skillData) + newLevel) : newLevel); typeof(SkillData).GetField(skillName).SetValue(skillData, num); if (!syncing) { int oldValue = UpdateDictionarySkillByName(steamID, skillName, num); if (!GameManager.Multiplayer() || PlayerHelper.IsLocalSteamID(steamID)) { UpdateLocalSkillByName(skillName, num, oldValue); } } } public void PurchaseNode(string steamID, string nodeID) { if (skillDatas == null) { Plugin.LogInfo("Skill Data does not exist! Creating. . ."); skillDatas = new List(); } SkillData skillData = skillDatas.Find((SkillData data) => data.steamID == steamID); if (skillData == null) { Plugin.LogInfo("Creating skill data for player " + steamID); skillData = new SkillData(steamID); skillDatas.Add(skillData); } if (!skillData.ownedNodes.Contains(nodeID)) { skillData.ownedNodes.Add(nodeID); } } public void UpdateLocalSkillByName(string skillName, int newLevel, int oldValue = 0) { switch (skillName) { case "healthLevels": PlayerHelper.UpdatePlayerHealth(newLevel, oldValue); break; case "staminaLevels": PlayerHelper.UpdatePlayerEnergy(newLevel); break; case "extraJumpLevels": PlayerHelper.UpdatePlayerExtraJump(newLevel); break; case "launchLevels": PlayerHelper.UpdatePlayerLaunch(newLevel); break; case "tumbleClimbLevels": PlayerHelper.UpdatePlayerTumbleClimb(newLevel); break; case "deathHeadBatteryLevels": PlayerHelper.UpdatePlayerDeathHeadBattery(newLevel); break; case "mapPlayerCountLevels": PlayerHelper.UpdatePlayerMapPlayerCount(newLevel); break; case "speedLevels": PlayerHelper.UpdatePlayerSpeed(newLevel); break; case "strengthLevels": PlayerHelper.UpdatePlayerStrength(newLevel); break; case "rangeLevels": PlayerHelper.UpdatePlayerRange(newLevel); break; case "throwLevels": PlayerHelper.UpdatePlayerThrow(newLevel); break; case "crouchRestLevels": PlayerHelper.UpdatePlayerCrouchRest(newLevel); break; case "tumbleWingsLevels": PlayerHelper.UpdatePlayerTumbleWings(newLevel); break; } } public int UpdateDictionarySkillByName(string steamID, string skillName, int newLevel) { int num = 0; switch (skillName) { case "healthLevels": { num = (StatsManager.instance.playerUpgradeHealth.ContainsKey(steamID) ? StatsManager.instance.playerUpgradeHealth[steamID] : 0); PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(steamID); int num2 = (int)AccessTools.Field(typeof(PlayerHealth), "health").GetValue(val.playerHealth); StatsManager.instance.SetPlayerHealth(steamID, num2 + (newLevel - num) * 20, true); StatsManager.instance.playerUpgradeHealth[steamID] = newLevel; break; } case "staminaLevels": StatsManager.instance.playerUpgradeStamina[steamID] = newLevel; break; case "extraJumpLevels": StatsManager.instance.playerUpgradeExtraJump[steamID] = newLevel; break; case "launchLevels": StatsManager.instance.playerUpgradeLaunch[steamID] = newLevel; break; case "tumbleClimbLevels": StatsManager.instance.playerUpgradeTumbleClimb[steamID] = newLevel; break; case "deathHeadBatteryLevels": StatsManager.instance.playerUpgradeDeathHeadBattery[steamID] = newLevel; break; case "mapPlayerCountLevels": StatsManager.instance.playerUpgradeMapPlayerCount[steamID] = newLevel; break; case "speedLevels": StatsManager.instance.playerUpgradeSpeed[steamID] = newLevel; break; case "strengthLevels": StatsManager.instance.playerUpgradeStrength[steamID] = newLevel; break; case "rangeLevels": StatsManager.instance.playerUpgradeRange[steamID] = newLevel; break; case "throwLevels": StatsManager.instance.playerUpgradeThrow[steamID] = newLevel; break; case "crouchRestLevels": StatsManager.instance.playerUpgradeCrouchRest[steamID] = newLevel; break; case "tumbleWingsLevels": StatsManager.instance.playerUpgradeTumbleWings[steamID] = newLevel; break; } return num; } public void ReconcileCareer(int correctPoints, int ownedPoints) { if (GetLocalPlayerData() != null) { GetLocalPlayerData().pointsGained = ownedPoints; } careerPoints = ownedPoints; int num = correctPoints - ownedPoints; if (num > 0) { SkillNetworkSync.UpdateSkillPoints(PlayerHelper.GetLocalSteamID(), num); } } } [Serializable] public class SkillData { public enum SKILL_TYPE { HEALTH, STAMINA, EXTRA_JUMP, LAUNCH, TUMBLE_CLIMB, DEATH_HEAD_BATTERY, MAP_PLAYER_COUNT, SPEED, STRENGTH, RANGE, THROW, CROUCH_REST, TUMBLE_WINGS } public static string[] SKILL_DISPLAY_NAMES = new string[13] { "Health", "Stamina", "Extra Jump", "Tumble Launch", "Tumble Climb", "Death Battery", "Player Count", "Speed", "Strength", "Range", "Throw", "Crouch Rest", "Tumble Wings" }; public static string[] SKILL_NAMES = new string[13] { "healthLevels", "staminaLevels", "extraJumpLevels", "launchLevels", "tumbleClimbLevels", "deathHeadBatteryLevels", "mapPlayerCountLevels", "speedLevels", "strengthLevels", "rangeLevels", "throwLevels", "crouchRestLevels", "tumbleWingsLevels" }; public string steamID; public int healthLevels; public int staminaLevels; public int extraJumpLevels; public int launchLevels; public int tumbleClimbLevels; public int deathHeadBatteryLevels; public int mapPlayerCountLevels; public int speedLevels; public int strengthLevels; public int rangeLevels; public int throwLevels; public int crouchRestLevels; public int tumbleWingsLevels; public int skillPoints; public int pointsGained; [JsonProperty("ownedNodes")] public List ownedNodes { get; set; } public SkillData(string steamID) { this.steamID = steamID; healthLevels = 0; staminaLevels = 0; extraJumpLevels = 0; launchLevels = 0; tumbleClimbLevels = 0; deathHeadBatteryLevels = 0; mapPlayerCountLevels = 0; speedLevels = 0; strengthLevels = 0; rangeLevels = 0; throwLevels = 0; crouchRestLevels = 0; tumbleWingsLevels = 0; skillPoints = ConfigHelper.StartingSkillPoints(); ownedNodes = new List(); pointsGained = ConfigHelper.StartingSkillPoints(); } [JsonConstructor] public SkillData() { ownedNodes = new List(); } } } namespace SkillFern.UI { public class HoverText : MonoBehaviour { public float yOffset = 10f; public float flyInTime = 0.1f; public float fadeInTime = 0.05f; public float fadeOutTime = 0.2f; private TextMeshProUGUI textMesh; private bool shown; public void Start() { textMesh = ((Component)this).GetComponentInChildren(); ((TMP_Text)textMesh).alpha = 0f; ((TMP_Text)textMesh).SetText("", true); shown = false; } public void Update() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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) if (shown) { if (((TMP_Text)textMesh).alpha < 1f) { TextMeshProUGUI obj = textMesh; ((TMP_Text)obj).alpha = ((TMP_Text)obj).alpha + Time.deltaTime / fadeInTime; } Vector2 anchoredPosition = ((TMP_Text)textMesh).rectTransform.anchoredPosition; float num = yOffset / flyInTime; if (anchoredPosition.y < 0f) { anchoredPosition.y = Mathf.MoveTowards(anchoredPosition.y, 0f, num * Time.deltaTime); } ((TMP_Text)textMesh).rectTransform.anchoredPosition = anchoredPosition; } else if (((TMP_Text)textMesh).alpha > 0f) { TextMeshProUGUI obj2 = textMesh; ((TMP_Text)obj2).alpha = ((TMP_Text)obj2).alpha - Time.deltaTime / fadeOutTime; } } public void SetText(string text) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (!shown) { ((TMP_Text)textMesh).alpha = 0f; ((TMP_Text)textMesh).SetText(text, true); shown = true; ((TMP_Text)textMesh).rectTransform.anchoredPosition = new Vector2(((TMP_Text)textMesh).rectTransform.anchoredPosition.x, 0f - yOffset); } } public void Clear() { shown = false; } } public class UpgradeNode : FernNode { public SkillData.SKILL_TYPE upgradeType; public int upgradeAmount; public new void Reset() { base.Reset(); upgradeAmount = 1; } public override void Activate() { SkillNetworkSync.UpdateSkill(PlayerHelper.GetLocalSteamID(), upgradeType, upgradeAmount); } public override string GetDisplayString() { return SkillData.SKILL_DISPLAY_NAMES[(int)upgradeType]; } } public class FernNode : MonoBehaviour { private class LineContainer { public GameObject lineObject; public FernNode dependency; } private List lines = new List(); public string nodeID; public Image targetImage; public TextMeshProUGUI costText; public FernNode[] dependencies; public int cost; protected bool owned; protected MenuButton button; protected bool wasHovering; private MenuPageSkills skillsPage; public bool isOwned() { return owned; } public void setOwned(bool newOwned) { owned = newOwned; } public void DrawLines(GameObject lineContainer, int lineThickness) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: 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_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: 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_00f1: 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) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) lines = new List(); for (int i = 0; i < dependencies.Length; i++) { LineContainer lineContainer2 = new LineContainer(); lineContainer2.lineObject = new GameObject("Fern Line"); lineContainer2.dependency = dependencies[i]; lineContainer2.lineObject.transform.SetParent(lineContainer.transform, false); Image val = lineContainer2.lineObject.AddComponent(); ((Graphic)val).color = Color.white; RectTransform rectTransform = ((Graphic)val).rectTransform; rectTransform.pivot = new Vector2(0f, 0.5f); RectTransform component = ((Component)this).GetComponent(); RectTransform component2 = ((Component)dependencies[i]).GetComponent(); Rect rect = component.rect; Vector3 val2 = ((Transform)component).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center)); rect = component2.rect; Vector3 val3 = ((Transform)component2).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center)); ((Transform)rectTransform).position = val2; float num = Vector3.Distance(val2, val3); rectTransform.sizeDelta = new Vector2(num, (float)lineThickness); Vector3 val4 = val3 - val2; float num2 = Mathf.Atan2(val4.y, val4.x) * 57.29578f; ((Transform)rectTransform).rotation = Quaternion.Euler(0f, 0f, num2); lines.Add(lineContainer2); } } public void OnValidate() { ((TMP_Text)costText).SetText(cost.ToString() ?? "", true); } public void Reset() { cost = 1; nodeID = Guid.NewGuid().ToString(); targetImage = ((Component)((Component)this).transform.GetChild(0)).GetComponent(); dependencies = new FernNode[1]; costText = ((Component)((Component)this).transform.GetChild(2)).GetComponent(); ((TMP_Text)costText).SetText(cost.ToString() ?? "", true); } public void Awake() { button = ((Component)this).GetComponent(); skillsPage = ((Component)this).GetComponentInParent(); } public void Update() { if ((int)AccessTools.Field(typeof(MenuButton), "buttonState").GetValue(button) <= 1 && !owned) { wasHovering = true; skillsPage.SetHoverText(GetDisplayString()); } else if (wasHovering) { wasHovering = false; skillsPage.ClearHoverText(); } } public void Initialize() { UpdateStatus(); } public bool CanPurchase() { return !owned && (dependencies.Length == 0 || dependencies.Any((FernNode node) => node.isOwned())); } public void Purchase() { int localSkillPoints = SkillDataManager.instance.GetLocalSkillPoints(); if (localSkillPoints >= cost) { Plugin.LogInfo("Purchased node " + nodeID); owned = true; SkillNetworkSync.UpdateSkillPoints(PlayerHelper.GetLocalSteamID(), -cost); skillsPage.UpdateAllNodes(localSkillPoints - cost); Activate(); SkillNetworkSync.PurchaseNode(PlayerHelper.GetLocalSteamID(), nodeID); } } public virtual void Activate() { Plugin.LogInfo("Node " + nodeID + " has no effects"); } public void UpdateStatus() { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) if (owned) { ((Behaviour)costText).enabled = false; } else { ((TMP_Text)costText).SetText(cost.ToString() ?? "", true); } if (!owned && CanPurchase()) { ((Behaviour)button).enabled = true; } else { ((Behaviour)button).enabled = false; } if (owned) { ((Graphic)targetImage).color = Color.green; } else if (CanPurchase()) { ((Graphic)targetImage).color = Color.white; } else { ((Graphic)targetImage).color = Color.gray; } foreach (LineContainer line in lines) { if (owned && line.dependency.isOwned()) { ((Graphic)line.lineObject.GetComponent()).color = Color.green; } else if (owned || line.dependency.isOwned()) { ((Graphic)line.lineObject.GetComponent()).color = Color.white; } else { ((Graphic)line.lineObject.GetComponent()).color = Color.gray; } } } public virtual string GetDisplayString() { return "[BLANK]"; } } public class CustomMenuPage : MonoBehaviour { [HideInInspector] public static CustomMenuPage instance; [HideInInspector] public int customPageIndex; [HideInInspector] public MenuPage menuPage; protected virtual void Start() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) instance = this; menuPage = ((Component)this).GetComponent(); menuPage.menuPageIndex = (MenuPageIndex)customPageIndex; } } public class MenuPageSkills : CustomMenuPage { public int lineThickness = 5; public GameObject lineContainer; public TextMeshProUGUI skillPointsText; [HideInInspector] public static string PREFAB_NAME = "Menu Page Skills"; [HideInInspector] public static int CUSTOM_PAGE_INDEX = 23; private List nodes; private HoverText hoverText; [ContextMenu("Draw Lines")] public void DrawLines() { for (int num = lineContainer.transform.childCount - 1; num >= 0; num--) { Object.DestroyImmediate((Object)(object)((Component)lineContainer.transform.GetChild(num)).gameObject); } List list = new List(); FernNode[] componentsInChildren = ((Component)this).GetComponentsInChildren(); foreach (FernNode fernNode in componentsInChildren) { fernNode.DrawLines(lineContainer, lineThickness); } } protected override void Start() { customPageIndex = CUSTOM_PAGE_INDEX; base.Start(); hoverText = ((Component)this).GetComponentInChildren(); ((TMP_Text)skillPointsText).SetText("Points: " + SkillDataManager.instance.GetLocalSkillPoints(), true); nodes = new List(((Component)this).GetComponentsInChildren()); DrawLines(); List ownedNodes = SkillDataManager.instance.GetPlayerData(PlayerHelper.GetLocalSteamID()).ownedNodes; foreach (string id in ownedNodes) { FernNode fernNode = nodes.Find((FernNode n) => n.nodeID == id); if ((Object)(object)fernNode != (Object)null) { fernNode.setOwned(newOwned: true); } } foreach (FernNode node in nodes) { node.Initialize(); } } private void Update() { if (SemiFunc.InputDown((InputKey)18)) { ButtonEventBack(); } } public void UpdateAllNodes(int pointsCorrection = -1) { ((TMP_Text)skillPointsText).SetText("Points: " + ((pointsCorrection == -1) ? SkillDataManager.instance.GetLocalSkillPoints() : pointsCorrection), true); foreach (FernNode node in nodes) { node.UpdateStatus(); } } public void ButtonEventBack() { MenuManager.instance.PageCloseAll(); MenuManager.instance.PageOpen((MenuPageIndex)1, false); } public void ClearHoverText() { hoverText.Clear(); } public void SetHoverText(string text) { hoverText.SetText(text); } } public class SkillsButton : MonoBehaviour { public void ButtonEventOpenSkills() { Plugin.LogInfo("Opening skills menu"); MenuManager.instance.PageSwap((MenuPageIndex)MenuPageSkills.CUSTOM_PAGE_INDEX); } } } namespace SkillFern.Patches { [HarmonyPatch(typeof(RunManager))] public class RunManagerPatch { [HarmonyPatch("ResetProgress")] [HarmonyPrefix] private static void ResetSkillDataManager() { SkillDataManager.instance = new SkillDataManager(); } [HarmonyPatch("MoonGetAttributes")] [HarmonyPostfix] private static void SkillPointsMessage(ref List __result) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown string text = "Earn " + SkillDataManager.CalculatePointsEarned(1) + " skill points per level."; foreach (MoonAttribute item in __result) { if ((Object)(object)item.LocalizedText == (Object)null) { return; } } __result.Add(new MoonAttribute { text = text, LocalizedText = null }); } } [HarmonyPatch(typeof(ResultScreenUI))] public class ResultScreenUIPatch { [HarmonyPatch("FinishResultScreen")] [HarmonyPostfix] private static void AwardSkillPoints() { if (PlayerHelper.IsHost()) { Plugin.LogInfo("Awarding points. . ."); SkillNetworkSync.AwardSkillPointsForLevel(); } } } [HarmonyPatch(typeof(MenuManager))] public class MenuManagerPatch { [HarmonyPatch("Start")] [HarmonyPostfix] public static void RegisterMenus(MenuManager __instance) { Plugin.LogInfo("Waiting to register menus..."); ((MonoBehaviour)__instance).StartCoroutine(AssetHelper.RegisterMenus(__instance)); } } [HarmonyPatch(typeof(MenuPageEsc))] public class MenuPageEscPatch { [HarmonyPatch("Start")] [HarmonyPostfix] public static void AddSkillsButton(MenuPageEsc __instance) { GameObject val = Object.Instantiate(AssetHelper.GetPrefab("Menu Button - Skills"), ((Component)__instance).transform); } } [HarmonyPatch(typeof(MenuPageLobby))] public class MenuPageLobbyPatch { [HarmonyPatch("Start")] [HarmonyPostfix] public static void Start() { Plugin.LogInfo("LOBBY PAGE START"); SkillNetworkSync.Initialize(); if (!PlayerHelper.IsHost()) { SkillDataManager.instance = new SkillDataManager(); } } [HarmonyPatch("ButtonStart")] [HarmonyPrefix] public static void OnGameStart(bool ___joiningPlayer) { if (!___joiningPlayer) { SkillNetworkSync.SyncAll(); } } } [HarmonyPatch(typeof(ShopManager))] public class ShopManagerPatch { [HarmonyPatch("GetAllItemsFromStatsManager")] [HarmonyPostfix] private static void RemoveItemUpgrades(ref List ___potentialItemUpgrades) { if (ConfigHelper.ShopUpgradesDisabled()) { ___potentialItemUpgrades.Clear(); } } } [HarmonyPatch(typeof(StatsManager))] public class StatsManagerPatch { [HarmonyPatch("SaveGame")] [HarmonyPostfix] private static void SaveSkills(string fileName) { SaveManager.Save(fileName); } [HarmonyPatch("LoadGame")] [HarmonyPostfix] private static void LoadSkills(string fileName) { SaveManager.Load(fileName); } } }