# Minecraft Fabric Modding - 模組製作 ## 前置 [模板下載](https://github.com/FabricMC/fabric-example-mod/) [教學模組](https://github.com/Tutorials-By-Kaupenjoe/Fabric-Tutorial-1.17.1) ***※模組 id 務必使用「小寫」※*** ### Initialize function 在模組路經中,有一個主檔案,包含了一個函數 `onInitialize()`。 以下教程中,我們皆稱其為「***Init Function***」 ## 基本物品 *古人有言:「Minecraft之始,物品也」*,學習如何註冊一個物品可說是我們進入製作模組的第一步,也是最重要的一步。 ### Code 在 java 的模組路徑下新增資料夾 `item`,並加入檔案 `ModItems.java` 首先,我們需要寫一個可以註冊物品的函數: ```java= private static Item registerItem(String name, Item item) { return Registry.register(Registry.ITEM, new Identifier(TestMod.MOD_ID, name), item); } ``` 接著我們就可以註冊物品: ```java= public static final Item RUBY = registerItem("ruby", new Item(new FabricItemSettings().group(ItemGroup.MISC))); ``` 最後,寫一個初始化函數並放到 ***Init Funciton*** 中即完成: ```java= public static void registerModItems() { System.out.println("Registering Mod Items for " + More_Ores.MOD_ID); } ``` ### Texture resource 路徑的檔案樹狀圖: ``` resource └─ assets └─ <ModId> ├─ lang │ └─ en_us.json ├─ models │ └─ item │ └─ <itemId>.json └─ textures └─ item └─ <itemId>.png ``` 基本的物品 json 可以這樣寫: ```json= { "parent": "item/generated", "textures": { "layer0": "<ModName>:item/<itemId>" } } ``` 翻譯檔 lang 則是: ```json= "item.<ModName>.<Id>": "<Name>" ``` [Item Models 教學](https://www.youtube.com/watch?v=rQFI8ewFADc&ab_channel=UncleJam) ## 基本方塊 ### Code 方塊較為特別的部分就是我們需要有方塊的「物品型態」,因此我們需要另外創建一個 function 來創建方塊的物品。 一樣在模組路徑下的 `block` 資料夾下加入 `ModBlocks.java`: ```java= private static Block registerBlock(String name, Block block) { registerBlockItem(name, block); return Registry.register(Registry.BLOCK, new Identifier(TestMod.MOD_ID, name), block); } private static Item registerBlockItem(String name, Block block) { return Registry.register(Registry.ITEM, new Identifier(TestMod.MOD_ID, name), new BlockItem(block, new FabricItemSettings().group(ItemGroup.MISC))); } ``` 註冊方塊: ```java= public static final Block RUBY_ORE = registerBlock("ruby_ore", new Block(FabricBlockSettings.of(Material.STONE).strength(4.0f) .breakByTool(FabricToolTags.PICKAXES, 2).requiresTool())); ``` 其中,`breakByTool` 的第二個參數是工具等級,對照如下: ``` 0 -> Wooden / Golden Pickaxe 1 -> Stone Pickaxe 2 -> Iron Pickaxe 3 -> Diamond Pickaxe 4 -> Netherite Pickaxe ``` 常用 Settings: ```java= of(Material.<material>) - 相當於<material>的材質 strength(<sec>f) - 挖掘時間 breakByTool(FabricToolTags.<tool>, <level>) - 挖掘工具 requiresTool() - 必須使用工具 ``` ### Texture 檔案架構: ``` resource └─ assets └─ <ModId> ├─ blockstates │ └─ <blockId>.json ├─ lang │ └─ en_us.json ├─ models │ ├─ block │ │ └─ <blockId>.json │ └─ item │ └─ <blockId>.json └─ textures └─ block └─ <blockId>.png ``` ```json= // blockstates/<blockId>.json { "variants": { "": { "model": "testmod:block/ruby_ore" } } } ``` ```json= // models/block/<blockId>.json { "parent": "block/cube_all", "textures": { "all": "testmod:block/ruby_ore" } } ``` ```json= // models/item/<blockId>.json { "parent": "testmod:block/ruby_ore" } ``` `blockstates` 主要是在描述一個方塊的六個面要用哪些 `models`;而 `models` 的檔案則是描述要從 `png` 檔取哪一個部分。 [Block Models 教學](https://www.youtube.com/watch?v=3lECPqoSNeg&ab_channel=UncleJam) [神的工具:Block Bench](https://www.blockbench.net/) [進階方塊](https://hackmd.io/@Chocomint/FabricModding-Block) ## ItemGroup Tab 創建 `ItemGroup`: ```java= public static final ItemGroup RUBY = FabricItemGroupBuilder.build(new Identifier(TestMod.MOD_ID, "ruby"), () -> new ItemStack(ModItems.RUBY)); ``` 其中,`ItemStack` 中的物品是 Tab 的標題物品。 再使用 `ModItemGroup.RUBY` 來代替 `ItemGroup.MISC` 即可。 ## 合成表 ``` resource └─ data └─ <ModId> └─ recipes └─ <RecipeId>.json ``` 包含工作臺合成、 [Recipes Generator](https://misode.github.io/recipe/) ## 食物 *俗話說的好:「呷飯皇帝大」*,Minceraft 怎麼能少的了食物呢? ```java= public static final Item PEPPER = registerItem("pepper", new Item(new FabricItemSettings() .food(new FoodComponent.Builder().hunger(2).saturationModifier(0.2f).build()) .group(ModItemGroup.RUBY))); ``` ```java= hunger(int) - 飽食度 saturationModifier(float) - 隱藏飽食度 ``` ## 燃料 在 `<ModId>/registries` 下創建檔案 `ModRegistries.java`: ```java= public static void registerModFuel() { System.out.println("Registering Fuels for " + <ModId>); FuelRegistry registry = FuelRegistry.INSTANCE; registry.add(<Item>, <tick>); } ``` * 請記得先註冊物品 ## 工具 ### Custom Material 在 `<ModId>/item` 下創建檔案 `ModToolMaterial.java`: ```java= public enum ModToolMaterial implements ToolMaterial { <MaterialName>(<miningLevel>, <itemDurability>, <miningSpeed>, <attackDamage>, <enchantability>, () -> { return Ingredient.ofItems(<Item>); }); private final int miningLevel; private final int itemDurability; private final float miningSpeed; private final float attackDamage; private final int enchantability; private final Lazy<Ingredient> repairIngredient; private ModToolMaterial(int miningLevel, int itemDurability, float miningSpeed, float attackDamage, int enchantability, Supplier<Ingredient> repairIngredient) { this.miningLevel = miningLevel; this.itemDurability = itemDurability; this.miningSpeed = miningSpeed; this.attackDamage = attackDamage; this.enchantability = enchantability; this.repairIngredient = new Lazy(repairIngredient); } public int getDurability() { return this.itemDurability; } public float getMiningSpeedMultiplier() { return this.miningSpeed; } public float getAttackDamage() { return this.attackDamage; } public int getMiningLevel() { return this.miningLevel; } public int getEnchantability() { return this.enchantability; } public Ingredient getRepairIngredient() { return (Ingredient)this.repairIngredient.get(); } } ``` ### 定義工具 種類分成:protected(`PickaxeItem`, `HoeItem`, `AxeItem`)、public(`ShovelItem`, `SwordItem`) protected 的 class 意味著你需要另外創建一個有 public 建構子的 class 來繼承他。 可在 `<ModId>/item/custom` 下分別創建檔案,建立 class: ```java= public class ModPickaxeItem extends PickaxeItem { public ModPickaxeItem(ToolMaterial material, int attackDamage, float attackSpeed, Settings settings) { super(material, attackDamage, attackSpeed, settings); } } ``` ## 藥水 加入檔案 `ModPotions.java` ```java= public class ModPotions { public static Potion STRENGTHEN_FIRE_RESISTANCE_POTION = registerPotion("strengthen_fire_resistance", new PotionRecipe(Potions.LONG_FIRE_RESISTANCE, ModItems.GLOW_BLAZE_ALLOY), new StatusEffectInstance(StatusEffects.FIRE_RESISTANCE, minute(3), 0), new StatusEffectInstance(StatusEffects.RESISTANCE, minute(3), 2)); private static Potion registerPotion(String name, PotionRecipe potionRecipe, StatusEffectInstance... sei) { Potion p = Registry.register(Registry.POTION, new Identifier(More_Ores.MOD_ID, name), new Potion(sei)); BrewingRecipeRegistryMixin.invokeRegisterPotionRecipe(potionRecipe.getPotion(), potionRecipe.getItem(), p); return p; } public static void registerModPotions() { System.out.println("Registering Mod Potions for " + More_Ores.MOD_ID); } private static int minute(double min) { // my custom function return (int)Math.round(min * 60 * 20); } private static int second(double sec) { // my custom function return (int)Math.round(sec * 20); } private record PotionRecipe(Potion potion, Item item) { // my custom class public Potion getPotion() { return potion; } public Item getItem() { return item; } } } ``` ### 顏色 顏色是由效果顏色疊加而成 ## 世界生成 [連結]() <!-- ``` OreConfiguredFeatures STONE_ORE_REPLACEABLES - 僅替換石頭 (也就是生成在 y>0) DEEPSLATE_ORE_REPLACEABLES BASE_STONE_NETHER BASE_STONE_OVERWORLD - 可在主世界生成 NETHERRACK BiomeSelectors foundInOverworld() foundInTheEnd() foundInTheNether() ... Vein Size: 一叢礦物的個數 Veins Pre Chunk: 一個區塊裡的礦物叢數 Bottom: 最底部生成 ``` --> ## Config Config 可以調整模組的一些參數,是一個很好管理變數的方法。 在模組路徑下創建資料夾 `config`,加入三個檔案: `SimpleConfig.java` - [By magistermaks](https://github.com/magistermaks/fabric-simplelibs/blob/master/simple-config/SimpleConfig.java) `ModConfigProvider.java` - [By Kaupenjoe](https://github.com/Tutorials-By-Kaupenjoe/Fabric-Tutorial-1.17.1/blob/28-simpleConfigs/src/main/java/net/tutorialsbykaupenjoe/tutorialmod/config/ModConfigProvider.java) `ModConfig.java`: ```java= import com.mojang.datafixers.util.Pair; public class ModConfigs { public static SimpleConfig CONFIG; private static ModConfigProvider configs; public static String TEST; // your config object public static void registerConfigs() { configs = new ModConfigProvider(); createConfigs(); CONFIG = SimpleConfig.of(More_Ores.MOD_ID + ".config").provider(configs).request(); assignConfigs(); } private static void createConfigs() { configs.addKeyValuePair(new Pair<>("key.test.value1", "Just a Testing string!"), "String"); // initialize } private static void assignConfigs() { TEST = CONFIG.getOrDefault("key.test.value1", "Nothing"); // path, default System.out.println("All " + configs.getConfigsList().size() + " have been set properly"); } } ``` 最後將 `ModConfigs.registerConfigs();` 加入模組初始化函數即可。 ## 交易選項 建立檔案 Trade.java (record): ```java= public record Trade(VillagerProfession profession, int level, TradeOffer tradeOffer) { public static final Trade[] ModTrades = { new Trade(VillagerProfession.TOOLSMITH, 1, new TradeOffer( new ItemStack(Items.EMERALD, 1), new ItemStack(Items.DIAMOND, 1), 5, 5, 0.08f)), ... }; } ``` - `level` 由 1 算起 - `TradeOffer`(買進1, [買進2], 賣出, 最大使用次數, 每次交易村民經驗, 價錢乘數) 再加入 Registry 即可: ```java= for(Trade info : Trade.ModTrades) { TradeOfferHelper.registerVillagerOffers(info.profession(), info.level(), factories -> factories.add((entity, random) -> info.tradeOffer())); } ``` ## 創建 jar 檔 ```java= Terminal-ModPath> ./gradlew build ``` ## 參考資料 [Fabric Wiki](https://fabricmc.net/wiki/start) [Fabric API](https://github.com/FabricMC/fabric) [Kaupenjoe - YouTube](https://www.youtube.com/channel/UCbzPhyLcO8VP25dZ7kaUyAw) [Tutorials By Kaupenjoe](https://github.com/Tutorials-By-Kaupenjoe/Fabric-Tutorial-1.18.1) [Nico Kaupenjohann](https://github.com/Kaupenjoe/Fabric-Course-118) [Mixin Wiki](https://github.com/SpongePowered/Mixin/wiki) [Mixin Java Document](https://jenkins.liteloader.com/view/Other/job/Mixin/javadoc/index.html) <!-- [fabric-api 0.32.5+local-1.16 API](https://docproject.github.io/fabricmc_fabric/overview-summary.html) --> --- ###### tags: `Minecraft Fabric Modding`