第一個藥水效果 === 邱比特的箭,可以讓人戀愛 (╭ ̄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)中查看*