【MCreator/Forge】古いMCreatorでダメージの種類を判別したい

どうも、Motchiyの友人ことさいほうへいきです。ところでMotchiyって誰？
この記事では古いバージョンのMCreatorで火炎耐性もどきを作る時、悪戦苦闘したので忘備録としてまとめてあります。ソースコードを直接いじくるので、すべてをJavaで書いてる方にも多少参考になるかもしれません。
では。

- [概要](#概要)
- [対象者](#対象者)
- [さっそく、やりかた](#さっそく、やりかた)
    - [普通にポーション効果を作る](#普通にポーション効果を作る)
    - [ダメージを受ける時のプロシージャを作る（ブロック編）](#ダメージを受ける時のプロシージャを作る（ブロック編）)
    - [ダメージを受ける時のプロシージャを作る（Java編）](#ダメージを受ける時のプロシージャを作る（Java編）)
    - [結局どうなるの](#結局どうなるの)
- [コード解説・改変ガイド](#コード解説・改変ガイド)
- [参考にしたページ](#参考にしたページ)


## 概要
火炎耐性や落下耐性はそれぞれ、火、落下によるダメージを判別した上で軽減しているので、そういうのを調べるコードを書きたかったわけです。
僕はいつもMCreatorで遊んでるのですが、火炎耐性もどきを作ろうとした時、ダメージの種類を判別するためのブロックが見当たらず、しかも調べてもあんまり有益なデータが出てきませんでした（英語が分からないだけかもしれませんが）。唯一の収穫はバージョンが後になると実装されるようです。友人のサーバーが1.16.5のため、MCreatorが対応していなかったらしいです。
というわけでJavaをいじくることにしました。
Javaを直接バーッて書いてる人に向けて書いているわけではありませんが、コードが多少は手伝えるかもしれません。あわせて下の方のコード解説・改変ガイドをご覧ください。

## 対象者
誰に：
- 古いMCreatorを使ってます
目的は：
- 火炎耐性みたいなポーションを作りたい
- 落下耐性みたいなエンチャントを作りたい

これが実装できれば、他にも『奈落のダメージを軽減する鎧』とか『空腹を無効にするポーション』とかも作れます。
MCreatorを多少使い慣れている人向けに書いていますので、分からない用語もちらほらあるかもしれません。
一応、僕は英語版ユーザーなので、日本語化してMCreatorを使っている方は頑張ってください。

なお、最新のMCreatorはブロックが追加されているため、コードを書く必要はありません。

## さっそく、やりかた
ここでのMCreatorのバージョンは2022.2です。
Javaが分からん！　って人は書くのがめちゃめちゃ面倒だと思うので、いきなりカスタムエレメントを書き始めるのはお勧めしません。ブロックを途中まで作ってから、ダメージ判別のコードだけ書き足すと楽です。僕はそうしてます。
### 普通にポーション効果を作る
これをしないと始まりません。
左上の『＋』から『Potion Effect』を選択して、マニュアルに従って適当に名前、アイコン、色くらいを設定してください。ポーション自体にプロシージャ（プログラム）をつける必要はないです。
ここでは仮に、AntiExplodeという名前にしておきます。
### ダメージを受ける時のプロシージャを作る（ブロック編）
次は『Procedure』を作りましょう。
グローバルトリガー（メインになるでかいブロックにあるリスト）で『Before entity is hurt』を選びます。これで、エンティティがダメージを受ける時に自動でこれが呼び出されるようになります。
それからブロックを次のような感じで組みます。
![画像が読み込めてない場合は次のコードを見てください](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994795/ae024a3c-a32b-4733-b84a-6f3443356553.png)
もし画像が読み込めてない場合は次のコードを見てください。すごく見づらいですが。
```
if[Has [Event/Target Entity] active potion [CUSTOM:AntiExplode]]
	if[true]
		[Cancel event that triggered global triger]
```
とりあえずこの時点では、ダメージを受ける時、AntiExplodeという効果を持っていたならそれをキャンセルする（=無効化する）というコードになっています。ではこれをいったん保存し、コードを編集しましょう。

### ダメージを受ける時のプロシージャを作る（Java編）
コードを直接編集するには『＋』のよっつ下にある、棒とペンのアイコンをクリックしてください。
先ほどのプロシージャが開けたら、だいたいこんなふうになっていると思います。
```java:PotionWorking.java（編集前）
package net.mcreator.mycustom.procedures;

import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.event.entity.living.LivingHurtEvent;

import net.minecraft.world.World;
import net.minecraft.potion.EffectInstance;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.Entity;

import net.mcreator.mycustom.potion.AntiExplodePotionEffect;
import net.mcreator.mycustom.MyCustomMod;

import java.util.Map;
import java.util.HashMap;
import java.util.Collection;

public class PotionWorking {
	@Mod.EventBusSubscriber
	private static class GlobalTrigger {
		@SubscribeEvent
		public static void onEntityAttacked(LivingHurtEvent event) {
			if (event != null && event.getEntity() != null) {
				Entity entity = event.getEntity();
				Entity sourceentity = event.getSource().getTrueSource();
				double i = entity.getPosX();
				double j = entity.getPosY();
				double k = entity.getPosZ();
				double amount = event.getAmount();
				World world = entity.world;
				Map<String, Object> dependencies = new HashMap<>();
				dependencies.put("x", i);
				dependencies.put("y", j);
				dependencies.put("z", k);
				dependencies.put("amount", amount);
				dependencies.put("world", world);
				dependencies.put("entity", entity);
				dependencies.put("sourceentity", sourceentity);
				dependencies.put("event", event);
				executeProcedure(dependencies);
			}
		}
	}

	public static void executeProcedure(Map<String, Object> dependencies) {
		if (dependencies.get("entity") == null) {
			if (!dependencies.containsKey("entity"))
				MyCustomMod.LOGGER.warn("Failed to load dependency entity for procedure PotionWorking!");
			return;
		}
		Entity entity = (Entity) dependencies.get("entity");
		if (new Object() {
			boolean check(Entity _entity) {
				if (_entity instanceof LivingEntity) {
					Collection<EffectInstance> effects = ((LivingEntity) _entity).getActivePotionEffects();
					for (EffectInstance effect : effects) {
						if (effect.getPotion() == AntiExplodePotionEffect.potion)
							return true;
					}
				}
				return false;
			}
		}.check(entity)) {
			if (true) {
				if (dependencies.get("event") != null) {
					Object _obj = dependencies.get("event");
					if (_obj instanceof Event) {
						Event _evt = (Event) _obj;
						if (_evt.isCancelable())
							_evt.setCanceled(true);
					}
				}
			}
		}
	}
}
```
では、これを書き換えていきます。
まず上の方にonEntityAttackedというのがありますが、その内部に書き足します。
```java
// 以上省略……

		public static void onEntityAttacked(LivingHurtEvent event) {
			if (event != null && event.getEntity() != null) {
				Entity entity = event.getEntity();
				Entity sourceentity = event.getSource().getTrueSource();
				double i = entity.getPosX();
				double j = entity.getPosY();
				double k = entity.getPosZ();
				double amount = event.getAmount();
				World world = entity.world;
				Map<String, Object> dependencies = new HashMap<>();
				dependencies.put("x", i);
				dependencies.put("y", j);
				dependencies.put("z", k);
				dependencies.put("amount", amount);
				dependencies.put("world", world);
				dependencies.put("entity", entity);
				dependencies.put("sourceentity", sourceentity);
				dependencies.put("event", event);

				DamageSource damagesource = event.getSource();		// ココ
				dependencies.put("damagesource", damagesource);		// ココ

				executeProcedure(dependencies);
			}
		}

// 以下省略……
```
次は下の方にif (true)という一文があります。それを書き換えます。
```java
// 以上省略……

			if (((DamageSource) dependencies.get("source")).isExplosion()) {	// ココ
				if (dependencies.get("event") != null) {
					Object _obj = dependencies.get("event");
					if (_obj instanceof Event) {
						Event _evt = (Event) _obj;
						if (_evt.isCancelable())
							_evt.setCanceled(true);
					}
				}
			}

// 以下省略……
```

次のテキストをEntity entity = (Entity) dependencies.get("entity")の上に入れてください。入れなくても動きはしますが、あった方が安全です。役割は正常にデータを読み込めなかった時に、何か面倒（たとえばフリーズ）が起きる前にログを出し、終了するためのプログラムです。
MyCustomMod（Mod名）やPotionWorking（ファイル名）は置き換えてください。
```java
// 以上省略……

		if (dependencies.get("source") == null) {
			if (!dependencies.containsKey("source"))
				MyCustomMod.LOGGER.warn("Failed to load dependency source for procedure PotionWorking!");
			return;
		}

// 以下省略……
```

これで完成です。もしもAntiExplodeという効果を持っているなら、爆発に巻き込まれてもダメージを受けなくなります。
これをCtrl+Sで保存して、起動してから実験してみてください。保存する時にメッセージが表示されたらロックするようにします。さもないと、MCreatorに上書きされて元に戻ってしまいます。
このままだと効果はeffectコマンドで入手するしかないので、『Potion item』から実際の飲めるポーションを追加しておくとよいでしょう。

コードの解説は後のセクションにのせてあるので、他のダメージにも使えるようにしたいなら、そちらをどうぞ。

### 結局どうなるの
完成したコードは次の通りです。もちろんModの名前とかが異なりますので、丸コピペされても動きません。
Javaを直接書きたい方向けにコメントもいくつか追加しています。
```java:PotionWorking.java（完成！）
package net.mcreator.mycustom.procedures;	// 僕のModです。適宜書き換えてください。

import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.event.entity.living.LivingHurtEvent;

import net.minecraft.world.World;
import net.minecraft.potion.EffectInstance;	// ポーション効果のインスタンスに関するあれこれです。
import net.minecraft.entity.LivingEntity;	// いきてるエンティティに関するあれこれ。
import net.minecraft.entity.Entity;

import net.mcreator.mycustom.potion.AntiExplodePotionEffect;	// 僕が先ほど追加していた効果です。適宜Mod名、ポーション名を置き換えてください。
import net.mcreator.mycustom.MyCustomMod;			// 僕のModです。適宜書き換えてください。

import java.util.Map;
import java.util.HashMap;
import java.util.Collection;

public class PotionWorking {			// ファイル名です。適切なクラス名へ変えるようにしてください。
	@Mod.EventBusSubscriber
	private static class GlobalTrigger {
		@SubscribeEvent
		public static void onEntityAttacked(LivingHurtEvent event) {
			if (event != null && event.getEntity() != null) {
				Entity entity = event.getEntity();
				Entity sourceentity = event.getSource().getTrueSource();
				double i = entity.getPosX();
				double j = entity.getPosY();
				double k = entity.getPosZ();
				double amount = event.getAmount();
				World world = entity.world;
				Map<String, Object> dependencies = new HashMap<>();
				dependencies.put("x", i);
				dependencies.put("y", j);
				dependencies.put("z", k);
				dependencies.put("amount", amount);			// 受けるダメージの量を取得します。上のgetAmount()で取得したものです。
				dependencies.put("world", world);
				dependencies.put("entity", entity);			// ターゲット、つまり実際にダメージを受けるエンティティです。
				dependencies.put("sourceentity", sourceentity);		// ソース、つまり攻撃者です。サボテンなど一部のダメージタイプは攻撃者がいないことに注意。
				dependencies.put("event", event);

				DamageSource damagesource = event.getSource();		// ここを追加する。ダメージソースの情報を取得。
				dependencies.put("damagesource", damagesource);		// ここを追加する。取得したダメージソースを実際のプログラムに渡す。

				executeProcedure(dependencies);
			}
		}
	}

	public static void executeProcedure(Map<String, Object> dependencies) {
		if (dependencies.get("entity") == null) {	// 依存関係を読み込めなかった時にエラーメッセージを出します。この効果はエンティティとソースだけですが、攻撃者やダメージ量を扱う時はそれらも用意した方が良いでしょう。
			if (!dependencies.containsKey("entity"))
				MyCustomMod.LOGGER.warn("Failed to load dependency entity for procedure PotionWorking!");
			return;
		}
		if (dependencies.get("source") == null) {	// 依存関係を読み込めなかった時にエラーメッセージを出します。
			if (!dependencies.containsKey("source"))
				MyCustomMod.LOGGER.warn("Failed to load dependency source for procedure PotionWorking!");
			return;
		}

		Entity entity = (Entity) dependencies.get("entity");
		if (new Object() {
			boolean check(Entity _entity) {
				if (_entity instanceof LivingEntity) {
					Collection<EffectInstance> effects = ((LivingEntity) _entity).getActivePotionEffects();		// 現在対象のエンティティが持っている効果をすべて取得します。
					for (EffectInstance effect : effects) {
						if (effect.getPotion() == AntiExplodePotionEffect.potion)	// AntiExplodeというポーション効果を持っていたかを調べます。実際に持っていればtrueです。
							return true;
					}
				}
				return false;
			}
		}.check(entity)) {
			if (((DamageSource) dependencies.get("source")).isExplosion()) {	// ここを書き換える。上で渡してもらったデータを見て、爆発かどうかを調べる。
				if (dependencies.get("event") != null) {
					Object _obj = dependencies.get("event");
					if (_obj instanceof Event) {
						Event _evt = (Event) _obj;
						if (_evt.isCancelable())		// キャンセル可能なイベントかどうかです。ダメージを受けるイベントはキャンセルできるので次へ。
							_evt.setCanceled(true);		// キャンセルします。ここを別のコードに置き換えれば、爆発を受けると即死する呪い、爆発を喰らうと逆に回復する、のような効果も作れるはずです。
					}
				}
			}
		}
	}
}
```

## コード解説・改変ガイド
ダメージの種類には種類があります（？）
というのも、これを知らないとかなり謎めいてしまうのでしっかり理解した上でコードを書く必要があります。少々技術的な話なので、ある程度プログラミングができる方向けに書いてありますが、そう難しくもないはずです。

実はダメージ種類の判別に使ったDamageSourceという型ですが、これはただ単に『奈落』『爆発』『空腹』などを列挙しているわけではありません。
次の表はDamageSource型に内包されている、ダメージの種類を表すもののリストです。

|コード|説明|
|---|---|
|DamageSource.ANVIL|金床によるダメージ|
|DamageSource.CACTUS|サボテンによるダメージ|
|DamageSource.CRAMMING|圧迫によるダメージ（一定ブロックにMobが密集すると受けます）|
|DamageSource.DRAGON_BREATH|使われていないようです。ドラゴンブレスは内部的には即時ダメージとなっています。|
|DamageSource.DROWN|溺れるダメージ|
|DamageSource.DRYOUT|乾燥によるダメージ（イカやウーパールーパーが地上にいる時に受けます）|
|DamageSource.FALL|落下によるダメージ|
|DamageSource.FALLING_BLOCK|一部の落下ブロックによるダメージ|
|DamageSource.FLY_INTO_WALL|壁に突撃した時のダメージ（十分に加速したエリトラでぶつかる時）|
|DamageSource.GENERIC|汎用ダメージ|
|DamageSource.HOT_FLOOR|熱い床によるダメージ（マグマブロックなど）|
|DamageSource.IN_FIRE|炎によるダメージ（炎ブロック、焚き火の中にいる時。離れた後の燃焼はON_FIREです）|
|DamageSource.IN_WALL|ブロック内での窒息によるダメージ（葉っぱのような一部透過ブロックは例外です）、ワールドの壁の外に出たダメージ|
|DamageSource.LAVA|溶岩によるダメージ|
|DamageSource.LIGHTNING_BOLT|雷によるダメージ（その後しばらく燃焼によりON_FIREダメージも受けるかもしれません）|
|DamageSource.MAGIC|魔法によるダメージ（即時ダメージなど）|
|DamageSource.ON_FIRE|炎上によるダメージ（燃焼中。炎の内部にいる時はIN_FIREです）|
|DamageSource.OUT_OF_WORLD|奈落に落下したダメージ、killコマンドによるダメージ|
|DamageSource.STARVE|空腹によるダメージ|
|DamageSource.SWEET_BERRY_BUSH|スイートベリーの低木によるダメージ|
|DamageSource.WITHER|ウィザー状態によるダメージ|

新しいバージョンでは変更になっている場合があります。たとえば焚き火ダメージがON_FIREから独立したり、ワールドの壁のダメージがIN_WALLから独立したりしています。
新しいバージョンでは他にもSTALAGMITE（石筍）などが追加されています。
最新のものはWikiなどを参照してください。

これらは(DamageSource)dependencies.get("source")とイコールで結ぶことができます。まあ、だいたいのダメージ種類を判別できますね。
これをよく見たらわかると思うのですが、爆発や遠距離攻撃などの一部種類が判別できません。

なので、次の表を参照してください。これらはメソッドであり、((DamageSource)dependencies.get("source"))のあとにつなげる形で使うとBoolean（真か偽か）を返します。
|コード|説明|
|---|---|
|isExplosion()|爆発によるダメージ|
|isFire()|炎によるダメージ|
|isMagic()|魔法によるダメージ|
|isProjectile()|遠距離攻撃によるダメージ|

これを使うことで爆発耐性などが作れるようになります。

あとは、これらを適宜組み合わせてキャンセルするなり、追加ダメージを与えるなり、いろいろしてみてください。
この記事は以上です。最後までお読みいただきありがとうございました！

誤りがあるかもしれないので、間違っていたらぜひ指摘を頂けるとありがたいです(m"_ _)mﾍﾟｺﾘ

## 参考にしたページ
DamageSource (forge 1.16.5-36.2.39)
https://nekoyue.github.io/ForgeJavaDocs-NG/javadoc/1.16.5/net/minecraft/util/DamageSource

ダメージ - Minecraft Japan Wiki
https://minecraftjapan.miraheze.org/wiki/ダメージ