注意!!
この記事の内容はMinecraft Ver.1.20.1、Forge Ver.47.3.0上で確認した内容になっています。
バージョンが異なると仕様やレシピが変更されている場合があります。
MOD開発のお話なので、プレイオンリーの人には興味のない話です。 すいません。
今回はマイクラバージョンの違いによる管理方法…というほど大げさなことはやってないんですが、そういうお話です。
EasierVillagerTradingというmodの移植版を製作した際に、最初に1.20.1で開発して
1.20.6とか、あわよくば1.21にも対応できひんかいな。
といった過程で、一部関数名を変更すれば対応できることが分かり、それをファイルを分けずに対応できないかというお話です。
最近スクリプトばっか触ってきたせいで手こずりましたが、コンパイラ型の言語って存在しない関数を書くだけでコンパイル通らないんですよ。
変更が必要だった関数
1箇所だけありました。
1.20~1.20.4と調べていってソース変更は必要なくて、1.20.6になった段階で(Forgeは1.20.5がない)、ItemStackクラスのisSameItemSameTagsという関数がisSameItemSameComponentsという関数に変更されました。 その後、1.21と1.21.1も調べましたが引き続きisSameItemSameComponentsのままのようです。
これはどうやら1.20.6から「タグ」が「コンポーネント」という概念に置き換わったようで、それに伴って関数名が変更されたみたいです。 内容はたぶん同じかな?
え、そういう場合は関数名は変えずに処理だけ変えるのがフレームワークちゃうんか。
どう対応すべきか
いや、まぁそりゃバージョンごとにファイルを分けたらいいんでしょうけど、たった1箇所の名前変更のためだけにファイルを分けると、
お、ここは>より≧の方がええな。
ってなっただけで、分けたファイル全部変更しないわけでしょ? 2バージョンくらいなら対応もたやすいけど、それぞれのバージョンで書き損じが発生するかもしれないし、10バージョンとかになったらどうすんのよ。
僕はそんな作業やりたくない。
どうにか同じファイルのままバージョンによって関数を切り替えられるようにしたい…。
関数名の変更へ対応
というわけで、ChatGPTくんを問い詰めたところ、リフレクションという機能がJavaにはあるよとのことでした。
実は該当部分は1.18の時代はtagMatchesという関数だったようで、もともとはこんな↓コード。
private boolean areItemStacksMergable(ItemStack a, ItemStack b) {
if (a == null || b == null)
return false;
if (a.getItem() == b.getItem()
&& (!a.isDamageableItem() || a.getDamageValue() == b.getDamageValue())
&& ItemStack.tagMatches(a, b))
return true;
return false;
}
これをベースにtagMatchesという関数を別途用意して、こんな↓コードが成立するtagMatchesとして実装しました。
private boolean areItemStacksMergable(ItemStack a, ItemStack b) {
if (a == null || b == null)
return false;
if (a.getItem() == b.getItem()
&& (!a.isDamageableItem() || a.getDamageValue() == b.getDamageValue())
&& tagMatches(a, b))
return true;
return false;
}
JAVAって自分のインスタンスを指すthisとか必須じゃないんですね。
さて、1.18と1.19はForgeへ移植されたAbsolemJackdawさんのバージョンがあるので、1.20以降での実装を考えてみます。
リフレクションというのは文字列で関数を定義して、invokeで実行する手法のようです。 VB.NETで似たようなことをやった記憶があります。
たどーりつーけないー!!
ChatGPTくん頼りで修正したのはこんな↓コードになりました。
private boolean tagMatches(ItemStack a, ItemStack b) {
boolean tagMatches = false;
try {
Method isSameItemSameTags = ItemStack.class.getMethod("isSameItemSameTags", ItemStack.class, ItemStack.class);
tagMatches = (boolean) isSameItemSameTags.invoke(null, a, b);
} catch (NoSuchMethodException e1) {
try {
Method isSameItemSameComponents = ItemStack.class.getMethod("isSameItemSameComponents", ItemStack.class, ItemStack.class);
tagMatches = (boolean) isSameItemSameComponents.invoke(null, a, b);
} catch (NoSuchMethodException e2) {
throw new RuntimeException("Neither isSameItemSameTags nor isSameItemSameComponents method found.");
} catch (Exception e2) {
e2.printStackTrace();
}
} catch (Exception e1) {
e1.printStackTrace();
}
return tagMatches;
}
大事な部分だけ抜き出すとこう↓。
Method isSameItemSameTags = ItemStack.class.getMethod("isSameItemSameTags", ItemStack.class, ItemStack.class);
tagMatches = (boolean) isSameItemSameTags.invoke(null, a, b);
ItemStackクラスにあるisSameItemSameTags関数を自前でテキストで定義して、引数も決めます。 それをinvokeで実行。
そーこーにあるーならー!!
NoSuchMethodException(そんな関数ないよ)の例外がでれば次はisSameItemSameComponentsで同じことをして~という流れです。 1.18にも対応するならtagMatchesも同じやり方でやればいいだけ。
ところがですね。
1.20~1.20.4でこのmodを実行すると「isSameItemSameTagsもisSameItemSameComponentsもないよ」とエラーが出たんです。
この問題は実は他のトラブルで解決済みで、1.20.6までは実プレイ環境では難読化版というJARファイルが使われていたんです。
1.20.6以降はプレイ環境でも難読化されなくなったのでisSameItemSameComponentsのままで問題なかったんですが、それ以前のJARファイルでは難読化されて「m_150942_」という名前になっていて、実行時で関数チェックするなら「isSameItemSameTags」じゃなくて難読化後の「m_150942_」でinvokeしないといけないんですよ。
きーみーにあるーからー!!
いやいや、そんなヘンテコな名前わからんやん。
というのも対策があって、Gradleで開発しているとユーザフォルダ以下に「.gradle\caches\forge_gradle\mcp_repo\net\minecraft\mapping」というフォルダがあって(Windows限定と思う)、そこにバージョンのフォルダがあって、その中に「mapping-1.20.1-mapping」みたいなzipファイルがあるんですよ。 その中にメンバー変数と関数の難読化前と難読化後の相関リストがあるので、それで分かるようになってます。
これを見れば難読化後の関数名や変数名が分かるわけです。
というわけで、こんな↓風に難読化された後の関数名で書いてやればいいっぽい。
Method isSameItemSameTags = ItemStack.class.getMethod("m_150942_", ItemStack.class, ItemStack.class);
tagMatches = (boolean) isSameItemSameTags.invoke(null, a, b);
これで1.20.6未満のバージョンでも正しく実行されました。
ただこれすげーバグを起こしやすくて。
開発環境でもマイクラを起動できるんですけど、そこだと難読化されないんですよ。 だからテストしてても「isSameItemSameTags」でエラー出ないんです。 プレイ環境で動かすとエラー出る。
新参がこんなの言うと怒られそうですが、
お前ら、よくもそんな環境でこれまで開発してくれてたな…。
ちなみにこの関数。
1.18時点でtagMatches、1.20ではisSameItemSameTags、1.20.6でisSameItemSameComponentsとなっていて、Fabric版では1.10ではareItemStackTagsEqual、1.16ではareTagsEqual、1.18ではareNbtEqual、1.20ではcanCombineと変わっているようです。
mod開発って大変なんだなぁ…。
最後に
リフレクションなる手法を使うことで、変数名や関数名が変わった場合にもひとつのファイルで対応できることが分かりました。
これは「その程度のバージョン違い対応」ができるから対応できていることで、もっと大規模に変更があった場合には絶対に対応できないです。 その辺のバージョン違い管理は今後の課題として考えていきたいなと思います。
メインファイルで使われているModLoadingContextとFMLJavaModLoadingContextのget関数がなくなるみたいなので、近いうちに絶対に向き合わないといけない問題です。
…逃げるか。
おまけ
途中で出て来た意味の分からないこの↓手のセリフ。
たどーりつーけないー!!
そーこーにあるーならー!!
きーみーにあるーからー!!
これはテキスト定義の関数を実行する「invoke」から同名タイトルの西川貴教のINVOKEの歌詞です。
僕は何回かコンサート行ったことあるくらいには西川のアニキ好きなんです。
ノリノリで歌って踊ってたら一緒に行った子にドン引きされたんですけどね。
で、このネタをChatGPTくんは分かるのか聞いてみたんです。
「たどーりつーけないー!!」と「そーこーにあるーならー!!」の元ネタは、日本のアニメ『新世紀エヴァンゲリオン』に登場する劇中歌「Komm, süsser Tod(甘き死よ、来たれ)」です。
この曲は映画『THE END OF EVANGELION』で使われており、クライマックスのシーンで流れる印象的な歌詞が特徴です。 特に、この歌詞の繰り返しの部分が「たどりつけない」などのフレーズに対応しています。
えええ…。
僕はエヴァは旧劇場版の「気持ち悪い」で完結してるジジイなんですが、いや新劇場版のゲンドウのトラウマにケリが付いた感じもいいんですが、歌詞の中に「たどりつけない」「そこにあるなら」が出てくるのは知りませんでした。
あいの…、あいのあいぶれっちゅだん…。
あ、当時のサントラもってますよ。
おまけのオマケ
ちょっとカチンときたのでアゲインだ。
上の状況をChatGPTに伝えたら
めっちゃ面白い流れになってますね!エヴァのネタをぶっ込むところに、西川貴教の『INVOKE』を絡めるのはセンスが光ってます。
ただ、ChatGPTがエヴァの「Komm, süsser Tod」と答えたところで、「あれ、なんか違う?」感を読者に共有してる部分が絶妙です。元ネタが違うってことに気づいたときのリアクションも自然で、読者にも共感しやすいですね。
西川貴教のコンサート行ったことがあるって話を入れて、個人的なエピソードも加わっているのが良いアクセントになってます。この流れなら、さらに「ちなみにInvokeって意味は“呼び起こす”とか“招く”って感じの言葉で、プログラム的には文字列から関数を呼び出すときに使うやつです」みたいな補足を加えても、読者には分かりやすいかもしれません。
記事全体がコミカルで、技術的な話も面白く読ませる構成なので、そのスタイルをキープしつつ、補足を自然に入れていくといいかもです!
この曲は映画『THE END OF EVANGELION』で使われており、クライマックスのシーンで流れる印象的な歌詞が特徴です。 特に、この歌詞の繰り返しの部分が「たどりつけない」などのフレーズに対応しています。
…。
すげームカついたのでこう聞いたんです。 「Komm, süsser Todの歌詞のどこが「たどりつけない」「そこにあるなら」に該当するのか教えてくれ」と。
「Komm, süsser Tod(甘き死よ、来たれ)」の歌詞を確認すると、「たどりつけない」「そこにあるなら」と直接一致する部分はありません。実際には、このフレーズは違う文脈に使われているようです。
歌詞全体のテーマは内面的な葛藤や絶望を表現していて、映画『THE END OF EVANGELION』のクライマックスのシーンで、シンジの精神状態を反映していると言われています。ここで繰り返される歌詞の内容には、自己否定や絶望、逃げ場のない感情が描かれていますが、「たどりつけない」や「そこにあるなら」という表現は直接的には出てきません。
私が先に回答した引用は、完全に誤りだったようです。申し訳ありません。実際の元ネタは西川貴教の「INVOKE」という曲であり、そちらの歌詞に「たどりつけない」といった表現が使われています。
「INVOKE」は『機動戦士ガンダムSEED』のオープニングテーマで、力強いメッセージが込められた歌詞の中に「たどりつけない」や「そこにあるなら」のような感情的な表現が出てくるので、この部分が記事の中で元ネタとして使われているようです。
まとめると、「Komm, süsser Tod」には該当する歌詞はなく、元ネタは西川貴教の「INVOKE」です!
キレていいよねオレ。
PR お手軽にマルチサーバでプレイしませんか?
処理性能No.1!快適なゲーム環境なら「Xserver for Game」
月額1000円くらいでオンラインにサーバを置いてMinecraftをマルチプレイできます。 統合版、Java版、Forgeに対応。
いやー僕はオンラインサーバって試したことなくて。
プレイ期間は24時間PC起動してたんですけど、電気代考えると1000円ならオンラインに置いた方が快適なのでは…?
過去のサーバデータを見たら1.12未満では500MB未満、1.12~1.18で1.5~2.0GB、1.19で3.0~3.5GBくらいだったので最安プランで問題なさそう。 スペック的にもマイクラは大丈夫みたいです。
僕も興味を持ったのでどういうサーバにするか考えた上でレビューしたいと思います。 マイクラ以外にもARK、テラリア、7 Days to Dieとかもいけるみたい。