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 SparkleArtificerTweaks v1.10.0
plugins/SparkleArtificerTweaks.dll
Decompiled 21 hours agousing System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using EntityStates; using EntityStates.Mage; using EntityStates.Mage.Weapon; using HarmonyLib; using Microsoft.CodeAnalysis; using R2API; using RoR2; using RoR2.Orbs; 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("SparkleArtificerTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.10.0.0")] [assembly: AssemblyInformationalVersion("1.10.0")] [assembly: AssemblyProduct("SparkleArtificerTweaks")] [assembly: AssemblyTitle("SparkleArtificerTweaks")] [assembly: AssemblyVersion("1.10.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 SparkleArtificerTweaks { public static class ArtificerChainLightning { private const string PlasmaLabel = "等离子弹"; private const string NanoLabel = "充能完毕的纳米炸弹"; private const int IgniteOnHitBit = 128; private const int PercentIgniteOnHitBit = 8192; private const int ElectricalBit = 8192; private static ConfigEntry<bool> _enabled; private static ConfigEntry<bool> _plasmaEnabled; private static ConfigEntry<bool> _nanoEnabled; private static ConfigEntry<float> _damageRatio; private static ConfigEntry<float> _procCoefficient; private static ConfigEntry<int> _chainTargets; private static ConfigEntry<float> _range; private static ConfigEntry<bool> _inheritCrit; private static ConfigEntry<bool> _excludePrimaryTarget; private static ConfigEntry<bool> _singleTargetFallback; private static ConfigEntry<bool> _plasmaElectrical; private static ConfigEntry<bool> _plasmaNoIgnite; private static ConfigEntry<bool> _nanoNoIgnite; private static ConfigEntry<float> _nanoRangeBonus; private static ConfigEntry<int> _nanoTargetsBonus; private static ConfigEntry<string> _plasmaProjectileName; private static ConfigEntry<string> _nanoProjectileName; private static ManualLogSource _log; private static readonly HashSet<string> _seenProjectiles = new HashSet<string>(); private static GameObject _plasmaPrefab; private static GameObject _nanoPrefab; private static FieldInfo _controllerField; private static FieldInfo _damageField; private static FieldInfo _applyDotField; private static FieldInfo _dotIndexField; private static bool _fieldsTried; private static bool _plasmaFiredLogged; private static bool _nanoFiredLogged; private static bool _firedLogged; private static bool _igniteLogged; 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>("ChainLightning", "Enabled", true, "是否启用「等离子弹 / 纳米炸弹命中后甩出一道尤克里里式连锁闪电」。"); _plasmaEnabled = cfg.Bind<bool>("ChainLightning", "PlasmaBolt", true, "等离子弹(主要技能·电)也吃这个效果。"); _nanoEnabled = cfg.Bind<bool>("ChainLightning", "ChargedNanoBomb", true, "充能完毕的纳米炸弹(辅助技能·电)也吃这个效果。"); _damageRatio = cfg.Bind<float>("ChainLightning", "DamageRatio", 0.8f, "闪电每一跳的伤害 = 原本爆炸伤害 × 这个值。\n尤克里里是 0.8(原版还会保底 1 点伤害)。运行期读取。"); _procCoefficient = cfg.Bind<float>("ChainLightning", "ProcCoefficient", 0.2f, "闪电的触发系数。尤克里里是 0.2(填 0 就等于完全不触发道具)。运行期读取。"); _chainTargets = cfg.Bind<int>("ChainLightning", "ChainTargets", 3, "这道闪电一共能打到几个敌人(不含被射弹直接命中的那个)。\n尤克里里 1 层是 3 个,每多一层 +2。运行期读取。"); _range = cfg.Bind<float>("ChainLightning", "Range", 20f, "每次弹跳的搜索半径(米)。尤克里里基础 20,每层 +2。运行期读取。"); _inheritCrit = cfg.Bind<bool>("ChainLightning", "InheritCrit", true, "true = 射弹那一下暴击的话,闪电也跟着暴击(尤克里里就是这么干的)。"); _excludePrimaryTarget = cfg.Bind<bool>("ChainLightning", "ExcludePrimaryTarget", true, "true = 被射弹直接命中的那个敌人不会再被闪电打一次(跟尤克里里一样);\nfalse = 闪电也可能回头打他。"); _singleTargetFallback = cfg.Bind<bool>("ChainLightning", "SingleTargetFallback", false, "附近没有别的敌人时,要不要让闪电回头打被直接命中的那个?\nfalse(默认)= 跟尤克里里完全一致:只有 1 个敌人时不会有任何闪电;\ntrue = 至少能看到一道闪电(打在被命中的那个身上)。运行期读取。"); _plasmaElectrical = cfg.Bind<bool>("ChainLightning", "PlasmaBoltElectrical", false, "等离子弹的闪电要不要带 <b>Electrical</b>(电)伤害标记。\nfalse(默认)= 不带。原因:原版 FireLightningBolt.ModifyProjectileInfo 会在运行时把\n等离子弹的伤害类型覆盖成 DamageTypeCombo.GenericPrimary(Generic + Primary,没有 Electrical),\n而这个标记一旦带上,HealthComponent.CheckElectrocutionSpread 就会以 50% 概率在 15 米内\n再放一次范围伤害并挂上持续伤害 —— 等于让等离子弹「点燃」了敌人,这是原版没有的。\ntrue = 跟尤克里里完全一致(带 Electrical 标记,会有电击扩散)。"); _nanoRangeBonus = cfg.Bind<float>("ChainLightning", "NanoBombRangeBonus", 10f, "充能完毕的纳米炸弹的闪电,搜索半径在这个基础上再加多少米(基础值见 Range)。\n默认 +10 → 纳米炸弹是 30 米,等离子弹仍是 20 米。运行期读取。"); _nanoTargetsBonus = cfg.Bind<int>("ChainLightning", "NanoBombChainTargetsBonus", 2, "充能完毕的纳米炸弹的闪电,最多能打的敌人数在这个基础上再加几个(基础值见 ChainTargets)。\n默认 +2 → 纳米炸弹是 5 个,等离子弹仍是 3 个。运行期读取。"); _plasmaNoIgnite = cfg.Bind<bool>("ChainLightning", "PlasmaBoltNoIgnite", true, "等离子弹「直接命中」的那一下,强制不点燃敌人。\ntrue(默认)= 结算爆炸伤害之前,把伤害类型里的点燃标记\n(IgniteOnHit / PercentIgniteOnHit)和电击标记(Electrical)全部清掉。\n为什么需要:射弹 prefab 上写死的是 {damageType=0, damageTypeExtended=8192(Electrical)},\n而 Electrical 会让 HealthComponent.CheckElectrocutionSpread 以 50% 概率在 15 米内\n再放一次范围伤害并挂上持续伤害 —— 看起来就跟「点燃」一样。\n开火时 FireLightningBolt 本来会把它覆盖成 GenericPrimary,但只要这条覆盖没走到\n(prefab 默认值生效的路径),等离子弹就会烧起来。false = 完全不动原版伤害类型。"); _nanoNoIgnite = cfg.Bind<bool>("ChainLightning", "NanoBombNoIgnite", false, "充能完毕的纳米炸弹要不要也做上面那个「禁止点燃/电击」清理。\nfalse(默认)= 保持原版(纳米炸弹本来就该带电击,会电击扩散);true = 也把它的 Electrical 标记清掉。"); _plasmaProjectileName = cfg.Bind<string>("ChainLightning", "PlasmaBoltProjectileName", "MageLightningboltBasic", "等离子弹的射弹对象名(兜底用,不区分大小写)。游戏更新后改名了可以在这里改。"); _nanoProjectileName = cfg.Bind<string>("ChainLightning", "NanoBombProjectileName", "MageLightningBombProjectile", "纳米炸弹的射弹对象名(兜底用,不区分大小写)。"); } public static string Summary() { if (!IsEnabled) { return " [连锁闪电:原版未改动]"; } string text = ""; if (PlasmaEnabled()) { text += "等离子弹"; } if (NanoEnabled()) { text = text + ((text.Length > 0) ? "/" : "") + "纳米炸弹"; } if (text.Length == 0) { return " [连锁闪电:两个技能都关了]"; } string text2 = "等离子弹 打 " + ChainTargets() + " 个 / 半径 " + Range().ToString("0.#") + (PlasmaElectrical() ? "(带电击标记)" : "(不带电击标记)"); if (NanoEnabled()) { text2 = text2 + ",纳米炸弹 打 " + (ChainTargets() + NanoTargetsBonus()) + " 个 / 半径 " + (Range() + NanoRangeBonus()).ToString("0.#"); } return " [连锁闪电:" + text + " 命中后弹雷 —— " + text2 + "," + DamageRatio().ToString("0.##") + " 倍伤害,触发系数 " + ProcCoefficient().ToString("0.##") + "]"; } private static bool PlasmaEnabled() { if (_plasmaEnabled != null) { return _plasmaEnabled.Value; } return true; } private static bool NanoEnabled() { if (_nanoEnabled != null) { return _nanoEnabled.Value; } return true; } private static float DamageRatio() { if (_damageRatio == null) { return 0.8f; } return _damageRatio.Value; } private static float ProcCoefficient() { if (_procCoefficient == null) { return 0.2f; } return _procCoefficient.Value; } private static int ChainTargets() { int num = ((_chainTargets != null) ? _chainTargets.Value : 3); if (num >= 1) { return num; } return 1; } private static float Range() { if (_range == null) { return 20f; } return _range.Value; } private static bool InheritCrit() { if (_inheritCrit != null) { return _inheritCrit.Value; } return true; } private static bool ExcludePrimaryTarget() { if (_excludePrimaryTarget != null) { return _excludePrimaryTarget.Value; } return true; } private static bool SingleTargetFallback() { if (_singleTargetFallback != null) { return _singleTargetFallback.Value; } return false; } private static bool PlasmaElectrical() { if (_plasmaElectrical != null) { return _plasmaElectrical.Value; } return false; } private static bool PlasmaNoIgnite() { if (_plasmaNoIgnite != null) { return _plasmaNoIgnite.Value; } return true; } private static bool NanoNoIgnite() { if (_nanoNoIgnite != null) { return _nanoNoIgnite.Value; } return false; } private static float NanoRangeBonus() { if (_nanoRangeBonus == null) { return 10f; } return _nanoRangeBonus.Value; } private static int NanoTargetsBonus() { if (_nanoTargetsBonus == null) { return 2; } return _nanoTargetsBonus.Value; } public static void Patch(Harmony harmony) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //IL_00bf: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "连锁闪电:按配置保持原版,不挂任何补丁。"); return; } PatchLearn(harmony, typeof(FireLightningBolt), "ModifyProjectileInfo", "PlasmaBoltPostfix", "等离子弹"); PatchLearn(harmony, typeof(ThrowNovabomb), "ModifyProjectile", "NanoBombPostfix", "充能完毕的纳米炸弹"); MethodInfo methodInfo = AccessTools.Method(typeof(ProjectileExplosion), "DetonateServer", (Type[])null, (Type[])null); if (methodInfo == null) { Log((LogLevel)2, "连锁闪电补丁失败:取不到 ProjectileExplosion.DetonateServer。"); return; } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(ArtificerChainLightning), "DetonateServerPrefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(ArtificerChainLightning), "DetonateServerPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:ProjectileExplosion.DetonateServer <- DetonateServerPrefix(清掉点燃/电击标记) + DetonateServerPostfix(命中后甩一道尤克里里式连锁闪电)"); } catch (Exception ex) { Log((LogLevel)2, "连锁闪电补丁失败(ProjectileExplosion.DetonateServer 没挂上,功能将完全失效)—— " + ex.GetType().Name + " " + ex.Message); } } private static void PatchLearn(Harmony harmony, Type stateType, string methodName, string postfixName, string label) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(stateType, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { Log((LogLevel)4, "连锁闪电:取不到 " + stateType.Name + "." + methodName + "(" + label + "的射弹认不出来,不影响功能,还有 cfg 里的名字兜底)。"); return; } try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(ArtificerChainLightning), postfixName, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:" + stateType.Name + "." + methodName + " <- " + postfixName + "(记住" + label + "的射弹)"); } catch (Exception ex) { Log((LogLevel)4, "连锁闪电:认" + label + "射弹的补丁没挂上(不影响功能,还有 cfg 里的名字兜底)—— " + ex.GetType().Name + " " + ex.Message); } } public static void PlasmaBoltPostfix(object[] __args) { Learn(ref _plasmaPrefab, ref _plasmaFiredLogged, __args, "等离子弹"); } public static void NanoBombPostfix(object[] __args) { Learn(ref _nanoPrefab, ref _nanoFiredLogged, __args, "充能完毕的纳米炸弹"); } private static void Learn(ref GameObject slot, ref bool logged, object[] args, string label) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) try { if (IsEnabled && args != null && args.Length != 0 && args[0] is FireProjectileInfo val && !((Object)(object)val.projectilePrefab == (Object)null) && !((Object)(object)slot == (Object)(object)val.projectilePrefab)) { slot = val.projectilePrefab; if (!logged) { logged = true; Log((LogLevel)16, "连锁闪电:已记住" + label + "的射弹 —— " + ((Object)val.projectilePrefab).name); } } } catch (Exception ex) { Log((LogLevel)4, "连锁闪电:记录" + label + "射弹时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } public static void DetonateServerPrefix(ProjectileExplosion __instance) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected I4, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected I4, but got Unknown //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected I4, but got Unknown try { if (!IsEnabled || (Object)(object)__instance == (Object)null || !NetworkServer.active) { return; } ProjectileController projectileController = GetProjectileController(__instance); ProjectileDamage projectileDamage = GetProjectileDamage(__instance); if ((Object)(object)projectileController == (Object)null || (Object)(object)projectileDamage == (Object)null) { return; } GameObject projectilePrefab = ProjectileCatalog.GetProjectilePrefab(projectileController.catalogIndex); if ((Object)(object)projectilePrefab == (Object)null) { return; } bool flag = PlasmaEnabled() && IsPlasma(projectilePrefab); bool flag2 = NanoEnabled() && IsNano(projectilePrefab); if ((flag || flag2) && !(flag ? (!PlasmaNoIgnite()) : (!NanoNoIgnite()))) { DamageTypeCombo damageType = projectileDamage.damageType; int num = (int)damageType.damageType; int num2 = (int)damageType.damageTypeExtended; int num3 = num & -129 & -8193; int num4 = num2 & -8193; if (!_igniteLogged) { _igniteLogged = true; Log((LogLevel)16, "禁止点燃(" + (flag ? "等离子弹" : "充能完毕的纳米炸弹") + "):实际伤害类型 = {damageType=" + num + ", damageTypeExtended=" + num2 + ", damageSource=" + (int)damageType.damageSource + "} → 清理后 = {" + num3 + ", " + num4 + ", source 不变};射弹组件 applyDot=" + DotInfo(__instance) + "。"); } if (num3 != num || num4 != num2) { damageType.damageType = (DamageType)num3; damageType.damageTypeExtended = (DamageTypeExtended)num4; projectileDamage.damageType = damageType; } } } catch (Exception ex) { Log((LogLevel)4, "禁止点燃:清理伤害类型时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static string DotInfo(ProjectileExplosion explosion) { EnsureFields(); string obj = ((_applyDotField != null) ? Convert.ToString(_applyDotField.GetValue(explosion)) : "?"); string text = ((_dotIndexField != null) ? Convert.ToString(_dotIndexField.GetValue(explosion)) : "?"); return obj + ",dotIndex=" + text; } public static void DetonateServerPostfix(ProjectileExplosion __instance) { //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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) //IL_0152: Unknown result type (might be due to invalid IL or missing references) try { if (!IsEnabled || (Object)(object)__instance == (Object)null || !NetworkServer.active) { return; } ProjectileController projectileController = GetProjectileController(__instance); ProjectileDamage projectileDamage = GetProjectileDamage(__instance); if ((Object)(object)projectileController == (Object)null || (Object)(object)projectileDamage == (Object)null) { return; } GameObject projectilePrefab = ProjectileCatalog.GetProjectilePrefab(projectileController.catalogIndex); if (!((Object)(object)projectilePrefab == (Object)null)) { bool flag = PlasmaEnabled() && IsPlasma(projectilePrefab); bool flag2 = NanoEnabled() && IsNano(projectilePrefab); if (!_seenProjectiles.Contains(((Object)projectilePrefab).name)) { _seenProjectiles.Add(((Object)projectilePrefab).name); Log((LogLevel)16, "连锁闪电:DetonateServer 收到射弹「" + ((Object)projectilePrefab).name + "」—— " + (flag ? "等离子弹,会引雷" : (flag2 ? "纳米炸弹,会引雷" : "不是目标技能,跳过"))); } if (flag || flag2) { float num = projectileDamage.damage * __instance.blastDamageCoefficient; float lightningDamage = Mathf.Max(1f, num * DamageRatio()); TeamIndex teamIndex = TeamOf(projectileController); FireChain(((Component)__instance).transform.position, range: Range() + (flag2 ? NanoRangeBonus() : 0f), targets: ChainTargets() + (flag2 ? NanoTargetsBonus() : 0), electrical: !flag || PlasmaElectrical(), teamIndex: teamIndex, attacker: projectileController.owner, crit: InheritCrit() && projectileDamage.crit, lightningDamage: lightningDamage, blastRadius: __instance.blastRadius, label: flag ? "等离子弹" : "充能完毕的纳米炸弹", originalDamage: num); } } } catch (Exception ex) { Log((LogLevel)4, "连锁闪电:引雷时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static void FireChain(Vector3 position, TeamIndex teamIndex, GameObject attacker, bool crit, float lightningDamage, float blastRadius, string label, float originalDamage, float range, int targets, bool electrical) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) HurtBox val = (ExcludePrimaryTarget() ? FindNearestEnemy(position, teamIndex, blastRadius) : null); Vector3 val2 = (((Object)(object)val != (Object)null) ? ((Component)val).transform.position : position); LightningOrb val3 = new LightningOrb(); ((Orb)val3).origin = val2; val3.damageValue = lightningDamage; val3.isCrit = crit; val3.teamIndex = teamIndex; val3.attacker = attacker; val3.bouncesRemaining = targets - 1; val3.bouncedObjects = new List<HealthComponent>(); if ((Object)(object)val != (Object)null && (Object)(object)val.healthComponent != (Object)null) { val3.bouncedObjects.Add(val.healthComponent); } val3.procChainMask = default(ProcChainMask); ((ProcChainMask)(ref val3.procChainMask)).AddProc((ProcType)3); val3.procCoefficient = ProcCoefficient(); val3.damageType = default(DamageTypeCombo); if (electrical) { val3.damageType.damageTypeExtended = (DamageTypeExtended)8192; } val3.lightningType = (LightningType)0; val3.damageColorIndex = (DamageColorIndex)3; val3.range = range; HurtBox val4 = val3.PickNextTarget(val2); if ((Object)(object)val4 == (Object)null && SingleTargetFallback() && (Object)(object)val != (Object)null) { val3.bouncedObjects = new List<HealthComponent>(); val4 = val; } if ((Object)(object)val4 == (Object)null) { Log((LogLevel)32, "连锁闪电:半径 " + range.ToString("0.#") + " 米内没有第二个敌人可弹,这一发没有闪电(想要单挑时也出闪电就把 cfg 里的 SingleTargetFallback 改成 true)。"); return; } ((Orb)val3).target = val4; OrbManager.instance.AddOrb((Orb)(object)val3); Log((LogLevel)32, "连锁闪电已放出:" + label + " 命中,原伤害 " + originalDamage.ToString("0.#") + " → 每跳 " + lightningDamage.ToString("0.#") + ",目标 " + ((Object)val4).name); if (!_firedLogged) { _firedLogged = true; Log((LogLevel)16, "连锁闪电已生效:" + label + " 命中,原伤害 " + originalDamage.ToString("0.#") + " → 闪电每跳 " + lightningDamage.ToString("0.#") + "(最多打 " + targets + " 个目标,半径 " + range.ToString("0.#") + (electrical ? ",带电击标记" : ",不带电击标记") + ",触发系数 " + ProcCoefficient().ToString("0.##") + ")"); } } private static HurtBox FindNearestEnemy(Vector3 position, TeamIndex teamIndex, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_0052: Unknown result type (might be due to invalid IL or missing references) BullseyeSearch val = new BullseyeSearch { searchOrigin = position, searchDirection = Vector3.zero, teamMaskFilter = TeamMask.allButNeutral }; ((TeamMask)(ref val.teamMaskFilter)).RemoveTeam(teamIndex); val.filterByLoS = false; val.sortMode = (SortMode)1; val.maxDistanceFilter = ((radius > 0f) ? radius : 1f); val.RefreshCandidates(); HurtBox result = null; using (IEnumerator<HurtBox> enumerator = val.GetResults().GetEnumerator()) { if (enumerator.MoveNext()) { result = enumerator.Current; } } return result; } private static void EnsureFields() { if (!_fieldsTried) { _fieldsTried = true; _controllerField = AccessTools.Field(typeof(ProjectileExplosion), "projectileController"); _damageField = AccessTools.Field(typeof(ProjectileExplosion), "projectileDamage"); _applyDotField = AccessTools.Field(typeof(ProjectileExplosion), "applyDot"); _dotIndexField = AccessTools.Field(typeof(ProjectileExplosion), "dotIndex"); if (_controllerField == null || _damageField == null) { Log((LogLevel)2, "连锁闪电:取不到 ProjectileExplosion 的 projectileController / projectileDamage 字段,认不出射弹也算不出伤害。"); } } } private static ProjectileController GetProjectileController(ProjectileExplosion explosion) { EnsureFields(); if (!(_controllerField != null)) { return null; } object? value = _controllerField.GetValue(explosion); return (ProjectileController)((value is ProjectileController) ? value : null); } private static ProjectileDamage GetProjectileDamage(ProjectileExplosion explosion) { EnsureFields(); if (!(_damageField != null)) { return null; } object? value = _damageField.GetValue(explosion); return (ProjectileDamage)((value is ProjectileDamage) ? value : null); } private static bool IsPlasma(GameObject prefab) { if (!((Object)(object)prefab == (Object)(object)_plasmaPrefab)) { return NameMatches(prefab, _plasmaProjectileName, "MageLightningboltBasic"); } return true; } private static bool IsNano(GameObject prefab) { if (!((Object)(object)prefab == (Object)(object)_nanoPrefab)) { return NameMatches(prefab, _nanoProjectileName, "MageLightningBombProjectile"); } return true; } private static bool NameMatches(GameObject prefab, ConfigEntry<string> entry, string fallback) { string value = ((entry != null) ? entry.Value : fallback); if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(((Object)prefab).name)) { return false; } return ((Object)prefab).name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; } private static TeamIndex TeamOf(ProjectileController controller) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)controller != (Object)null) { TeamFilter teamFilter = controller.teamFilter; if ((Object)(object)teamFilter != (Object)null) { return teamFilter.teamIndex; } if ((Object)(object)controller.owner != (Object)null) { return TeamComponent.GetObjectTeam(controller.owner); } } return (TeamIndex)0; } 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 ArtificerFlamethrower { private const string SkillLabel = "火焰喷射器"; private const float FallbackDuration = 3f; private const float AlwaysProc = 100f; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _duration; private static ConfigEntry<bool> _keepTickDamage; private static ConfigEntry<float> _rangeMultiplier; private static ConfigEntry<bool> _igniteEveryTick; private static ConfigEntry<bool> _keepProcBudget; private static ConfigEntry<bool> _holdAim; private static ManualLogSource _log; private static float _baseDuration = -1f; private static float _baseMaxDistance = -1f; private static float _baseProcCoefficient = -1f; private static float _baseIgniteChance = -1f; private static bool _logged; private static bool _fieldsTried; private static FieldInfo _durationField; private static FieldInfo _tickDamageField; private static FieldInfo _tickFrequencyField; private static FieldInfo _totalDamageField; private static FieldInfo _entryDurationField; private static FieldInfo _procField; private static FieldInfo _maxDistanceField; private static FieldInfo _igniteField; private static PropertyInfo _characterBodyProperty; 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>("Flamethrower", "Enabled", true, "是否启用「火焰喷射器」这几处改动(时长 / 射程 / 每一跳点燃)。false = 保持原版。"); _duration = cfg.Bind<float>("Flamethrower", "Duration", 3f, "喷射持续多少秒(原版 3)。运行期读取,改完下一次开火就生效。\n默认 3 秒 + 每一跳伤害不变 = 每秒伤害和原版一模一样。\n想喷得更久就调大(比如 30),总伤害会跟着线性增长。"); _keepTickDamage = cfg.Bind<bool>("Flamethrower", "KeepTickDamage", true, "每一跳的伤害要不要保持原版不变。\ntrue(默认)= 每一跳伤害跟原版 3 秒时一模一样(每秒伤害恒定),喷得越久总伤害越高。\nfalse = 用原版那套「总伤害固定、按 tick 摊薄」:拉长时长只会让每一跳变轻,总伤害不变。"); _rangeMultiplier = cfg.Bind<float>("Flamethrower", "RangeMultiplier", 2f, "射程 = 原版 maxDistance(20 米)× 这个值。2 = 翻倍到 40 米。填 1 = 保持原版。"); _igniteEveryTick = cfg.Bind<bool>("Flamethrower", "IgniteEveryTick", true, "每一跳都附带点燃。\n原版 ignitePercentChance = 50,也就是每一跳只有一半几率带 IgniteOnHit 标记;\ntrue = 把这个概率设成 100,每一跳都点燃。false = 保持原版的 50%。"); _keepProcBudget = cfg.Bind<bool>("Flamethrower", "KeepProcBudget", true, "道具触发要不要保持原版的「总预算」。\ntrue(默认)= 拉长时长时把 procCoefficientPerTick 按倍率摊薄,整个技能触发道具的总次数和原版 3 秒时一样。\nfalse = 每一跳的触发系数不变,喷得越久触发次数越多。\n(Duration 保持 3 秒时这一项不起作用,触发系数就是原版的 1.0。)"); _holdAim = cfg.Bind<bool>("Flamethrower", "HoldAim", true, "把瞄准锁定拉长到和喷射一样的时间。\n原版 OnEnter 里 SetAimTimer 用的是还没赋值的 flamethrowerDuration(等于只锁 1 秒左右),喷久了会提前解锁、转不动视角。true = 按实际时长重设一次。"); } public static string Summary() { if (!IsEnabled) { return ""; } string text = " [火焰喷射器:" + Duration().ToString("0.#") + "秒"; float num = RangeMultiplier(); if (num > 0f && Mathf.Abs(num - 1f) >= 0.0001f) { text = text + " · 射程×" + num.ToString("0.##"); } if (IgniteEveryTick()) { text += " · 每一跳点燃"; } if (KeepTickDamage()) { text += " · 每秒伤害不变"; } return text + "]"; } public static void Patch(Harmony harmony) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "火焰喷射器:按配置保持原版,不挂任何补丁。"); return; } MethodInfo methodInfo = AccessTools.Method(typeof(Flamethrower), "OnEnter", (Type[])null, (Type[])null); if (methodInfo == null) { Log((LogLevel)2, "火焰喷射器:取不到 Flamethrower.OnEnter,改动不会生效。"); return; } try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(ArtificerFlamethrower), "OnEnterPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:Flamethrower.OnEnter -> OnEnterPostfix(火焰喷射器 · 时长 / 射程 / 点燃)"); } catch (Exception ex) { Log((LogLevel)2, "火焰喷射器补丁失败(改动不会生效)—— " + ex.GetType().Name + " " + ex.Message); } } public static void OnEnterPostfix(Flamethrower __instance) { try { if (!IsEnabled || __instance == null || !PrepareFields()) { return; } float num = Float(_durationField, __instance); if (_baseDuration <= 0f) { _baseDuration = ((num > 0f) ? num : 3f); } float num2 = Duration(); bool flag = num2 > 0f && Mathf.Abs(num - num2) >= 0.0001f; if (flag) { _durationField.SetValue(__instance, num2); float num3 = Float(_tickFrequencyField, __instance); float num4 = Float(_totalDamageField, __instance); int num5 = Mathf.CeilToInt((KeepTickDamage() ? num : num2) * num3); if (num5 > 0) { _tickDamageField.SetValue(__instance, num4 / (float)num5); } } if (_procField != null) { if (_baseProcCoefficient < 0f) { _baseProcCoefficient = Float(_procField, null); } float num6 = ((flag && num2 > 0f) ? (num / num2) : 1f); _procField.SetValue(null, KeepProcBudget() ? (_baseProcCoefficient * num6) : _baseProcCoefficient); } ApplyRange(__instance); ApplyIgnite(); if (HoldAim() && _characterBodyProperty != null) { object? value = _characterBodyProperty.GetValue(__instance, null); CharacterBody val = (CharacterBody)((value is CharacterBody) ? value : null); if ((Object)(object)val != (Object)null) { val.SetAimTimer(Float(_entryDurationField, __instance) + (flag ? num2 : num) + 1f); } } if (!_logged) { _logged = true; Log((LogLevel)16, "火焰喷射器:时长 " + num.ToString("0.##") + " 秒 → " + (flag ? num2 : num).ToString("0.##") + " 秒,每一跳伤害系数 " + Float(_tickDamageField, __instance).ToString("0.####") + ",射程 " + Float(_maxDistanceField, __instance).ToString("0.#") + " 米,点燃概率 " + Float(_igniteField, null).ToString("0.#") + "%,每一跳触发系数 " + Float(_procField, null).ToString("0.####")); } } catch (Exception ex) { Log((LogLevel)4, "火焰喷射器:改动时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static void ApplyRange(Flamethrower instance) { if (!(_maxDistanceField == null)) { if (_baseMaxDistance < 0f) { _baseMaxDistance = Float(_maxDistanceField, instance); } float num = RangeMultiplier(); if (!(num <= 0f) && !(Mathf.Abs(num - 1f) < 0.0001f)) { _maxDistanceField.SetValue(instance, _baseMaxDistance * num); } } } private static void ApplyIgnite() { if (!(_igniteField == null)) { if (_baseIgniteChance < 0f) { _baseIgniteChance = Float(_igniteField, null); } _igniteField.SetValue(null, IgniteEveryTick() ? 100f : _baseIgniteChance); } } private static bool PrepareFields() { if (_fieldsTried) { if (_durationField != null) { return _tickDamageField != null; } return false; } _fieldsTried = true; Type? typeFromHandle = typeof(Flamethrower); _durationField = AccessTools.Field(typeFromHandle, "flamethrowerDuration"); _tickDamageField = AccessTools.Field(typeFromHandle, "tickDamageCoefficient"); _tickFrequencyField = AccessTools.Field(typeFromHandle, "tickFrequency"); _totalDamageField = AccessTools.Field(typeFromHandle, "totalDamageCoefficient"); _entryDurationField = AccessTools.Field(typeFromHandle, "entryDuration"); _procField = AccessTools.Field(typeFromHandle, "procCoefficientPerTick"); _maxDistanceField = AccessTools.Field(typeFromHandle, "maxDistance"); _igniteField = AccessTools.Field(typeFromHandle, "ignitePercentChance"); _characterBodyProperty = AccessTools.Property(typeof(EntityState), "characterBody"); if (_durationField == null || _tickDamageField == null) { Log((LogLevel)2, "火焰喷射器:反射取不到 flamethrowerDuration / tickDamageCoefficient,改动不会生效。"); return false; } if (_maxDistanceField == null) { Log((LogLevel)4, "火焰喷射器:反射取不到 maxDistance,射程不会改。"); } if (_igniteField == null) { Log((LogLevel)4, "火焰喷射器:反射取不到 ignitePercentChance,点燃不会改。"); } return true; } private static float Float(FieldInfo field, object target) { if (field == null) { return 0f; } object value = field.GetValue(target); if (!(value is float)) { return 0f; } return (float)value; } private static float Duration() { if (_duration == null) { return 3f; } return _duration.Value; } private static bool KeepTickDamage() { if (_keepTickDamage != null) { return _keepTickDamage.Value; } return true; } private static float RangeMultiplier() { if (_rangeMultiplier == null) { return 2f; } return _rangeMultiplier.Value; } private static bool IgniteEveryTick() { if (_igniteEveryTick != null) { return _igniteEveryTick.Value; } return true; } private static bool KeepProcBudget() { if (_keepProcBudget != null) { return _keepProcBudget.Value; } return true; } private static bool HoldAim() { if (_holdAim != null) { return _holdAim.Value; } return true; } 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 ArtificerHoverSpeed { private const string ArtificerBodyToken = "MAGE_BODY_NAME"; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _multiplier; private static ConfigEntry<bool> _onlyArtificer; private static ConfigEntry<bool> _onlyWhileHovering; private static ManualLogSource _log; private static FieldInfo _moveSpeedField; private static MethodInfo _moveSpeedSetter; private static bool _moveSpeedWriterTried; private static readonly ConditionalWeakTable<CharacterBody, StrongBox<bool>> LastBoost = new ConditionalWeakTable<CharacterBody, StrongBox<bool>>(); private static readonly ConditionalWeakTable<CharacterBody, StrongBox<EntityStateMachine[]>> MachineCache = new ConditionalWeakTable<CharacterBody, StrongBox<EntityStateMachine[]>>(); private static float _nextTick; private static bool _appliedLogged; private static bool _machinesLogged; 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>("Hover", "Enabled", true, "是否启用「悬浮时移动速度 ×1.25」:工匠用被动(按住跳跃)悬在空中时,\n移动速度额外乘一个独立倍率,不与其他移速加成相加。"); _multiplier = cfg.Bind<float>("Hover", "Multiplier", 1.25f, "悬浮时的移动速度倍率(1.25 = +25%)。独立乘区:在属性结算完成后单独乘最终移速,\n不与能量饮料之类的加成加算。填 1 = 不加成。这个值是运行期读取的(改 cfg 立即生效)。"); _onlyArtificer = cfg.Bind<bool>("Hover", "OnlyArtificer", true, "只对工匠(Mage / Artificer)生效。false = 任何角色只要悬浮就加速(包括用喷射背包的其他角色)。"); _onlyWhileHovering = cfg.Bind<bool>("Hover", "OnlyWhileHovering", true, "true = 只在被动悬浮真正生效(JetpackOn 状态)时加速;\nfalse = 放宽成只要双脚离地就加速(跳跃、坠落、被击飞都算)。"); } public static string Summary() { if (!IsEnabled) { return " [悬浮:原版未改动]"; } return " [悬浮:移速 ×" + Multiplier().ToString("0.##") + ((_onlyWhileHovering == null || _onlyWhileHovering.Value) ? "(被动悬浮中)" : "(离地即生效)") + "]"; } private static float Multiplier() { if (_multiplier == null) { return 1.25f; } return _multiplier.Value; } private static bool OnlyArtificer() { if (_onlyArtificer != null) { return _onlyArtificer.Value; } return true; } private static bool OnlyWhileHovering() { if (_onlyWhileHovering != null) { return _onlyWhileHovering.Value; } return true; } public static void Patch(Harmony harmony) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown if (!IsEnabled) { Log((LogLevel)16, "悬浮加速:按配置保持原版,不挂任何补丁。"); return; } MethodInfo methodInfo = AccessTools.Method(typeof(CharacterBody), "RecalculateStats", (Type[])null, (Type[])null); if (methodInfo == null) { Log((LogLevel)2, "悬浮加速补丁失败:取不到 CharacterBody.RecalculateStats。"); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(ArtificerHoverSpeed), "RecalculateStatsPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:CharacterBody.RecalculateStats <- RecalculateStatsPostfix(悬浮时移速独立乘 " + Multiplier().ToString("0.##") + ")"); EnsureMoveSpeedWriter(); } public static void RecalculateStatsPostfix(CharacterBody __instance) { try { if ((Object)(object)__instance == (Object)null || !ShouldBoost(__instance)) { return; } float num = Multiplier(); if (!(num <= 0f) && !Mathf.Approximately(num, 1f)) { float moveSpeed = __instance.moveSpeed; float num2 = moveSpeed * num; if (!(num2 <= moveSpeed) && SetMoveSpeed(__instance, num2) && !_appliedLogged) { _appliedLogged = true; Log((LogLevel)16, "悬浮加速已生效:" + BodyName(__instance) + " 移速 " + moveSpeed.ToString("0.##") + " → " + num2.ToString("0.##") + "(×" + num.ToString("0.##") + " 独立乘区)"); } } } catch (Exception ex) { Log((LogLevel)4, "悬浮加速:属性结算异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } public static void Tick() { if (!IsEnabled || Time.unscaledTime < _nextTick) { return; } _nextTick = Time.unscaledTime + 0.1f; try { ReadOnlyCollection<CharacterBody> readOnlyInstancesList = CharacterBody.readOnlyInstancesList; for (int i = 0; i < readOnlyInstancesList.Count; i++) { CharacterBody val = readOnlyInstancesList[i]; if ((Object)(object)val == (Object)null) { continue; } bool flag = ShouldBoost(val); if (!LastBoost.TryGetValue(val, out var value)) { value = new StrongBox<bool>(flag); LastBoost.Add(val, value); } else if (value.Value != flag) { value.Value = flag; if (flag) { val.RecalculateStats(); } else { val.RecalculateStats(); } } } } catch (Exception ex) { Log((LogLevel)4, "悬浮加速:状态扫描异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static bool ShouldBoost(CharacterBody body) { if (!IsEnabled || (Object)(object)body == (Object)null) { return false; } if (OnlyArtificer() && !IsArtificer(body)) { return false; } if (OnlyWhileHovering()) { return IsHovering(body); } return IsAirborne(body); } private static bool IsHovering(CharacterBody body) { EntityStateMachine[] machines = GetMachines(body); if (machines == null) { return false; } foreach (EntityStateMachine val in machines) { if (!((Object)(object)val == (Object)null) && val.state is JetpackOn) { return true; } } return false; } private static EntityStateMachine[] GetMachines(CharacterBody body) { if (MachineCache.TryGetValue(body, out var value)) { return value.Value; } EntityStateMachine[] components = ((Component)body).GetComponents<EntityStateMachine>(); MachineCache.Add(body, new StrongBox<EntityStateMachine[]>(components)); if (!_machinesLogged) { _machinesLogged = true; string text = ""; for (int i = 0; i < components.Length; i++) { if (!((Object)(object)components[i] == (Object)null)) { text = text + ((text.Length > 0) ? " / " : "") + components[i].customName; } } Log((LogLevel)16, "悬浮加速:已缓存 " + BodyName(body) + " 的状态机(" + components.Length + " 台:" + text + "),靠 JetpackOn 状态判断悬浮。"); } return components; } private static bool IsAirborne(CharacterBody body) { CharacterMotor characterMotor = body.characterMotor; if ((Object)(object)characterMotor != (Object)null) { return !characterMotor.isGrounded; } return false; } private static bool IsArtificer(CharacterBody body) { if (!string.IsNullOrEmpty(body.baseNameToken) && string.Equals(body.baseNameToken, "MAGE_BODY_NAME", StringComparison.Ordinal)) { return true; } if (!string.IsNullOrEmpty(((Object)body).name)) { return ((Object)body).name.IndexOf("MageBody", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static bool SetMoveSpeed(CharacterBody body, float value) { EnsureMoveSpeedWriter(); if (_moveSpeedField != null) { _moveSpeedField.SetValue(body, value); return true; } if (_moveSpeedSetter != null) { _moveSpeedSetter.Invoke(body, new object[1] { value }); return true; } return false; } private static void EnsureMoveSpeedWriter() { if (!_moveSpeedWriterTried) { _moveSpeedWriterTried = true; _moveSpeedField = AccessTools.Field(typeof(CharacterBody), "<moveSpeed>k__BackingField"); if (_moveSpeedField == null) { _moveSpeedField = AccessTools.Field(typeof(CharacterBody), "moveSpeed"); } _moveSpeedSetter = typeof(CharacterBody).GetProperty("moveSpeed", BindingFlags.Instance | BindingFlags.Public).GetSetMethod(nonPublic: true); if (_moveSpeedField == null && _moveSpeedSetter == null) { Log((LogLevel)2, "悬浮加速:取不到 CharacterBody.moveSpeed 的写入方式,加速不会生效。"); } } } private static string BodyName(CharacterBody body) { if (!((Object)(object)body != (Object)null) || string.IsNullOrEmpty(((Object)body).name)) { return "(未知角色)"; } return ((Object)body).name; } 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 ArtificerNanoSwarm { private const string SkillLabel = "攻击纳米枪"; private const int Freeze2sBit = 256; private static ConfigEntry<bool> _enabled; private static ConfigEntry<float> _maxDamageMultiplier; private static ConfigEntry<bool> _frostEnabled; private static ConfigEntry<float> _frostDuration; private static ConfigEntry<int> _frostStacks; private static ConfigEntry<bool> _updateDescription; private static ConfigEntry<string> _projectileName; private static ManualLogSource _log; private const float FallbackMaxCoefficient = 12f; private static float _baseMaxCoefficient = -1f; private static bool _damageLogged; private static BuffDef _frostDef; private static bool _frostDefTried; private static bool _frostLogged; 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>("NanoSwarm", "Enabled", true, "是否启用「攻击纳米枪」(工匠的替代辅助技能·冰)的这两处改动。"); _maxDamageMultiplier = cfg.Bind<float>("NanoSwarm", "MaxDamageMultiplier", 1.25f, "充能拉满时的最大伤害倍率 = 原版值 × 这个倍率。\n原版是 1200%,默认 1.25 → 1500%。填 1 = 保持原版。运行期读取。"); _frostEnabled = cfg.Bind<bool>("NanoSwarm", "FrostOnHit", true, "命中敌人后再给一层霜冻。"); _frostDuration = cfg.Bind<float>("NanoSwarm", "FrostDuration", 5f, "霜冻持续多少秒。运行期读取。"); _frostStacks = cfg.Bind<int>("NanoSwarm", "FrostMaxStacks", 3, "霜冻最多叠几层(原版冰霜一发给 6 层 6 秒,这里默认 3 层 5 秒)。运行期读取。"); _updateDescription = cfg.Bind<bool>("NanoSwarm", "UpdateSkillDescription", true, "把技能描述里的「400%-1200%」改成实际数值(比如「400%-1500%」)。\nfalse = 保持原版文案(游戏里显示的百分比会跟实际伤害对不上)。"); _projectileName = cfg.Bind<string>("NanoSwarm", "ProjectileName", "MageIceBombProjectile", "纳米枪射弹的对象名(认伤害来源用,不区分大小写)。"); } public static string Summary() { if (!IsEnabled) { return ""; } string text = " [攻击纳米枪:最大伤害 " + ((_baseMaxCoefficient > 0f) ? (_baseMaxCoefficient * Multiplier() * 100f).ToString("0") : "1500") + "%"; if (FrostEnabled()) { text = text + " + 命中给 " + FrostDuration().ToString("0.#") + " 秒 " + FrostStacks() + " 层霜冻"; } return text + "]"; } private static float Multiplier() { if (_maxDamageMultiplier == null) { return 1.25f; } return _maxDamageMultiplier.Value; } private static bool FrostEnabled() { if (_frostEnabled != null) { return _frostEnabled.Value; } return true; } private static float FrostDuration() { if (_frostDuration == null) { return 5f; } return _frostDuration.Value; } private static int FrostStacks() { int num = ((_frostStacks != null) ? _frostStacks.Value : 3); if (num >= 1) { return num; } return 1; } private static bool UpdateDescription() { if (_updateDescription != null) { return _updateDescription.Value; } return true; } public static float MaxDamagePercent() { if (!IsEnabled) { return 1200f; } return ((_baseMaxCoefficient > 0f) ? _baseMaxCoefficient : 12f) * Multiplier() * 100f; } public static void Patch(Harmony harmony) { if (!IsEnabled) { Log((LogLevel)16, "攻击纳米枪:按配置保持原版,不挂任何补丁。"); return; } PatchOne(harmony, typeof(BaseThrowBombState), "Fire", "ApplyMaxDamagePrefix", "抬高最大伤害倍率", asPrefix: true); PatchOne(harmony, typeof(GlobalEventManager), "OnHitEnemy", "OnHitEnemyPostfix", "命中后上霜冻"); } private static void PatchOne(Harmony harmony, Type targetType, string methodName, string patchName, string what, bool asPrefix = false) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { Log((LogLevel)2, "攻击纳米枪:取不到 " + targetType.Name + "." + methodName + "," + what + "不会生效。"); return; } try { HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(ArtificerNanoSwarm), patchName, (Type[])null, (Type[])null)); harmony.Patch((MethodBase)methodInfo, asPrefix ? val : null, asPrefix ? null : val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log((LogLevel)16, "补丁已挂载:" + targetType.Name + "." + methodName + (asPrefix ? " <- " : " -> ") + patchName + "(攻击纳米枪 · " + what + ")"); } catch (Exception ex) { Log((LogLevel)2, "攻击纳米枪补丁失败(" + what + " 不会生效)—— " + ex.GetType().Name + " " + ex.Message); } } public static void ApplyDescription(string language) { if (!IsEnabled || !UpdateDescription()) { return; } if (string.IsNullOrEmpty(language)) { Log((LogLevel)4, "攻击纳米枪:语言系统还没就绪,跳过文案更新。"); return; } try { string text = Language.GetString("MAGE_SECONDARY_ICE_DESCRIPTION"); if (string.IsNullOrEmpty(text)) { Log((LogLevel)4, "攻击纳米枪:读不到原版技能描述,跳过文案更新。"); return; } string text2 = ((_baseMaxCoefficient > 0f) ? (_baseMaxCoefficient * 100f).ToString("0") : "1200") + "%"; string text3 = ((_baseMaxCoefficient > 0f) ? (_baseMaxCoefficient * Multiplier() * 100f).ToString("0") : "1500") + "%"; if (text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0) { LanguageAPI.Add("MAGE_SECONDARY_ICE_DESCRIPTION", text.Replace(text2, text3), language); Log((LogLevel)16, "攻击纳米枪:技能描述已更新为 " + text3 + "(语言 " + language + ")。"); } } catch (Exception ex) { Log((LogLevel)4, "攻击纳米枪:更新技能描述时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } public static void ApplyMaxDamagePrefix(BaseThrowBombState __instance) { try { if (!IsEnabled || __instance == null || !(__instance is ThrowIcebomb)) { return; } float maxDamageCoefficient = __instance.maxDamageCoefficient; if (_baseMaxCoefficient <= 0f) { _baseMaxCoefficient = maxDamageCoefficient; } float num = _baseMaxCoefficient * Multiplier(); if (!(Mathf.Abs(maxDamageCoefficient - num) < 0.0001f)) { __instance.maxDamageCoefficient = num; if (!_damageLogged) { _damageLogged = true; Log((LogLevel)16, "攻击纳米枪:最大伤害倍率 " + (maxDamageCoefficient * 100f).ToString("0") + "% → " + (num * 100f).ToString("0") + "%(最小倍率不动,还是原版的 " + (__instance.minDamageCoefficient * 100f).ToString("0") + "%)"); } } } catch (Exception ex) { Log((LogLevel)4, "攻击纳米枪:改最大伤害倍率时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } public static void OnHitEnemyPostfix(object[] __args) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown try { if (!IsEnabled || !FrostEnabled() || __args == null || __args.Length < 2) { return; } DamageInfo val = null; GameObject val2 = null; foreach (object obj in __args) { if (obj is DamageInfo) { val = (DamageInfo)obj; } else if (obj is GameObject) { val2 = (GameObject)obj; } } if (val == null || (Object)(object)val2 == (Object)null || !IsNanoSwarmHit(val)) { return; } CharacterBody component = val2.GetComponent<CharacterBody>(); if ((Object)(object)component == (Object)null || !NetworkServer.active) { return; } BuffDef val3 = FrostBuffDef(); if (!((Object)(object)val3 == (Object)null)) { component.AddTimedBuff(val3, FrostDuration(), FrostStacks()); if (!_frostLogged) { _frostLogged = true; Log((LogLevel)16, "攻击纳米枪:命中已上霜冻 —— " + FrostDuration().ToString("0.#") + " 秒 / 最多 " + FrostStacks() + " 层,目标 " + ((Object)val2).name); } } } catch (Exception ex) { Log((LogLevel)4, "攻击纳米枪:上霜冻时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static bool IsNanoSwarmHit(DamageInfo damageInfo) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Invalid comparison between Unknown and I4 GameObject inflictor = damageInfo.inflictor; if ((Object)(object)inflictor == (Object)null || string.IsNullOrEmpty(((Object)inflictor).name)) { return false; } string value = ((_projectileName != null) ? _projectileName.Value : "MageIceBombProjectile"); if (string.IsNullOrEmpty(value) || ((Object)inflictor).name.IndexOf(value, StringComparison.OrdinalIgnoreCase) < 0) { return false; } return (damageInfo.damageType.damageType & 0x100) > 0; } private static BuffDef FrostBuffDef() { if (_frostDefTried) { return _frostDef; } _frostDefTried = true; string[] array = new string[5] { "RoR2.DLC2Content+Buffs", "RoR2.RoR2Content+Buffs", "DLC2Content+Buffs", "RoR2Content+Buffs", "RoR2.Buffs" }; foreach (string text in array) { Type type = AccessTools.TypeByName(text); if (type == null) { continue; } FieldInfo fieldInfo = AccessTools.Field(type, "Frost"); if (!(fieldInfo == null)) { object? value = fieldInfo.GetValue(null); _frostDef = (BuffDef)((value is BuffDef) ? value : null); if ((Object)(object)_frostDef != (Object)null) { Log((LogLevel)16, "攻击纳米枪:已取到霜冻 buff(" + text + ".Frost = " + ((Object)_frostDef).name + ")。"); return _frostDef; } } } _frostDef = FindBuffByName("frost"); if ((Object)(object)_frostDef != (Object)null) { Log((LogLevel)16, "攻击纳米枪:从 BuffCatalog 里找到霜冻 buff(" + ((Object)_frostDef).name + ")。"); return _frostDef; } Log((LogLevel)2, "攻击纳米枪:拿不到霜冻 buff,上霜冻这一条不会生效。"); return null; } private static BuffDef FindBuffByName(string keyword) { FieldInfo fieldInfo = AccessTools.Field(typeof(BuffCatalog), "buffDefs"); if (fieldInfo == null) { return null; } if (!(fieldInfo.GetValue(null) is Array array)) { return null; } foreach (object item in array) { BuffDef val = (BuffDef)((item is BuffDef) ? item : null); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(((Object)val).name) && ((Object)val).name.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) { return val; } } 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 ArtificerSkillText { private const string PlasmaBoltToken = "MAGE_PRIMARY_LIGHTNING_DESCRIPTION"; private const string NanoBombToken = "MAGE_SECONDARY_LIGHTNING_DESCRIPTION"; private const string NanoSwarmToken = "MAGE_SECONDARY_ICE_DESCRIPTION"; private const string MaxPlaceholder = "{MAX}"; private static ConfigEntry<bool> _enabled; private static ConfigEntry<bool> _onlyChinese; private static ConfigEntry<string> _plasmaBoltText; private static ConfigEntry<string> _nanoBombText; private static ConfigEntry<string> _nanoSwarmText; private static ManualLogSource _log; private static string _appliedLanguage; public static void Init(ConfigFile cfg, ManualLogSource logger) { _log = logger; _enabled = cfg.Bind<bool>("SkillText", "Enabled", true, "是否改写工匠三个技能在游戏里显示的技能描述。false = 全部保持原版文案。"); _onlyChinese = cfg.Bind<bool>("SkillText", "OnlyChinese", true, "只在游戏语言是中文时改写。false = 不管什么语言都套用下面这几条中文文案。"); _plasmaBoltText = cfg.Bind<string>("SkillText", "PlasmaBoltText", "发射一道能量矢,造成<style=cIsDamage>280%的伤害</style>并<style=cIsDamage>产生连锁闪电</style>。最多发射4次。", "等离子弹的技能描述。留空 = 保持原版。可写 \\n 换行,也保留 <style=cIsDamage> 这类原版富文本标记。"); _nanoBombText = cfg.Bind<string>("SkillText", "NanoBombText", "<style=cIsDamage>眩晕</style>。发射一颗会<style=cIsDamage>产生连锁闪电</style>的纳米炸弹,根据充能造成<style=cIsDamage>400%-2000%</style>的伤害。", "充能完毕的纳米炸弹的技能描述。留空 = 保持原版。"); _nanoSwarmText = cfg.Bind<string>("SkillText", "NanoSwarmText", "<style=cIsUtility>霜冻</style>。<style=cIsUtility>冰冻</style>。使用拥有<style=cIsDamage>穿透</style>效果的纳米枪发动攻击,根据充能造成<style=cIsDamage>400%-{MAX}%</style>的伤害。", "攻击纳米枪的技能描述。留空 = 保持原版。\n{MAX} 会自动换成实际最大伤害倍率(默认 1500,跟着 NanoSwarm.MaxDamageMultiplier 走)。"); } public static string CurrentLanguage() { try { string currentLanguageName = Language.currentLanguageName; if (!string.IsNullOrEmpty(currentLanguageName)) { return currentLanguageName; } Language currentLanguage = Language.currentLanguage; if (currentLanguage != null && !string.IsNullOrEmpty(currentLanguage.selfName)) { return currentLanguage.selfName; } } catch (Exception) { } return null; } public static void Tick() { if (!Enabled()) { return; } string text = CurrentLanguage(); if (string.IsNullOrEmpty(text) || string.Equals(_appliedLanguage, text, StringComparison.Ordinal)) { return; } _appliedLanguage = text; try { if (OnlyChinese() && !IsChinese(text)) { Log((LogLevel)16, "技能文案:当前语言是 " + text + "(不是中文),按配置跳过改写。"); return; } Apply("MAGE_PRIMARY_LIGHTNING_DESCRIPTION", Text(_plasmaBoltText, null), "等离子弹", text); Apply("MAGE_SECONDARY_LIGHTNING_DESCRIPTION", Text(_nanoBombText, null), "充能完毕的纳米炸弹", text); Apply("MAGE_SECONDARY_ICE_DESCRIPTION", Text(_nanoSwarmText, "{MAX}"), "攻击纳米枪", text); } catch (Exception ex) { Log((LogLevel)4, "技能文案:改写技能描述时异常(已忽略)—— " + ex.GetType().Name + " " + ex.Message); } } private static void Apply(string token, string text, string label, string language) { if (string.IsNullOrEmpty(text)) { Log((LogLevel)16, "技能文案:" + label + " 的文案是空的,保持原版。"); return; } LanguageAPI.Add(token, text, language); string text2 = ReadBack(token, text); Log((LogLevel)16, "技能文案:" + label + "(" + token + ",语言 " + language + ")已改为 —— " + Plain(text) + (string.Equals(text2, text, StringComparison.Ordinal) ? "" : (" ⚠\ufe0f 回读不一致,实际生效的是:" + Plain(text2)))); } private static string ReadBack(string token, string expected) { try { return Language.GetString(token) ?? expected; } catch (Exception) { return expected; } } private static string Text(ConfigEntry<string> entry, string placeholder) { if (entry == null) { return null; } string value = entry.Value; if (string.IsNullOrEmpty(value)) { return null; } value = value.Replace("\\n", "\n"); if (!string.IsNullOrEmpty(placeholder) && value.IndexOf(placeholder, StringComparison.Ordinal) >= 0) { value = value.Replace(placeholder, MaxDamagePercent().ToString("0")); } return value; } private static float MaxDamagePercent() { float num = ArtificerNanoSwarm.MaxDamagePercent(); if (!(num > 0f)) { return 1500f; } return num; } private static string Plain(string text) { string text2 = text.Replace("\n", " "); while (true) { int num = text2.IndexOf("<", StringComparison.Ordinal); if (num < 0) { break; } int num2 = text2.IndexOf(">", num, StringComparison.Ordinal); if (num2 < 0) { break; } text2 = text2.Substring(0, num) + text2.Substring(num2 + 1); } return text2; } private static bool IsChinese(string languageName) { if (string.IsNullOrEmpty(languageName)) { return false; } if (languageName.IndexOf("zh", StringComparison.OrdinalIgnoreCase) < 0 && languageName.IndexOf("CN", StringComparison.OrdinalIgnoreCase) < 0) { return languageName.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static bool Enabled() { if (_enabled != null) { return _enabled.Value; } return true; } private static bool OnlyChinese() { if (_onlyChinese != null) { return _onlyChinese.Value; } return true; } 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.sparkleartificertweaks", "SparkleArtificerTweaks", "1.10.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class SparkleArtificerTweaksPlugin : BaseUnityPlugin { public const string PluginGUID = "com.sparkletwilight.sparkleartificertweaks"; public const string PluginName = "SparkleArtificerTweaks"; public const string PluginVersion = "1.10.0"; private bool _descriptionApplied; public void Awake() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown ArtificerHoverSpeed.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); ArtificerChainLightning.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); ArtificerNanoSwarm.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); ArtificerSkillText.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); ArtificerFlamethrower.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); Harmony harmony = new Harmony("com.sparkletwilight.sparkleartificertweaks"); SafePatch("悬浮移速", delegate { ArtificerHoverSpeed.Patch(harmony); }); SafePatch("连锁闪电", delegate { ArtificerChainLightning.Patch(harmony); }); SafePatch("攻击纳米枪", delegate { ArtificerNanoSwarm.Patch(harmony); }); SafePatch("火焰喷射器", delegate { ArtificerFlamethrower.Patch(harmony); }); ((BaseUnityPlugin)this).Logger.LogInfo((object)("SparkleArtificerTweaks v1.10.0 已加载。" + ArtificerHoverSpeed.Summary() + ArtificerChainLightning.Summary() + ArtificerFlamethrower.Summary())); } private void SafePatch(string label, Action patch) { try { patch(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("SparkleArtificerTweaks:「" + label + "」挂补丁时抛异常,已跳过该模块 —— " + ex.GetType().Name + " " + ex.Message)); } } public void Update() { ArtificerHoverSpeed.Tick(); if (!_descriptionApplied) { string text = ArtificerSkillText.CurrentLanguage(); if (!string.IsNullOrEmpty(text)) { _descriptionApplied = true; ArtificerNanoSwarm.ApplyDescription(text); ((BaseUnityPlugin)this).Logger.LogInfo((object)("SparkleArtificerTweaks:" + ArtificerHoverSpeed.Summary() + ArtificerChainLightning.Summary() + ArtificerNanoSwarm.Summary() + ArtificerFlamethrower.Summary())); } } ArtificerSkillText.Tick(); } } }