0% found this document useful (0 votes)
17 views8 pages

Scala Plugin Development Guide

This document serves as a guide for setting up and building Scala projects using Gradle, emphasizing the use of Kotlin and specific dependencies. It details the structure of a Spigot plugin, including the use of annotations for plugin metadata, service management with the Flavor framework, and command handling with Aikar's Command Framework. Additionally, it covers event listener registration and best practices for organizing business logic within services.

Uploaded by

mksushil20
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views8 pages

Scala Plugin Development Guide

This document serves as a guide for setting up and building Scala projects using Gradle, emphasizing the use of Kotlin and specific dependencies. It details the structure of a Spigot plugin, including the use of annotations for plugin metadata, service management with the Flavor framework, and command handling with Aikar's Command Framework. Additionally, it covers event listener registration and best practices for organizing business logic within services.

Uploaded by

mksushil20
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Intro

Welcome to Scala.

Project Prep
To get started, you must start by including required dependencies into your project. We suggest
using Gradle, as that is what our boilerplate templates use. Our newest plugins use Gradle
Kotlin syntax, but older projects may use the groovy format.

If you're making a new project, create a new default Gradle project, and update your
[Link] to match ours.

I will be referring to this template below:

Make sure your Kotlin version is up-to-date, we run 2.0.0 at the minimum.

...
kotlin("jvm") version "2.0.0"
kotlin("kapt") version "2.0.0"

Make sure to update the group/version to be consistent with our standards. We typically use the
following syntax:
[Link].<pluginName> , for example [Link] for our SurvivalTransfer
plugin.

Project use semantic versioning, and will present in the allprojects clause as shown below:

group = "[Link]"
version = "1.0.0"

Scrolling below, you should be changing the archiveFileName :

[Link](
"[Link]"
)

As for the dependencies clause, projects are required to have the Scala commons., store,
spigot, cloudsync, and lemon dependency at the minimum. The Kotlin stdlib is shaded into the
commons plugin, so you should leave it as compileOnly.
dependencies {
compileOnly(kotlin("stdlib"))

kapt("[Link]:bukkit:4.0.0")
compileOnly("[Link]:bukkit:4.0.0")

compileOnly("[Link]:spigot:1.0.0")
compileOnly("[Link]:server:1.1.3")

compileOnly("[Link]:bukkit:1.9.1")
compileOnly("[Link]:spigot:1.0.4")
}

If you look above, you see that commons has two clauses. One of them is a kapt clause. This is
the Kotlin annotation processor system, and is required when using a Kotlin codebase.

If you are using a fusion of Java and Kotlin, make sure to add the respective
annotationProcessor clause for commons, as kapt does not do anything on Java classes. Be
aware that you may need an additional Gradle statement to make both Java
annotationProcessor and kapt clauses work properly in a single project:

kapt {
keepJavacAnnotationProcessors = true
}

If you are building a multi-module project, the setup is similar. Refer to the
[Link] project for a great example of this.

Building a Spigot Plugin


Scala Commons has tons of utilities that are exposed when you use an ExtendedScalaPlugin
instead of a JavaPlugin .

In Kotlin, you should be defining a main class with the following syntax:

The main class name should be: ProjectNamePlugin

@Plugin(
name = "ScStaff",
version = "%remote%/%branch%/%id%"
)

@PluginAuthor("Scala")
@PluginWebsite("[Link]

@PluginDependency("Lemon")
@PluginDependency("scala-commons")

@PluginDependency("ScBasics", soft = true)


@PluginDependency("cloudsync", soft = true)
class ScalaStaffPlugin : ExtendedScalaPlugin()

Or on java:

@Plugin(
name = "ScChestProtect",
version = "1.0"
)
@PluginAuthor("GrowlyX")
@PluginDependency("scala-commons")
public class ChestProtectPlugin extends ExtendedScalaPlugin {
}

You may see the annotations above as confusing, but these are really the annotation
processors in action. The annotations you see above this main class act as a [Link]
statement. So, you do not need to create your own [Link].

By including the version as: %remote%/%branch%/%id% , git branch/id/remote information


will be automatically included.

These statements are the only statements needed when building a plugin, so do not worry
about commands or library dependencies (like on 1.20).

Plugin Containers
An important thing to note with an ExtendedScalaPlugin is that we override the main
onEnable , onLoad , and onDisable methods that are given by the default JavaPlugin.
Instead, developers must use the following equivalents:

In Java, can be converted to Kotlin easily:

@ContainerPreEnable
public void containerPreEnable() {
}

@ContainerEnable
public void containerEnable() {
}

@ContainerDisable
public void containerDisable() {
}

An important thing to note is that the ExtendedScalaPlugin is also a Lucko Helper


ExtendedJavaPlugin, meaning that listeners, tasks, and anything else terminable can be binded
to the plugin (when plugin shuts down, those resources will also expire).

Look into these links if you want to learn more:


[Link]
[Link]
[Link]

In some existing plugins, you may see that we have absolutely nothing in the main class. This is
a common practice, as we separate out all of our business logic into Services, which I will talk
about more below.

Services
Scala uses the bespoke (and opinionated) dependency injection and service management
framework called Flavor, built in house. This has proven to work well, as even after 2+ years,
we haven't had any major issues with the system (last update to flavor was more than a year
ago!).

