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.

166 lines
8.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. //API.Utility.Logging.MetaLog($"Cooling down? {isInCooldown} for {cooldownLeft}s");
  78. // build functions for querying all Game.Handhelds.WeaponCooldownEntityView objects
  79. Type protoRaw =
  80. typeof(Reflection.QueryEntityViews<>)
  81. .MakeGenericType(
  82. AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView"));
  83. Delegate queryAllWCEV =
  84. Reflection.MethodAsDelegateRaw(
  85. typeof(IEntitiesDB),
  86. protoRaw,
  87. "QueryEntityViews",
  88. parameters: new[] {typeof(int)},
  89. generics: new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")},
  90. entitiesDB);
  91. object collectionStruct =
  92. queryAllWCEV.DynamicInvoke((int) baseGroup);
  93. int count = Traverse.Create(collectionStruct).Field<int>("_count").Value;
  94. PropertyInfo indexer = typeof(Svelto.DataStructures.ReadOnlyCollectionStruct<>)
  95. .MakeGenericType(AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView"))
  96. .GetIndexer();
  97. if (!isRunningTick)
  98. {
  99. isRunningTick = true;
  100. cooldownTickEverything(characterId, cwcevExists, queryCWCEV, queryWCEV, queryAllWCEV, indexer, baseGroup).Run();
  101. }
  102. }
  103. [HarmonyTargetMethod]
  104. public static MethodBase Target()
  105. {
  106. return AccessTools.Method("Game.Handhelds.WeaponCooldownEngine:OnQuickAttackStateChange",
  107. new[] {typeof(int), typeof(bool)});
  108. }
  109. private static IEnumerator cooldownTickEverything(int characterId, Reflection.ExistsV2 cwcevExists, Reflection.QueryEntityViewV2<object> cwcevQuery, Reflection.QueryEntityViewV2<object> wcevQuery, Delegate wcevQueryAll, PropertyInfo indexer, ExclusiveGroup baseGroup)
  110. {
  111. while (cwcevExists(characterId, (int) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP))
  112. {
  113. // get equipped handheld info
  114. object cwcevOriginal = cwcevQuery(characterId,
  115. (ExclusiveGroup.ExclusiveGroupStruct) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP);
  116. int toolId = Traverse.Create(cwcevOriginal).Field("handheldEquipmentComponent")
  117. .Property("equippedHandheldId")
  118. .GetValue<DispatchOnChange<int>>().value;
  119. // get cooldown info for equipped item
  120. object wcevOriginal = wcevQuery(toolId, baseGroup);
  121. object collectionStruct =
  122. wcevQueryAll.DynamicInvoke((int)baseGroup);
  123. int count = Traverse.Create(collectionStruct).Field<int>("_count").Value;
  124. float cooldownLeft =
  125. Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<float>("cooldownLeft").Value;
  126. bool isInCooldown =
  127. Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<bool>("isInCooldown").Value;
  128. object[] indexParams = {0};
  129. // iterate over other handhelds and sync their cooldowns to the held item
  130. #if DEBUG
  131. API.Utility.Logging.MetaDebugLog($"Syncing {count} weapon cooldowns with the held item {toolId}");
  132. #endif
  133. for (int index = 0; index < count; index++)
  134. {
  135. indexParams[0] = index;
  136. object wcev = indexer.GetValue(collectionStruct, indexParams);
  137. Traverse.Create(wcev)
  138. .Field("weaponCooldownComponent")
  139. .Property<float>("cooldownLeft")
  140. .Value = cooldownLeft;
  141. Traverse.Create(wcev)
  142. .Field("weaponCooldownComponent")
  143. .Property<bool>("isInCooldown").Value = isInCooldown;
  144. }
  145. // stop running this task after one final sync to set every handheld to non-cooldown state
  146. if (!isInCooldown && cooldownLeft <= 0) break;
  147. yield return null;
  148. }
  149. // cleanup
  150. #if DEBUG
  151. API.Utility.Logging.MetaDebugLog("Custom cooldown ticks complete");
  152. #endif
  153. isRunningTick = false;
  154. yield return null;
  155. }
  156. }
  157. }