using System; using System.Collections; using System.Reflection; using CLre.API.Utility; using HarmonyLib; using Svelto.DataStructures; using Svelto.ECS; namespace CLre.Fixes { [Bugfix(name = "CooldownCrossSlotSync", more = "https://trello.com/c/65FPrTjK/12-no-cooldown-between-inventory-slots", description = "Apply cooldown to all weapons", component = BugfixType.HarmonyPatch, id = 4)] [HarmonyPatch] class WeaponCooldownEngine_OnQuickAttackStateChange_Patch { public static bool Enable = true; private static bool isRunningTick = false; [HarmonyPostfix] public static void AfterMethodCall(object __instance, int characterId, bool isPerformingQuickAttack) { if (!Enable) return; // TODO optimise this... a lot // build functions for retrieving internal Game.Handhelds.CharacterWeaponCooldownEntityView object IEntitiesDB entitiesDB = (IEntitiesDB) AccessTools.PropertyGetter(AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEngine"), "entitiesDB") .Invoke(__instance, new object[0]); Reflection.ExistsV2 cwcevExists = Reflection.MethodAsDelegate( "Svelto.ECS.IEntitiesDB:Exists", new[] {typeof(int), typeof(int)}, new[] {AccessTools.TypeByName("Game.Handhelds.CharacterWeaponCooldownEntityView")}, entitiesDB); Reflection.QueryEntityViewV2 queryCWCEV = Reflection.MethodAsDelegate>( typeof(IEntitiesDB), "QueryEntityView", parameters: new[] {typeof(int), typeof(ExclusiveGroup.ExclusiveGroupStruct)}, generics: new[] {AccessTools.TypeByName("Game.Handhelds.CharacterWeaponCooldownEntityView")}, entitiesDB); // retrieve Game.Handhelds.CharacterWeaponCooldownEntityView if (!cwcevExists(characterId, (int) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP)) { //API.Utility.Logging.MetaDebugLog($"No CharacterWeaponCooldownEntityView with id {characterId} found"); return; } object cwcevOriginal = queryCWCEV(characterId, (ExclusiveGroup.ExclusiveGroupStruct) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP); // get id because that's important int toolId = Traverse.Create(cwcevOriginal).Field("handheldEquipmentComponent") .Property("equippedHandheldId") .GetValue>().value; //API.Utility.Logging.MetaLog("Got CharacterWeaponCooldownEntityView object using nasty code"); // build functions for retrieving internal Game.Handhelds.WeaponCooldownEntityView object Reflection.ExistsV2 wcevExists = Reflection.MethodAsDelegate( "Svelto.ECS.IEntitiesDB:Exists", new[] {typeof(int), typeof(int)}, new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")}, entitiesDB); Reflection.QueryEntityViewV2 queryWCEV = Reflection.MethodAsDelegate>( typeof(IEntitiesDB), "QueryEntityView", parameters: new[] {typeof(int), typeof(ExclusiveGroup.ExclusiveGroupStruct)}, generics: new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")}, entitiesDB); ExclusiveGroup baseGroup = (ExclusiveGroup) AccessTools .Field(AccessTools.TypeByName("Game.Handhelds.HandheldGroups"), "BaseGroup").GetValue(null); // retrieve WeaponCooldownEntityView if (!wcevExists(toolId, (int) baseGroup)) { //API.Utility.Logging.MetaDebugLog($"No WeaponCooldownEntityView with id {toolId} found"); return; } //object wcevOriginal = queryWCEV(toolId, baseGroup); //API.Utility.Logging.MetaLog($"Cooling down? {isInCooldown} for {cooldownLeft}s"); // build functions for querying all Game.Handhelds.WeaponCooldownEntityView objects Type protoRaw = typeof(Reflection.QueryEntityViews<>) .MakeGenericType( AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")); Delegate queryAllWCEV = Reflection.MethodAsDelegateRaw( typeof(IEntitiesDB), protoRaw, "QueryEntityViews", parameters: new[] {typeof(int)}, generics: new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")}, entitiesDB); object collectionStruct = queryAllWCEV.DynamicInvoke((int) baseGroup); int count = Traverse.Create(collectionStruct).Field("_count").Value; PropertyInfo indexer = typeof(Svelto.DataStructures.ReadOnlyCollectionStruct<>) .MakeGenericType(AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")) .GetIndexer(); if (!isRunningTick) { isRunningTick = true; cooldownTickEverything(characterId, cwcevExists, queryCWCEV, queryWCEV, queryAllWCEV, indexer, baseGroup).Run(); } } [HarmonyTargetMethod] public static MethodBase Target() { return AccessTools.Method("Game.Handhelds.WeaponCooldownEngine:OnQuickAttackStateChange", new[] {typeof(int), typeof(bool)}); } private static IEnumerator cooldownTickEverything(int characterId, Reflection.ExistsV2 cwcevExists, Reflection.QueryEntityViewV2 cwcevQuery, Reflection.QueryEntityViewV2 wcevQuery, Delegate wcevQueryAll, PropertyInfo indexer, ExclusiveGroup baseGroup) { while (cwcevExists(characterId, (int) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP)) { // get equipped handheld info object cwcevOriginal = cwcevQuery(characterId, (ExclusiveGroup.ExclusiveGroupStruct) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP); int toolId = Traverse.Create(cwcevOriginal).Field("handheldEquipmentComponent") .Property("equippedHandheldId") .GetValue>().value; // get cooldown info for equipped item object wcevOriginal = wcevQuery(toolId, baseGroup); object collectionStruct = wcevQueryAll.DynamicInvoke((int)baseGroup); int count = Traverse.Create(collectionStruct).Field("_count").Value; float cooldownLeft = Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property("cooldownLeft").Value; bool isInCooldown = Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property("isInCooldown").Value; object[] indexParams = {0}; // iterate over other handhelds and sync their cooldowns to the held item #if DEBUG API.Utility.Logging.MetaDebugLog($"Syncing {count} weapon cooldowns with the held item {toolId}"); #endif for (int index = 0; index < count; index++) { indexParams[0] = index; object wcev = indexer.GetValue(collectionStruct, indexParams); Traverse.Create(wcev) .Field("weaponCooldownComponent") .Property("cooldownLeft") .Value = cooldownLeft; Traverse.Create(wcev) .Field("weaponCooldownComponent") .Property("isInCooldown").Value = isInCooldown; } // stop running this task after one final sync to set every handheld to non-cooldown state if (!isInCooldown && cooldownLeft <= 0) break; yield return null; } // cleanup #if DEBUG API.Utility.Logging.MetaDebugLog("Custom cooldown ticks complete"); #endif isRunningTick = false; yield return null; } } }