第一個藥水效果
===
邱比特的箭,可以讓人戀愛 (╭ ̄3 ̄)╭♡
M鼠的箭,可以把怪物變成布丁 ( ´σ-\` )
...
對,我不是針對殭屍,我是說在場的各位,都是布丁 σ\`∀´)σ
在本章節你會了解
---
- 和藥水相關的類別
- 如何創建及註冊一個效果
- 如何創建及註冊一個藥水
- 將效果加到食物上
- 讓效果產生作用
和藥水效果相關的類別
---
首先,讓我們先分清楚並搞懂這四個類別在幹什麼
- `net.minecraft.potion.Effect`
這是最原始的 **效果** ,不帶有等級,不帶有時間
Ex: 速度、回復、幸運、毒
- `net.minecraft.potion.EffectInstance`
這是一個 **藥水實例**
簡單來說就是上面的`Effect`再加上等級和時間 *(如果不是立即效果)*,可以直接加在生物上
Ex: 2分鐘的速度II、10秒的毒I、立即回復VI
- `net.minecraft.potion.Potion`
這是指 **藥水** ,一個`Potion`可以帶有多個`EffectInstance`
Ex: 同時包含了30秒回復III和立即回復V的藥水
- `net.minecraft.item.PotionItem`
這是指 **藥水瓶** ,也就是一個`net.minecraft.item.Item`
請注意,原版的藥水瓶在物品中註冊的`Item`只有三個實例(Instance)
- `net.minecraft.item.Items.POTION`
- `net.minecraft.item.Items.SPLASH_POTION`
- `net.minecraft.item.Items.LINGERING_POTION`
也就是說,這三個`PotionItem`本身並不會記錄當前藥水瓶包含的`EffectInstance`
而一個藥水瓶會給予哪些`EffectInstance`是根據`ItemStack`上的NBT標籤決定的 (゚∀。)
如何創建及註冊一個效果
---
首先,我們先建一個套件(Package)`com.github.immortalmice.modtutorialim.potion`
然後在裡面建一個`PuddingEffect`類別,並且繼承`net.minecraft.potion.Effect` < ( ̄︶ ̄)>
```java=
public class PuddingEffect extends Effect{
public PuddingEffect(){
super(EffectType.BENEFICIAL, 0xFFBB00);
}
}
```
`Effect`的建構子傳入兩個參數
一個是`net.minecraft.potion.EffectType`,用來標記這個藥水的種類 Ex.有害、有益、一般
另一個是傳入整數,就是這個效果的顏色,這邊我用16進制,顏色是橘黃色
好了之後就來準備註冊了 ԅ(¯﹃¯ԅ)
`Effect`的註冊也已經被Forge接管,我們可以用和物品一模一樣的方式來進行註冊
*(以下註冊的部分我不會作太多解釋,如果你對註冊還不太了解的話請去看[第一個物品](https://hackmd.io/@immortalmice/SyGqBSyI) )*
現在我們在同一個套件中新建一個`Effects`類別
```java=
@ObjectHolder(ModTutorialIM.MODID)
public class Effects{
public static final PuddingEffect PUDDING_EFFECT = null;
public static DeferredRegister<Effect> getRegister(){
return Effects.EffectRegistry.REGISTER;
}
public static class EffectRegistry{
public static final DeferredRegister<Effect> REGISTER = new DeferredRegister<Effect>(ForgeRegistries.POTIONS, ModTutorialIM.MODID);
public static final RegistryObject<Effect> OBJ_PUDDING_EFFECT = EffectRegistry.REGISTER.register("pudding_effect", () -> new PuddingEffect());
}
}
```
最後記得去我們之前建的`com.github.immortalmice.modtutorialim.handlers.RegistryHandler`
這邊的`registAll()`要多加一行程式碼,把效果註冊到模組事件線上 ε(\*´・∀・`)з゙
```java=
public static void registAll(){
/* ... */
Effects.getRegister().register(RegistryHandler.BUS);
}
```
然後將語言檔補齊
- en_us.json
```json=
{
...
"_comment": "======================EFFECT======================",
"effect.modtutorialim.pudding_effect": "Pudding Spread",
...
}
```
- zh_tw.json
```json=
{
...
"_comment": "======================效果=======================",
"effect.modtutorialim.pudding_effect": "全都是布丁",
...
}
```
之後再把你精心準備的PNG圖片放到`resources/assets/modtutorialim/textures/mob_effect`
這邊 ~~為了節省經費~~ 會重複使用之前布丁的物品圖片 レ(゚∀゚;)ヘ=З=З=З
![pudding_effect](https://i.imgur.com/1d3Hsv0.png)
現在一切都大功告成了,打開遊戲,輸入以下命令
`/effect give @p modtutorialim:pudding_effect 30 0`
*(如果你對effect指令有疑問,你可以查閱[Wiki的這個頁面](https://minecraft-zh.gamepedia.com/%E5%91%BD%E4%BB%A4/effect) )*
現在你身上應該就有你新增的效果了 ( ~'ω')~
![圖1-7-1 效果展示](https://i.imgur.com/pDK3WG0.png)
這邊雖然M鼠繼承了`Effect`類別,但其實沒有做太多客制化的修改
關於讓效果有實際作用,我們會在這篇文後面的段落來講述
除此之外,你可以看一下`net.minecraftforge.common.extensions.IForgeEffect`這個介面
這個是由Forge新增的介面,並被Forge偷塞到了Minecraft的`Effect`類別
裡面的方法(Methtod)都有詳細說明,如果有需求可以自行覆寫(Override)
但有少量註解已過時,請自行求證並小心使用 (›´ω\`‹ )
順帶一提,InventoryEffect指的是上圖中左邊那個長條形的圖示
而HUDEffect則是畫面右上方的那個正方形圖示
如何創建及註冊一個藥水
---
這次一樣在`com.github.immortalmice.modtutorialim.potion`這個套件中建立一個`PuddingPotion`的類別,然後繼承`net.minecraft.potion.Potion` (“ ̄▽ ̄)-o█ █o-( ̄▽ ̄”)/
```java=
public class PuddingPotion extends Potion{
public PuddingPotion(int durationIn, int amplifierIn){
super("pudding_effect", new EffectInstance(Effects.PUDDING_EFFECT, durationIn, amplifierIn));
}
}
```
原版`Potion`的類別有兩個建構子,其中一個是直接呼叫另一個建構子的
所以我們就來講解這個主要的建構子
這個建構子傳入一個可以為`null`的字串,但還是建議傳一個正常的字串進去
這個字串稱為`baseName`,它會變成你藥水瓶的Translation Key的一部分 (´-ωก\`)
還記得我剛才說過,被註冊的藥水物品其實只有三個物品實例
那他的Translation Key照理說會長成這樣
```json=
"item.minecraft.potion"
"item.minecraft.splash_potion"
"item.minecraft.lingering_potion"
```
可是不對阿,藥水有速度藥水、有噁心藥水
所有人的Translation Key都一樣不就沒戲唱了? ヽ(\#\`Д´)ノ
因此,這邊給你的解決方案就是這個baseName
設定了baseName後,你的Translation Key就會變成這樣 ( \^ω\^)
```json=
"item.minecraft.potion.effect.base_name"
"item.minecraft.splash_potion.effect.base_name"
"item.minecraft.lingering_potion.effect.base_name"
```
這樣就沒問題了
再來我們可以傳入零個到多個`EffectInstance`
建立新的`EffectInstance`實例的時候,第一個參數是時間,以Tick為單位
第二個參數是效果等級,0是I,1是II,2是III,以此類推 (•‾ ⌣ ‾•)
現在我們來把這個藥水註冊,但這邊我們來註冊兩種藥水,一般版和延時版
在`com.github.immortalmice.modtutorialim.potion`套件中建立一個`Potions`類別
```java=
@ObjectHolder(ModTutorialIM.MODID)
public class Potions{
public static final PuddingPotion PUDDING_POTION = null;
public static final PuddingPotion LONG_PUDDING_POTION = null;
public static DeferredRegister<Potion> getRegister(){
return Potions.PotionRegistry.REGISTER;
}
public static class PotionRegistry{
public static final DeferredRegister<Potion> REGISTER = new DeferredRegister<Potion>(ForgeRegistries.POTION_TYPES, ModTutorialIM.MODID);
public static final RegistryObject<Potion> OBJ_PUDDING_POTION = PotionRegistry.REGISTER.register("pudding_potion", () -> new PuddingPotion(1200, 0));
public static final RegistryObject<Potion> OBJ_LONG_PUDDING_POTION = PotionRegistry.REGISTER.register("long_pudding_potion", () -> new PuddingPotion(6000, 0));
}
}
```
這邊的手法跟之前註冊其他東西的方法幾乎一樣
而這就是Forge的DeferredRegister的強大之處,一個類別,包辦所有註冊 (つ´ω\`)つ
但有個地方要注意,就是`Potions.PotionRegistry.REGISTER`宣告的地方
回顧一下我們前面註冊`Effect`的時候,然後再看看這邊
對,這裡的`ForgeRegistries`中這兩個靜態欄位的名稱並沒有很好的描述他們的作用,並作到清楚劃分兩者差異的效果,也就是說
```java=
// This is used on regist "Effect" objects
ForgeRegistries.POTIONS;
// This is used on regist "Potion" objects
ForgeRegistries.POTION_TYPES;
```
這兩個請別搞混了 。・゚・(つд\`゚)・゚・
> Well,如果你對程式碼的要求很高的話
> 你應該已經聞到這些程式碼的壞味道(Code Bad Smell)了
> 而且就是標準的Duplicated Code
>
> M鼠當然發現了,但我決定暫時不去重構(Refactor)程式碼解決這問題
> 原因是重構後的程式碼在理解上會更複雜,不利教學
好了之後我們就一樣去`com.github.immortalmice.modtutorialim.handlers.RegistryHandler`
這邊的`registAll()`要新增一行程式碼,把藥水註冊到模組事件線上
```java=
public static void registAll(){
/* ... */
Potions.getRegister().register(RegistryHandler.BUS);
}
```
然後來把語言檔補齊 Zz(´-ω-\`\*)
- en_us.json
```json=
{
...
"_comment": "======================POTION======================",
"item.minecraft.potion.effect.pudding_effect": "Potion of Pudding Spread",
"item.minecraft.splash_potion.effect.pudding_effect": "Splash Potion of Pudding Spread",
"item.minecraft.lingering_potion.effect.pudding_effect": "Lingering Potion of Pudding Spread",
...
}
```
- zh_tw.json
```json=
{
...
"_comment": "======================藥水=======================",
"item.minecraft.potion.effect.pudding_effect": "全都是布丁藥水",
"item.minecraft.splash_potion.effect.pudding_effect": "全都是布丁飛濺藥水",
"item.minecraft.lingering_potion.effect.pudding_effect": "全都是布丁滯留藥水",
...
}
```
好了就可以打開遊戲囉
嗯?你說藥水瓶的貼圖?不用擔心,Minecraft會用黑魔法幫我們解決的
現在你應該可以從創造模式背包的藥水物品欄中找到你的藥水了
並喝下去也可以獲得你指定的效果了 (ゝ∀・)b
![圖1-7-2 藥水展示](https://i.imgur.com/DS1seZA.png)
將效果加到食物上
---
要把效果加到食物上,其實很簡單
真的,請相信我 (ง๑ •̀_•́)ง
打開很久以前寫的`com.github.immortalmice.modtutorialim.item.Pudding`
在建構子中~~串一顆新的丸子~~ 呼叫一個effect的方法
```java=
public Pudding(){
super((new Item.Properties())
.maxStackSize(1)
.rarity(Rarity.EPIC)
.group(ItemGroups.ITEM_TAB)
.food((new Food.Builder())
.hunger(4)
.saturation(2.0f)
.setAlwaysEdible()
.effect(() -> new EffectInstance(Effects.PUDDING_EFFECT, 1200), 1.0f)
.build()
)
);
}
```
`Food.Builder.effect`有兩個,其中一個被棄用(Deprecated)了
並且Forge也寫下註解建議你用沒被棄用的那個方法,所以我們就只討論這個推薦的方法
這邊傳入一個`Supplier<EffectInstance>`和一個浮點數
這個浮點數代表的就是吃下食物後,獲得效果的機率,範圍`0.0f`~`1.0f`
Ex. 原版的腐肉這邊傳入了`0.8f`,也就是80%,而M鼠傳的`1.0f`就是100%
順帶一提,這個方法可以多次呼叫,代表一個食物可以給一個以上不同的效果 (๑´ㅁ\`)
就這樣,打開遊戲吧
吃下布丁,你應該會100%機率的得到效果
![圖1-7-3 食物展示](https://i.imgur.com/Q7M7EOJ.png)
怎樣,我就說很簡單了吧 ✧*。٩(ˊᗜˋ*)و✧*。
讓效果產生作用
---
要讓效果產生作用,主要有兩個方法
- 修改生物實體屬性
- 訂閱事件
事實上,第二個會比較常用到,如果你已經讀了[事件系統](https://hackmd.io/@immortalmice/HJ3VSHJ_I)這篇文,這對你來說應該不是問題
但我們還是來說說第一個吧
屬性`net.minecraft.entity.ai.attributes.IAttribute`是生物實體`net.minecraft.entity.LivingEntity`及子類可以擁有的一種標籤
我這邊給個列表,如果沒意外這應該是這版本中所有的原版屬性了,拿去,~~不用謝~~ (´∩ω∩`)
- 盔甲值 `net.minecraft.entity.SharedMonsterAttributes.ARMOR`
- 盔甲硬度 `net.minecraft.entity.SharedMonsterAttributes.ARMOR_TOUGHNESS`
- 攻擊力 `net.minecraft.entity.SharedMonsterAttributes.ATTACK_DAMAGE`
- 擊退能力 `net.minecraft.entity.SharedMonsterAttributes.ATTACK_KNOCKBACK`
- 攻擊速度 `net.minecraft.entity.SharedMonsterAttributes.ATTACK_SPEED`
- 重力 `net.minecraft.entity.LivingEntity.ENTITY_GRAVITY`
- 飛行速度 `net.minecraft.entity.SharedMonsterAttributes.FLYING_SPEED`
- 跟隨距離 `net.minecraft.entity.SharedMonsterAttributes.FOLLOW_RANGE`
- 跳躍力 `net.minecraft.entity.passive.horse.AbstractHorseEntity.JUMP_STRENGTH`
- 抗擊退能力 `net.minecraft.entity.SharedMonsterAttributes.KNOCKBACK_RESISTANCE`
- 幸運 `net.minecraft.entity.SharedMonsterAttributes.LUCK`
- 滿血量 `net.minecraft.entity.SharedMonsterAttributes.MAX_HEALTH`
- 移動速度 `net.minecraft.entity.SharedMonsterAttributes.MOVEMENT_SPEED`
- 可視姓名籤距離 `net.minecraft.entity.LivingEntity.NAMETAG_DISTANCE`
- 可觸距離 `net.minecraft.entity.player.PlayerEntity.REACH_DISTANCE`
- 增援機率 `net.minecraft.entity.monster.ZombieEntity.SPAWN_REINFORCEMENTS_CHANCE`
- 泳速 `net.minecraft.entity.LivingEntity.SWIM_SPEED`
請注意,不是所有生物實體都有這些屬性
每種生物實體只會有一部分的屬性,Ex.玩家實體 **沒有** 跳躍力屬性 (|||゚д゚)
想知道一個生物實體有哪些標籤,請看該類別的`registerAttributes`方法
*(但事實上,你可以為一個生物實體新增新的屬性,但M鼠沒做過,這邊先不提
如果你真的有這方面需求,你可以試著參考看看[這篇1.8.9的教學文](https://fmltutor.ustc-zzzz.net/3.1.3-%E7%94%9F%E7%89%A9%E7%9A%84%E5%9B%BA%E6%9C%89%E5%B1%9E%E6%80%A7%E5%92%8C%E8%87%AA%E7%84%B6%E7%94%9F%E6%88%90.html))*
所以,如果你的效果`Effect`是影響這些屬性的話,恭喜你,用這邊的方法就可以了
這邊作為範例,我們來讓`PuddingEffect`來去幫玩家增加最大血量吧 ( ºωº )
現在來到我們`PuddingEffect`的建構子,加入一些程式碼
```java=
public PuddingEffect(){
super(EffectType.BENEFICIAL, 0xFFBB00);
this.addAttributesModifier(
SharedMonsterAttributes.MAX_HEALTH
, "bfee1736-7fa7-4438-9ba2-4de281f85014"
, 4.0D
, AttributeModifier.Operation.ADDITION);
}
```
也就是呼叫`Effect.addAttributesModifier`,它傳入幾個參數
第一個`net.minecraft.entity.ai.attributes.IAttribute`就是你要修改的屬性
第二個是一個UUID,直接找網路上隨便一個[UUID生成器](https://1024tools.com/uuid?)即可
第三個是你要用在修改上的數值
第四個是上面的數值要如何進行運算,有三種運算 ( δ△δ )
- `AttributeModifier.Operation.ADDITION`
運算方式為:`currentValue += modifierValue;`
- `AttributeModifier.Operation.MULTIPLY_BASE`
運算方式為:`currentValue += preValue * modifierValue;`
- `AttributeModifier.Operation.MULTIPLY_TOTAL`
運算方式為:`currentValue += 1.0D + modifierValue`
對於要得到一個生物實體(`LivingEntity`)實例(Instance)該屬性的最終數值
遊戲會從該屬性的基本數值 *(創建屬性時指定,Ex:`MAXHEALTH`的基本數值是`20.0D`)* 開始
將所有登記的`AttributeModifier`依照`ADDITION`->`MULTIPLY_BASE`->`MULTIPLY_TOTAL`的順序
全部運算完來獲得最終的數值 ( ×ω× )
上面我寫的運算式中的`currentValue`指的是 **"運算到當前的數值"**
`preValue`指 **"`ADDITION`全部運算完後的數值"**
`modifierValue`指你在`addAttributesModifier`方法(Method)中傳入的 **"第三個參數"**
所以,總結,M鼠在範例中寫的程式碼意思是 => **將最高血量的數值增加`4.0D`** \_:(´□\`」 ∠):_
打開遊戲,現在在你擁有效果(`Effect`)的期間,你的血量應該會多增加4.0,也就是2顆心
![圖1-7-4 最大血量增加](https://i.imgur.com/1wGCLQd.png)
好的...
**這麼單純的東西M鼠怎麼可能滿足!這可是布丁耶!只加血量是對布丁的褻瀆!** (╯‵□′)╯︵┴─┴
**這是對布丁的褻瀆!** (╯°Д°)╯ ┻---┻
**這是對布丁的褻瀆!**(╯°▽°)╯ ┻---┻
這是對...$%^$^@$% (系統公告:M鼠已被拖出去揍)
好啦,我們這邊來做另一個作用 ( ̄ε(# ̄)☆
**當擁有效果的期間,被我們弓箭打到的生物會變成布丁!** ┳---┳ノ( ' - 'ノ)
來訂閱事件吧,這邊要訂閱`net.minecraftforge.event.entity.living.LivingAttackEvent`
上程式碼! (〃∀〃)
```java=
@SubscribeEvent
public static void onLivingAttacked(LivingAttackEvent event){
if(event.getEntityLiving().world.isRemote()) return;
LivingEntity target = event.getEntityLiving();
/* If target is monster && damage is caused by arrow && attaker is entity */
if(target instanceof MonsterEntity
&& event.getSource().getImmediateSource() instanceof ArrowEntity
&& event.getSource() instanceof EntityDamageSource){
Entity shooter = ((EntityDamageSource) event.getSource()).getTrueSource();
/* If attacker is player && player has PUDDING_EFFECT active */
if(shooter instanceof PlayerEntity
&& ((PlayerEntity) shooter).getActivePotionEffect(Effects.PUDDING_EFFECT) != null){
BlockPos pos = target.getPosition();
World world = target.world;
ItemEntity pudding = new ItemEntity(world, pos.getX(), pos.getY(), pos.getZ(), new ItemStack(Items.PUDDING));
target.remove(false);
world.addEntity(pudding);
}
}
}
```
恩...其實沒什麼好解釋的 ╮( ╯_╰ )╭
就是檢查所有條件通過後,把目標移除,加入布丁,就這樣
不過還是提一下,所謂的 "ImmediateSource" 是最直接造成傷害的東西,像這邊就是箭的實體
而 "Source" 就是所謂的幕後黑手啦,是誰射出箭的呢? ~~我不知道呢~~
而`EntityDamageSource`是一個`DamageSource`的子類
其實很好理解,當這個傷害來源是由一個實體 (Entity) 造成的時候
他就是一個`EntityDamageSource` σ ゚∀ ゚) ゚∀゚)σ
打開遊戲,就可以測試了 ლ(╹◡╹ლ)
![圖1-7-5 變布丁](https://i.imgur.com/72J99Tu.gif)
恩,我早就說過,~~我不是針對殭屍~~ ( ' ∀' )つt[ ]
---
*本頁面撰寫於2020/06/18,目前最後更新日期為2020/06/18*
*若上述時間與你閱讀的時間相距過遠,請自行斟酌是否採用本頁面的資訊*
*完整的程式碼可以到本教學文的[Github Repo](https://github.com/immortalmice/MinecraftForge1.15.2-ModdingTutorial)中查看*