Add fix for cooldown only applying to current held item
This commit is contained in:
parent
ed99fa26f2
commit
af37d4ab42
5 changed files with 312 additions and 2 deletions
|
@ -79,7 +79,7 @@ namespace CLre.API.Utility
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The object to log</param>
|
/// <param name="obj">The object to log</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void MetaDebugLog(object obj)
|
internal static void MetaDebugLog(object obj)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
MetaLog($"[MetaDebug]{obj.ToString()}");
|
MetaLog($"[MetaDebug]{obj.ToString()}");
|
||||||
|
|
115
CLre/API/Utility/Reflection.cs
Normal file
115
CLre/API/Utility/Reflection.cs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Svelto.DataStructures;
|
||||||
|
using Svelto.ECS;
|
||||||
|
|
||||||
|
namespace CLre.API.Utility
|
||||||
|
{
|
||||||
|
public static class Reflection
|
||||||
|
{
|
||||||
|
// useful function & method prototypes
|
||||||
|
public delegate T Getter<T>();
|
||||||
|
|
||||||
|
public delegate bool ExistsV1(EGID egid);
|
||||||
|
|
||||||
|
public delegate bool ExistsV2(int id, int groupid);
|
||||||
|
|
||||||
|
public delegate T QueryEntityViewV1<T>(EGID egid);
|
||||||
|
|
||||||
|
public delegate T QueryEntityViewV2<T>(int id, ExclusiveGroup.ExclusiveGroupStruct groupid);
|
||||||
|
|
||||||
|
public delegate ReadOnlyCollectionStruct<T> QueryEntityViews<T>(int group) where T : class, IEntityViewStruct;
|
||||||
|
|
||||||
|
public delegate T[] QueryEntitiesV2<T>(int group, out int count) where T : IEntityStruct;
|
||||||
|
|
||||||
|
public delegate T[] QueryEntitiesV1<T>(ExclusiveGroup.ExclusiveGroupStruct group, out int count) where T : IEntityStruct;
|
||||||
|
|
||||||
|
// useful reflection functions
|
||||||
|
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method) where TFuncProto : Delegate
|
||||||
|
{
|
||||||
|
return (TFuncProto) Delegate.CreateDelegate(typeof(TFuncProto), method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Delegate BuildDelegateRaw(MethodInfo method, Type TFuncProto)
|
||||||
|
{
|
||||||
|
return Delegate.CreateDelegate(TFuncProto, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method, object instance) where TFuncProto : Delegate
|
||||||
|
{
|
||||||
|
return (TFuncProto) Delegate.CreateDelegate(typeof(TFuncProto), instance, method, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Delegate BuildDelegateRaw(MethodInfo method, object instance, Type TFuncProto)
|
||||||
|
{
|
||||||
|
return Delegate.CreateDelegate(TFuncProto, instance, method, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TFuncProto MethodAsDelegate<TFuncProto>(Type class_, string methodName, Type[] parameters = null, Type[] generics = null, object instance = null) where TFuncProto : Delegate
|
||||||
|
{
|
||||||
|
MethodInfo method = AccessTools.Method(class_, methodName, parameters, generics);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
return BuildDelegate<TFuncProto>(method, instance);
|
||||||
|
}
|
||||||
|
return BuildDelegate<TFuncProto>(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Delegate MethodAsDelegateRaw(Type class_, Type TFuncProto, string methodName, Type[] parameters = null, Type[] generics = null, object instance = null)
|
||||||
|
{
|
||||||
|
MethodInfo method = AccessTools.Method(class_, methodName, parameters, generics);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
return BuildDelegateRaw(method, instance, TFuncProto);
|
||||||
|
}
|
||||||
|
return BuildDelegateRaw(method, TFuncProto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TFuncProto MethodAsDelegate<TFuncProto>(string typeColonName, Type[] parameters = null, Type[] generics = null, object instance = null) where TFuncProto : Delegate
|
||||||
|
{
|
||||||
|
MethodInfo method = AccessTools.Method(typeColonName, parameters, generics);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
return BuildDelegate<TFuncProto>(method, instance);
|
||||||
|
}
|
||||||
|
return BuildDelegate<TFuncProto>(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Delegate MethodAsDelegateRaw(string typeColonName, Type TFuncProto, Type[] parameters = null, Type[] generics = null, object instance = null)
|
||||||
|
{
|
||||||
|
MethodInfo method = AccessTools.Method(typeColonName, parameters, generics);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
return BuildDelegateRaw(method, instance, TFuncProto);
|
||||||
|
}
|
||||||
|
return BuildDelegateRaw(method, TFuncProto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyInfo GetIndexer(this Type type, Type[] arguments = null, BindingFlags bindingFlags = BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetField | BindingFlags.SetProperty | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
foreach (PropertyInfo p in type.GetProperties(bindingFlags))
|
||||||
|
{
|
||||||
|
if (arguments == null && p.GetIndexParameters().Length != 0) return p;
|
||||||
|
if (arguments != null)
|
||||||
|
{
|
||||||
|
uint count = 0;
|
||||||
|
foreach (ParameterInfo param in p.GetIndexParameters())
|
||||||
|
{
|
||||||
|
if (param.ParameterType != arguments[count])
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count == arguments.Length)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,5 +83,13 @@ namespace CLre
|
||||||
sb.AppendFormat("-----------------------------\n");
|
sb.AppendFormat("-----------------------------\n");
|
||||||
API.Utility.Logging.Log(sb.ToString());
|
API.Utility.Logging.Log(sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnGUI()
|
||||||
|
{
|
||||||
|
if (GUI.Button(new Rect(10, 10, 50, 50), "TEST"))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net472</TargetFramework>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<Version>0.0.1</Version>
|
<Version>0.0.2</Version>
|
||||||
<Authors>NGnius</Authors>
|
<Authors>NGnius</Authors>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageProjectUrl>https://git.exmods.org/NGnius/CLre</PackageProjectUrl>
|
<PackageProjectUrl>https://git.exmods.org/NGnius/CLre</PackageProjectUrl>
|
||||||
|
@ -281,6 +281,9 @@
|
||||||
<HintPath>..\..\cl\Cardlife_Data\Managed\IllusionPlugin.dll</HintPath>
|
<HintPath>..\..\cl\Cardlife_Data\Managed\IllusionPlugin.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="API\Debug" />
|
||||||
|
</ItemGroup>
|
||||||
<!--End Dependencies-->
|
<!--End Dependencies-->
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
184
CLre/Fixes/CooldownCrossSlotSync.cs
Normal file
184
CLre/Fixes/CooldownCrossSlotSync.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue