2021/04/07 時点での Minecraft 1.16.5 のMod (FML)をScalaで開発する方法をメモ程度に残します。簡単なメッセージを出す部分までを作ります。ほとんどMinecraft Wiki (Javaでの方法)に従っています。
書き忘れ・訂正などありましたらよろしくお願いします。
環境
Minecraft 1.16.5
Forge 1.16.5-36.1.0
JDK 1.8.0_281
Scala 2.13.4
sbtは使わずgradleでビルドします。
セットアップ
Minecraft Wikiのページに従ってIntelliJ IDEAで環境を構築します。追記しつつまとめますが必要なければ読み飛ばしてください。
IntelliJ IDEAのインストールが前提です。
1. MDKの準備
ForgeのMDK(Mod Development Kit)をForgeの配布サイト のShow all Versionsからダウンロードし、zipの中身の
- gradle/
- src/
- build.gradle
- gradlew
- gradlew.bat
をプロジェクトフォルダに配置します。
build.gradleを次のように変更します。難読化を解除する際の名前のマップだと思います(詳しく調べてません)。
mappings channel: 'official', version: '1.16.5'
mappings channel: 'snapshot', version: '20210309-1.16.5'
2. IntelliJ IDEAにインポート
IntelliJ IDEAを起動し、Open File or Project から build.gradle を選択するとインポートできます。初回は時間がかかります。インポートが完了すると画面右端にGradleのタブができるので、 Tasks/fg_runs/genIntellijRuns
を実行します。
1でのマップの変更に伴い src/main/ExampleMod.java
を次のように変更します。
LOGGER.info("Got game settings {}", event.getMinecraftSupplier().get().options);
LOGGER.info("Got game settings {}", event.getMinecraftSupplier().get().gameSettings);
3. 名前の変更
ExampleModとなっているため名前を変更します。2021/04/07 時点では Minecraft Wiki のページは記述途中であったため書きます。
Mod名, ModIDの変更
mods.toml
を編集します。[[mods]]
内のmodId
を変更します。その他modに関する情報は[[mods]]にあるので適宜変更・追加してください。以下に例を示します。
折りたたみ
modLoader="javafml"
loaderVersion="[36,)"
license="MIT"
[[mods]]
modId="MOD_NAME"
namespace="MOD_NAME"
version="0.1"
displayName="MOD_NAME"
authors="Tsukina_7mochi"
description='''
Created
with
Scala!
'''
[[dependencies.MOD_NAME]]
modId="forge"
mandatory=true
versionRange="[36,)"
ordering="NONE"
side="BOTH"
[[dependencies.MOD_NAME]]
modId="minecraft"
mandatory=true
versionRange="[1.16.5,1.17)"
ordering="NONE"
side="BOTH"
次に ExampleMod.java
を編集します。
@Mod
アノテーションの引数の部分を mods.toml
と同じにします。またクラス名を変更します。おすすめは次のような感じです(IronChestsのソースを見てきた)。
@Mod(Chalcedony.MODID)
class Chalcedony{
public static final String MODID = "MOD ID";
// ... 処理
}
パッケージ名の変更
ご自由にどうぞ
Scalaではパッケージ名とディレクトリ構成は一致していなくても問題ないですがJava同様一致させるのが慣例らしいです。
Scalaを使えるようにする
そのままだとScalaでコーディングができないため設定をします。全体をScalaで書くとFMLから参照できない1ため、FMLから呼ばれる部分のみJavaで記述します2。
IntelliJ IDEAにScalaプラグインを追加
File > Settings > Plugins からScalaプラグインを検索して導入します。導入後エディタの再起動が必要です。
build.gradleの編集
ここが2番目に躓きました(1番は全部Scalaで書けない理由を探すところ)
Scala Pluginの追加
ルートに次の記述を追加します。最初の方に net.minecraftforge.gradle
とか maven-publish
をapplyしてるところがあるのでそこに追記すればいいと思います。
apply plugin: 'scala'
JavaファイルとScalaファイルのジョイント
ここが肝です。114行目ぐらいの
sourceSets.main.resources { srcDir 'src/generated/resources' }
を
sourceSets {
main {
scala {
srcDirs = ['src/main/scala', 'src/main/java']
}
java {
srcDirs = []
}
resources {
srcDir 'src/generated/resources'
}
}
}
のように変更します3。
サンプル
scalaでログインしたユーザーにメッセージを表示するロジックを書き、メインのクラスで登録します。
package ex.client
// import省略
class LoginMessage(){
@SubscribeEvent
def onPlayerLoggedIn(event: PlayerEvent.PlayerLoggedInEvent): Unit = {
val player = event.getPlayer
val pos = player.getPosition
val message = s"""
|Hello, World
|name = [${player.getName.getString}]
|pos = [${pos.getX}. ${pos.getY}, pos.getZ]
""".stripMargin
player.sendMessage(new StringTextComponent(message), player.getUniqueID)
}
}
package ex
// import省略
import ex.client.LoginMessage;
@Mod(TestMod.MODID)
class TestMod{
public static final String MODID = "testmod";
private static final Logger LOGGER = LogManager.getLogger();
public Chalcedony() {
final IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
bus.addListener(this::setup);
bus.addListener(this::enqueueIMC);
bus.addListener(this::processIMC);
bus.addListener(this::doClientStuff);
MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.register(new LoginMessage());
}
// ... 処理
}
参考にした記事: https://qiita.com/niwasawa/items/ca565091468abce48f53
-
ScalaからJVMの中間言語にコンパイルすると名前に
$
サフィクスがつくためMod本体であるシングルトンオブジェクト(のコンパニオンクラス)のコンストラクタを参照できずIllegalAccessException
が投げられるようです。
参照: https://stackoverflow.com/questions/17401565/instantiating-a-scala-class-using-reflection-javas-newinstance
参照: https://github.com/scala/bug/issues/3276 ↩ -
追加でScalaに対応するMDKやScorgeなどがあるようですが導入しなくても開発できそうです。 ↩
-
src/main/java
以下のファイルをJavaではなくScalaとして読み込むようにしています(Scalaは単純にJavaの拡張であるためJavaのコードははScalaとして解釈可能です)。これによりパッケージの依存関係を解決しています。 ↩