using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("RandomHandCosmetics")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("RandomHandCosmetics")] [assembly: AssemblyTitle("RandomHandCosmetics")] [assembly: AssemblyVersion("1.0.0.0")] namespace RandomHandCosmetics; [BepInPlugin("com.yourname.RandomHandCosmetics", "Random Hand Cosmetics", "1.0.0")] public class Plugin : BaseUnityPlugin { [HarmonyPatch(typeof(CL_CosmeticManager), "GetCosmeticInHand")] public static class GetCosmeticInHandPatch { private static bool Prefix(int i, ref Cosmetic_HandItem __result) { if (string.IsNullOrEmpty(leftSelectedId) || string.IsNullOrEmpty(rightSelectedId)) { InitializeCosmetics(); SelectRandomCosmetics(); } string text = ((i == 0) ? leftSelectedId : rightSelectedId); if (!string.IsNullOrEmpty(text) && text != "default" && text != "climber") { FieldInfo field = typeof(CL_CosmeticManager).GetField("cosmeticHandDict", BindingFlags.Static | BindingFlags.NonPublic); if (field != null && field.GetValue(null) is Dictionary dictionary && dictionary.TryGetValue(text, out var value)) { __result = value; if (i == 0) { SettingsManager.settings.cosmeticSaveData.leftHandCosmetic.id = text; } else { SettingsManager.settings.cosmeticSaveData.rightHandCosmetic.id = text; } return false; } } return true; } } [HarmonyPatch(typeof(ENT_Player), "Start")] public static class PlayerStartPatch { private static void Postfix(ENT_Player __instance) { if (!((Object)(object)__instance == (Object)null) && !((GameEntity)__instance).dead) { ForceApplyCosmeticToHand(__instance, 0, leftSelectedId); ForceApplyCosmeticToHand(__instance, 1, rightSelectedId); if (!(ChangeInterval.Value <= 0f)) { ((MonoBehaviour)__instance).StartCoroutine(DynamicChangeRoutine(__instance)); } } } private static IEnumerator DynamicChangeRoutine(ENT_Player player) { float interval = ChangeInterval.Value; while (true) { yield return (object)new WaitForSeconds(interval); if (!((Object)(object)player == (Object)null) && !((GameEntity)player).dead && !CL_GameManager.gMan.isPaused) { SelectRandomCosmetics(); ForceApplyCosmeticToHand(player, 0, leftSelectedId); ForceApplyCosmeticToHand(player, 1, rightSelectedId); } } } } [HarmonyPatch(typeof(Hand), "OnEnable")] public static class HandOnEnablePatch { private static void Postfix(Hand __instance) { ENT_Player player = __instance.GetPlayer(); if (!((Object)(object)player == (Object)null) && !((GameEntity)player).dead) { string text = ((__instance.id == 0) ? leftSelectedId : rightSelectedId); if (!string.IsNullOrEmpty(text) && text != "default" && text != "climber") { ForceApplyCosmeticToHand(player, __instance.id, text); } } } } [HarmonyPatch(typeof(Hand), "LateUpdate")] public static class HandLateUpdatePatch { private static void Postfix(Hand __instance) { ENT_Player player = __instance.GetPlayer(); if (!((Object)(object)player == (Object)null) && !((GameEntity)player).dead && !CL_GameManager.gMan.isPaused) { string text = ((__instance.id == 0) ? leftSelectedId : rightSelectedId); if (!string.IsNullOrEmpty(text) && text != "default" && text != "climber") { ForceApplyCosmeticToHand(player, __instance.id, text); } } } } public static ConfigEntry ExcludedIdsConfig; public static ConfigEntry SameCosmeticForBothHands; public static ConfigEntry ChangeInterval; private static List availableCosmetics = new List(); private static List recentIds = new List(); private const int historySize = 4; private static bool isInitialized = false; private static string leftSelectedId = null; private static string rightSelectedId = null; private void Awake() { ExcludedIdsConfig = ((BaseUnityPlugin)this).Config.Bind("General", "ExcludedCosmeticIds", "default,climber", "English: List of cosmetic IDs (comma separated) to exclude from randomization.\nРусский: Список ID косметики (через запятую), исключаемых из рандома."); SameCosmeticForBothHands = ((BaseUnityPlugin)this).Config.Bind("General", "SameCosmeticForBothHands", false, "English: True = both hands get the same cosmetic; False = each hand gets its own.\nРусский: True = обе руки получают одинаковую косметику; False = каждая руку свою."); ChangeInterval = ((BaseUnityPlugin)this).Config.Bind("General", "ChangeInterval", 30f, "English: Interval in seconds between cosmetic changes (0 = disable dynamic change).\nРусский: Интервал смены скинов в секундах (0 = отключить динамическую смену)."); SceneManager.sceneLoaded += OnSceneLoaded; Harmony.CreateAndPatchAll(typeof(GetCosmeticInHandPatch), (string)null); Harmony.CreateAndPatchAll(typeof(PlayerStartPatch), (string)null); Harmony.CreateAndPatchAll(typeof(HandOnEnablePatch), (string)null); Harmony.CreateAndPatchAll(typeof(HandLateUpdatePatch), (string)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"RandomHandCosmetics loaded with forced application."); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name == "Game-Main") { isInitialized = false; recentIds.Clear(); leftSelectedId = null; rightSelectedId = null; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Cosmetic selection reset for new run."); } } private static void InitializeCosmetics() { if (isInitialized) { return; } CL_CosmeticManager.Initialize(); FieldInfo field = typeof(CL_CosmeticManager).GetField("cosmeticHands", BindingFlags.Static | BindingFlags.NonPublic); if (field == null) { Debug.LogError((object)"cosmeticHands field not found."); } else if (field.GetValue(null) is List { Count: not 0 } list) { HashSet excludedIds = new HashSet { "default", "climber" }; string value = ExcludedIdsConfig.Value; if (!string.IsNullOrEmpty(value)) { foreach (string item in from s in value.Split(',') select s.Trim()) { excludedIds.Add(item); } } List list2 = list.Where((Cosmetic_HandItem c) => ((Cosmetic_Base)c).IsUnlocked()).ToList(); if (list2.Count == 0) { list2 = list; } availableCosmetics = (from c in list2 where !excludedIds.Contains(c.cosmeticData.id) && !string.IsNullOrEmpty(c.cosmeticData.id) group c by c.cosmeticData.id into g select g.First()).ToList(); isInitialized = true; Debug.Log((object)$"Initialized {availableCosmetics.Count} unique cosmetics."); if (availableCosmetics.Count > 0) { string text = string.Join(", ", availableCosmetics.Select((Cosmetic_HandItem c) => c.cosmeticData.id)); Debug.Log((object)("Available IDs: " + text)); } } else { Debug.LogWarning((object)"No cosmetics found."); } } private static void SelectRandomCosmetics() { if (availableCosmetics.Count == 0) { Debug.LogWarning((object)"No cosmetics available."); return; } List list = availableCosmetics; if (recentIds.Count > 0) { list = availableCosmetics.Where((Cosmetic_HandItem c) => !recentIds.Contains(c.cosmeticData.id)).ToList(); if (list.Count == 0) { list = availableCosmetics; } } List list2 = list.Where((Cosmetic_HandItem c) => c.cosmeticData.id != "default" && c.cosmeticData.id != "climber").ToList(); if (list2.Count > 0) { list = list2; } string text = null; string text2 = null; for (int num = 0; num < 30; num++) { if (!string.IsNullOrEmpty(text) && !(text == "default") && !(text == "climber")) { break; } text = list[Random.Range(0, list.Count)].cosmeticData.id; } if (string.IsNullOrEmpty(text) || text == "default" || text == "climber") { text = list[0].cosmeticData.id; } if (SameCosmeticForBothHands.Value) { text2 = text; } else { for (int num = 0; num < 30; num++) { if (!string.IsNullOrEmpty(text2) && !(text2 == "default") && !(text2 == "climber") && !(text2 == text)) { break; } text2 = list[Random.Range(0, list.Count)].cosmeticData.id; } if (string.IsNullOrEmpty(text2) || text2 == "default" || text2 == "climber") { text2 = text; } } leftSelectedId = text; rightSelectedId = text2; if (!recentIds.Contains(text)) { recentIds.Add(text); } if (!SameCosmeticForBothHands.Value && !recentIds.Contains(text2)) { recentIds.Add(text2); } while (recentIds.Count > 4) { recentIds.RemoveAt(0); } Debug.Log((object)("Selected cosmetics: left=" + text + ", right=" + text2)); } private static void ForceApplyCosmeticToHand(ENT_Player player, int handIndex, string id) { if (string.IsNullOrEmpty(id) || id == "default" || id == "climber" || handIndex >= player.hands.Length) { return; } Hand val = player.hands[handIndex]; if (val == null || (Object)(object)val.handModel == (Object)null) { return; } FieldInfo field = typeof(CL_CosmeticManager).GetField("cosmeticHandDict", BindingFlags.Static | BindingFlags.NonPublic); if (!(field == null) && field.GetValue(null) is Dictionary dictionary && dictionary.TryGetValue(id, out var value)) { val.currentCosmetics.Clear(); val.currentCosmetics.Add(value); Cosmetic_HandItem.ReturnSprites(); value.SpriteSwap(((Component)val.handModel).gameObject, val); if (handIndex == 0) { SettingsManager.settings.cosmeticSaveData.leftHandCosmetic.id = id; } else { SettingsManager.settings.cosmeticSaveData.rightHandCosmetic.id = id; } } } }