Harmony是一个开放源代码库,旨在在运行时替换、修饰或修改任何现有C#方法。它的主要用在用Mono语言编写的游戏和插件,但是该技术可以与任何.NET版本一起使用。它还照顾对同一方法的多次更改(它们累积而不是覆盖)。
它为每个原始方法创建DynamicMethod方法,并向其织入代码,该代码在开始和结束时调用自定义方法。它还允许您编写过滤器来处理原始的IL代码,从而可以对原始方法进行更详细的操作。
使用Yet Another Zombie Defense HD作为本文修改的对象 同样使用BepinEx加载器加载dll
分析代码
打开dnspy找到ChainRaw分析判定代码
public override bool shoot(bool remoteShoot)
{
if ((this.ammoLeft > 0f && this.cooldown <= 0f) || remoteShoot)
{
this.chainsawRunning = true;
this.cooldown += this.cooldownTime;
TheGame.achievements[21].canReceive = false;
TheGame.achievements[22].canReceive = false;
this.flashlightShakeTime = 0.05f;
base.playShootSound();
this.shootTime += Time.deltaTime;
float num = 0.9f; //攻击距离
Vector2 vector = base.Owner.Position.toVector2();
this.target = vector + base.Owner.getShootDirection().toVector2() * num;
float num2 = 0.6f; //攻击宽度
Vector2 vector2 = base.Owner.getShootDirection().toVector2().perpendicular();
Vector2 vector3 = vector + vector2 * num2;
Vector2 vector4 = this.target + vector2 * num2;
Vector2 vector5 = this.target + vector2 * -num2;
Vector2 vector6 = vector + vector2 * -num2;
foreach (Enemy enemy in TheGame.Get.Level.Enemies)
{
if (!enemy.isDead)
{
Vector2 vector7 = enemy.Position.toVector2() + new Vector2(0f, 0.1f);
bool flag = true;
float num3 = global::Chainsaw.orderTest(vector3, vector4, vector7);
float num4 = global::Chainsaw.orderTest(vector4, vector5, vector7);
if (num3 * num4 < 0f)
{
flag = false;
}
else
{
float num5 = global::Chainsaw.orderTest(vector5, vector6, vector7);
float num6 = global::Chainsaw.orderTest(vector6, vector3, vector7);
if (num4 * num5 < 0f || num5 * num6 < 0f)
{
flag = false;
}
}
if (flag)
{
Vector3 vector8 = (vector + (this.target - vector).normalizedSafeCopy() * ((enemy.Position.toVector2() - vector).magnitude - 0.1f)).toVector3(base.transform.position.z);
enemy.killedByChainsaw = true;
enemy.getHit(5f * this.deltaTime, base.Owner.getShootDirection() * 3f, vector8, base.Owner as YAZDObject);
enemy.killedByChainsaw = false;
SoundModule.Get.PlayContinuousSequence(base.transform.GetChild(0), this.goreSound, base.transform.position);
}
}
}
return true;
}
return false;
}
- 攻击距离:
num=0.9f
(从玩家位置到target
点的距离) - 攻击宽度:
num2=0.6f
(左右各延伸0.6单位宽度) - 判定形状:由4个点组成的四边形:
- vector3 (左上起点)
- vector4 (左上终点)
- vector5 (右下终点)
- vector6 (右下起点)
所以需要修改num,num2,由于num,num2为局部变量,无法直接赋值,所以需要使用harmony的Transpiler工具操作IL码修改
代码
Transpiler基础概念
- 特殊补丁类型,直接操作IL代码
- 必须返回
IEnumerable<CodeInstruction>
[HarmonyPatch(typeof(RocketLauncher))]
class ChainSawPatches
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
static void DoAttackPostfix(Chainsaw __instance)
{
var traverse = Traverse.Create(__instance);
traverse.Field("effectiveRange").SetValue(15f);
traverse.Field("reloadTime").SetValue(0.001f);
}
}
[HarmonyPatch(typeof(Chainsaw), "shoot")]
class ChainsawPatch
{
static void Prefix(Chainsaw __instance, ref bool remoteShoot)
{
}
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.9f)
{
codes[i].operand = 5f;
}
else if (codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.6f)
{
codes[i].operand = 5f;
}
}
return codes;
}
}
完整代码如下
using System;
using HarmonyLib;
using BepInEx;
using System.Reflection.Emit;
using UnityEngine;
using BepInEx.Unity.Mono;
using System.Collections.Generic;
[BepInPlugin(guid,pname,version)]
public class Mod : BaseUnityPlugin
{
private const string guid = "com.example.mod";
private const string pname = "Example Mod";
private const string version = "1.0.0";
Harmony harmony = new Harmony(guid);
private void Awake()
{
harmony.PatchAll();
}
}
[HarmonyPatch(typeof(RocketLauncher))]
class ChainSawPatches
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
static void DoAttackPostfix(Chainsaw __instance)
{
var traverse = Traverse.Create(__instance);
traverse.Field("effectiveRange").SetValue(15f);
traverse.Field("reloadTime").SetValue(0.001f);
}
}
[HarmonyPatch(typeof(Chainsaw), "shoot")]
class ChainsawPatch
{
static void Prefix(Chainsaw __instance, ref bool remoteShoot)
{
}
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.9f)
{
codes[i].operand = 5f;
}
else if (codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.6f)
{
codes[i].operand = 5f;
}
}
return codes;
}
}