Unofficial CardLife revival project, pronounced like "celery"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

183 lines
9.5KB

  1. using System;
  2. using System.Collections;
  3. using System.Reflection;
  4. using CLre.API.Utility;
  5. using HarmonyLib;
  6. using Svelto.DataStructures;
  7. using Svelto.ECS;
  8. namespace CLre.Fixes
  9. {
  10. [Bugfix(name = "CooldownCrossSlotSync",
  11. more = "https://trello.com/c/65FPrTjK/12-no-cooldown-between-inventory-slots",
  12. description = "Apply cooldown to all weapons",
  13. component = BugfixType.HarmonyPatch, id = 4)]
  14. [HarmonyPatch]
  15. class WeaponCooldownEngine_OnQuickAttackStateChange_Patch
  16. {
  17. public static bool Enable = true;
  18. private static bool isRunningTick = false;
  19. [HarmonyPostfix]
  20. public static void AfterMethodCall(object __instance, int characterId, bool isPerformingQuickAttack)
  21. {
  22. if (!Enable) return;
  23. // TODO optimise this... a lot
  24. // build functions for retrieving internal Game.Handhelds.CharacterWeaponCooldownEntityView object
  25. IEntitiesDB entitiesDB = (IEntitiesDB)
  26. AccessTools.PropertyGetter(AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEngine"), "entitiesDB")
  27. .Invoke(__instance, new object[0]);
  28. Reflection.ExistsV2 cwcevExists =
  29. Reflection.MethodAsDelegate<Reflection.ExistsV2>(
  30. "Svelto.ECS.IEntitiesDB:Exists",
  31. new[] {typeof(int), typeof(int)},
  32. new[] {AccessTools.TypeByName("Game.Handhelds.CharacterWeaponCooldownEntityView")},
  33. entitiesDB);
  34. Reflection.QueryEntityViewV2<object> queryCWCEV =
  35. Reflection.MethodAsDelegate<Reflection.QueryEntityViewV2<object>>(
  36. typeof(IEntitiesDB),
  37. "QueryEntityView",
  38. parameters: new[] {typeof(int), typeof(ExclusiveGroup.ExclusiveGroupStruct)},
  39. generics: new[] {AccessTools.TypeByName("Game.Handhelds.CharacterWeaponCooldownEntityView")},
  40. entitiesDB);
  41. // retrieve Game.Handhelds.CharacterWeaponCooldownEntityView
  42. if (!cwcevExists(characterId, (int) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP))
  43. {
  44. //API.Utility.Logging.MetaDebugLog($"No CharacterWeaponCooldownEntityView with id {characterId} found");
  45. return;
  46. }
  47. object cwcevOriginal = queryCWCEV(characterId,
  48. (ExclusiveGroup.ExclusiveGroupStruct) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP);
  49. // get id because that's important
  50. int toolId = Traverse.Create(cwcevOriginal).Field("handheldEquipmentComponent")
  51. .Property("equippedHandheldId")
  52. .GetValue<DispatchOnChange<int>>().value;
  53. //API.Utility.Logging.MetaLog("Got CharacterWeaponCooldownEntityView object using nasty code");
  54. // build functions for retrieving internal Game.Handhelds.WeaponCooldownEntityView object
  55. Reflection.ExistsV2 wcevExists =
  56. Reflection.MethodAsDelegate<Reflection.ExistsV2>(
  57. "Svelto.ECS.IEntitiesDB:Exists",
  58. new[] {typeof(int), typeof(int)},
  59. new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")},
  60. entitiesDB);
  61. Reflection.QueryEntityViewV2<object> queryWCEV =
  62. Reflection.MethodAsDelegate<Reflection.QueryEntityViewV2<object>>(
  63. typeof(IEntitiesDB),
  64. "QueryEntityView",
  65. parameters: new[] {typeof(int), typeof(ExclusiveGroup.ExclusiveGroupStruct)},
  66. generics: new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")},
  67. entitiesDB);
  68. ExclusiveGroup baseGroup = (ExclusiveGroup) AccessTools
  69. .Field(AccessTools.TypeByName("Game.Handhelds.HandheldGroups"), "BaseGroup").GetValue(null);
  70. // retrieve WeaponCooldownEntityView
  71. if (!wcevExists(toolId, (int) baseGroup))
  72. {
  73. //API.Utility.Logging.MetaDebugLog($"No WeaponCooldownEntityView with id {toolId} found");
  74. return;
  75. }
  76. object wcevOriginal = queryWCEV(toolId, baseGroup);
  77. float cooldownLeft =
  78. Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<float>("cooldownLeft").Value;
  79. bool isInCooldown =
  80. Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<bool>("isInCooldown").Value;
  81. //API.Utility.Logging.MetaLog($"Cooling down? {isInCooldown} for {cooldownLeft}s");
  82. // build functions for querying all Game.Handhelds.WeaponCooldownEntityView objects
  83. Type protoRaw =
  84. typeof(Reflection.QueryEntityViews<>)
  85. .MakeGenericType(
  86. AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView"));
  87. Delegate queryAllWCEV =
  88. Reflection.MethodAsDelegateRaw(
  89. typeof(IEntitiesDB),
  90. protoRaw,
  91. "QueryEntityViews",
  92. parameters: new[] {typeof(int)},
  93. generics: new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")},
  94. entitiesDB);
  95. object collectionStruct =
  96. queryAllWCEV.DynamicInvoke((int) baseGroup);
  97. int count = Traverse.Create(collectionStruct).Field<int>("_count").Value;
  98. PropertyInfo indexer = typeof(Svelto.DataStructures.ReadOnlyCollectionStruct<>)
  99. .MakeGenericType(AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView"))
  100. .GetIndexer();
  101. if (!isRunningTick)
  102. {
  103. isRunningTick = true;
  104. cooldownTickEverything(characterId, cwcevExists, queryCWCEV, queryWCEV, queryAllWCEV, indexer, baseGroup).Run();
  105. }
  106. /*object[] indexParams = {0};
  107. for (int index = 0; index < count; index++)
  108. {
  109. #if DEBUG
  110. API.Utility.Logging.MetaLog("Syncing cooldown with another weapon");
  111. #endif
  112. indexParams[0] = index;
  113. object wcev = indexer.GetValue(collectionStruct, indexParams);
  114. Traverse.Create(wcev)
  115. .Field("weaponCooldownComponent")
  116. .Property<float>("cooldownLeft")
  117. .Value = cooldownLeft;
  118. Traverse.Create(wcev)
  119. .Field("weaponCooldownComponent")
  120. .Property<bool>("isInCooldown").Value = isInCooldown;
  121. }*/
  122. }
  123. [HarmonyTargetMethod]
  124. public static MethodBase Target()
  125. {
  126. return AccessTools.Method("Game.Handhelds.WeaponCooldownEngine:OnQuickAttackStateChange",
  127. new[] {typeof(int), typeof(bool)});
  128. }
  129. private static IEnumerator cooldownTickEverything(int characterId, Reflection.ExistsV2 cwcevExists, Reflection.QueryEntityViewV2<object> cwcevQuery, Reflection.QueryEntityViewV2<object> wcevQuery, Delegate wcevQueryAll, PropertyInfo indexer, ExclusiveGroup baseGroup)
  130. {
  131. while (cwcevExists(characterId, (int) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP))
  132. {
  133. // get equipped handheld info
  134. object cwcevOriginal = cwcevQuery(characterId,
  135. (ExclusiveGroup.ExclusiveGroupStruct) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP);
  136. int toolId = Traverse.Create(cwcevOriginal).Field("handheldEquipmentComponent")
  137. .Property("equippedHandheldId")
  138. .GetValue<DispatchOnChange<int>>().value;
  139. // get cooldown info for equipped item
  140. object wcevOriginal = wcevQuery(toolId, baseGroup);
  141. object collectionStruct =
  142. wcevQueryAll.DynamicInvoke((int)baseGroup);
  143. int count = Traverse.Create(collectionStruct).Field<int>("_count").Value;
  144. float cooldownLeft =
  145. Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<float>("cooldownLeft").Value;
  146. bool isInCooldown =
  147. Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<bool>("isInCooldown").Value;
  148. object[] indexParams = {0};
  149. // iterate over other handhelds and sync their cooldowns to the held item
  150. API.Utility.Logging.MetaDebugLog($"Syncing {count} weapon cooldowns with the held item {toolId}");
  151. for (int index = 0; index < count; index++)
  152. {
  153. indexParams[0] = index;
  154. object wcev = indexer.GetValue(collectionStruct, indexParams);
  155. Traverse.Create(wcev)
  156. .Field("weaponCooldownComponent")
  157. .Property<float>("cooldownLeft")
  158. .Value = cooldownLeft;
  159. Traverse.Create(wcev)
  160. .Field("weaponCooldownComponent")
  161. .Property<bool>("isInCooldown").Value = isInCooldown;
  162. }
  163. // stop running this task after one final sync to set every handheld to non-cooldown state
  164. if (!isInCooldown && cooldownLeft <= 0) break;
  165. yield return null;
  166. }
  167. // cleanup
  168. API.Utility.Logging.MetaDebugLog("Custom cooldown ticks complete");
  169. isRunningTick = false;
  170. yield return null;
  171. }
  172. }
  173. }