Перейти к основному содержимому
The project's repository is archived as part of the GitHub Archive Program. RogueLibs' code and the documentation will no longer be updated.

Утилиты для патчей

Атрибут RLSetup

Так как RogueLibs обрабатывает всё кастомное как классы, вы можете забыть инициализировать новый класс в Awake вашего плагина. Именно поэтому тут есть атрибут RLSetup. Вы можете добавить его к статическому методу и инициализировать ваш кастомный класс там.

MyCustomItem.cs
public class MyCustomItem : CustomItem
{
[RLSetup]
public static void Setup()
{
RogueLibs.CreateCustomItem<MyCustomItem>()
.WithName(new CustomNameInfo("Name"))
.WithDescription(new CustomNameInfo("Description"))
.WithSprite(Properties.Resources.Sprite)
.WithUnlock(new ItemUnlock());

RogueLibs.CreateCustomName("SomeName", "Dialogue", new CustomNameInfo("Text"));
}
}

Вам просто надо будет вызвать следующий метод в Awake вашего плагина:

MyCoolPlugin.cs
    public void Awake()
{
RogueLibs.LoadFromAssembly();
/* ... */
}
Совет от профи

Серьёзно, используйте его. Это также помогает с версионингом. Всё в одном месте.

RoguePatcher

RoguePatcher - маленький вспомогательный класс, делающий написание патчей немного быстрее и проще. Если вам надо больше контроля (порядок патчей, приоритет и т.п.), тогда используйте оригинальные методы Harmony.

RoguePatcher patcher = new RoguePatcher(this);

patcher.Postfix(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect));

patcher.Postfix(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) });

Совет от профи

Вместо указывания названий методов с помощью строк, указывайте их с помощью ключевого слова nameof. Используйте строковые названия только если метод, который вы хотите пропатчить, не публичный.

Методы-патчи должны иметь следующее имя: <ЦелевойТип>_<ЦелевойМетод>. В примере выше, RoguePatcher будет искать методы-патчи с названиями StatusEffects_hasStatusEffect и InvDatabase_ChooseArmor в классе вашего плагина.

Вы можете изменить тип, в котором будут искаться методы-патчи. Укажите его в конструкторе или выставьте свойство между патчами:

public class MyCoolPlugin : BaseUnityPlugin
{
public void Awake()
{
RoguePatcher patcher = new RoguePatcher(this, typeof(MyCoolPatches));

patcher.Postfix(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect));

patcher.TypeWithPatches = typeof(MyEvenCoolerPatches);

patcher.Postfix(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) });
}
}
public class MyCoolPatches
{
public static void StatusEffects_hasStatusEffect(StatusEffects __instance)
{
/* ... */
}
}
public class MyEvenCoolerPatches
{
public static void InvDatabase_ChooseArmor(InvDatabase __instance, string previousArmorName)
{
/* ... */
}
}

Вспомогательные методы для транспилирования

Транспиляторы довольно сложные.

Вот пример из RogueLibs:

public static IEnumerable<CodeInstruction> StatusEffects_AddStatusEffect(IEnumerable<CodeInstruction> codeEnumerable)
=> codeEnumerable.AddRegionAfter(
new Func<CodeInstruction, bool>[]
{
i => i.IsLdloc(),
i => i.opcode == OpCodes.Ldarg_3,
i => i.opcode == OpCodes.Stfld && i.StoresField(causingAgentField),
},
new Func<CodeInstruction[], CodeInstruction>[]
{
a => a[0],
_ => new CodeInstruction(OpCodes.Ldarg_0),
_ => new CodeInstruction(OpCodes.Call, typeof(RogueLibsPlugin).GetMethod(nameof(SetupEffectHook))),
});

private static readonly FieldInfo causingAgentField = typeof(StatusEffect).GetField(nameof(StatusEffect.causingAgent));

Избегайте тяжёлых вычислений

При написании предикатов, помните, что они могут быть вызваны сотни или тысячи раз. Например, вы можете заранее вычислить значение FieldInfo, используемое вашим предикатом, просто положите его в статическое поле, как в примере выше.

Такие тяжёлые вычисления могут стоить вам сотен миллисекунд времени запуска (или даже целые секунды, если вы работаете над крупным проектом).

Вот ещё один пример из RogueLibs:

public static IEnumerable<CodeInstruction> Unlocks_LoadInitialUnlocks(IEnumerable<CodeInstruction> codeEnumerable)
=> codeEnumerable.ReplaceRegion(
new Func<CodeInstruction, bool>[]
{
i => i.opcode == OpCodes.Callvirt && i.Calls(List_Unlock_GetEnumerator),
i => i.IsStloc(),
},
new Func<CodeInstruction, bool>[]
{
i => i.opcode == OpCodes.Callvirt,
i => i.opcode == OpCodes.Endfinally,
i => i.opcode == OpCodes.Ldarg_0,
},
new CodeInstruction[]
{
new CodeInstruction(OpCodes.Pop),
new CodeInstruction(OpCodes.Pop),
new CodeInstruction(OpCodes.Call, typeof(RogueLibsPlugin).GetMethod(nameof(LoadUnlockWrappersAndCategorize))),
});

private static readonly MethodInfo List_Unlock_GetEnumerator = typeof(List<Unlock>).GetMethod("GetEnumerator");

The project's repository is archived as part of the GitHub Archive Program. RogueLibs' code and the documentation will no longer be updated.