Перейти к основному содержимому
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.