Site icon May's Notes

Minecraft 1.21 Fabric 模組開發(4)-資料產生(Data Generation)

Minecraft 1.21 Fabric 模組開發(4)-資料產生(Data Generation)

Minecraft 1.21 Fabric 模組開發(1)-前置準備
Minecraft 1.21 Fabric 模組開發(2)-新增物品
Minecraft 1.21 Fabric 模組開發(3)-新增方塊

寫在前頭
1.不是JAVA教學所以不解釋程式碼為何這樣寫,想知道為什麼這麼寫可以在 Fabric docs 或 Fabric wiki 裡面找到。
2.我的模組名稱為 mays-mod,程式碼中看到 mays-mod 的地方請自行換成你的 mod 名稱。
3.圖片看不清的話可以點擊放大看。

為什麼需要 Data Generation

模組東西一旦很多的時候一個一個 json 檔去寫會很麻煩,為了提升效率,建議改成用程式碼去自動產生 json。

src/main/java/com/<mod_id> 底下新增 datagen 資料夾並新增五個 java 檔:

好奇Providers怎麼寫請參考:datagen_setup#adding_providers
語系檔也可以生成,不過我這邊還是沿用之前手動建的json,有需要可以看 Language Generation

ModBlockTagProvider

/data/tags/block/special_ores.json

{
  "replace": false,
  "values": [
    "mays-mod:creeper_ore",
    "#minecraft:coal_ores",
    "#minecraft:iron_ores",
    "#minecraft:gold_ores",
    "#minecraft:lapis_ores",
    "#minecraft:diamond_ores",
    "#minecraft:redstone_ores",
    "#minecraft:emerald_ores",
    "#minecraft:copper_ores"
  ]
}

轉換為 /java/com/datagen/ModBlockTagProvider.java

package com.may.firstmod.datagen;

import com.may.firstmod.block.ModBlocks;
import com.may.firstmod.util.ModTags;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.tag.BlockTags;

import java.util.concurrent.CompletableFuture;

public class ModBlockTagProvider extends FabricTagProvider.BlockTagProvider {
    public ModBlockTagProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) {
        super(output, registriesFuture);
    }

    @Override
    protected void configure(RegistryWrapper.WrapperLookup arg) {
        getOrCreateTagBuilder(ModTags.Blocks.SPECIAL_ORES)
                .add(ModBlocks.CREEPER_ORE)
                .forceAddTag(BlockTags.COAL_ORES)
                .forceAddTag(BlockTags.IRON_ORES)
                .forceAddTag(BlockTags.GOLD_ORES)
                .forceAddTag(BlockTags.LAPIS_ORES)
                .forceAddTag(BlockTags.DIAMOND_ORES)
                .forceAddTag(BlockTags.REDSTONE_ORES)
                .forceAddTag(BlockTags.EMERALD_ORES)
                .forceAddTag(BlockTags.COPPER_ORES);

        getOrCreateTagBuilder(BlockTags.PICKAXE_MINEABLE)
                .add(ModBlocks.CREEPER_ORE)
                .add(ModBlocks.CREEPER_BLOCK);

        getOrCreateTagBuilder(BlockTags.NEEDS_IRON_TOOL)
                .add(ModBlocks.CREEPER_ORE)
                .add(ModBlocks.CREEPER_BLOCK);

    }
}

ModItemTagProvider

目前我們沒用到 item tag,所以內容留空,之後有需要再修改。

/java/com/datagen/ModItemTagProvider.java

package com.may.firstmod.datagen;

import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
import net.minecraft.registry.RegistryWrapper;

import java.util.concurrent.CompletableFuture;

public class ModItemTagProvider extends FabricTagProvider.ItemTagProvider {
    public ModItemTagProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> completableFuture) {
        super(output, completableFuture);
    }

    @Override
    protected void configure(RegistryWrapper.WrapperLookup wrapperLookup) {

    }
}

ModLootTablesProvider

/data/loot_table/blocks

轉換為 /java/com/datagen/ModLootTablesProvider.java

package com.may.firstmod.datagen;

import com.may.firstmod.block.ModBlocks;
import com.may.firstmod.item.ModItems;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider;
import net.minecraft.block.Block;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.item.Item;
import net.minecraft.loot.LootTable;
import net.minecraft.loot.entry.ItemEntry;
import net.minecraft.loot.entry.LootPoolEntry;
import net.minecraft.loot.function.ApplyBonusLootFunction;
import net.minecraft.loot.function.SetCountLootFunction;
import net.minecraft.loot.provider.number.UniformLootNumberProvider;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryWrapper;

import java.util.concurrent.CompletableFuture;

public class ModLootTablesProvider extends FabricBlockLootTableProvider {
    public ModLootTablesProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
        super(dataOutput, registryLookup);
    }

    @Override
    public void generate() {
        addDrop(ModBlocks.CREEPER_ORE, copperOreDrops(ModBlocks.CREEPER_ORE, ModItems.RAW_CREEPER, 1.0f, 1.0f));
        addDrop(ModBlocks.CREEPER_BLOCK, copperOreDrops(ModBlocks.CREEPER_BLOCK, ModItems.CREEPER_INGOT, 1.0f, 1.0f));
    }

    public LootTable.Builder copperOreDrops(Block drop, Item item, float minExp, float maxExp) {
        RegistryWrapper.Impl<Enchantment> impl = this.registryLookup.getWrapperOrThrow(RegistryKeys.ENCHANTMENT);
        return this.dropsWithSilkTouch(
                drop,
                (LootPoolEntry.Builder<?>) this.applyExplosionDecay(
                        drop,
                        ItemEntry.builder(item)
                                .apply(SetCountLootFunction.builder(UniformLootNumberProvider.create(minExp, maxExp)))
                                .apply(ApplyBonusLootFunction.oreDrops(impl.getOrThrow(Enchantments.FORTUNE)))
                )
        );
    }
}

ModModelsProvider

/resources/assets/models

轉換為 /java/com/datagen/ModModelsProvider.java

package com.may.firstmod.datagen;

import com.may.firstmod.block.ModBlocks;
import com.may.firstmod.item.ModItems;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider;
import net.minecraft.data.client.BlockStateModelGenerator;
import net.minecraft.data.client.ItemModelGenerator;
import net.minecraft.data.client.Models;

public class ModModelsProvider extends FabricModelProvider {
    public ModModelsProvider(FabricDataOutput output) {
        super(output);
    }

    @Override
    public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
        blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.CREEPER_ORE);
        blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.CREEPER_BLOCK);
    }

    @Override
    public void generateItemModels(ItemModelGenerator itemModelGenerator) {
        itemModelGenerator.register(ModItems.RAW_CREEPER, Models.GENERATED);
        itemModelGenerator.register(ModItems.CREEPER_INGOT, Models.GENERATED);
    }
}

ModRecipesProvider

/resources/data/recipe

轉換為 /java/com/datagen/ModRecipesProvider.java

package com.may.firstmod.datagen;

import com.may.firstmod.block.ModBlocks;
import com.may.firstmod.item.ModItems;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.item.ItemConvertible;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.registry.RegistryWrapper;

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

public class ModRecipesProvider extends FabricRecipeProvider {
    public static final List<ItemConvertible> SMELTABLE_TO_INGOT = List.of(ModItems.RAW_CREEPER);

    public ModRecipesProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) {
        super(output, registriesFuture);
    }

    @Override
    public void generate(RecipeExporter exporter) {
        // offerReversibleCompactingRecipes 可逆
        // offerCompactingRecipe 不可逆
        offerReversibleCompactingRecipes(exporter, RecipeCategory.MISC, ModItems.CREEPER_INGOT, RecipeCategory.BUILDING_BLOCKS, ModBlocks.CREEPER_BLOCK);
        // offerSmelting 熔爐
        offerSmelting(exporter, SMELTABLE_TO_INGOT, RecipeCategory.MISC, ModItems.CREEPER_INGOT, 0.7F, 200, "creeper_ingot");
        // offerBlasting 高爐
        offerBlasting(exporter, SMELTABLE_TO_INGOT, RecipeCategory.MISC, ModItems.CREEPER_INGOT, 0.7F, 100, "creeper_ingot");
    }
}

初始化 Providers

在 /java/com/XxxModDataGenerator.java 中初始化上面這些 provider

每當執行我們先前建立的 gradle 任務(runDatagen)時,都會呼叫onInitializeDataGenerator函數。

// MaysModDataGenerator.java
package com.may.firstmod;

import com.may.firstmod.datagen.*;
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;

public class MaysModDataGenerator implements DataGeneratorEntrypoint {
	@Override
	public void onInitializeDataGenerator(FabricDataGenerator generator) {
		FabricDataGenerator.Pack pack = generator.createPack();

		pack.addProvider(ModModelsProvider::new);
		pack.addProvider(ModItemTagProvider::new);
		pack.addProvider(ModBlockTagProvider::new);
		pack.addProvider(ModLootTablesProvider::new);
		pack.addProvider(ModRecipesProvider::new);
	}
}

接著刪除以下資料夾(記得備份或先建新的branch):

啟用Data generation

如果一開始使用Fabric模组生成器有勾選 Data generation 的話可跳過這節。

在 build.gradle 中加上:

fabricApi {
	configureDataGeneration()
}

並確認一下 entrypoints 有沒有 fabric-datagen,沒有的話一樣手動補上:

"entrypoints": {
    "main": [
        "com.may.firstmod.MaysMod"
    ],
    "client": [
        "com.may.firstmod.MaysModClient"
    ],
    "fabric-datagen": [
        "com.may.firstmod.MaysModDataGenerator"
    ]
},

生成json

以上都做完之後,從IDE右側開啟gradle視窗,找到 Tasks.fabric.runDatagen 雙擊執行。

完成之後,src/main 底下會多出一個資料夾 generated,裡面的內容就是我們之前手動新建的那些 json檔。

現在可以重新開啟遊戲看看之前新增的方塊、物品、配方有沒有都正常,沒有的話就是有程式碼寫錯了,再認真檢查一下。

Exit mobile version