# 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`