Перейти к основному содержимому

Расширение модификации (API)

Если вы хотите реализовать своё задание, действие, условие, то мод позволяет это провернуть

Действие

Действия выполняются если нажатие кнопки произошло успешно (если для квеста не выполнены условия, кнопка перекидает на alt_to_go но не активирует действия, кроме save, close) Действия выполняются поочерёдно и со стороны сервера.

Для его добавления нужно наследовать класс AbstractAction

import net.noyji.thequestforge.api.quest.action.AbstractAction;

package paht;

public class YourAction extends AbstractAction {
@Override
public boolean handler(ActionContext context) {
//Логика действия которая возвращает true - всё прошло успешно, false - что-то пошло не так.
}

@Override
public ResourceLocation getLocation() {
// Тут должно быть ID вашего действия, как пример your_mod_id:name_action
// return TheQuestForge.id("accept");
}
}

В ActionContext находятся :

  • Player
  • Entity (NPC)
  • Нажатая кнопка диалога (TemplateDialogButton)

Далее регистрируем

import net.noyji.thequestforge.api.quest.registry.ActionRegistry;
import net.noyji.thequestforge.api.quest.action.AbstractAction;

public class YourModAxtionRegisty {
public static final DeferredRegister<AbstractAction> ACTIONS = DeferredRegister.create(ActionRegistry.ACTION_REGISTRY_KEY, YourMod.MODID);

public static final RegistryObject<AbstractAction> YOUR_ACTION = ACTIONS.register("your_action_name", () -> new YourAction());

public static void register(IEventBus eventBus) { ACTIONS.register(eventBus); }
}


Условия

Условиями можно контролировать появление квеста, и сделать так чтобы он генерировался только в нужных вам условиях. Условие проверяется в момент генерации квеста, (При первом взаимодействии с НПС ), со стороны сервера.

import net.noyji.thequestforge.api.quest.requirements.AbstractRequirement;

public class YourRequirement extends AbstractRequirement{
@Override
public boolean check(RequirementContext context) {
//Логика условия которая возвращает true - всё прошло успешно, false - что-то пошло не так.
}

@Override
public ResourceLocation getLocation() {
// Тут должно быть ID вашего условия, как пример your_mod_id:name_requirement
}
}

В RequirementContext находятся :

  • Player - сам игрок
  • Entity (NPC) - НПС с которой игрок говорит.
  • QuestRarity - Редкость квеста
  • String value - Дальнейшие значения после your_mod_id:name_requirement: ... , Пример есть thequestforge:biome_is_not:minecraft:plains, то value = minecraft:plains, то есть вся остальная строка после ID.
  • Map<String, String> custonData - Кастомные данные виде строки.
// Что бы добавить свою дату в квест
context.addCustomData("data_key", "string_data");

// моде есто используется для квестов, которые ведут поиск структур или биомов.
//context.addCustomData("direction", Util.getDirectionText(blockPos, blockPosTo));

//А кастомные данные можно достать из квеста.
String direction = quest.getCustomData("direction");
//Где quest это объект PlayerQuest или Quest.

И регистрация

import net.noyji.thequestforge.api.quest.registry.RequirementRegistry;
import net.noyji.thequestforge.api.quest.requirements.AbstractRequirement;

public class YourModRequirements {
public static final DeferredRegister<AbstractRequirement> REQUIREMENTS =
DeferredRegister.create(RequirementRegistry.REQUIREMENT_REGISTRY_KEY, YourMod.MODID);

public static final RegistryObject<AbstractRequirement> YOUR_REQUIREMENT = REQUIREMENTS.register("name_requirement", () -> new YourRequirement());

public static void register(IEventBus eventBus) {
REQUIREMENTS.register(eventBus);
}
}

Задание

Основная логика того что надо сделать чтобы прогресс квеста прибавился. Что надо для этого сделать :

  • Создать задание.
  • Зарегистрировать его.
  • Создать рендер данного задания в книге.
  • Зарегистрировать рендер.
  • Подписаться на ивент.

Для примера разберём существующее задание на сбор.

package net.noyji.thequestforge.api.quest.task;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.event.TickEvent;
import net.noyji.thequestforge.TheQuestForge;
import net.noyji.thequestforge.common.util.Util;
import net.noyji.thequestforge.data.group.components.JsonTask;
import net.noyji.thequestforge.data.quest.player.components.QuestRarity;

//TickEvent.PlayerTickEvent - это ивент не который мы подписались.
public class CollectTask extends AbstractTask<TickEvent.PlayerTickEvent>{
//В классе AbstractTask уже есть поля int goal - цель которцю надо достич, int progress = 0 - текущий прогресс.

//Кастомное поля только для этого задания.
private ItemStack itemStack;

public CollectTask() {
}
//Это метод нужен для рендера и он тоже только для данного класса.
public ItemStack getItemStack(){
return itemStack;
}

//ID того чего нам нада, в данном случае (ItemStack).
@Override
public ResourceLocation getTarget() {
return Util.getItemResourceLocation(itemStack);
}

//ID задания.
@Override
public ResourceLocation getLocation() {
return TheQuestForge.id("collect");
}

//Тут обязвтельно нужно вернуть класс вашего ивента.
@Override
public Class<TickEvent.PlayerTickEvent> getEventClass() {
return TickEvent.PlayerTickEvent.class;
}

//Парсер который превращяет JsonTask ваше задание.
@Override
public void parse(JsonTask jsonTask, RandomSource randomSource, QuestRarity rarity) {
if (jsonTask == null) return;
//Тут мы получаем количество нужных собираемых предметов через метод getCount(randomSource, rarity).
goal = jsonTask.getCount(randomSource, rarity);

//Тут мы получаем сам предмет через утилитарный метод, вы можете глянуть как он работает в исходниках, а так скажу, что в jsonTask есть методы ResourceLocation getTarget(), CompoundTag getTag(), List<EnchantmentContext> getEnchantments(RandomSource randomSource)
itemStack = Util.parseItemStack(jsonTask, goal, randomSource);
}

// Что делать если квест выполнен и сдается. Впримере просто удаляем небхожимые предметы, необязательный метод.
@Override
public void inComplete(Player player) {
Util.removeItemFromPlayer(player, itemStack, progress);
}

// Получения имени целя для того что бы отобразить его при разговоре с НПС, пример (Блок золота)
@Override
public String getTargetName() {
return itemStack.getCount() + " " + itemStack.getDisplayName().getString().replace("[", "").replace("]", "");
}

//Обработчик задания, в примере прогресс ставится на значение предметов target в инвентаре игрока, со стороны сервера.
@Override
public void handle(TickEvent.PlayerTickEvent event) {
Player player = event.player;

progress = Util.countMatchingItems(player, itemStack);
}

//Необязательный метод дебага.
@Override
public void info() {
TheQuestForge.LOGGER.debug("{}: Target: {} Goal: {}", getLocation(), getTarget(), goal);
}

//Ну и обязательно сохранение и загрузка.
@Override
public void serializeNBT(CompoundTag nbt) {
nbt.putInt("Goal", this.goal);
nbt.putInt("Count", this.progress);

CompoundTag itemTag = new CompoundTag();
if (this.itemStack != null && !this.itemStack.isEmpty()) {
this.itemStack.save(itemTag);
}
nbt.put("ItemStack", itemTag);
}

@Override
public void deserializeNBT(CompoundTag nbt) {
this.goal = nbt.getInt("Goal");
this.progress = nbt.getInt("Count");

if (nbt.contains("ItemStack")) {
this.itemStack = ItemStack.of(nbt.getCompound("ItemStack"));
} else {
this.itemStack = ItemStack.EMPTY;
}
}
}

Регистрация (Вашего задания)

public class YourTasks {
public static final DeferredRegister<TaskType<?>> TASK_TYPES = DeferredRegister.create(TaskHandlerRegistry.TASK_REGISTRY_KEY, YourMod.MODID);

public static final RegistryObject<TaskType<CraftTask>> YOUR_TASK = TASK_TYPES.register("name_task", () -> new TaskType<>(YourTask::new));

public static void register(IEventBus eventBus) {
TASK_TYPES.register(eventBus);
}
}

Это класс ивентов для триггера квестов

public class QuestEvents {
private static final ResourceLocation KILL_KEY = TheQuestForge.id("kill");
@SubscribeEvent
public static void onLivingDeath(LivingDeathEvent event) {
LivingEntity deathMob = event.getEntity();
if (deathMob.level().isClientSide()) return;

Entity killer = event.getSource().getEntity();

if (killer == null){
killer = deathMob.getLastAttacker();
}

if (killer instanceof Player player){
ResourceLocation mob = Util.getEntityResourceLocation(deathMob);
CapabilityUtil.getPlayerQuestData(player).progressUpdate(KILL_KEY, mob, event, player);
}
}

@SubscribeEvent
public static void onTickPlayerTick(TickEvent.PlayerTickEvent event) {
if (event.side.isClient() || event.phase == TickEvent.Phase.START) return;

Player player = event.player;

if ((player.tickCount + player.getId()) % 20 == 0){
//Тут немного другой метод нахождения потому отдельный метод
CapabilityUtil.getPlayerQuestData(player).checkCollectTasks(player, event);
}
}
}

Класс рендера

public class CollectTaskRenderer implements ITaskRenderer<CollectTask> {
// Имя таска, как пример имя предмета.
@Override
public Component getName(CollectTask task) {
if (task.getItemStack() == null || task.getItemStack().isEmpty() ) {
return Component.literal("Empty Item");
}
return task.getItemStack().getHoverName();
}

// Тут сама иконка предмета.
@Override
public void renderIcon(GuiGraphics guiGraphics, CollectTask task, int x, int y) {
if (task.getItemStack() != null && !task.getItemStack().isEmpty()) {
guiGraphics.renderItem(task.getItemStack(), x, y);
}
}
}

Регистрация рендера(Вашего)

import net.noyji.thequestforge.api.client.registry.TaskRendererRegistry;

@Mod.EventBusSubscriber(modid = YourMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)

public class YourSetup {
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
event.enqueueWork(() -> {
TaskRendererRegistry.register(YourTask.class, new YourTaskRenderer());
});
}
}

Плейсхолдеры

Для чего нужны плейсхолдеры уже известно, теперь узнаем как их делать.

public class YourPlaceholder implements IPlaceholderResolver {
@Override public String resolve(Player player, Entity entity, PlayerQuest quest, String[] args) {
// тут логика вашего плейсхолдера.
// Есть поддержка аргументов, "your_placeholder_name-arg1-arg2-agr3- и так далее", так что я думаю это даст вам много возможностей.
}
}

и регистрация

@Mod.EventBusSubscriber(modid = YourMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class YourPlaceholderRegistry {

@SubscribeEvent
public static void onCommonSetup(FMLCommonSetupEvent event) {
event.enqueueWork(() -> {
PlaceholderRegistry.register("your_placeholder_name", new YourPlaceholder());
});
}
}

Вот и всё!