Flavor is similar to other dependency injection systems out there, but it is most similar to HK2. If
you want to look into HK2 for general knowledge, I recommend you to do so:
[Link]

The centerpiece of Flavor is its great service management system. This allows us to separate
all of our business logic into separate, well-managed files called Services that are automatically
registered, injected, and lifecycle-tracked.

An example Service would look like:

@Service
// @IgnoreAutoScan - if you do not want Flavor to
// automatically register this service
object SomeService
{
@Inject
lateinit var something: String
@Inject
lateinit var plugin: MyMainPlugin

@Configure
fun configure()
{
// this method is invoked once all
// fields have been injected!
}

@Close
fun close()
{
// this method is invoked on your
// platform's shutdown!
}
}

Please note that, if you want an auto-scanned Java service, you must have a statement in the
class as shown below (as services must be singletons):

public static final @NotNull SomeService INSTANCE = new SomeService();

For injection, the ExtendedScalaPlugin automatically binds several things for you to use in your
services. Here are some:

ExtendedScalaPlugin, JavaPlugin, Plugin


All plugins available on the server at the time of injection, with a @Named annotation
Server
PluginManager
String named bukkit:version
BukkitScheduler
Messenger

You may also access the plugin's Flavor instance to inject something yourself using:

[Link]().bind(someInstance).to<SomeClass>()

With this system, you can also easily limit service initialization and add requirements:
For example, if we want a service that only initializes if the Hors plugin is enabled, you would
do so with the following syntax:

@Service
@IgnoreAutoScan // don't automatically initialize, it requires Hors
@SoftDependency("Hors") // tell it that it requires Hors
object HorsSettingProvider : SettingProvider
{
// ...
}

A HIGHLY recommended service for all plugins is a CloudSyncFeature service. This will make
it so your plugin is tracked by CloudSync, and updates are automatically installed when
available. This class is boilerplate, and very easy to add into your project:

Within this statement: [Link]:game:SolaraPractice-game

The first argument is the project package, as described in your main [Link](.kts)
group value.
The second argument is the module, if in a multi-module project, or the project name in the
root [Link](.kts) file.
The third argument is the jar name, as described in the archiveFileName in your root
[Link](.kts) file. Please exclude the .jar

@Service
@IgnoreAutoScan
@SoftDependency("cloudsync")
object CloudSyncFeature
{
@Configure
fun configure()
{
CloudSyncDiscoveryService
.[Link](
"[Link]:game:SolaraPractice-game${
if ("dev" in [Link]().groups)
":gradle-dev" else ""
}"
)
}
}

Commands
Commands are heavily wrapped in scala commons to provide really easy usage for the
developer. Our framework is a wrapped version of Aikar's Command Framework, which is a
powerful command framework.

I highly suggest looking into ACF before reading on:


[Link]

A feature of our wrappers on ACF is that we can Auto Register commands similar to how
Services are automatically registered.

However, if you want to manually register a command in a plugin, you can do so by adding
a @ManualRegister function in the main class of your project, that takes in the
ScalaCommandManager as an argument.
See this for a good example.

ACF also automatically creates /commandLabel help commands for you.

All commands must extend the ScalaCommand class, but they do not need to be singletons.
However, it is a common practice to make them singletons in Kotlin.

@AutoRegister // <-- this does the automatic registration


@CommandPermission("[Link]")
@CommandAlias("note|notes|profilenotes|staffnotes")
object StaffNotesCommand : ScalaCommand()
{
@Default
@HelpCommand
fun onHelp(help: CommandHelp)
{
[Link]()
}

@Subcommand("list")
@CommandCompletion("@players")
@Description("List all notes attached to a player.")
fun onList(sender: Player, target: AsyncLemonPlayer) = // ...

You may need to create custom conditions, completions, or command contexts. You can do this
in an ExtendedScalaPlugin by creating a new non-Service singleton, and creating a function
called customize that has the argument of a ScalaCommandManager . This will be invoked on
startup, right after the plugin's command manager is initialized:

object BasicPluginCommandCustomizer
{
@CommandManagerCustomizer
fun customize(manager: ScalaCommandManager)
{
[Link]
.addCondition("requires-disguise") {
if (![Link]().isDisguised([Link]))
throw ConditionFailedException(
"You are not disguised! Disguise yourself using
${[Link]}/disguise${[Link]} to use this command!"
)
}
}

Something you should note is that CommandManagerCustomizers are global, and will be
applied to every single plugin prior to the plugin's initialization.

Event Listeners
If you want to subscribe to an event, I HIGHLY recommend using a new service, and in the
@Configure function, using helper's Events class. See this class for an example.

However, if you absolutely need to, you may define events the traditional way using the Listener
class. We make this easier for you by providing a system that will automatically inject your
class, as well as automatically register the listener for you. You can use the @Listeners for
this functionality.

@Listeners
object NametagListener : Listener
{
@Inject
lateinit var plugin: ScalaCommonsSpigot

@EventHandler
fun onPlayerQuit(event: PlayerQuitEvent)
{
[Link]("Nametag-Applied", [Link])
[Link]([Link])
}
}

You might also like