顯示具有 Minecraft 標籤的文章。 顯示所有文章
顯示具有 Minecraft 標籤的文章。 顯示所有文章

2021年2月7日 星期日

Minecraft Java 版 (1.16.4, jdk 1.8) 自製 Forge Mod - 實作物品、合成表、物品特殊效果

繼上次
後,這次我們要練習自制物品,其中會包括物品的建立、名字的翻譯、相應的合成表和物品特殊效果之類的實作。

這次要建立的是一個叫做 "超級劍 (Super Sword)" 的物品,其有以下特性:
  1. 其物品類別為 "戰鬥 (Combat)" 物品。 
  2. 其有自己的名字 (英文叫 Super Sword,中文叫超級劍)。
  3. 其為"劍 (Sword)"這個物品的客制版本,有自己的攻擊傷害等數值。
  4. 其有自己的2D圖示,並拿此圖示做為 3D 時的樣子 (例如玩家拿在手上的樣子)。
  5. 其被玩家拿在手上時,有自己效果,此例為玩家不會受到攻擊及效果傷害,並且攻擊者會受到最大生命值一半的傷害。
實作版本:
Minecraft Java 版 - 1.16.4
JDK1.8
forge-1.16.4-35.1.4-mdk

先來看一下最後成品的資料結構,在這裡不會重頭無中生有的撰寫程式碼,
我會直接拿這篇文章
的程式碼拿來修改:



首先是建立 SuperSword 的物件,
main.java.com.my.mode.item.SuperSword.java :
package com.my.mode.item;

import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.IItemTier;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.Rarity;
import net.minecraft.item.SwordItem;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;

public class SuperSword extends SwordItem{

	public final static String ITEM_ID = "super_sword"; 
	
