|
|
@@ -0,0 +1,184 @@ |
|
|
|
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<Reflection.ExistsV2>( |
|
|
|
"Svelto.ECS.IEntitiesDB:Exists", |
|
|
|
new[] {typeof(int), typeof(int)}, |
|
|
|
new[] {AccessTools.TypeByName("Game.Handhelds.CharacterWeaponCooldownEntityView")}, |
|
|
|
entitiesDB); |
|
|
|
Reflection.QueryEntityViewV2<object> queryCWCEV = |
|
|
|
Reflection.MethodAsDelegate<Reflection.QueryEntityViewV2<object>>( |
|
|
|
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<DispatchOnChange<int>>().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<Reflection.ExistsV2>( |
|
|
|
"Svelto.ECS.IEntitiesDB:Exists", |
|
|
|
new[] {typeof(int), typeof(int)}, |
|
|
|
new[] {AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")}, |
|
|
|
entitiesDB); |
|
|
|
Reflection.QueryEntityViewV2<object> queryWCEV = |
|
|
|
Reflection.MethodAsDelegate<Reflection.QueryEntityViewV2<object>>( |
|
|
|
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); |
|
|
|
float cooldownLeft = |
|
|
|
Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<float>("cooldownLeft").Value; |
|
|
|
bool isInCooldown = |
|
|
|
Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<bool>("isInCooldown").Value; |
|
|
|
//API.Utility.Logging.MetaLog($"Cooling down? {isInCooldown} for {cooldownLeft}s"); |
|
|
|
// build functions for querying all Game.Handhelds.WeaponCooldownEntityView objects |
|
|
|
Type protoRaw = |
|
|
|
typeof(Reflection.QueryEntityViews<IEntityViewStruct>).GetGenericTypeDefinition() |
|
|
|
.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<int>("_count").Value; |
|
|
|
PropertyInfo indexer = typeof(Svelto.DataStructures.ReadOnlyCollectionStruct<IEntityStruct>).GetGenericTypeDefinition() |
|
|
|
.MakeGenericType(AccessTools.TypeByName("Game.Handhelds.WeaponCooldownEntityView")) |
|
|
|
.GetIndexer(); |
|
|
|
object[] indexParams = {0}; |
|
|
|
for (int index = 0; index < count; index++) |
|
|
|
{ |
|
|
|
#if DEBUG |
|
|
|
API.Utility.Logging.MetaLog("Syncing cooldown with another weapon"); |
|
|
|
#endif |
|
|
|
indexParams[0] = index; |
|
|
|
object wcev = indexer.GetValue(collectionStruct, indexParams); |
|
|
|
Traverse.Create(wcev) |
|
|
|
.Field("weaponCooldownComponent") |
|
|
|
.Property<float>("cooldownLeft") |
|
|
|
.Value = cooldownLeft; |
|
|
|
|
|
|
|
Traverse.Create(wcev) |
|
|
|
.Field("weaponCooldownComponent") |
|
|
|
.Property<bool>("isInCooldown").Value = isInCooldown; |
|
|
|
} |
|
|
|
|
|
|
|
if (!isRunningTick) |
|
|
|
{ |
|
|
|
cooldownTickEverything(characterId, entitiesDB, 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, IEntitiesDB entitiesDb, Reflection.ExistsV2 cwcevExists, Reflection.QueryEntityViewV2<object> cwcevQuery, Reflection.QueryEntityViewV2<object> wcevQuery, Delegate wcevQueryAll, PropertyInfo indexer, ExclusiveGroup baseGroup) |
|
|
|
{ |
|
|
|
isRunningTick = true; |
|
|
|
while (cwcevExists(characterId, (int) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP)) |
|
|
|
{ |
|
|
|
#if DEBUG |
|
|
|
API.Utility.Logging.MetaLog("Doing cooldown tick for all weapons"); |
|
|
|
#endif |
|
|
|
// get equipped handheld info |
|
|
|
object cwcevOriginal = cwcevQuery(characterId, |
|
|
|
(ExclusiveGroup.ExclusiveGroupStruct) DEPRECATED_SveltoExtensions.DEPRECATED_GROUP); |
|
|
|
int toolId = Traverse.Create(cwcevOriginal).Field("handheldEquipmentComponent") |
|
|
|
.Property("equippedHandheldId") |
|
|
|
.GetValue<DispatchOnChange<int>>().value; |
|
|
|
// get cooldown info for equipped item |
|
|
|
object wcevOriginal = wcevQuery(toolId, baseGroup); |
|
|
|
object collectionStruct = |
|
|
|
wcevQueryAll.DynamicInvoke((int)baseGroup); |
|
|
|
int count = Traverse.Create(collectionStruct).Field<int>("_count").Value; |
|
|
|
float cooldownLeft = |
|
|
|
Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<float>("cooldownLeft").Value; |
|
|
|
bool isInCooldown = |
|
|
|
Traverse.Create(wcevOriginal).Field("weaponCooldownComponent").Property<bool>("isInCooldown").Value; |
|
|
|
if (!isInCooldown) break; |
|
|
|
object[] indexParams = {0}; |
|
|
|
// iterate over other handhelds and sync their cooldowns to the held item |
|
|
|
for (int index = 0; index < count; index++) |
|
|
|
{ |
|
|
|
indexParams[0] = index; |
|
|
|
object wcev = indexer.GetValue(collectionStruct, indexParams); |
|
|
|
Traverse.Create(wcev) |
|
|
|
.Field("weaponCooldownComponent") |
|
|
|
.Property<float>("cooldownLeft") |
|
|
|
.Value = cooldownLeft; |
|
|
|
|
|
|
|
Traverse.Create(wcev) |
|
|
|
.Field("weaponCooldownComponent") |
|
|
|
.Property<bool>("isInCooldown").Value = isInCooldown; |
|
|
|
} |
|
|
|
|
|
|
|
yield return null; |
|
|
|
} |
|
|
|
// cleanup |
|
|
|
isRunningTick = false; |
|
|
|
yield return null; |
|
|
|
} |
|
|
|
} |
|
|
|
} |