Создаём кастомное взаимодействие
Используем класс SimpleInteractionProvider
Наи простейший способ создания кастомных взаимодействий - использование класса SimpleInteractionProvider
. Он позволяет вам использовать все принципы объектно-ориентированного программирования и упрощает написание кода. Используйте методы RogueInteractions.CreateProvider<T>
для создания экземпляров этого класса. Добавлять кнопки вы можете с помощью h.AddButton
внутри обработчика.
RogueInteractions.CreateProvider<Crate>(static h => /* h - handler (обработчик) */
{
// При взаимодействии через взлом, не добавлять кнопку
if (h.Helper.interactingFar) return;
InvItem crateOpener = h.Agent.inventory.FindItem("CrateOpener");
if (crateOpener is not null)
{
// Добавить кнопку с именем "UseCrateOpener", со строкой " (<count>) -1", добавленной в конец
string extra = $" ({crateOpener.invItemCount}) -1";
h.AddButton("UseCrateOpener", extra, static m => /* m - interaction model (модель взаимодействия) */
{
m.Agent.inventory.SubtractFromItemCount(m.Agent.inventory.FindItem("CrateOpener"), 1);
m.Object.UnlockCrate();
m.Object.ShowChest();
});
}
});
// Не забудьте добавить строки локализации для "UseCrateOpener"
RogueLibs.CreateCustomName("UseCrateOpener", NameTypes.Interface,
new CustomNameInfo("Use Crate Opener"));
Методы обработчиков должны быть чистыми, а то есть, они не должны вносить какие-либо видимые изменения. Вся логика должна находиться внутри кнопок, остановочных обратных вызовов и побочных эффектах.
Если вам надо что-то сделать сразу после взаимодействия с объектом, используйте побочные эффекты. НЕ ПИШИТЕ такой код в обработчике взаимодействия, так как он также используется для определения взаимодействуемости объекта и вызывается очень часто.
Если у вас запутанная логика добавления кнопок, вы можете перенести их действия в локальные или объявленные методы:
RogueInteractions.CreateProvider<Crate>(static h =>
{
static void UseCrateOpener(InteractionModel<Crate> model)
{
/* ... */
}
h.AddButton("UseCrateOpener", UseCrateOpener);
});
Указывая типовой параметр в методе (как в CreateProvider<Crate>
), вы сужаете круг объектов, к которым вы хотите добавить взаимодействия. Если ваше действие может касаться нескольких типов объектов, вы можете использовать более общий метод CreateProvider
, который срабатывает на всех типах объектов.
RogueInteractions.CreateProvider(static h =>
{
if (h.Object is Crate)
h.AddButton("UseCrateOpener", static m => { /* ... */ });
else if (h.Object is Safe)
h.AddButton("UseSafeOpener", static m => { /* ... */ });
else if (h.Object is Agent)
h.AddButton("UseSkullOpener", static m => { /* ... */ });
});
Не обращайтесь к h
или к другим переменным внутри действий кнопок, так как они вызываются в разных фазах процесса взаимодействия (будет кинуто исключение). Я рекомендую использовать ключевое слово static
при написании лямбд выражений чтобы этого избежать.
Неявные кнопки
Иногда кнопки представляют из себя такие очевидные действия, что вы не хотите чтобы игрок явно нажимал их. Например, двери. Это было бы довольно неудобно нажимать "Открыть" каждый раз когда вы взаимодействуете с дверью. Неявная кнопка нажимается автоматически, если она - единственная в меню; иначе, она работает как обычная кнопка.
RogueInteractions.CreateProvider<Crate>(static h =>
{
h.AddImplicitButton("InspectWeirdCrate", static m =>
{
/* ... */
m.Agent.SayDialogue("InspectWeirdCrate");
});
if (h.Agent.inventory.HasItem("CrateOpener"))
{
h.AddButton("UseCrateOpener", static m => { /* ... */ });
}
});
Если у игрока нет Открывашки ящиков, кнопка "InspectWeirdCrate"
нажмётся сразу же, даже не показывая сами кнопки. А вот если у игрока есть Открывашка ящиков, то откроется меню с 2 кнопками (2, не считая кнопки "Готово"
).
Остановка взаимодействия
Если ваше действие ужасно провалилось, не позволяя игроку нажать какие-либо другие кнопки, или вы хотите чтобы игрок пошёл делать что-то другое сразу после этого, используйте m.StopInteraction()
.
RogueInteractions.CreateProvider<Crate>(static h =>
{
if (h.Helper.interactingFar) return;
if (h.Agent.HasTrait("CrateBomber"))
{
h.AddButton("TriggerBomb", static m =>
{
m.gc.spawnerMain.SpawnExplosion(m.Object, m.Object.tr.position, "Big");
m.StopInteraction();
})
}
});
Тут также есть перегрузка принимающая forced: bool
параметр. По умолчанию, остановка взаимодействия откладывается, пока не будут выполнены все взаимодействия и побочные эффекты. Если вы передадите true
в качестве аргумента, взаимодействие будет остановлено сразу после вызова StopInteraction(true)
.
Используйте это при открытии другого меню или перенаправления взаимодействия на другой объект.
Остановочные обратные вызовы
Если конкретно ваше действие не удалось, но другие взаимодействия из других модов всё ещё могут работать, используйте остановочные обратные вызовы. Они вызываются только если в меню нет никаких кнопок, или если взаимодействие было остановлено с помощью StopInteraction
.
Остановочные обратные вызовы обычно используются для передачи информации, почему взаимодействие оказалось неудачным.
RogueInteractions.CreateProvider<Crate>(static h =>
{
if (h.Helper.interactingFar) return;
if (!h.Agent.inventory.HasItem("CrateOpener"))
{
h.SetStopCallback(static m =>
{
m.gc.audioHandler.Play(m.Agent, "CantDo");
m.Agent.SayDialogue("NeedCrateOpener");
});
}
/* ... */
});
По умолчанию, SetStopCallback
переопределяет ранее выставленные остановочные обратные вызовы. Если вы хотите совместить свой с ранее выставленными, используйте CombineStopCallback
.
Побочные эффекты
Иногда вам может понадобиться сделать что-то сразу после настройки кнопок. Например, заставить цель взаимодействия отреагировать на ваше взаимодействие, или взорвать бомбу как только кто-то её тронет. Побочные эффекты вызываются сразу после настройки кнопок, но перед остановочными обратными вызовами. Так что, побочные эффекты могут быть вызваны, даже если взаимодействие провалилось или в меню нет каких-либо кнопок.
RogueInteractions.CreateProvider<Crate>(static h =>
{
// Заставить игрока сказать что-то сразу после взаимодействия
// с ящиком, даже если у них нету Открывавшки ящиков
h.SetSideEffect(static m => m.Agent.SayDialogue("DialogueWeirdCrate"));
if (h.Agent.inventory.HasItem("CrateOpener"))
{
h.AddButton("UseCrateOpener", static m =>
{
/* ... */
});
}
});
По умолчанию, SetSideEffect
переопределяет ранее выставленные побочные эффекты. Если вы хотите совместить свой с ранее выставленными, используйте CombineSideEffect
.
Манипуляции с кнопками
Класс SimpleInteractionProvider
содержит методы: HasButton
и RemoveButton
.
Используйте их для аугментирования или изменения ванильных взаимодействий или взаимодействий от других модов.
RogueInteractions.CreateProvider<Door>(static h =>
{
if (h.Agent.HasTrait("KeyIlliterate"))
{
if (h.HasButton("UseKey"))
{
h.RemoveButton("UseKey");
h.SetStopCallback(static m => m.Agent.SayDialogue("IlliterateCantUseKeys"));
}
if (h.HasButton("UseSkeletonKey"))
{
h.RemoveButton("UseSkeletonKey");
h.SetStopCallback(static m => m.Agent.SayDialogue("IlliterateCantUseKeys"));
}
}
});
Примеры
Вы можете найти целую кучу примеров тут (Исходный код RogueLibs, перереализовывающий ванильные взаимодействия).