Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SparkleRexTweaks v1.12.0
plugins/SparkleRexTweaks.dll
Decompiled 6 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using R2API; using RoR2; using RoR2.Projectile; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("SparkleRexTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.12.0.0")] [assembly: AssemblyInformationalVersion("1.12.0")] [assembly: AssemblyProduct("SparkleRexTweaks")] [assembly: AssemblyTitle("SparkleRexTweaks")] [assembly: AssemblyVersion("1.12.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SparkleRexTweaks { public static class RexBrambleVolley { private const string StateTypeName = "EntityStates.Treebot.Weapon.FirePlantSonicBoom"; private const string MethodName = "CalculateProcCoefficient"; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _procCoefficient; private static ManualLogSource _log; private static bool _patched; private static bool _appliedLogged; private static float _vanillaValue = float.NaN; private static bool IsEnabled { get { if (_enabled != null) { return _enabled.Value; } return true; } } public static void Init(ConfigFile cfg, ManualLogSource logger) { _log = logger; _enabled = cfg.Bind<bool>("BrambleVolley", "Enabled", true, "是否调整替代 Shift「树莓齐射」(Bramble Volley)的触发系数。false = 保持原版。"); _procCoefficient = cfg.Bind<float>("BrambleVolley", "ProcCoefficient", 1f, "「树莓齐射」的触发系数(原版 0.5)。\n系数越高,尤克里里、导弹、三尖匕首之类的命中类道具越容易触发。\n填 1 = 每次命中都按满概率判定。填 0 或负数 = 沿用原版数值。\n这个值是运行期读取的(改完存档重进即可,不用重启游戏)。"); } public static string Summary() { if (!IsEnabled) { return " [树莓齐射:原版未改动]"; } if (!_patched) { return " [树莓齐射:补丁未挂载]"; } float num = TargetValue(); if (num <= 0f) { return " [树莓齐射:沿用原版触发系数]"; } return " [树莓齐射:触发系数 → " + num.ToString("0.##") + "]"; } private static float TargetValue() { if (_procCoefficient == null) { return 1f; } return _procCoefficient.Value; } public static void Patch(Harmony harmony) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "树莓齐射:按配置保持原版,不挂补丁。"); return; } Type type = AccessTools.TypeByName("EntityStates.Treebot.Weapon.FirePlantSonicBoom"); if (type == null) { Log((LogLevel)2, "树莓齐射补丁失败:取不到类型 EntityStates.Treebot.Weapon.FirePlantSonicBoom。"); return; } MethodInfo methodInfo = AccessTools.Method(type, "CalculateProcCoefficient", (Type[])null, (Type[])null); if (methodInfo == null) { methodInfo = type.GetMethod("CalculateProcCoefficient", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (methodInfo == null) { Log((LogLevel)2, "树莓齐射补丁失败:取不到 EntityStates.Treebot.Weapon.FirePlantSonicBoom.CalculateProcCoefficient。"); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RexBrambleVolley), "CalculateProcCoefficientPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patched = true; Log((LogLevel)16, "补丁已挂载:EntityStates.Treebot.Weapon.FirePlantSonicBoom.CalculateProcCoefficient <- CalculateProcCoefficientPostfix(触发系数 0.5 → " + TargetValue().ToString("0.##") + ")"); } public static void CalculateProcCoefficientPostfix(ref float __result) { try { if (!IsEnabled) { return; } float vanillaValue = __result; if (float.IsNaN(_vanillaValue)) { _vanillaValue = vanillaValue; } float num = TargetValue(); if (!(num <= 0f)) { __result = num; if (!_appliedLogged) { _appliedLogged = true; Log((LogLevel)16, "树莓齐射已生效 —— 触发系数 " + vanillaValue.ToString("0.##") + " → " + num.ToString("0.##")); } } } catch (Exception ex) { Log((LogLevel)4, "树莓齐射:结算异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static void Log(LogLevel level, string message) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (_log != null) { _log.Log(level, (object)message); } } } public static class RexEntangle { private const string FlowerTypeNameNested = "EntityStates.Treebot.TreebotFlower+TreebotFlower2Projectile"; private const string FlowerTypeNameDotted = "EntityStates.Treebot.TreebotFlower.TreebotFlower2Projectile"; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _interval; private static ConfigEntry<float> _tendrilLifetime; private static ConfigEntry<float> _damageMultiplier; private static ConfigEntry<float> _weakenDuration; private static ConfigEntry<float> _catchInterval; private static ConfigEntry<float> _procCoefficient; private static ConfigEntry<bool> _triggerProcs; private static ManualLogSource _log; private static Type _flowerType; private static MethodInfo _rootPulseMethod; private static float _intervalValue = 1.5f; private static float _tendrilValue = 1.5f; private static float _weakenValue = 3f; private static bool _triggerProcsValue = true; private static bool _firstPulseLogged; private static bool _fieldResolveFailedLogged; private static float _lastLogAt = -999f; private static float _storedDamage = float.NaN; private static bool _damageFieldFailedLogged; private static float _vanillaProcCoefficient = float.NaN; private static bool _procCoefficientLogged; private static bool _procCoefficientFieldFailedLogged; public static bool CatchOnlyNew; private static bool _skipContainsPatched; private static bool _catchLogged; private static bool _catchFailedLogged; private static bool _procTriggerLogged; private static bool _procTriggerFailedLogged; private static readonly ConditionalWeakTable<object, StrongBox<float>> _catchTimers = new ConditionalWeakTable<object, StrongBox<float>>(); public static bool IsEnabled { get { if (_enabled != null) { return _enabled.Value; } return true; } } public static void Init(ConfigFile cfg, ManualLogSource logger) { _log = logger; _enabled = cfg.Bind<bool>("TendrilGrowth", "Enabled", true, "是否让「触须生长」在花朵存续期间反复捆绑敌人。false = 完全保持原版(每个目标只被拉动、伤害一次)。"); _interval = cfg.Bind<float>("TendrilGrowth", "PulseInterval", 1.5f, "触须重新捆绑的间隔(秒)。原版是 duration / rootPulseCount。\n这个值会被写进 IL,改完需要重启游戏;填 0 或负数 = 沿用原版间隔。"); _tendrilLifetime = cfg.Bind<float>("TendrilGrowth", "TendrilLifetime", 1.5f, "每次生成的触须存活多久(秒)。原版是「一直到花朵消失」。\n这个值会被写进 IL,改完需要重启游戏;填 0 或负数 = 沿用原版(一直挂到花谢)。\n和上面的间隔填一样的数,就能做到「旧触须刚消失,新触须立刻捆上」。"); _damageMultiplier = cfg.Bind<float>("TendrilGrowth", "DamageMultiplier", 1f, "每次捆绑造成的伤害倍率。1 = 和原版那一次伤害一样(200%);\n想削弱持续输出就填 0.5 之类。这个值是运行期读取的。"); _weakenDuration = cfg.Bind<float>("TendrilGrowth", "WeakenDuration", 3f, "触须每次拉扯时给目标附加的「弱化」时间(秒)。\n弱化 = 雷克斯被动「自然毒素」那个减益(降低移动速度、护甲和伤害),\n也就是 M1 最后一个注射器附带的效果。填 0 或负数 = 不附加弱化。\n这个值会被写进 IL,改完需要重启游戏。"); _catchInterval = cfg.Bind<float>("TendrilGrowth", "CatchScanInterval", 0.15f, "额外扫描「溜进范围但还没被捆过」的敌人的间隔(秒)。\n原版只在每次脉冲那一刻做一次范围检测,移速快的敌人可能从两次脉冲之间\n直接冲过花朵范围、压根没被捆住。开着这个就会高频补扫,新目标一进范围\n立刻被拉住、吃一次伤害(已有的目标仍按上面的间隔结算,不会多打)。\n填 0 或负数 = 关掉补扫(回到原版那种只在脉冲时检测的行为)。这个值运行期读取。"); _procCoefficient = cfg.Bind<float>("TendrilGrowth", "ProcCoefficient", 1f, "捆绑伤害的触发系数(procCoefficient)。1 = 每次捆绑都能正常触发道具效果\n(尤克里里、导弹、吸血镰刀等),原版这个技能通常是 0(完全不触发)。\n填负数 = 沿用原版值。这个值运行期读取。"); _triggerProcs = cfg.Bind<bool>("TendrilGrowth", "TriggerItemProcs", true, "让捆绑伤害走官方的命中流程(TakeDamage 之后补一句 GlobalEventManager.OnHitEnemy),这样道具效果才会真的触发。\n原版花朵只调了 TakeDamage、从没调过 OnHitEnemy,所以哪怕触发系数是 1,\n三尖匕首、尤克里里之类的命中类道具也一次都不会触发。\nfalse = 保持原版(不触发任何命中类道具)。"); } public static string Summary() { if (_enabled == null || !_enabled.Value) { return " [触须生长:原版未改动]"; } string text = " [触须生长:每" + IntervalValue().ToString("0.##") + "s 重新捆绑一次,触须存活 " + TendrilLifetimeValue().ToString("0.##") + "s"; if (WeakenDurationValue() > 0f) { text = text + ",每次拉扯附加弱化 " + WeakenDurationValue().ToString("0.##") + "s"; } if (CatchIntervalValue() > 0f) { text += ",新目标进范围立刻捆住"; } float num = ProcCoefficientValue(); if (num >= 0f) { text = text + ",触发系数 " + num.ToString("0.##"); } return text + "]"; } public static void Patch(Harmony harmony) { //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Expected O, but got Unknown //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Expected O, but got Unknown //IL_01ff: Expected O, but got Unknown //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "触须生长:按配置保持原版,不挂任何补丁。"); return; } _intervalValue = IntervalValue(); _tendrilValue = TendrilLifetimeValue(); _weakenValue = WeakenDurationValue(); _triggerProcsValue = TriggerProcsValue(); _flowerType = AccessTools.TypeByName("EntityStates.Treebot.TreebotFlower+TreebotFlower2Projectile") ?? AccessTools.TypeByName("EntityStates.Treebot.TreebotFlower.TreebotFlower2Projectile"); if (_flowerType == null) { Log((LogLevel)2, "触须生长补丁失败:找不到状态类型 EntityStates.Treebot.TreebotFlower.TreebotFlower2Projectile(EntityStates.Treebot.TreebotFlower+TreebotFlower2Projectile)。"); return; } MethodInfo methodInfo = AccessTools.Method(_flowerType, "FixedUpdate", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(_flowerType, "RootPulse", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { Log((LogLevel)2, "触须生长补丁失败:取不到方法(FixedUpdate=" + ((methodInfo == null) ? "缺失" : "OK") + ",RootPulse=" + ((methodInfo2 == null) ? "缺失" : "OK") + ")。"); return; } bool num = _intervalValue > 0f; bool flag = _tendrilValue > 0f; if (num) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RexEntangle), "PulseIntervalTranspiler", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:" + _flowerType.Name + ".FixedUpdate <- PulseIntervalTranspiler(脉冲间隔写死为 " + _intervalValue.ToString("0.##") + "s)"); } else { Log((LogLevel)16, "脉冲间隔沿用原版 duration / rootPulseCount。"); } _rootPulseMethod = methodInfo2; HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(RexEntangle), "RootPulseTranspiler", (Type[])null, (Type[])null)); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.Method(typeof(RexEntangle), "RootPulsePrefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(RexEntangle), "RootPulsePostfix", (Type[])null, (Type[])null)), val, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:" + _flowerType.Name + ".RootPulse <- RootPulseTranspiler(已捆绑目标不再跳过" + (flag ? (",触须存活写死为 " + _tendrilValue.ToString("0.##") + "s") : ",触须存活沿用原版") + ")"); float num2 = CatchIntervalValue(); if (num2 > 0f && _skipContainsPatched) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RexEntangle), "CatchStragglersPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:" + _flowerType.Name + ".FixedUpdate <- CatchStragglersPostfix(每 " + num2.ToString("0.##") + "s 补扫一次新目标)"); } else if (num2 <= 0f) { Log((LogLevel)16, "补扫按配置关闭:只在每次脉冲那一刻检测范围。"); } else { Log((LogLevel)4, "补扫未启用:「已捆绑不再跳过」那段 IL 没改成功,开了补扫会变成高频伤害,已跳过。"); } } private static float IntervalValue() { if (_interval != null) { return _interval.Value; } return 1.5f; } private static float TendrilLifetimeValue() { if (_tendrilLifetime != null) { return _tendrilLifetime.Value; } return 1.5f; } private static float WeakenDurationValue() { if (_weakenDuration != null) { return _weakenDuration.Value; } return 3f; } private static float CatchIntervalValue() { if (_catchInterval != null) { return _catchInterval.Value; } return 0.15f; } private static float ProcCoefficientValue() { if (_procCoefficient != null) { return _procCoefficient.Value; } return 1f; } private static bool TriggerProcsValue() { if (_triggerProcs != null) { return _triggerProcs.Value; } return true; } public static IEnumerable<CodeInstruction> PulseIntervalTranspiler(IEnumerable<CodeInstruction> instructions) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); FieldInfo fieldInfo = Field("duration"); FieldInfo fieldInfo2 = Field("rootPulseCount"); if (fieldInfo == null || fieldInfo2 == null) { LogResolveFailed("PulseIntervalTranspiler", "duration=" + ((fieldInfo == null) ? "缺失" : "OK") + ",rootPulseCount=" + ((fieldInfo2 == null) ? "缺失" : "OK")); return list; } for (int i = 0; i + 2 < list.Count; i++) { if (list[i].opcode == OpCodes.Ldsfld && IsField(list[i].operand, fieldInfo) && list[i + 1].opcode == OpCodes.Ldsfld && IsField(list[i + 1].operand, fieldInfo2) && list[i + 2].opcode == OpCodes.Div) { list[i] = new CodeInstruction(OpCodes.Ldc_R4, (object)_intervalValue); list[i + 1] = new CodeInstruction(OpCodes.Nop, (object)null); list[i + 2] = new CodeInstruction(OpCodes.Nop, (object)null); Log((LogLevel)16, "脉冲间隔已改写为 " + _intervalValue.ToString("0.##") + "s(原版是 duration / rootPulseCount)。"); return list; } } Log((LogLevel)2, "脉冲间隔改写失败:FixedUpdate 里没找到 duration / rootPulseCount。"); return list; } public static IEnumerable<CodeInstruction> RootPulseTranspiler(IEnumerable<CodeInstruction> instructions) { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0833: Unknown result type (might be due to invalid IL or missing references) //IL_083d: Expected O, but got Unknown //IL_0849: Unknown result type (might be due to invalid IL or missing references) //IL_0853: Expected O, but got Unknown //IL_04a0: Unknown result type (might be due to invalid IL or missing references) //IL_04a6: Expected O, but got Unknown //IL_04af: Unknown result type (might be due to invalid IL or missing references) //IL_04b5: Expected O, but got Unknown //IL_04c6: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Expected O, but got Unknown //IL_04d5: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Expected O, but got Unknown //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Expected O, but got Unknown //IL_06cd: Unknown result type (might be due to invalid IL or missing references) //IL_06d3: Expected O, but got Unknown //IL_06f3: Unknown result type (might be due to invalid IL or missing references) //IL_06f9: Expected O, but got Unknown //IL_0702: Unknown result type (might be due to invalid IL or missing references) //IL_0708: Expected O, but got Unknown //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); FieldInfo fieldInfo = Field("rootedBodies"); FieldInfo fieldInfo2 = Field("duration"); if (fieldInfo != null) { FieldInfo fieldInfo3 = AccessTools.Field(typeof(RexEntangle), "CatchOnlyNew"); int num = 0; if (fieldInfo3 == null) { Log((LogLevel)2, "已捆绑判定改写失败:取不到字段 CatchOnlyNew。"); } else { for (int i = 3; i + 1 < list.Count; i++) { MethodInfo methodInfo = list[i].operand as MethodInfo; bool flag = list[i + 1].opcode == OpCodes.Brtrue || list[i + 1].opcode == OpCodes.Brtrue_S; if (list[i].opcode == OpCodes.Callvirt && methodInfo != null && methodInfo.Name == "Contains" && flag && PrecededBy(list, i, 3, OpCodes.Ldfld, fieldInfo)) { list.InsertRange(i + 1, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Ldsfld, (object)fieldInfo3), new CodeInstruction(OpCodes.And, (object)null) }); num++; i += 2; } } if (num > 0) { _skipContainsPatched = true; Log((LogLevel)16, "已捆绑判定已改写(" + num + " 处:跳过条件变成「已捆绑 且 本次是补抓调用」):正常脉冲时被捆住的目标每轮都会再拉一次、再吃一次伤害;补扫只处理新目标。"); } else { Log((LogLevel)2, "已捆绑判定改写失败:RootPulse 里没找到 rootedBodies.Contains + brtrue。"); } } } else { LogResolveFailed("RootPulseTranspiler", "rootedBodies=缺失"); } if (_tendrilValue > 0f) { if (fieldInfo2 != null) { bool flag2 = false; for (int j = 1; j + 4 < list.Count; j++) { MethodInfo methodInfo2 = list[j + 4].operand as MethodInfo; if (list[j].opcode == OpCodes.Ldsfld && IsField(list[j].operand, fieldInfo2) && list[j + 1].opcode == OpCodes.Ldarg_0 && list[j + 2].opcode == OpCodes.Call && list[j + 3].opcode == OpCodes.Sub && list[j + 4].opcode == OpCodes.Call && methodInfo2 != null && methodInfo2.Name == "Max" && list[j - 1].opcode == OpCodes.Ldc_R4) { list[j - 1] = new CodeInstruction(OpCodes.Ldc_R4, (object)_tendrilValue); for (int k = j; k <= j + 4; k++) { list[k] = new CodeInstruction(OpCodes.Nop, (object)null); } flag2 = true; Log((LogLevel)16, "触须存活时间已改写为 " + _tendrilValue.ToString("0.##") + "s(原版是一直挂到花朵消失)。"); break; } } if (!flag2) { Log((LogLevel)2, "触须存活时间改写失败:RootPulse 里没找到 Mathf.Max(0.2, duration - fixedAge)。"); } } else { LogResolveFailed("RootPulseTranspiler(触须存活)", "duration=缺失"); } } if (_weakenValue > 0f) { FieldInfo fieldInfo4 = WeakBuffField(); MethodInfo methodInfo3 = AddTimedBuffMethod(fieldInfo4); if (fieldInfo4 == null || methodInfo3 == null) { Log((LogLevel)2, "弱化附加失败:取不到 RoR2Content.Buffs.Weak(" + ((fieldInfo4 == null) ? "缺失" : "OK") + ")或 CharacterBody.AddTimedBuff(" + ((methodInfo3 == null) ? "缺失" : "OK") + ")。"); } else { bool flag3 = false; for (int l = 1; l + 1 < list.Count; l++) { if (flag3) { break; } FieldInfo fieldInfo5 = list[l].operand as FieldInfo; if (!(list[l].opcode != OpCodes.Ldsfld) && !(fieldInfo5 == null) && !(fieldInfo5.Name != "Entangle") && !(list[l + 1].opcode != OpCodes.Callvirt)) { CodeInstruction val = list[l - 1]; if (IsLoadLocal(val.opcode)) { list.InsertRange(l + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[4] { new CodeInstruction(val.opcode, val.operand), new CodeInstruction(OpCodes.Ldsfld, (object)fieldInfo4), new CodeInstruction(OpCodes.Ldc_R4, (object)_weakenValue), new CodeInstruction(OpCodes.Callvirt, (object)methodInfo3) }); flag3 = true; Log((LogLevel)16, "弱化附加已插入:每次拉扯给目标 " + _weakenValue.ToString("0.##") + "s 弱化(RoR2Content.Buffs.Weak)。"); } } } if (!flag3) { Log((LogLevel)2, "弱化附加失败:RootPulse 里没找到 AddBuff(Entangle) 那一段。"); } } } if (_triggerProcsValue) { MethodInfo methodInfo4 = AccessTools.Method(typeof(RexEntangle), "TriggerOnHit", (Type[])null, (Type[])null); FieldInfo fieldInfo6 = AccessTools.Field(typeof(HurtBox), "healthComponent"); if (methodInfo4 == null || fieldInfo6 == null) { Log((LogLevel)2, "命中流程接入失败:取不到 TriggerOnHit(" + ((methodInfo4 == null) ? "缺失" : "OK") + ")或 HurtBox.healthComponent(" + ((fieldInfo6 == null) ? "缺失" : "OK") + ")。"); } else { bool flag4 = false; for (int m = 3; m < list.Count; m++) { if (flag4) { break; } MethodInfo methodInfo5 = list[m].operand as MethodInfo; if (!(list[m].opcode != OpCodes.Callvirt) && !(methodInfo5 == null) && !(methodInfo5.Name != "TakeDamage") && IsLoadLocal(list[m - 1].opcode) && !(list[m - 2].opcode != OpCodes.Ldfld) && IsField(list[m - 2].operand, fieldInfo6) && IsLoadLocal(list[m - 3].opcode)) { list.InsertRange(m + 1, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3] { new CodeInstruction(list[m - 1].opcode, list[m - 1].operand), new CodeInstruction(list[m - 3].opcode, list[m - 3].operand), new CodeInstruction(OpCodes.Call, (object)methodInfo4) }); flag4 = true; Log((LogLevel)16, "命中流程已接入:TakeDamage 之后补一句 GlobalEventManager.OnHitEnemy(照抄官方 FireSonicBoom 的写法),现在捆绑伤害能正常触发三尖匕首、尤克里里之类的道具了。"); } } if (!flag4) { Log((LogLevel)2, "命中流程接入失败:RootPulse 里没找到 hurtBox.healthComponent.TakeDamage。"); } } } FieldInfo fieldInfo7 = Field("procCoefficient"); MethodInfo methodInfo6 = AccessTools.Method(typeof(RexEntangle), "CurrentProcCoefficient", (Type[])null, (Type[])null); if (fieldInfo7 == null || methodInfo6 == null) { Log((LogLevel)2, "触发系数 IL 改写失败:取不到字段 procCoefficient(" + ((fieldInfo7 == null) ? "缺失" : "OK") + ")或方法 CurrentProcCoefficient(" + ((methodInfo6 == null) ? "缺失" : "OK") + ")。"); } else { bool flag5 = false; for (int n = 0; n + 1 < list.Count; n++) { if (flag5) { break; } if (IsLoadArg0(list[n]) && !(list[n + 1].opcode != OpCodes.Ldfld) && IsField(list[n + 1].operand, fieldInfo7)) { list[n] = new CodeInstruction(OpCodes.Nop, (object)null); list[n + 1] = new CodeInstruction(OpCodes.Call, (object)methodInfo6); flag5 = true; Log((LogLevel)16, "触发系数 IL 已改写:DamageInfo.procCoefficient 现在直接取配置值(当前 " + CurrentProcCoefficient().ToString("0.##") + "),每次捆绑都能触发道具效果;原版字段值见首次扎根那行日志。"); } } if (!flag5) { Log((LogLevel)2, "触发系数 IL 改写失败:RootPulse 里没找到 ldarg.0 / ldfld procCoefficient。"); } } return list; } public static void CatchStragglersPostfix(object __instance) { try { if (!IsEnabled || __instance == null || _rootPulseMethod == null) { return; } float interval = CatchIntervalValue(); if (interval <= 0f) { return; } float num = Time.fixedDeltaTime; if (num <= 0f) { num = Time.deltaTime; } StrongBox<float> value = _catchTimers.GetValue(__instance, (object k) => new StrongBox<float>(interval)); value.Value -= num; if (!(value.Value > 0f)) { value.Value = interval; CatchOnlyNew = true; try { _rootPulseMethod.Invoke(__instance, null); } finally { CatchOnlyNew = false; } if (!_catchLogged) { _catchLogged = true; Log((LogLevel)16, "补扫已启用:每 " + interval.ToString("0.##") + "s 额外检测一次范围内的新目标(持续伤害仍按每 " + ((_intervalValue > 0f) ? _intervalValue.ToString("0.##") : "原版") + "s 结算)。"); } } } catch (Exception ex) { if (!_catchFailedLogged) { _catchFailedLogged = true; Log((LogLevel)4, "补扫调用异常(已忽略,之后不再提示):" + ex.GetType().Name + " " + ex.Message); } } } private static void ApplyProcCoefficient(object instance) { float num = ProcCoefficientValue(); if (num < 0f || instance == null) { return; } FieldInfo fieldInfo = Field("procCoefficient"); if (fieldInfo == null) { if (!_procCoefficientFieldFailedLogged) { _procCoefficientFieldFailedLogged = true; Log((LogLevel)2, "触发系数未生效:取不到字段 procCoefficient。"); } return; } float num2 = Convert.ToSingle(fieldInfo.GetValue(instance)); if (float.IsNaN(_vanillaProcCoefficient)) { _vanillaProcCoefficient = num2; } if (!(Mathf.Abs(num2 - num) < 0.0001f)) { fieldInfo.SetValue(instance, num); if (!_procCoefficientLogged) { _procCoefficientLogged = true; Log((LogLevel)16, "触发系数(实例字段覆写)已设为 " + num.ToString("0.##") + "(原版 " + _vanillaProcCoefficient.ToString("0.##") + ")。"); } } } public static float CurrentProcCoefficient() { float num = ProcCoefficientValue(); if (num < 0f) { if (!float.IsNaN(_vanillaProcCoefficient)) { return _vanillaProcCoefficient; } return 0f; } return num; } public static void RootPulsePrefix(object __instance) { _storedDamage = float.NaN; try { ApplyProcCoefficient(__instance); } catch (Exception ex) { Log((LogLevel)4, "触发系数(实例字段覆写)异常,IL 改写那路仍会生效:" + ex.GetType().Name + " " + ex.Message); } try { if (_enabled == null || !_enabled.Value || __instance == null || _damageMultiplier == null) { return; } float value = _damageMultiplier.Value; if (value <= 0f || Mathf.Abs(value - 1f) < 0.001f) { return; } FieldInfo fieldInfo = Field("damage"); if (fieldInfo == null) { if (!_damageFieldFailedLogged) { _damageFieldFailedLogged = true; Log((LogLevel)2, "伤害倍率未生效:取不到字段 damage。"); } } else { float num = Convert.ToSingle(fieldInfo.GetValue(__instance)); fieldInfo.SetValue(__instance, num * value); _storedDamage = num; } } catch (Exception ex2) { Log((LogLevel)4, "伤害倍率改写异常(已忽略):" + ex2.GetType().Name + " " + ex2.Message); } } public static void RootPulsePostfix(object __instance) { try { if (!float.IsNaN(_storedDamage) && __instance != null) { FieldInfo fieldInfo = Field("damage"); if (fieldInfo != null) { fieldInfo.SetValue(__instance, _storedDamage); } } } catch (Exception ex) { Log((LogLevel)4, "伤害倍率还原异常(已忽略):" + ex.GetType().Name + " " + ex.Message); } finally { _storedDamage = float.NaN; } if (_firstPulseLogged) { return; } _firstPulseLogged = true; try { FieldInfo fieldInfo2 = Field("duration"); FieldInfo fieldInfo3 = Field("rootPulseCount"); if (!(fieldInfo2 == null) && !(fieldInfo3 == null)) { float num = Convert.ToSingle(fieldInfo2.GetValue(null)); float num2 = Convert.ToSingle(fieldInfo3.GetValue(null)); float num3 = ((num2 > 0f) ? (num / num2) : num); float num4 = ((_intervalValue > 0f) ? _intervalValue : num3); float num5 = ((_tendrilValue > 0f) ? _tendrilValue : num3); string text = (float.IsNaN(_vanillaProcCoefficient) ? "未知" : _vanillaProcCoefficient.ToString("0.##")); Log((LogLevel)16, "触须生长已生效:花朵存活 " + num.ToString("0.##") + "s,原版脉冲间隔 " + num3.ToString("0.##") + "s → 现在 " + num4.ToString("0.##") + "s;触须存活 " + num5.ToString("0.##") + "s;触发系数 " + text + " → " + ((ProcCoefficientValue() < 0f) ? "沿用原版" : ProcCoefficientValue().ToString("0.##")) + "。"); LogFlowerSnapshot(__instance); } } catch (Exception ex2) { Log((LogLevel)4, "读取原版节奏失败(已忽略):" + ex2.GetType().Name + " " + ex2.Message); } } private static FieldInfo Field(string name) { if (_flowerType == null) { return null; } return AccessTools.Field(_flowerType, name); } private static bool IsField(object operand, FieldInfo field) { FieldInfo fieldInfo = operand as FieldInfo; if (fieldInfo == null || field == null) { return false; } if (!(fieldInfo == field) && !fieldInfo.Equals(field)) { if (fieldInfo.Name == field.Name) { return fieldInfo.DeclaringType == field.DeclaringType; } return false; } return true; } private static bool PrecededBy(List<CodeInstruction> list, int i, int back, OpCode opcode, FieldInfo field) { for (int j = Mathf.Max(0, i - back); j < i; j++) { if (list[j].opcode == opcode && IsField(list[j].operand, field)) { return true; } } return false; } public static void TriggerOnHit(DamageInfo damageInfo, HurtBox hurtBox) { try { GlobalEventManager instance = GlobalEventManager.instance; if ((Object)(object)instance == (Object)null || (Object)(object)hurtBox == (Object)null) { return; } HealthComponent healthComponent = hurtBox.healthComponent; if (!((Object)(object)healthComponent == (Object)null)) { instance.OnHitEnemy(damageInfo, ((Component)healthComponent).gameObject); if (!_procTriggerLogged) { _procTriggerLogged = true; Log((LogLevel)16, "捆绑伤害已接入命中流程:触发系数 " + damageInfo.procCoefficient.ToString("0.##") + ",attacker=" + (((Object)(object)damageInfo.attacker == (Object)null) ? "null" : ((Object)damageInfo.attacker).name) + ",victim=" + ((Object)((Component)healthComponent).gameObject).name); } } } catch (Exception ex) { if (!_procTriggerFailedLogged) { _procTriggerFailedLogged = true; Log((LogLevel)4, "命中流程调用异常(已忽略,之后不再提示):" + ex.GetType().Name + " " + ex.Message); } } } private static void LogFlowerSnapshot(object instance) { if (instance == null) { return; } try { string text = "缺失"; FieldInfo fieldInfo = Field("owner"); if (fieldInfo != null) { object? value = fieldInfo.GetValue(instance); Object val = (Object)((value is Object) ? value : null); text = ((val == (Object)null) ? "null" : (val.name + "(" + ((object)val).GetType().Name + ")")); } string text2 = "缺失"; FieldInfo fieldInfo2 = Field("procChainMask"); if (fieldInfo2 != null) { object value2 = fieldInfo2.GetValue(instance); text2 = ((value2 == null) ? "null" : value2.ToString()); } string text3 = "缺失"; FieldInfo fieldInfo3 = Field("damageType"); if (fieldInfo3 != null) { object value3 = fieldInfo3.GetValue(instance); text3 = ((value3 == null) ? "null" : value3.ToString()); } string text4 = "缺失"; FieldInfo fieldInfo4 = Field("procCoefficient"); if (fieldInfo4 != null) { text4 = Convert.ToSingle(fieldInfo4.GetValue(instance)).ToString("0.###"); } Log((LogLevel)16, "花朵字段快照:procCoefficient=" + text4 + ",procChainMask=" + text2 + ",damageType=" + text3 + ",owner=" + text + "。"); } catch (Exception ex) { Log((LogLevel)4, "花朵字段快照失败(已忽略):" + ex.GetType().Name + " " + ex.Message); } } private static FieldInfo WeakBuffField() { Type type = typeof(CharacterBody).Assembly.GetType("RoR2.RoR2Content+Buffs"); if (type == null) { type = AccessTools.TypeByName("RoR2.RoR2Content+Buffs"); } if (!(type == null)) { return AccessTools.Field(type, "Weak"); } return null; } private static MethodInfo AddTimedBuffMethod(FieldInfo weakField) { Type type = ((weakField != null) ? weakField.FieldType : AccessTools.TypeByName("RoR2.BuffIndex")); if (type == null) { return null; } MethodInfo methodInfo = AccessTools.Method(typeof(CharacterBody), "AddTimedBuff", new Type[2] { type, typeof(float) }, (Type[])null); if (methodInfo != null) { return methodInfo; } MethodInfo[] methods = typeof(CharacterBody).GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "AddTimedBuff")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 2 && parameters[0].ParameterType == type && parameters[1].ParameterType == typeof(float)) { return methodInfo2; } } } return null; } private static bool IsLoadArg0(CodeInstruction ins) { if (ins.opcode == OpCodes.Ldarg_0) { return true; } if (ins.opcode != OpCodes.Ldarg && ins.opcode != OpCodes.Ldarg_S) { return false; } try { return Convert.ToInt32(ins.operand) == 0; } catch (Exception) { return false; } } private static bool IsLoadLocal(OpCode opcode) { if (!(opcode == OpCodes.Ldloc) && !(opcode == OpCodes.Ldloc_S) && !(opcode == OpCodes.Ldloc_0) && !(opcode == OpCodes.Ldloc_1) && !(opcode == OpCodes.Ldloc_2)) { return opcode == OpCodes.Ldloc_3; } return true; } private static void LogResolveFailed(string where, string detail) { if (!_fieldResolveFailedLogged) { _fieldResolveFailedLogged = true; Log((LogLevel)2, "触须生长未生效:" + where + " 取不到字段(" + detail + ")。"); } } private static void Log(LogLevel level, string message) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (_log != null) { _log.Log(level, (object)message); } } private static void LogThrottled(string message) { if (!(Time.time - _lastLogAt < 3f)) { _lastLogAt = Time.time; Log((LogLevel)16, message); } } } public static class RexFruitHarvest { private const string BuffAssetName = "SparkleRexFruitHarvest"; private const string RexBodyNameToken = "TREEBOT_BODY_NAME"; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _healthPercentPerStack; private static ConfigEntry<int> _maxStacks; private static ConfigEntry<bool> _onlyRex; private static ConfigEntry<bool> _onlyHarvestFruit; private static ConfigEntry<string> _fruitNameFilter; private static ManualLogSource _log; private static BuffDef _buffDef; private static BuffIndex _buffIndex = (BuffIndex)(-1); private static bool _buffIndexTried; private static FieldInfo _aliveField; private static FieldInfo _baseObjectField; private static FieldInfo _teamFilterField; private static bool _fieldsTried; private static MethodInfo _maxHealthSetter; private static FieldInfo _maxHealthField; private static bool _maxHealthWriterTried; private static bool _iconResolved; private static bool _grantedLogged; private static bool _statsLogged; private static readonly ConditionalWeakTable<HealthPickup, StrongBox<bool>> AlreadyGranted = new ConditionalWeakTable<HealthPickup, StrongBox<bool>>(); private static readonly HashSet<string> _unknownFruitNames = new HashSet<string>(); public static bool IsEnabled { get { if (_enabled != null) { return _enabled.Value; } return true; } } public static void Init(ConfigFile cfg, ManualLogSource logger) { _log = logger; _enabled = cfg.Bind<bool>("Harvest", "Enabled", true, "是否启用「拾取治疗果实获得一层生机」:雷克斯捡起「命令:收割」掉落的果实后,\n获得一层可叠加的 buff,每层提升 1% 最大生命值(普通 buff,下一关清空)。"); _healthPercentPerStack = cfg.Bind<float>("Harvest", "HealthPercentPerStack", 1f, "每层 buff 提升的最大生命值百分比(1 = 每层 +1%)。\n和游戏里的「珍珠」一样是按<b>当前最大生命值</b>的比例加成,\n所以等级越高、最大生命值越大,每层加的血也越多。这个值是运行期读取的。"); _maxStacks = cfg.Bind<int>("Harvest", "MaxStacks", 0, "层数上限。0 = 不限制。这个值是运行期读取的。"); _onlyRex = cfg.Bind<bool>("Harvest", "OnlyRex", true, "只有雷克斯(Treebot)拾取果实才给 buff。false = 任何角色捡到都给。"); _onlyHarvestFruit = cfg.Bind<bool>("Harvest", "OnlyHarvestFruit", true, "只认「命令:收割」掉落的果实(按果实对象名匹配)。\nfalse = 任何带 HealthPickup 的治疗拾取物都算(比如星际书桌植物种的果实)。"); _fruitNameFilter = cfg.Bind<string>("Harvest", "FruitObjectName", "TreebotFruitPack", "果实对象名关键字(不区分大小写)。游戏改版导致名字变了可以在这里改,不用重新编译。"); TryCreateBuff(); AddLanguage(); } public static string Summary() { if (!IsEnabled) { return " [果实:原版未改动]"; } if ((Object)(object)_buffDef == (Object)null) { return " [果实:buff 注册失败]"; } return " [果实:拾取治疗果实 +" + HealthPercentPerStack().ToString("0.##") + "% 最大生命值/层(随等级成长)]"; } private static float HealthPercentPerStack() { if (_healthPercentPerStack == null) { return 1f; } return _healthPercentPerStack.Value; } private static int MaxStacksValue() { if (_maxStacks == null) { return 0; } return _maxStacks.Value; } private static bool OnlyRexValue() { if (_onlyRex != null) { return _onlyRex.Value; } return true; } private static bool OnlyHarvestFruitValue() { if (_onlyHarvestFruit != null) { return _onlyHarvestFruit.Value; } return true; } private static void TryCreateBuff() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) try { BuffDef val = ScriptableObject.CreateInstance<BuffDef>(); ((Object)val).name = "SparkleRexFruitHarvest"; val.canStack = true; val.isDebuff = false; val.isHidden = false; val.isCooldown = false; val.isDOT = false; val.buffColor = new Color(0.42f, 0.88f, 0.34f, 1f); if (!ContentAddition.AddBuffDef(val)) { Log((LogLevel)2, "果实生机:R2API 拒绝了自定义 buff(AddBuffDef 返回 false),本功能不会生效。"); return; } _buffDef = val; Log((LogLevel)16, "果实生机:自定义 buff 已注册(SparkleRexFruitHarvest)。"); } catch (Exception ex) { Log((LogLevel)2, "果实生机:注册 buff 失败 —— " + ex.GetType().Name + " " + ex.Message + "。请确认安装了 R2API 的 ContentManagement 模块。"); _buffDef = null; } } private static void ResolveIcon() { if (_iconResolved || (Object)(object)_buffDef == (Object)null) { return; } _iconResolved = true; try { FieldInfo fieldInfo = AccessTools.Field(typeof(BuffCatalog), "buffDefs"); BuffDef[] array = ((fieldInfo != null) ? (fieldInfo.GetValue(null) as BuffDef[]) : null); if (array == null) { return; } foreach (BuffDef val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.iconSprite == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && ((Object)val).name.IndexOf("fruiting", StringComparison.OrdinalIgnoreCase) >= 0) { _buffDef.iconSprite = val.iconSprite; Log((LogLevel)16, "果实生机:buff 图标已借用 " + ((Object)val).name + " 的图标。"); break; } } } catch (Exception ex) { Log((LogLevel)4, "果实生机:取图标失败(不影响数值)—— " + ex.Message); } } private static void AddLanguage() { string text = "果实生机"; string text2 = "每层使<style=cIsHealing>最大生命值</style>提升 " + HealthPercentPerStack().ToString("0.##") + "%(按当前最大生命值计算,随等级成长)。"; string text3 = "Harvest Vitality"; string text4 = "Increases <style=cIsHealing>maximum health</style> by " + HealthPercentPerStack().ToString("0.##") + "% per stack."; string[] array = new string[3] { "BUFF_SPARKLEREXFRUITHARVEST_NAME", "SparkleRexFruitHarvest_NAME", "SparkleRexFruitHarvest" }; foreach (string obj in array) { LanguageAPI.Add(obj, text, "zh-CN"); LanguageAPI.Add(obj, text3, "en"); } array = new string[2] { "BUFF_SPARKLEREXFRUITHARVEST_DESCRIPTION", "SparkleRexFruitHarvest_DESCRIPTION" }; foreach (string obj2 in array) { LanguageAPI.Add(obj2, text2, "zh-CN"); LanguageAPI.Add(obj2, text4, "en"); } } private static bool EnsureBuffIndex() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if ((int)_buffIndex != -1) { return true; } if ((Object)(object)_buffDef == (Object)null) { return false; } int num = Convert.ToInt32(_buffDef.buffIndex); if (num < 0) { object obj = BuffCatalog.FindBuffIndex(((Object)_buffDef).name); num = ((obj == null) ? (-1) : Convert.ToInt32(obj)); } if (num < 0) { if (!_buffIndexTried) { _buffIndexTried = true; Log((LogLevel)4, "果实生机:还没拿到自定义 buff 的序号(BuffCatalog 未初始化?),稍后自动重试。"); } return false; } _buffIndex = (BuffIndex)Enum.ToObject(typeof(BuffIndex), num); Log((LogLevel)16, "果实生机:buff 序号已定位(" + num + ")。"); return true; } public static void Patch(Harmony harmony) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "果实生机:按配置保持原版,不挂任何补丁。"); return; } if ((Object)(object)_buffDef == (Object)null) { Log((LogLevel)4, "果实生机:buff 没注册成功,不挂补丁。"); return; } MethodInfo methodInfo = AccessTools.Method(typeof(HealthPickup), "OnTriggerStay", (Type[])null, (Type[])null); if (methodInfo == null) { Log((LogLevel)2, "果实生机补丁失败:取不到 HealthPickup.OnTriggerStay。"); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(RexFruitHarvest), "HealthPickupPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:HealthPickup.OnTriggerStay <- HealthPickupPrefix(拾取果实发一层 buff)"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(CharacterBody), "RecalculateStats", (Type[])null, (Type[])null); if (methodInfo2 == null) { Log((LogLevel)2, "果实生机补丁失败:取不到 CharacterBody.RecalculateStats。"); return; } harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RexFruitHarvest), "RecalculateStatsPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:CharacterBody.RecalculateStats <- RecalculateStatsPostfix(按层数放大最终 maxHealth,并补上差值)"); } public static void HealthPickupPrefix(HealthPickup __instance, Collider other) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) try { if (!IsEnabled || (Object)(object)__instance == (Object)null || (Object)(object)other == (Object)null || !NetworkServer.active || !IsAlive(__instance)) { return; } GameObject gameObject = ((Component)other).gameObject; if ((Object)(object)gameObject == (Object)null) { return; } int num = FruitTeamIndex(__instance); int num2 = Convert.ToInt32(TeamComponent.GetObjectTeam(gameObject)); if (num >= 0 && num2 != num) { return; } CharacterBody component = ((Component)other).GetComponent<CharacterBody>(); if ((Object)(object)component == (Object)null) { return; } if (OnlyHarvestFruitValue() && !IsHarvestFruit(__instance)) { LogUnknownFruit(FruitName(__instance)); } else { if ((OnlyRexValue() && !IsRex(component)) || AlreadyGranted.TryGetValue(__instance, out var _)) { return; } AlreadyGranted.Add(__instance, new StrongBox<bool>(value: true)); ResolveIcon(); if (!EnsureBuffIndex()) { return; } int buffCount = component.GetBuffCount(_buffIndex); int num3 = MaxStacksValue(); if (num3 <= 0 || buffCount < num3) { component.AddBuff(_buffIndex); if (!_grantedLogged) { _grantedLogged = true; Log((LogLevel)16, "果实生机已生效:" + ((Object)component).name + " 拾取 " + FruitName(__instance) + ",第 " + (buffCount + 1) + " 层 —— 最大生命值 +" + (HealthPercentPerStack() * (float)(buffCount + 1)).ToString("0.##") + "%"); } } } } catch (Exception ex) { Log((LogLevel)4, "果实生机:拾取判定异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } public static void RecalculateStatsPostfix(CharacterBody __instance) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) try { if (!IsEnabled || (Object)(object)__instance == (Object)null || !EnsureBuffIndex()) { return; } int num = __instance.GetBuffCount(_buffIndex); if (num <= 0) { return; } int num2 = MaxStacksValue(); if (num2 > 0 && num > num2) { num = num2; } float maxHealth = __instance.maxHealth; float num3 = maxHealth * (1f + HealthPercentPerStack() / 100f * (float)num); if (num3 <= maxHealth) { return; } SetMaxHealth(__instance, num3); HealthComponent healthComponent = __instance.healthComponent; if (!((Object)(object)healthComponent == (Object)null)) { float num4 = num3 - maxHealth; if (num4 > 0f) { healthComponent.Heal(num4, default(ProcChainMask), false); } else if (healthComponent.health > num3) { healthComponent.Networkhealth = Mathf.Max(healthComponent.health + num4, num3); } if (!_statsLogged) { _statsLogged = true; Log((LogLevel)16, "果实生机:属性已加成 —— " + ((Object)__instance).name + " 等级 " + __instance.level + ",层数 " + num + ",最大生命值 " + maxHealth.ToString("0.#") + " → " + num3.ToString("0.#")); } } } catch (Exception ex) { Log((LogLevel)4, "果实生机:属性结算异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static bool SetMaxHealth(CharacterBody body, float value) { EnsureMaxHealthWriter(); if (_maxHealthSetter != null) { _maxHealthSetter.Invoke(body, new object[1] { value }); return true; } if (_maxHealthField != null) { _maxHealthField.SetValue(body, value); return true; } return false; } private static void EnsureMaxHealthWriter() { if (!_maxHealthWriterTried) { _maxHealthWriterTried = true; _maxHealthField = AccessTools.Field(typeof(CharacterBody), "<maxHealth>k__BackingField"); if (_maxHealthField == null) { _maxHealthField = AccessTools.Field(typeof(CharacterBody), "maxHealth"); } _maxHealthSetter = typeof(CharacterBody).GetProperty("maxHealth", BindingFlags.Instance | BindingFlags.Public).GetSetMethod(nonPublic: true); if (_maxHealthField == null && _maxHealthSetter == null) { Log((LogLevel)2, "果实生机:取不到 CharacterBody.maxHealth 的写入方式,生命值加成不会生效。"); } } } private static void EnsureFields() { if (!_fieldsTried) { _fieldsTried = true; _aliveField = AccessTools.Field(typeof(HealthPickup), "alive"); _baseObjectField = AccessTools.Field(typeof(HealthPickup), "baseObject"); _teamFilterField = AccessTools.Field(typeof(HealthPickup), "teamFilter"); } } private static bool IsAlive(HealthPickup pickup) { EnsureFields(); if (_aliveField == null) { return true; } object value = _aliveField.GetValue(pickup); if (value != null) { return Convert.ToBoolean(value); } return true; } private static int FruitTeamIndex(HealthPickup pickup) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) EnsureFields(); if (_teamFilterField == null) { return -1; } object? value = _teamFilterField.GetValue(pickup); TeamFilter val = (TeamFilter)((value is TeamFilter) ? value : null); if ((Object)(object)val == (Object)null) { return -1; } return Convert.ToInt32(val.teamIndex); } private static string FruitName(HealthPickup pickup) { EnsureFields(); GameObject val = (GameObject)((_baseObjectField != null) ? /*isinst with value type is only supported in some contexts*/: null); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(((Object)val).name)) { return ((Object)val).name; } if (!((Object)(object)((Component)pickup).gameObject != (Object)null)) { return "(未知)"; } return ((Object)((Component)pickup).gameObject).name; } private static bool IsHarvestFruit(HealthPickup pickup) { string text = ((_fruitNameFilter != null) ? _fruitNameFilter.Value : "TreebotFruitPack"); if (string.IsNullOrEmpty(text)) { return true; } if (!NameMatches(FruitName(pickup), text)) { return NameMatches(((Object)(object)((Component)pickup).gameObject != (Object)null) ? ((Object)((Component)pickup).gameObject).name : null, text); } return true; } private static bool NameMatches(string name, string filter) { if (!string.IsNullOrEmpty(name)) { return name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static void LogUnknownFruit(string name) { if (!string.IsNullOrEmpty(name) && !_unknownFruitNames.Contains(name)) { _unknownFruitNames.Add(name); Log((LogLevel)16, "果实生机:跳过了一个治疗拾取物(对象名 " + name + ")—— 如果它就是「命令:收割」掉落的果实,请把 Harvest/FruitObjectName 改成 " + name + " 的一部分。"); } } private static bool IsRex(CharacterBody body) { if (!string.IsNullOrEmpty(body.baseNameToken) && string.Equals(body.baseNameToken, "TREEBOT_BODY_NAME", StringComparison.Ordinal)) { return true; } if (!string.IsNullOrEmpty(((Object)body).name)) { return ((Object)body).name.IndexOf("TreebotBody", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static void Log(LogLevel level, string message) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (_log != null) { _log.Log(level, (object)message); } } } public static class RexSelfHarm { private const int Sentinel = int.MaxValue; private static ConfigEntry<bool> _enabled; private static ConfigEntry<bool> _skipCurse; private static ConfigEntry<bool> _onlyTreebot; private static ManualLogSource _log; private static FieldInfo _comboDamageType; private static bool _firstSuppressLogged; private static bool IsEnabled { get { if (_enabled != null) { return _enabled.Value; } return true; } } public static void Init(ConfigFile cfg, ManualLogSource logger) { _log = logger; _enabled = cfg.Bind<bool>("SelfHarm", "Enabled", true, "是否处理技能自伤在日食难度下的副作用。false = 完全保持原版。"); _skipCurse = cfg.Bind<bool>("SelfHarm", "SkipEclipseCurse", true, "技能自伤是否跳过日食 8 的「永久诅咒」(每次受伤都会永久降低一点生命值上限)。\n扣的血一分不少,只是不再叠这个层数。被真正的敌人打到时照常叠。\n这个值是运行期读取的。"); _onlyTreebot = cfg.Bind<bool>("SelfHarm", "OnlyForTreebot", false, "只处理雷克斯的自伤。false(默认)= 任何角色符合特征的自伤都不叠诅咒,\n和 CurseCatcher 的 Self Damage=false 是一个语义。这个值是运行期读取的。"); } public static string Summary() { if (!IsEnabled) { return " [自伤:原版未改动]"; } if (SkipCurseValue()) { if (_onlyTreebot == null || !_onlyTreebot.Value) { return " [自伤:技能自伤不叠日食诅咒]"; } return " [自伤:雷克斯技能不叠日食诅咒]"; } return " [自伤:照常叠日食诅咒]"; } private static bool SkipCurseValue() { if (_skipCurse != null) { return _skipCurse.Value; } return true; } private static bool OnlyTreebotValue() { if (_onlyTreebot != null) { return _onlyTreebot.Value; } return false; } public static void Patch(Harmony harmony) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "自伤处理:按配置保持原版,不挂任何补丁。"); return; } if (!SkipCurseValue()) { Log((LogLevel)16, "自伤处理:按配置保留日食诅咒,不挂补丁。"); return; } MethodInfo methodInfo = AccessTools.Method(typeof(HealthComponent), "TakeDamageProcess", (Type[])null, (Type[])null); if (methodInfo == null) { Log((LogLevel)2, "自伤补丁失败:取不到 HealthComponent.TakeDamageProcess。"); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RexSelfHarm), "TakeDamageProcessTranspiler", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:HealthComponent.TakeDamageProcess <- TakeDamageProcessTranspiler(自伤跳过日食诅咒)"); } public static IEnumerable<CodeInstruction> TakeDamageProcessTranspiler(IEnumerable<CodeInstruction> instructions) { //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Expected O, but got Unknown //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); MethodInfo methodInfo = AccessTools.Method(typeof(RexSelfHarm), "TeamComparand", (Type[])null, (Type[])null); MethodInfo methodInfo2 = FindGetter("get_teamComponent"); MethodInfo methodInfo3 = FindGetter("get_teamIndex"); FieldInfo fieldInfo = AccessTools.Field(typeof(HealthComponent), "body"); if (methodInfo2 == null || methodInfo3 == null || fieldInfo == null) { Log((LogLevel)2, "日食诅咒改写失败:取不到 teamComponent / teamIndex / body 引用。"); return list; } int num = 0; for (int i = 0; i + 3 < list.Count; i++) { if (IsCall(list[i], methodInfo2) && IsCall(list[i + 1], methodInfo3) && !(list[i + 2].opcode != OpCodes.Ldc_I4_1)) { OpCode opcode = list[i + 3].opcode; if (!(opcode != OpCodes.Bne_Un) || !(opcode != OpCodes.Bne_Un_S)) { List<CodeInstruction> list2 = new List<CodeInstruction> { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo), new CodeInstruction(OpCodes.Ldarg_1, (object)null), new CodeInstruction(OpCodes.Call, (object)methodInfo) }; list[i + 2] = list2[0]; list.InsertRange(i + 3, list2.GetRange(1, list2.Count - 1)); num++; i += 3; } } } if (num > 0) { Log((LogLevel)16, "日食诅咒已改写(" + num + " 处 TeamIndex.Player 比较 → TeamComparand):符合条件的技术自伤照常扣血,但不再叠「永久诅咒」。"); } else { Log((LogLevel)2, "日食诅咒改写失败:TakeDamageProcess 里没找到 get_teamComponent / get_teamIndex / ldc.i4.1 / bne.un 这一串。"); } return list; } public static int TeamComparand(CharacterBody body, DamageInfo damageInfo) { try { if (!IsEnabled || !SkipCurseValue() || (Object)(object)body == (Object)null || damageInfo == null) { return 1; } if (!IsSelfHarm(damageInfo)) { return 1; } if (OnlyTreebotValue() && ((Object)body).name == null) { return 1; } if (OnlyTreebotValue() && !((Object)body).name.StartsWith("TreebotBody")) { return 1; } if (!_firstSuppressLogged) { _firstSuppressLogged = true; Log((LogLevel)16, "技能自伤已识别(damage=" + damageInfo.damage.ToString("0.##") + ",body=" + ((Object)body).name + "):扣血保留,但不会再叠日食的「永久诅咒」。"); } return int.MaxValue; } catch (Exception ex) { Log((LogLevel)4, "自伤判定异常(已忽略):" + ex.GetType().Name + " " + ex.Message); return 1; } } private static bool IsSelfHarm(DamageInfo info) { if ((Object)(object)info.attacker != (Object)null || (Object)(object)info.inflictor != (Object)null) { return false; } if (Math.Abs(info.procCoefficient) > 0.0001f) { return false; } return (DamageTypeValue(info) & 1) != 0; } private static int DamageTypeValue(DamageInfo info) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) try { if (_comboDamageType == null) { _comboDamageType = AccessTools.Field(((object)info.damageType).GetType(), "damageType"); } if (_comboDamageType == null) { return 0; } return Convert.ToInt32(_comboDamageType.GetValue(info.damageType)); } catch (Exception) { return 0; } } private static bool IsCall(CodeInstruction instruction, MethodInfo method) { if (instruction.opcode == OpCodes.Callvirt) { return instruction.operand as MethodInfo == method; } return false; } private static MethodInfo FindGetter(string name) { Type[] obj = new Type[3] { typeof(HealthComponent), typeof(CharacterBody), typeof(TeamComponent) }; string text = (name.StartsWith("get_") ? name.Substring(4) : name); Type[] array = obj; for (int i = 0; i < array.Length; i++) { MethodInfo methodInfo = AccessTools.PropertyGetter(array[i], text); if (methodInfo != null) { return methodInfo; } } return null; } private static void Log(LogLevel level, string message) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (_log != null) { _log.Log(level, (object)message); } } } public static class RexSyringe { private const string StateTypeName = "EntityStates.Treebot.Weapon.FireSyringe"; private static ConfigEntry<bool> _enabled; private static ConfigEntry<bool> _firstSyringeSpecial; private static ConfigEntry<float> _procCoefficient; private static ManualLogSource _log; private static Type _stateType; private static bool _firstApplyLogged; private static float _vanillaProcCoefficient = float.NaN; public static bool IsEnabled { get { if (_enabled != null) { return _enabled.Value; } return true; } } public static bool FirstSyringeSpecialEnabled { get { if (IsEnabled) { if (_firstSyringeSpecial != null) { return _firstSyringeSpecial.Value; } return true; } return false; } } private static bool IsSpecialOrderEnabled { get { if (_firstSyringeSpecial != null) { return _firstSyringeSpecial.Value; } return true; } } public static void Init(ConfigFile cfg, ManualLogSource logger) { _log = logger; _enabled = cfg.Bind<bool>("Inject", "Enabled", true, "是否调整 M1「命令:注射」。false = 完全保持原版。"); _firstSyringeSpecial = cfg.Bind<bool>("Inject", "FirstSyringeIsSpecial", true, "把带弱化 + 吸取的那枚注射器挪到**最先发射**的那枚。\n原版它是最后一枚(技能原文:The last syringe Weakens and heals for 60% of damage dealt)。\n这样第一发先挂上弱化,后面两发吃到更高的伤害。\nfalse = 保持原版顺序(最后一枚才是特殊的)。改完需要重启游戏。"); _procCoefficient = cfg.Bind<float>("Inject", "ProcCoefficient", 0.7f, "M1 每枚注射器的触发系数(原版 0.5)。系数越高,\n尤克里里、导弹、三尖匕首之类的命中类道具越容易触发。\n填负数 = 沿用原版 0.5。这个值运行期读取,改完下次开火就生效。"); } public static string Summary() { if (!IsEnabled) { return " [命令:注射:原版未改动]"; } string text = " [命令:注射:"; text = ((!FirstSyringeSpecialEnabled) ? (text + "顺序沿用原版") : (text + "第一枚带弱化+吸取")); if (ProcCoefficientValue() >= 0f) { text = text + ",触发系数 0.5 → " + ProcCoefficientValue().ToString("0.##"); } return text + "]"; } private static float ProcCoefficientValue() { if (_procCoefficient != null) { return _procCoefficient.Value; } return 0.7f; } public static void Patch(Harmony harmony) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "命令:注射:按配置保持原版,不挂任何补丁。"); return; } _stateType = AccessTools.TypeByName("EntityStates.Treebot.Weapon.FireSyringe"); if (_stateType == null) { Log((LogLevel)2, "命令:注射补丁失败:找不到状态类型 EntityStates.Treebot.Weapon.FireSyringe。"); return; } if (IsSpecialOrderEnabled) { MethodInfo methodInfo = AccessTools.Method(_stateType, "FixedUpdate", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RexSyringe), "FirstSyringeTranspiler", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:" + _stateType.Name + ".FixedUpdate <- FirstSyringeTranspiler(特殊注射器挪到第一枚)"); } else { Log((LogLevel)2, "命令:注射补丁失败:取不到 FixedUpdate。"); } } else { Log((LogLevel)16, "命令:注射:按配置保持原版顺序(最后一枚才是特殊的)。"); } if (ProcCoefficientValue() >= 0f) { MethodInfo methodInfo2 = AccessTools.Method(typeof(ProjectileManager), "InitializeProjectile", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RexSyringe), "InitializeProjectilePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:ProjectileManager.InitializeProjectile <- InitializeProjectilePostfix(触发系数 → " + ProcCoefficientValue().ToString("0.##") + ")"); } else { Log((LogLevel)2, "命令:注射补丁失败:取不到 ProjectileManager.InitializeProjectile。"); } } } public static IEnumerable<CodeInstruction> FirstSyringeTranspiler(IEnumerable<CodeInstruction> instructions) { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); FieldInfo fieldInfo = AccessTools.Field(_stateType, "projectileCount"); if (fieldInfo == null) { Log((LogLevel)2, "顺序调整失败:取不到字段 projectileCount。"); return list; } int num = 0; for (int i = 0; i + 3 < list.Count; i++) { FieldInfo fieldInfo2 = list[i].operand as FieldInfo; if (!(list[i].opcode != OpCodes.Ldsfld) && !(fieldInfo2 == null) && !(fieldInfo2 != fieldInfo) && !(list[i + 1].opcode != OpCodes.Ldc_I4_1) && !(list[i + 2].opcode != OpCodes.Sub)) { OpCode opcode = list[i + 3].opcode; if (!(opcode != OpCodes.Bne_Un) || !(opcode != OpCodes.Bne_Un_S)) { list[i] = new CodeInstruction(OpCodes.Ldc_I4_0, (object)null); list[i + 1].opcode = OpCodes.Nop; list[i + 1].operand = null; list[i + 2].opcode = OpCodes.Nop; list[i + 2].operand = null; num++; i += 3; } } } if (num > 0) { Log((LogLevel)16, "顺序已调整(" + num + " 处 projectileCount-1 → 0):最先发射的那枚现在是带弱化 + 吸取的特殊注射器。"); } else { Log((LogLevel)2, "顺序调整失败:FixedUpdate 里没找到 projectileCount - 1 + bne.un 那一串。"); } return list; } public static void InitializeProjectilePostfix(ProjectileController projectileController, FireProjectileInfo fireProjectileInfo) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) try { if (!IsEnabled || (Object)(object)projectileController == (Object)null || !IsSyringePrefab(fireProjectileInfo.projectilePrefab)) { return; } float num = ProcCoefficientValue(); if (num < 0f) { return; } float procCoefficient = projectileController.procCoefficient; if (float.IsNaN(_vanillaProcCoefficient)) { _vanillaProcCoefficient = procCoefficient; } if (!(Mathf.Abs(procCoefficient - num) < 0.0001f)) { projectileController.procCoefficient = num; if (!_firstApplyLogged) { _firstApplyLogged = true; Log((LogLevel)16, "命令:注射已生效 —— 触发系数 " + _vanillaProcCoefficient.ToString("0.##") + " → " + num.ToString("0.##") + ",每一枚注射器都能更容易触发道具效果。"); } } } catch (Exception ex) { Log((LogLevel)4, "触发系数改写异常(已忽略):" + ex.GetType().Name + " " + ex.Message); } } private static bool IsSyringePrefab(GameObject prefab) { if ((Object)(object)prefab == (Object)null || _stateType == null) { return false; } FieldInfo fieldInfo = AccessTools.Field(_stateType, "projectilePrefab"); FieldInfo fieldInfo2 = AccessTools.Field(_stateType, "finalProjectilePrefab"); if (fieldInfo != null) { object value = fieldInfo.GetValue(null); if (prefab == value) { return true; } } if (fieldInfo2 != null) { object value2 = fieldInfo2.GetValue(null); if (prefab == value2) { return true; } } return false; } private static void Log(LogLevel level, string message) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (_log != null) { _log.Log(level, (object)message); } } } public static class SkillDescriptions { private const string SpecialToken = "TREEBOT_SPECIAL_DESCRIPTION"; private const string SpecialAltToken = "TREEBOT_SPECIAL_ALT1_DESCRIPTION"; private const string PrimaryToken = "TREEBOT_PRIMARY_DESCRIPTION"; private const string DefaultZh = "<style=cIsHealth>25%生命值</style>。发射一朵会<style=cIsDamage>扎根</style>并持续造成<style=cIsDamage>200%伤害</style>的花朵。能对范围内的目标造成<style=cIsUtility>弱化</style>。"; private const string DefaultEn = "<style=cIsHealth>25% HP</style>. Fire a flower that <style=cIsDamage>roots</style> and continuously deals <style=cIsDamage>200% damage</style>. <style=cIsUtility>Weakens</style> targets in the area."; private const string PreviousDefaultZh = "<style=cIsHealth>25%生命值</style>。发射一朵会<style=cIsDamage>扎根</style>并造成<style=cIsDamage>200%伤害</style>的花朵。<style=cIsHealing>每命中一个目标便会对你进行一次治疗</style>。<style=cIsUtility>花朵存续期间,触须每 1.5 秒重新捆住目标</style>,<style=cIsUtility>将其拉近、再次造成伤害,并使其弱化 3 秒</style>。<style=cIsUtility>进入范围的新目标会被立刻捆住</style>。<style=cIsUtility>捆绑伤害可触发道具效果</style>。"; private const string DefaultAltZh = "发射弩弹,造成<style=cIsDamage>330%的伤害</style>且弩弹将<style=cIsDamage>注入</style>敌人。此敌人被击败时,掉落多个<style=cIsHealing>治疗果实</style>,可治疗<style=cIsHealing>25%的生命值</style>并<style=cIsHealing>略微提升最大生命值</style>。"; private const string DefaultAltEn = "Fire a bolt for <style=cIsDamage>330% damage</style> that <style=cIsDamage>infects</style> the enemy. When defeated, they drop multiple <style=cIsHealing>healing fruits</style> that heal for <style=cIsHealing>25% of your health</style> and <style=cIsHealing>slightly increase your maximum health</style>."; private const string DefaultPrimaryZh = "发射3个注射器,造成<style=cIsDamage>3x80%伤害</style>。第一个注射器会造成<style=cIsDamage>弱化</style>并<style=cIsHealing>将所施加伤害的60%转化为生命值</style>。"; private const string DefaultPrimaryEn = "Fire 3 syringes for <style=cIsDamage>3x80% damage</style>. The first syringe <style=cIsDamage>Weakens</style> and <style=cIsHealing>heals for 60% of damage dealt</style>."; private static ConfigEntry<bool> _enabled; private static ConfigEntry<string> _specialText; private static ConfigEntry<string> _specialAltText; private static ConfigEntry<string> _primaryText; private static ManualLogSource _log; public static void Init(ConfigFile cfg, ManualLogSource logger) { _log = logger; _enabled = cfg.Bind<bool>("Descriptions", "Enabled", true, "是否改写技能说明文字。false = 完全保留原版文案。"); _specialText = cfg.Bind<string>("Descriptions", "TendrilGrowthDescription", "<style=cIsHealth>25%生命值</style>。发射一朵会<style=cIsDamage>扎根</style>并持续造成<style=cIsDamage>200%伤害</style>的花朵。能对范围内的目标造成<style=cIsUtility>弱化</style>。", "「触须生长」(R)的说明文字。留空 = 不改写。\n支持 <style=cIsUtility> / <style=cIsDamage> 之类的富文本标签。"); _specialAltText = cfg.Bind<string>("Descriptions", "HarvestDescription", "发射弩弹,造成<style=cIsDamage>330%的伤害</style>且弩弹将<style=cIsDamage>注入</style>敌人。此敌人被击败时,掉落多个<style=cIsHealing>治疗果实</style>,可治疗<style=cIsHealing>25%的生命值</style>并<style=cIsHealing>略微提升最大生命值</style>。", "「命令:收割」(替代 R)的说明文字。只在「拾取果实给 buff」开启时才会改写,\n留空 = 不改写。支持 <style=cIsHealing> 之类的富文本标签。"); _primaryText = cfg.Bind<string>("Descriptions", "InjectDescription", "发射3个注射器,造成<style=cIsDamage>3x80%伤害</style>。第一个注射器会造成<style=cIsDamage>弱化</style>并<style=cIsHealing>将所施加伤害的60%转化为生命值</style>。", "「命令:注射」(M1)的说明文字。只在「第一枚换成特殊注射器」开启时才会改写,\n留空 = 不改写。支持 <style=cIsUtility> / <style=cIsDamage> 之类的富文本标签。"); } public static void Apply(bool tendrilEnabled, bool syringeReordered, bool harvestEnabled) { try { if (_enabled == null || !_enabled.Value) { Log((LogLevel)16, "技能说明:按配置保留原版文案。"); return; } if (tendrilEnabled) { ApplyToken("TREEBOT_SPECIAL_DESCRIPTION", "<style=cIsHealth>25% HP</style>. Fire a flower that <style=cIsDamage>roots</style> and continuously deals <style=cIsDamage>200% damage</style>. <style=cIsUtility>Weakens</style> targets in the area.", "触须生长", Resolved(_specialText, "<style=cIsHealth>25%生命值</style>。发射一朵会<style=cIsDamage>扎根</style>并持续造成<style=cIsDamage>200%伤害</style>的花朵。能对范围内的目标造成<style=cIsUtility>弱化</style>。", "<style=cIsHealth>25%生命值</style>。发射一朵会<style=cIsDamage>扎根</style>并造成<style=cIsDamage>200%伤害</style>的花朵。<style=cIsHealing>每命中一个目标便会对你进行一次治疗</style>。<style=cIsUtility>花朵存续期间,触须每 1.5 秒重新捆住目标</style>,<style=cIsUtility>将其拉近、再次造成伤害,并使其弱化 3 秒</style>。<style=cIsUtility>进入范围的新目标会被立刻捆住</style>。<style=cIsUtility>捆绑伤害可触发道具效果</style>。")); } else { Log((LogLevel)16, "技能说明:触须生长未启用,保留原版文案。"); } if (harvestEnabled) { ApplyToken("TREEBOT_SPECIAL_ALT1_DESCRIPTION", "Fire a bolt for <style=cIsDamage>330% damage</style> that <style=cIsDamage>infects</style> the enemy. When defeated, they drop multiple <style=cIsHealing>healing fruits</style> that heal for <style=cIsHealing>25% of your health</style> and <style=cIsHealing>slightly increase your maximum health</style>.", "命令:收割", Resolved(_specialAltText, "发射弩弹,造成<style=cIsDamage>330%的伤害</style>且弩弹将<style=cIsDamage>注入</style>敌人。此敌人被击败时,掉落多个<style=cIsHealing>治疗果实</style>,可治疗<style=cIsHealing>25%的生命值</style>并<style=cIsHealing>略微提升最大生命值</style>。", null)); } else { Log((LogLevel)16, "技能说明:果实 buff 未启用,保留「命令:收割」原版文案。"); } if (syringeReordered) { ApplyToken("TREEBOT_PRIMARY_DESCRIPTION", "Fire 3 syringes for <style=cIsDamage>3x80% damage</style>. The first syringe <style=cIsDamage>Weakens</style> and <style=cIsHealing>heals for 60% of damage dealt</style>.", "命令:注射", Resolved(_primaryText, "发射3个注射器,造成<style=cIsDamage>3x80%伤害</style>。第一个注射器会造成<style=cIsDamage>弱化</style>并<style=cIsHealing>将所施加伤害的60%转化为生命值</style>。", null)); } else { Log((LogLevel)16, "技能说明:命令:注射未调整顺序,保留原版文案。"); } } catch (Exception ex) { Log((LogLevel)4, "技能说明改写失败(已忽略):" + ex.GetType().Name + " " + ex.Message); } } private static string Resolved(ConfigEntry<string> entry, string current, string previous) { if (entry == null) { return current; } if (!string.IsNullOrEmpty(previous) && entry.Value == previous) { return current; } return entry.Value; } private static void ApplyToken(string token, string english, string label, string chinese) { if (!string.IsNullOrEmpty(chinese)) { LanguageAPI.Add(token, chinese, "zh-CN"); LanguageAPI.Add(token, english, "en"); Log((LogLevel)16, "技能说明已改写:" + token + "(" + label + ")。"); } } private static void Log(LogLevel level, string message) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (_log != null) { _log.Log(level, (object)message); } } } [BepInPlugin("com.sparkletwilight.sparklerextweaks", "SparkleRexTweaks", "1.12.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class SparkleRexTweaksPlugin : BaseUnityPlugin { public const string PluginGUID = "com.sparkletwilight.sparklerextweaks"; public const string PluginName = "SparkleRexTweaks"; public const string PluginVersion = "1.12.0"; public void Awake() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00a1: Expected O, but got Unknown RexEntangle.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); RexSyringe.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); RexSelfHarm.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); RexFruitHarvest.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); RexBrambleVolley.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); SkillDescriptions.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); SkillDescriptions.Apply(RexEntangle.IsEnabled, RexSyringe.FirstSyringeSpecialEnabled, RexFruitHarvest.IsEnabled); Harmony val = new Harmony("com.sparkletwilight.sparklerextweaks"); RexEntangle.Patch(val); RexSyringe.Patch(val); RexSelfHarm.Patch(val); RexFruitHarvest.Patch(val); RexBrambleVolley.Patch(val); ((BaseUnityPlugin)this).Logger.LogInfo((object)("SparkleRexTweaks v1.12.0 已加载。" + RexEntangle.Summary() + RexSyringe.Summary() + RexSelfHarm.Summary() + RexFruitHarvest.Summary() + RexBrambleVolley.Summary())); } } }