使用 Scorge 编写基于 Scala 的 Mod

从 Minecraft 1.13 开始,Forge 不再自带 Scala 标准库作为依赖(作为一个 Scala 吹,这让我很不爽),但 Forge 也为 Scala 提供了一个名为 Scorge 的解决方案。

Scorge 这个项目相当低调,虽然我很快就从 GitHub 上找到了 Scorge 的源代码,但我翻遍了 CurseForge 都没有找到 Scorge 这个 Mod 本身的下载链接,最后通过对 URL 的连蒙带猜从 Forge 官网上找到了下载地址(目前的最新版本是 3.0.6):http://files.minecraftforge.net/maven/net/minecraftforge/Scorge

虽然说他们的代码仍然是基于 1.14 的,但事实上 1.15 也能用,本文将基于 Minecraft 1.15.2 和 Forge 31.2.0,使用的 Gradle 版本是 5.6.4。

构建文件

让我们先从 build.gradle 开始:

buildscript {  
    repositories {
        maven {
            url = 'https://files.minecraftforge.net/maven'
        }
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
    }
}

apply plugin: 'scala'  
apply plugin: 'net.minecraftforge.gradle'

version = '1.0.0'  
group = 'com.github.ustc-zzzz'  
archivesBaseName = 'ScorgeExampleMod'

sourceCompatibility = targetCompatibility = '1.8'

minecraft {  
    mappings channel: 'snapshot', version: '20200514-1.15.1'
    runs {
        client {
            properties 'forge.logging.markers': 'SCAN,REGISTRIES,REGISTRYDUMP'
            properties 'forge.logging.console.level': 'debug'
            workingDirectory project.file('run')
            source sourceSets.main
        }
        server {
            properties 'forge.logging.markers': 'SCAN,REGISTRIES,REGISTRYDUMP,CLASSLOADING'
            properties 'forge.logging.console.level': 'debug,trace'
            workingDirectory project.file('run')
            source sourceSets.main
        }
    }
}

dependencies {  
    minecraft 'net.minecraftforge:forge:1.15.2-31.2.0'
    compile 'org.scala-lang:scala-library:2.13.1'
    compile 'net.minecraftforge:Scorge:3.0.6'
}

jar {  
    manifest.attributes([
        "Specification-Title"     : "scorgeexamplemod",
        "Specification-Vendor"    : "scorgeexamplemodsareus",
        "Specification-Version"   : "1", // We are version 1 of ourselves
        "Implementation-Title"    : project.name,
        "Implementation-Version"  : project.version,
        "Implementation-Vendor"   : "scorgeexamplemodsareus",
        "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
    ])
}

一方面,和基于 Java 的项目相比,基于 Scala 的项目需要 apply plugin: 'scala' 才行。

另一方面,我们需要留意 dependency 块的变化:

dependencies {  
    minecraft 'net.minecraftforge:forge:1.15.2-31.2.0'
    compile 'org.scala-lang:scala-library:2.13.1'
    compile 'net.minecraftforge:Scorge:3.0.6'
}

我们不仅要引入 Scorge 的依赖,还需要引入 Scala 标准库的依赖(请保证 Scala 标准库版本和 Scorge 所使用的版本一致,这里是 2.13.1)。

主类

然后我们随便写一个主类:

package com.github.ustc_zzzz

import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent  
import net.minecraftforge.scorge.lang.ScorgeModLoadingContext  
import org.apache.logging.log4j.LogManager

class ScorgeExampleMod {  
  ScorgeModLoadingContext.get.getModEventBus.addListener { _: FMLCommonSetupEvent =>
    LogManager.getLogger(classOf[ScorgeExampleMod]).info("Hello Scorge Example Mod!")
  }
}

这里有三点需要注意:

  • 我们不需要为主类添加 @Mod 注解。
  • 和 1.12 或更低版本不同,Scorge 要求主类是 class 而不是 object
  • 需要使用 ScorgeModLoadingContext 而非 FMLJavaModLoadingContext 获取事件总线。

得益于 Scala 编译器从 2.12 开始能够将相关代码编译到 Lambda 表达式,我们可以直接使用 Lambda 表达式块的格式向事件总线注册监听器(1.12 或更低不行,因为使用的是 Scala 2.11)。

因为主类没有 @Mod 注解,所以如果想要在游戏启动的时候看到这句话,我们还需要修改 META_INF 目录下的 mods.toml

Mod 描述文件

只有我们在 mods.toml 中指定了主类名,我们才能让 Scorge 加载我们的 Mod 主类:

modLoader="scorge"  
loaderVersion="[3,)"

[[mods]]
version="1.0.0"  
authors="ustc-zzzz"  
modId="scorgeexamplemod"  
displayName="Scorge Example Mod"  
description="Scorge Example Mod"  
entryClass="com.github.ustc_zzzz.ScorgeExampleMod"

[[dependencies.watersprayer]]
modId="forge"  
mandatory=true  
versionRange="[31,)"  
ordering="NONE"  
side="BOTH"

[[dependencies.watersprayer]]
modId="minecraft"  
mandatory=true  
versionRange="[1.15.2]"  
ordering="NONE"  
side="BOTH"  

注意以下两点:

  • modLoader 应取为 scorge,而 loaderVersion 应使用 Scorge 版本。
  • 和基于 Java 的 Mod 相比,我们还需要额外指定一个 entryClass 描述主类的位置。

现在打开游戏就可以看到这句话了(别忘了把 Scorge 本体放进 mods 目录):