進捗の追加 (1.19.4)

概要

進捗の追加処理とMOD固有の進捗解除処理を追加します。

動作確認

2024年01月01日

  • Minecraft 1.19.4
  • Forge 45.2.0

解説

GitHub

ExampleTNT.java

ExampleTNT/src/main/java/com/tntmodders/exampletnt/ExampleTNT.java


package com.tntmodders.exampletnt;

import com.tntmodders.exampletnt.provider.*;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;

@Mod(ExampleTNT.MOD_ID)
public class ExampleTNT {

    public static final String MOD_ID = "exampletnt";

    public ExampleTNT() {
        IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
        modEventBus.addListener(this::registerProviders);
        ExampleTNTBlocks.register(modEventBus);
        ExampleTNTItems.register(modEventBus);
    }

    private void registerProviders(GatherDataEvent event) {
        DataGenerator gen = event.getGenerator();
        PackOutput packOutput = gen.getPackOutput();
        ExistingFileHelper fileHelper = event.getExistingFileHelper();
        gen.addProvider(event.includeClient(), new ExampleTNTItemModelProvider(packOutput, fileHelper));
        gen.addProvider(event.includeClient(), new ExampleTNTBlockStateProvider(packOutput, fileHelper));
        gen.addProvider(event.includeClient(), new ExampleTNTLangProvider.ExampleTNTLangUS(gen.getPackOutput()));
        gen.addProvider(event.includeClient(), new ExampleTNTLangProvider.ExampleTNTLangJP(gen.getPackOutput()));
        gen.addProvider(event.includeServer(), new ExampleTNTRecipeProvider(gen.getPackOutput()));
        gen.addProvider(event.includeServer(), new ExampleTNTAdvancementProvider(packOutput, event.getLookupProvider(), fileHelper));
    }
}

ExampleTNTAdvancementProvider.java

ExampleTNT/src/main/java/com/tntmodders/exampletnt/provider/ExampleTNTAdvancementProvider.java


package com.tntmodders.exampletnt.provider;

import com.tntmodders.exampletnt.ExampleTNT;
import com.tntmodders.exampletnt.ExampleTNTBlocks;
import com.tntmodders.exampletnt.ExampleTNTItems;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.FrameType;
import net.minecraft.advancements.critereon.ImpossibleTrigger;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.advancements.critereon.PlacedBlockTrigger;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.PackOutput;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.common.data.ForgeAdvancementProvider;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

public class ExampleTNTAdvancementProvider extends ForgeAdvancementProvider {
    public ExampleTNTAdvancementProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, ExistingFileHelper existingFileHelper) {
        super(output, registries, existingFileHelper, List.of(new ExampleTNTAdvancementGenerator()));
    }

    public static class ExampleTNTAdvancementGenerator implements AdvancementGenerator {

        @Override
        public void generate(HolderLookup.Provider registries, Consumer<Advancement> saver, ExistingFileHelper helper) {
            Advancement root = Advancement.Builder.advancement()
                    .display(new ItemStack(Blocks.TNT), Component.literal("ExampleTNT"), Component.translatable("block.minecraft.tnt"), new ResourceLocation(ExampleTNT.MOD_ID, "textures/block/large_tnt.png"), FrameType.TASK, true, false, true)
                    .addCriterion("has_tnt", InventoryChangeTrigger.TriggerInstance.hasItems(Blocks.TNT))
                    .save(saver, new ResourceLocation(ExampleTNT.MOD_ID, "root"), helper);

            Advancement large_tnt = Advancement.Builder.advancement()
                    .display(new ItemStack(ExampleTNTBlocks.LARGE_TNT.get()), Component.translatable("block.exampletnt.large_tnt"), Component.translatable("block.exampletnt.large_tnt"), null, FrameType.CHALLENGE, true, true, false)
                    .addCriterion("place_large_tnt", PlacedBlockTrigger.TriggerInstance.placedBlock(ExampleTNTBlocks.LARGE_TNT.get()))
                    .addCriterion("has_large_tnt", InventoryChangeTrigger.TriggerInstance.hasItems(ExampleTNTBlocks.LARGE_TNT.get()))
                    .parent(root)
                    .save(saver, new ResourceLocation(ExampleTNT.MOD_ID, "large_tnt"), helper);

            Advancement small_tnt = Advancement.Builder.advancement()
                    .display(new ItemStack(ExampleTNTItems.SMALL_TNT.get()), Component.translatable("item.exampletnt.small_tnt"), Component.translatable("item.exampletnt.small_tnt"), null, FrameType.GOAL, true, true, true)
                    .addCriterion("toss_small_tnt", new ImpossibleTrigger.TriggerInstance())
                    .rewards(AdvancementRewards.Builder.loot(new ResourceLocation("chests/spawn_bonus_chest")))
                    .parent(root)
                    .save(saver, new ResourceLocation(ExampleTNT.MOD_ID, "small_tnt"), helper);
        }
    }
}

ForgeAdvancementProviderを継承することで進捗を生成することができます。他のプロバイダーと同様にregisterProvidersで登録を行う必要があります。
AdvancementProvider.generate()内でAdvancement.Builderの処理を行うことで、各進捗を生成することが可能です。
Advancement.Builderの各処理について説明します。