	public SuperSword() {
		super(new IItemTier() {

			@Override
			public int getMaxUses() {
				// TODO Auto-generated method stub
				return 100;
			}

			@Override
			public float getEfficiency() {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public float getAttackDamage() {
				// TODO Auto-generated method stub
				return 100;
			}

			@Override
			public int getHarvestLevel() {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public int getEnchantability() {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public Ingredient getRepairMaterial() {
				// TODO Auto-generated method stub
				return null;
			}
			
		}, 100, 1, (new Item.Properties()).group(ItemGroup.COMBAT));
	}

	@SubscribeEvent
	public void onLivingAttackEvent(LivingAttackEvent event) {
		LivingEntity livingEntity = event.getEntityLiving();
		World world = livingEntity.getEntityWorld();
		if(!world.isRemote && livingEntity instanceof PlayerEntity) {
			PlayerEntity playerEntity = (PlayerEntity) livingEntity;
			if (playerEntity.getHeldItemMainhand().getItem() instanceof SuperSword
			 || playerEntity.getHeldItemOffhand().getItem() instanceof SuperSword) {
				//把事件取消,效果為攻擊無效,不會愛傷
				event.setCanceled(true);
				
				//增加效果,效果為對攻擊玩家 (player) 的生物造成傷害
				if (event.getSource().getTrueSource() instanceof LivingEntity) {
					LivingEntity attacker = (LivingEntity) event.getSource().getTrueSource();			
					attacker.attackEntityFrom(DamageSource.GENERIC, attacker.getMaxHealth() / 2);
				}
			}
		}
	}
	
	@SubscribeEvent
	public void onLivingAttackEvent(LivingHurtEvent event) {
		LivingEntity livingEntity = event.getEntityLiving();
		World world = livingEntity.getEntityWorld();

		if(!world.isRemote && livingEntity instanceof PlayerEntity) {
			PlayerEntity playerEntity = (PlayerEntity) livingEntity;
			if (playerEntity.getHeldItemMainhand().getItem() instanceof SuperSword
			 || playerEntity.getHeldItemOffhand().getItem() instanceof SuperSword) {
				//把事件取消,效果為傷害無效,不會愛傷
				event.setCanceled(true);
			}
		}
	}
}


在 SuperSword.java 的建構子 (constructor) 中,SuperSword 繼承了 SwordItem,
所以其實本質就是一個 "劍 (Sword)" 物品,
在建構子中,我們用
net.minecraft.item.SwordItem.SwordItem(IItemTier tier, int attackDamageIn, float attackSpeedIn, Properties builderIn)
重新定義了 SuperSword 的攻擊傷害 (attackDamageIn)、攻擊速度 (attackSpeedIn) 和物品類別 (ItemGroup.COMBAT)。

再來我們實作兩個監聽事件的 method,分別為 onLivingAttackEvent()、onLivingAttackEvent() 來監聽 LivingAttackEvent (當生物被攻擊時觸發)、onLivingAttackEvent (當生物被傷害時觸發)。
在 method 中,先取得事件的對象,即被攻擊、被傷害的對象,
LivingEntity livingEntity = event.getEntityLiving();
如果對象為玩家 (PlayerEntity) 的話,再來判斷玩家手上是否拿著 SuperSword 物品
if (playerEntity.getHeldItemMainhand().getItem() instanceof SuperSword
			 || playerEntity.getHeldItemOffhand().getItem() instanceof SuperSword)
如果玩家拿著 SuperSword,則將事件取消,而
LivingAttackEvent 各 LivingHurtEvent 被取消的效果為:事件的對象不會受到攻擊、受傷的傷害。

在 onLivingAttackEvent() 裡我們使用
if (event.getSource().getTrueSource() instanceof LivingEntity) {
     LivingEntity attacker = (LivingEntity) event.getSource().getTrueSource();			
     attacker.attackEntityFrom(DamageSource.GENERIC, attacker.getMaxHealth() / 2);
}
                
再多取得攻擊者物件,判斷攻擊者是否為 LiveingEntity 後,
對其產生了一個普通傷害,傷害為攻擊者最大生命值的一半。

建立設計好 SuperSword.java 後,再來是要把 SuperSword 物品註冊到遊戲中 (先不管名字和外觀),首先先建立一個工具類, Items.java
  
main.java.com.my.mode.item.Items.java :
package com.my.mode.item;

import com.my.mode.MyMod;

import net.minecraft.item.Item;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;

public class Items {
	private static DeferredRegister<Item> REGISTER = null;
	
	public static DeferredRegister<Item> getRegister(){
		if (REGISTER == null) {
			REGISTER = DeferredRegister.create(ForgeRegistries.ITEMS, MyMod.MOD_ID);
		}		
		//注冊自製物品
		REGISTER.register(SuperSword.ITEM_ID, () -> new SuperSword());
		
        return REGISTER;
    }
}

在 Items.java 裡, 我們會使用 DeferredRegister 來將 SuperSword 註冊進去。
接著我們修改一下 ModEventBusHandler.java

main/java/com/my/mode/ModEventBusHandler.java :
package com.my.mode;

import com.my.mode.item.Items;

import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;

public class ModEventBusHandler {
	public static void register() {
		IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
		modEventBus.register(ModEventBusHandler.class);
		modEventBus.register(new ModEventBusHandler());
		//註冊自製物品,並將其註冊到 ModEventBus 中
		Items.getRegister().register(modEventBus);
	}
	
	@SubscribeEvent
	public void onCommonSetupEvent(FMLCommonSetupEvent event) {
		ForgeEventBusHandler.register();
	}
}

在這裡,我們使用了
Items.getRegister().register(modEventBus);
來將 SuperSword 主冊到遊戲中。
此時 SuperSword 這個物品已經存在在遊戲中了,但它還沒有效果,即是說 SuperSword 裡的
onLivingAttackEvent() 和 onLivingAttackEvent() 不會有任何的效果,
因為我們只有註冊物品,但還沒有把監聽事 method 給註冊到遊戲中。
所以我們要來修改
com.my.mode.ForgeEventBusHandler.java :
package com.my.mode;

import com.my.mode.item.SuperSword;

import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.monster.MonsterEntity;
import net.minecraft.entity.passive.FoxEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;

public class ForgeEventBusHandler {
	public static void register() {
		IEventBus forgeEventBus = MinecraftForge.EVENT_BUS;
		forgeEventBus.register(ForgeEventBusHandler.class);
		forgeEventBus.register(new ForgeEventBusHandler());
		
		//註冊超級劍 (super sword) 的事件效果
		forgeEventBus.register(new SuperSword());
		forgeEventBus.register(SuperSword.class);
	}
	
	// Event 有分成在 ModEventUs 上的,或在 ForgeEventBus 上的,
	// 此例的 ItemTossEvent 為在 ForgeEventBus 上的 Event
	@SubscribeEvent
	public void onItemTossEvent(ItemTossEvent event) {
		//ItemTossEvent: 物品丟棄時觸發的 Event, 
		//在遊戲中可用按鍵 q 來丟棄物品
		ItemEntity item = event.getEntityItem();
		World world = item.getEntityWorld();
		
		if (!world.isRemote) { // 判斷是否為 logical server 端,即處理邏輯的那端
							   // 如果此例的 isRemote 為 true,即為 logical client 端
							   // 可參考
							   // https://hackmd.io/@immortalmice/Hkj9s-CvU/https%3A%2F%2Fhackmd.io%2F%40immortalmice%2FrJKayrf9U
			
			//建立一個狐狸物件
			FoxEntity fox = new FoxEntity(EntityType.FOX, world);
			fox.setPosition(item.getPosX(), item.getPosY(), item.getPosZ());
			//將物件放到世界中
			world.addEntity(fox);
		}
	}
}
在 ForgeEventBusHandler.java 中,只有多加了兩行程式碼,就是
forgeEventBus.register(new SuperSword());
forgeEventBus.register(SuperSword.class);
用來把 SuperSword 裡的事件監聽 method 給註冊到遊戲中 (或說 ForgeEventBus 中)。

Note:

forgeEventBus.register(new SuperSword()); 用來註冊 SuperSword 裡的 non-static method,
forgeEventBus.register(SuperSword.class); 用來註冊 SuperSwrod 裡的 static method。

到這裡,SuperSword 已經正常的被加到遊戲中了,並且只要玩家拿著 SuperSword (左手或是右手),就不會受到任何攻擊及效果傷害,攻擊者如果對玩家發動攻擊就會受到攻擊者一半生命值的傷害。

但是 SuperSword 這時還沒有自己的名字和外觀,此時 SuperSword 在遊戲中的名字會是:
item.mymod.super_sword。

所以我們要來設定 SuperSword 的名字和外觀。
先建立兩個 json 格式的翻譯檔,en_us.json 和 zh_tw.json,其中內容為:
/main/resources/assets/mymod/lang/en_us.json :
{
    "item.mymod.super_sword": "Super Sword"
}
/main/resources/assets/mymod/lang/zh_tw.json :
{
    "item.mymod.super_sword": "超級劍"
}

可以看到其定義了英文 (en_us) 和繁體中文 (zh_tw) 的名字,
要注意的是 json 檔所放的路徑是規定的,其中 "mymod" 可改成自己 mod 的 id。

再來是設定外觀,在這邊我己經先畫了一張 16x16 pxiel 背景透明的圖,super_sword.png,
擺在以下位置,
/main/resources/assets/mymod/textures/item/super_sword.png :
一樣的,路徑是規定的,"mymod" 可換成自己 mod 的 id。

有了圖了以後,建立一個 super_sword.json 檔來設定外觀的資料:
/main/resources/assets/mymod/models/item/super_sword.json:
{
    "parent": "item/generated",
    "textures": {
        "layer0": "mymod:item/super_sword"
    },
    "display": {
	    "thirdperson_righthand": {
	      "rotation": [0, 90, 0],
	      "translation" : [0, 3, 0],
	      "scale" : [1, 1, 1]
	    }
  	}
}

在這邊我們多設定了 thirdpersion_righthand,也就是當物品拿在右手上時,
第三人稱看到時,物品的旋轉(rotation), 位移 (translation) 和縮放 (scale) 狀態,
三個數字分別代表 x, y, z 軸。
其中我調整了 ratation 和 translation,把 SuperSword 調成看起來像是被玩家握在劍柄的狀態,
如果沒調整的話,玩家會握在物件圖片 y 軸 (也就是高度沒軸) 的 2/4 處,對 16 x 16 pixel 的圖就是高度第 5 格到第 8 格處。

Note:

此時如果把 SuperSword 拿在左手,會發現一樣如拿在右手上一樣有吃到調整的設定,
應該是因為當有設定右手時,如果左手沒設定的話會自動以右手設定為準。

此時 SuperSword 的設定大致已完成了,但這時 SuperSword 還沒有自己的合成表配方,
也就是除非使用了遊戲指令,例如:
/give @p mymod:super_sword
不然無法正常得到此物品。

所以我們現在要來建立 SuperSword 的配方。
合成設定除了用程式來實以外,
minecraft forge 推出了方便使用的 json 格式設定,這邊會以 json 格式來做設定。
建立一個 super_sword_from_crafting.json,內容如下:

/main/resources/data/mymod/recipes/super_sword_from_crafting.json :
{
    "type": "minecraft:crafting_shaped",
    "pattern":
    [
        " x ",
        "xxx",
        " x "
    ],
    "key":
    {
        "x":
        {
            "item": "minecraft:dirt"
        }
    },
    "result":
    {
        "item": "mymod:super_sword",
        "count": 1
    }
}

"type" 代表了合成表的類型
minecraft:crafting_shaped 是有序配方,
代表配方為特定物品用特定方式排列,類如 minecraft 中的 "門 (door)" 的合成。
或是可以選 minecraft:crafting_shapeless,為無序配方,
代表此配方不在乎原料如何排列,只有由特定的原料即可合成,
類如 minecraft 中的 "火焰彈 (fire_charge)" 的合成。
可參考

"pattern" 即為合成表,給 type = minecraft:crafting_shaped 使用 (minecraft:crafting_shapeless 則是使用 ingredients),x 為物品的 key 值 (不一定要用x,可以用任何想要的英文字)。
而"key" 代物你在 "pattern" 中使用的 key 值各代表什麼物品。
"Result" 代表產生的物品和數目。

在此例中,我們設定了,只要把5個泥土排成十字就可以合成出 SuperSword。

至此已大功告成了! 我們可以開始來實際看看在遊戲中 SuperSword 的效果了,
以下為最後的成果實機測試影片:



源碼下載:

參考資料:

2021年1月1日 星期五

Minecraft Java 版 (1.16.4, jdk 1.8) 自製 Mod - 使用 Forge - 以 Subscribe Event 為例子

 如果只是要在 Minecraft 中玩 Forge 的 Mod (自己做好的或別人做好的),

只需要以下步驟:

  1. Forge 官網下載對應 Minecraft 版本的 Installer。
  2. 執行下載下來的 Installer 並安裝 Client 端。
  3. 把要玩的 Mod (為 jar 檔) 放到 Minecraft 的 mods 資料夾中。
    預設 Minecraft 的根目錄會裝在這:
    C:\Users\XXX\AppData\Roaming\.minecraft
  4. 執行 Minecraft 的 Launcher 啟動 Minecraft,
    點開 "安裝檔" 分頁,可以看到 Forge 的開始遊戲方式,用 Forge 開始遊戲,
    即可開始使用 Mod 玩遊戲了 (在選單介面可以看到點),如下圖所示。



以下開始介紹使用 Forge 製作自己 Mod 的方法:

我的環境為 Win10

使用的 IDE 為 Eclipse

Note:

如果 Eclipse 所使用的 jdk 不是 jdk 1.8,

此時記得要把 Forge 的 Mod 專案的 compile Java 設定成 jdk1.8,

還有在執行 Forge Mod 專案中的 gradle task 時,要指定 jdk1.8,

例如:

gradlew build -Dorg.gradle.java.home={JDK_PATH}

或是在 Eclipse 裡的 gradle plugin run configuration 裡對 gradle task 設定 java_home 的位置


  1. 去 Forge 官網下載對應 Minecraft 版本的 Mdk。

  2. 解壓縮下載下來的檔案後,裡面的東西就是一個完整所需的 Minecraft Forge Mod Java 專案 (以下暫稱 Minecraft Forge Mod 專案),不過還要執行一些 forge 已經幫我們寫好的 gradle task 來設定一些東西。
  3. 以 Windows 環境為例,打開命令列模式視窗執行
    gradlew genEclipseRuns
  4. 執行完 genEclipseRuns 的 gradle task後,再使用 Eclipse 的 Import project - Exsiting Gradle Project 功能把 Minecraft Forge Mod 專案給 Import 進來,就可以開始撰寫程式了。
  5. 要測試自己寫的 Mod 的時候,可以執行 runClient 這個 gradle task,
    此時會有一個 Minecraft 遊戲視窗被打開,進去後應可以看到自己的 Mod 已被載入,開始遊戲後就可以在遊戲中使用自制的 Mod 了。
  6. 寫好 Mod 想要輸出時,可以執行 build 這個 grade task,就會在專案的
    /build/libs 看到輸出的 Mod 的 jar 檔,把此 jar 檔放到遊戲的 mods 資料夾就可以在遊戲中使用了。


我有找到幾個不錯的參考資料,裡面的教學對我幫助很大,

雖然可能 Forge 版本不同有些微差異,但還是能幫助理解使用 Forge 自制 Minecraft Mod 的許多相關知識。

請參考本篇文章下方的 "參考資料"。


接下來我會以 Minecraft Mod 中註冊事件 (Subscribe Event) 為例子,

來實作一個在 Minecraft 中, "丟棄物品時,會在其位置產生一個XXX" 的功能

Note:

在 Minecraft 中,"丟棄" 和 "丟擲" 不同 ,其事件對應到 Forge 的 ItemTossEventProjectileImpactEvent.Throwable

以下是專案的檔案結構:



可以看到裡面已經有一個範例 Mod, ExampleMod.java,

之後會建立自己的 Mod java class,所以可以 ExampleMod.java 刪掉或是把其中的
@Mod 那行刪掉,

被紅框框起來的部份是我自己建立的 Mod Class 檔: com.my.mode.MyMod.java,

接著要為此 Mod 決定一個 Mod 的名稱,也可以說是 Mod Id,只能英文小寫,

此例我們決定叫做 mymod,

決定好 Mod Id 後,要在 

main/resources/META-INF/mods.toml

裡設定 Mod Id,在 mods.toml 中,紀錄著 Mod 的相關資訊,例如 Mod Id, Mod 的名稱、敘述、作者資料等資訊,在 Minecraft 的 Mod 資訊頁面上也會顯示這些東西。

為了簡單,我們不修改其他東西,因為並不影響自制 Mod 的功能,

只要把下列這行修改成自己的 Mod Id 即可:

# The modid of the mod

modId="mymod" #mandatory


以下直接看程式碼:

mods.toml

# This is an example mods.toml file. It contains the data relating to the loading mods.
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file.
# Find more information on toml format here:  https://github.com/toml-lang/toml
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion="[35,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="All rights reserved"
# A URL to refer people to when problems occur with this mod
issueTrackerURL="http://my.issue.tracker/" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
# The modid of the mod
modId="mymod" #mandatory
# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
# see the associated build.gradle script for how to populate this completely automatically during a build
version="${file.jarVersion}" #mandatory
 # A display name for the mod
displayName="My Mod" #mandatory
# A URL to query for updates for this mod. See the JSON update specification <here>
updateJSONURL="http://myurl.me/" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
displayURL="http://example.com/" #optional
# A file name (in the root of the mod JAR) containing a logo for display
logoFile="mymod.png" #optional
# A text field displayed in the mod UI
credits="Thanks for this example mod goes to Java" #optional
# A text field displayed in the mod UI
authors="Love, Cheese and small house plants" #optional
# The description text for the mod (multi line!) (#mandatory)
description='''
This is a long form description of the mod. You can write whatever you want here

Have some lorem ipsum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis lacinia magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed sagittis luctus odio eu tempus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque volutpat ligula eget lacus auctor sagittis. In hac habitasse platea dictumst. Nunc gravida elit vitae sem vehicula efficitur. Donec mattis ipsum et arcu lobortis, eleifend sagittis sem rutrum. Cras pharetra quam eget posuere fermentum. Sed id tincidunt justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
[[dependencies.examplemod]] #optional
    # the modid of the dependency
    modId="forge" #mandatory
    # Does this dependency have to exist - if not, ordering below must be specified
    mandatory=true #mandatory
    # The version range of the dependency
    versionRange="[35,)" #mandatory
    # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory
    ordering="NONE"
    # Side this dependency is applied on - BOTH, CLIENT or SERVER
    side="BOTH"
# Here's another dependency
[[dependencies.examplemod]]
    modId="minecraft"
    mandatory=true
# This version range declares a minimum of the current minecraft version up to but not including the next major version
    versionRange="[1.16.4,1.17)"
    ordering="NONE"
    side="BOTH"

com.my.mod.MyMod.java :

package com.my.mode;

import net.minecraftforge.fml.common.Mod;

@Mod("mymod") // 設定 Mod Id, 請先把 ExampleMod.java 拿掉或把其 @Mod 的那行註解掉
public class MyMod {
	
	public MyMod() {
		// 註冊 EventBus,
		// 先註冊 ModEventBus, 之後 onCommonSetupEvent 會被觸發執行,
		// 接著屬於 ModEventBus 的 Event 都會被觸發執行。
		// 再註冊 ForgeEventBus,
		// 之後屬於 ForgeEventBus 的 Event (例如 ItemTossEvent, ProjectileImpactEvent.Throwable 等) 都會被觸發執行。
		ModEventBusHandler.register();
	}
}

com.my.mod.ModEventBusHandler.java :

package com.my.mode;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;

public class ModEventBusHandler {
	public static void register() {
		IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
		modEventBus.register(ModEventBusHandler.class);
		modEventBus.register(new ModEventBusHandler());
	}
	
	@SubscribeEvent
	public void onCommonSetupEvent(FMLCommonSetupEvent event) {
		ForgeEventBusHandler.register();
	}
}

com.my.mod.ForgeEventBusHandler.java :

package com.my.mode;

import net.minecraft.entity.EntityType;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.passive.FoxEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;

public class ForgeEventBusHandler {
	public static void register() {
		IEventBus forgeEventBus = MinecraftForge.EVENT_BUS;
		forgeEventBus.register(ForgeEventBusHandler.class);
		forgeEventBus.register(new ForgeEventBusHandler());
	}
	
	// Event 有分成在 ModEventUs 上的,或在 ForgeEventBus 上的,
	// 此例的 ItemTossEvent 為在 ForgeEventBus 上的 Event
	@SubscribeEvent
	public void onItemTossEvent(ItemTossEvent event) {
		//ItemTossEvent: 物品丟棄時觸發的 Event, 
		//在遊戲中可用按鍵 q 來丟棄物品
		ItemEntity item = event.getEntityItem();
		World world = item.getEntityWorld();
		
		if (!world.isRemote) { // 判斷是否為 logical server 端,即處理邏輯的那端
							   // 如果此例的 isRemote 為 true,即為 logical client 端
							   // 可參考
							   // https://hackmd.io/@immortalmice/Hkj9s-CvU/https%3A%2F%2Fhackmd.io%2F%40immortalmice%2FrJKayrf9U
			
			//建立一個狐狸物件
			FoxEntity fox = new FoxEntity(EntityType.FOX, world);
			fox.setPosition(item.getPosX(), item.getPosY(), item.getPosZ());
			//將物件放到世界中
			world.addEntity(fox);
		}
	}
}

資源分享: