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

Расширение модификации (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());
});
}
}

Вот и всё!