So you want to write plugins in Scala? Great! Or not so great? Scala runtime classes are not on the class/module path by default. Server administrators could add them using the -classpath commandline option, but in reality most bukkit servers run in managed environments by minecraft-specialized server hosts. The standard workaround for this problem is to include the classes in your own plugin and relocate them using a shading plugin in your build process. While this does work, it is not ideal because your plugin will increase in size by a lot. As of Scala 2.12.6, the standard library has a size of 3.5 MB. The reflection library is another 5 MB. Using both libraries in multiple plugins results unnecessarily large plugins sizes, while really those Scala classes should only be loaded once. Spigot 1.16.5 introduced a new library loading api, but it has similar problems. Introducing...
ScalaLoader uses a custom PluginLoader that loads the Scala runtime classes for you! Javadoc available here!
JavaPlugin#getProvidingPlugin(Class<?>)
internally), which will error when the class is loaded by the ScalaPluginClassLoader.
If possible, always use methods that let you pass the Plugin instance explicitly!There's only seven two features that are missing in my opinion:
package xyz.janboerman.scalaloader.example.scala
import org.bukkit.ChatColor
import org.bukkit.command.{CommandSender, Command}
import org.bukkit.event.{EventHandler, Listener}
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.permissions.PermissionDefault
import xyz.janboerman.scalaloader.plugin.ScalaPluginDescription.{Command => SPCommand, Permission => SPPermission}
import xyz.janboerman.scalaloader.plugin.{ScalaPlugin, ScalaPluginDescription}
import xyz.janboerman.scalaloader.plugin.description.{Scala, ScalaVersion, Api, ApiVersion}
@Scala(version = ScalaVersion.v2_13_10)
@Api(ApiVersion.v1_19)
object ExamplePlugin
extends ScalaPlugin(new ScalaPluginDescription("ScalaExample", "1.0")
.commands(new SPCommand("foo")
.permission("scalaexample.foo"))
.permissions(new SPPermission("scalaexample.foo")
.permissionDefault(PermissionDefault.TRUE)))
with Listener {
getLogger().info("ScalaExample - I am constructed!")
override def onLoad(): Unit = {
getLogger.info("ScalaExample - I am loaded!")
}
override def onEnable(): Unit = {
getLogger.info("ScalaExample - I am enabled!")
getServer.getPluginManager.registerEvents(this, this)
}
override def onDisable(): Unit = {
getLogger.info("ScalaExample - I am disabled!")
}
@EventHandler
def onJoin(event: PlayerJoinEvent): Unit = {
event.setJoinMessage(s"${ChatColor.GREEN} Howdy ${event.getPlayer.getName}!")
}
override def onCommand(sender: CommandSender, command: Command, label: String, args: Array[String]): Boolean = {
sender.sendMessage("Executed foo command!")
true
}
def getInt() = 42
}
plugin.yml:
name: DummyPlugin
version: 1.0
main: xyz.janboerman.dummy.dummyplugin.DummyPlugin
depend: [ScalaLoader]
softdepend: [ScalaExample] #A hard dependency will not work! Your plugin will not load!
Java code:
package xyz.janboerman.dummy.dummyplugin;
import org.bukkit.plugin.java.JavaPlugin;
import xyz.janboerman.scalaloader.compat.IScalaLoader;
import xyz.janboerman.scalaloader.plugin.ScalaPluginLoader;
import xyz.janboerman.scalaloader.example.scala.ExamplePlugin$;
public final class DummyPlugin extends JavaPlugin {
@Override
public void onEnable() {
//get the plugin instance
ExamplePlugin$ scalaPlugin = (ExamplePlugin$) getServer().getPluginManager().getPlugin("ScalaExample");
//make sure all classes from the scala plugin can be accessed
IScalaPluginLoader.openUpToJavaPlugin(scalaPlugin, this);
//do whatever you want afterwards!
getLogger().info("We got " + scalaPlugin.getInt() + " from Scala!");
}
}
It's a maven project, so just run mvn package
and you're good to go.
The jar file will be built at ./ScalaLoader/target/ScalaLoader-<version>.jar
Note that while ScalaLoader can run on Java 8, it requires JDK-21 to compile.
As of ScalaLoader 0.18.x, it is required that Paper is installed in the local maven repository.
Available on SpigotMC.
Available on GitHub Pages.
resolvers += "jannyboy11-minecraft-repo" at "https://repo.repsy.io/mvn/jannyboy11/minecraft"
libraryDependencies += "com.janboerman.scalaloader" % "ScalaLoader" % "0.18.6-SNAPSHOT" % "provided"
<repository>
<id>jannyboy11-minecraft</id>
<name>Jannyboy11 Minecraft Repo</name>
<url>https://repo.repsy.io/mvn/jannyboy11/minecraft</url>
</repository>
<dependency>
<groupId>com.janboerman.scalaloader</groupId>
<artifactId>ScalaLoader</artifactId>
<version>0.18.10-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
LGPL, because I want forks of this thing to be open for auditing. If you however which to include parts this code base in your own open source project but not adopt the (L)GPL license, please contact me and I will likely permit you to use this under a different license. Sending me a private message on the SpigotMC forums or an issue on this repository will do.