display()

進捗のアイコンや説明文などを定義します。
第1引数はアイコンに使うアイテムをItemStack型で渡します。
第2引数、第3引数はそれぞれタイトルと説明文をComponent型で渡します。翻訳が必要なければliteral, 翻訳が必要ならtranslatableを使うことをおすすめします。
第4引数は進捗タブの背景を設定します。ResourseLocation型として、ネームスペースとパスを指定する必要があります。この引数はroot,つまり進捗の一番最初のもののみに渡す必要があるため、rootでない進捗にはnullを渡して問題ありません。
第5引数はFrameTypeを指定します。TASK, CHALLENGE, GOALの三種類があり、それぞれ進捗タブでの見た目や進捗達成時の音などが変わります。
第6引数は達成時にトースト通知を出すか否か、第7引数は達成時にチャット通知を(全プレイヤーに)出すか否か、第8引数は進捗未達成時に進捗タブに表示されるか否かをそれぞれ指定しています。
なお、display()メソッドは2つ存在しますが、もう一つはここまでの第1-8引数を一つのDisplayInfoクラスに統合して渡すものになります。

addCriterion()

進捗達成条件(Criterion)を設定します。このCriterionは複数設定することができ、その場合はその都度このメソッドを呼び登録して下さい。large_tntは2つのCriterionを登録しており、この2つともが達成された場合に進捗を開放するようにしています。
第1引数はCriterion名を登録しています。これは固有の名称にするようにして下さい。複数のCriterionを登録する際に同じ名称を重複させることはできません。コマンドや後述のaward()メソッドで利用できます。
第2引数でCriterionの達成条件を設定します。パッケージnet.minecraft.advancements.critereonに配置されているCriterionTriggerInstance継承オブジェクトを渡すことが可能です。進捗/JSONフォーマット - Minecraft wikiなどを参考に、対応するインスタンスを利用して下さい。
本記事では、以下の3つを利用しましたが、これら以外にも複数のトリガーが存在します。

InventoryChangeTrigger

インベントリに変化が起きたときに判定を行います。基本的に特定のアイテムを入手した際に進捗を達成する場合に利用します。hasItems()メソッドに判定したいアイテムを渡すことでインスタンスを生成できます。

PlacedBlockTrigger

ブロックを設置したときに判定を行います。placedBlockメソッドに判定したいブロックを渡すことでインスタンスを生成できます。

ImpossibleTrigger

バニラの処理では達成されないCriterionです。後述のMOD側処理での解除で利用します。

parent()

その進捗の親を設定します。ここでは、rootとして定義した進捗を引数として渡すことで親として設定できます。進捗タブでツリー状に表示するなどに利用できます。

rewards()

進捗達成時の報酬を設定できます。AdvancementRewards.Builderに設定されているように、経験値、ルートテーブル、レシピ、ファンクションを設定できます。今回はsmall_tntが達成されたときにボーナスチェストの内容をプレイヤーに取得させる処理を登録しています。

save()

ここまでBuilderで設定した内容を設定したResourceLocation上に保存します。必ず最後に呼んで下さい。このメソッドを呼ぶことで、ここまでBuilder型で処理していた内容をjsonファイルに書き出すとともに、Advancement型で渡すことが可能になります。このメソッドの返り値を変数に保存することでparent()メソッドなどを利用しやすくなるため、本記事ではローカル変数として取得しています。

ExampleTNTHooks.java

ExampleTNT/src/main/java/com/tntmodders/exampletnt/ExampleTNTHooks.java


package com.tntmodders.exampletnt;

import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.entity.monster.Phantom;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.entity.EntityMobGriefingEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.MobSpawnEvent;
import net.minecraftforge.event.level.ExplosionEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber(modid = ExampleTNT.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class ExampleTNTHooks {

    @SubscribeEvent
    public static void itemTossEvent(ItemTossEvent event) {
        if (event.getEntity().getItem().is(ExampleTNTItems.SMALL_TNT.get()) && event.getPlayer() instanceof ServerPlayer serverPlayer) {
            serverPlayer.getAdvancements().award(serverPlayer.getServer().getAdvancements().getAdvancement(new ResourceLocation(ExampleTNT.MOD_ID, "small_tnt")), "toss_small_tnt");
        }
    }
}

本記事では、ServerPlayer.getAdvancements()を利用してバニラで定義されている進捗達成トリガー以外の任意の場所をトリガーとする方法を紹介します。
この処理ではaward()メソッドを用いて、特定のCriterionをコマンドで行う手法と同様に達成させることが可能です。第1引数にAdvancementを、第2引数に達成させたいCriterionの名称を指定することで解除させています。Impossibleとして設定したCriterionをイベントなどの場所から解除させることで自由に進捗を達成させることができます。ここでは、ItemTossEventからSMALL_TNTを投げたことを確認した場合にCriterionを達成しています。注意点として、この方法はServerPlayerからしか呼ぶことができないメソッドを利用しているため、必ずサーバー側で処理を行って下さい。

リンク


前:クリエイティブタブの追加

次:タグの追加