Skip to content

Project Structure

alexstaeding edited this page Mar 5, 2020 · 1 revision

This page serves as a comprehensive template for your plugin and is not meant to be a "tutorial" as such but instead a reference for what your plugin "should" look like.

Root Project

No actual java code in this module, but the build.gradle should look something like this to avoid repeating yourself in subprojects:

plugins {
    id 'java'
}

subprojects {
    group "org.anvilpowered"
    version "A1.0-1.0.0-SNAPSHOT"
    sourceCompatibility = 1.8
    if (project.hasProperty("buildNumber") && version.contains("-SNAPSHOT")) {
        version = version.replace("-SNAPSHOT", "-RC${buildNumber}")
    }
}
A1.0

This marks the Anvil API version this plugin was built for (A1.0 means compatibility with v >= 1.0 && v < 2.0)

if (project.hasProp...

This checks whether the property "buildNumber" was provided and is responsible for injecting the build number into release candidate jars. Can be run with e.g. ./gradlew build -PbuildNumber=58.

API

The API is where the structure of your plugin is defined and is full of method contracts but no actual implementation. Package names in this module should start with the format <groupid>.<artifactid>.api. For Anvil, this is org.anvilpowered.anvil.api. The build.gradle for this module should look something like this:

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.anvilpowered:anvil-api:1.0'
    implementation 'org.anvilpowered:anvil-api-mongodb:1.0'
    implementation 'org.anvilpowered:anvil-api-xodus:1.0'

    implementation guice
    implementation morphia
    implementation xodus_entity_store
}

Common

This is where your shared code goes. Anything that doesn't require direct access to a platform's code should go in here. One of the things you should have in this module is the main plugin class, which will later be extended by the main classes in the platform modules. It should look something like this:

package org.anvilpowered.simpletickets.common.plugin;

import com.google.inject.Injector;
import com.google.inject.Module;
import org.anvilpowered.anvil.api.Environment;
import org.anvilpowered.anvil.base.plugin.BasePlugin;

public abstract class SimpleTickets<TPluginContainer> extends BasePlugin<TPluginContainer> {

    protected SimpleTickets(Injector injector, Module module) {
        super(SimpleTicketsPluginInfo.id, injector, module);
    }

    @Override
    protected void applyToBuilder(Environment.Builder builder) {
        builder.withRootCommand(); // tell Anvil to load root command node
    }
}

The main function of BasePlugin is to provide a quick way to create an environment. Very simple plugins will only need a constructor here. However, you can use applyToBuilder(Environment.Builder) (as in the above example) for more control. For complete control, use the BasePlugin(String) constructor which does not create an environment for you. If you do this, you must use Anvil.getEnvironmentBuilder() to create an environment yourself.

Please note that you do not have to extend BasePlugin. This base implementation should cover most use cases, but it is also possible to just implement Plugin instead.

Implement the PluginInfo interface to store your plugin's metadata. This should look something like this:

package org.anvilpowered.simpletickets.common.plugin;

import com.google.inject.Inject;
import org.anvilpowered.anvil.api.plugin.PluginInfo;
import org.anvilpowered.anvil.api.util.TextService;

public class SimpleTicketsPluginInfo<TString, TCommandSource> implements PluginInfo<TString> {
    public static final String id = "simpletickets";
    public static final String name = "Simple Tickets";
    public static final String version = "$modVersion"; // replaced by blossom from gradle
    public static final String description = "A simple ticket plugin";
    public static final String url = "https://github.com/AnvilPowered/SimpleTickets";
    public static final String[] authors = {"Cableguy20"};
    public static final String organizationName = "AnvilPowered";
    public static final String buildDate = "$buildDate"; // replaced by blossom from gradle
    public TString pluginPrefix;

    @Inject
    public void setPluginPrefix(TextService<TString, TCommandSource> textService) {
        pluginPrefix = textService.builder()
                .blue().append("[")
                .aqua().append(name)
                .blue().append("] ") // space at the end to make it easier to append text later
                .build();
    }

... getters and setters not included ...

The build.gradle for this module should look something like this:

import java.text.SimpleDateFormat

plugins {
    id 'java'
    id "net.kyori.blossom" version "1.1.0"
}

repositories {
    mavenCentral();
}

dependencies {
    implementation project(':api')

    implementation 'org.anvilpowered:anvil-api:1.0'
    implementation 'org.anvilpowered:anvil-api-mongodb:1.0'
    implementation 'org.anvilpowered:anvil-api-xodus:1.0'

    implementation 'org.anvilpowered:anvil-base:1.0'
    implementation 'org.anvilpowered:anvil-base-mongodb:1.0'
    implementation 'org.anvilpowered:anvil-base-xodus:1.0'

    implementation guice
    implementation morphia
    implementation xodus_entity_store
}

// for replacing placeholders in string literals
blossom {
    replaceToken '$modVersion', version
    SimpleDateFormat format = new SimpleDateFormat("uuuu-MM-dd-HH:mm:ss z")
    format.setTimeZone(TimeZone.getTimeZone("UTC"))
    String buildDate = format.format(new Date())
    replaceToken '$buildDate', buildDate
}

Platform

The platform module is where all code goes that needs direct access to a platform's API. A good example of this is command registration with CommandSpec from Sponge.

This is also where the main plugin classes are located. These main classes are very important, as they are currently the only way your plugin can get loaded. A Sponge main class should look something like this:

package org.anvilpowered.simpletickets.sponge.plugin;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import org.anvilpowered.simpletickets.common.plugin.SimpleTickets;
import org.anvilpowered.simpletickets.common.plugin.SimpleTicketsPluginInfo;
import org.anvilpowered.simpletickets.sponge.module.SpongeModule;
import org.spongepowered.api.plugin.Dependency;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.plugin.PluginContainer;

@Plugin(
    id = SimpleTicketsPluginInfo.id,
    name = SimpleTicketsPluginInfo.name,
    version = SimpleTicketsPluginInfo.version,
    dependencies = @Dependency(id = "anvil"),
    description = SimpleTicketsPluginInfo.description,
    url = SimpleTicketsPluginInfo.url,
    authors = "Cableguy20"
)
public class SimpleTicketsSponge extends SimpleTickets<PluginContainer> {

    @Inject
    public SimpleTicketsSponge(Injector injector) {
        super(injector, new SpongeModule());
    }
}

Finally, the build.gradle for a Sponge module should look something like this:

plugins {
    id 'java'
    id 'com.github.johnrengelman.shadow' version '5.2.0'
    id 'org.spongepowered.plugin' version '0.9.0'
}

jar.enabled = false // we only want shadowJar

repositories {
    mavenCentral()
    maven { url 'https://repo.spongepowered.org/maven' }
}

dependencies {
    implementation project(':api')
    implementation project(':common')
    
    implementation 'org.anvilpowered:anvil-api:1.0'
    implementation 'org.anvilpowered:anvil-api-mongodb:1.0'
    implementation 'org.anvilpowered:anvil-api-xodus:1.0'

    implementation 'org.anvilpowered:anvil-base:1.0'
    implementation 'org.anvilpowered:anvil-base-mongodb:1.0'
    implementation 'org.anvilpowered:anvil-base-xodus:1.0'

    implementation guice
    implementation bson
    implementation javasisst
    implementation mongo_java_driver
    implementation xodus_entity_store

    compileOnly('org.spongepowered:spongeapi:7.2.0-SNAPSHOT') {
        exclude(module: 'configurate-gson')
        exclude(module: 'configurate-yaml')
    }
    annotationProcessor 'org.spongepowered:spongeapi:7.2.0-SNAPSHOT'
}

shadowJar {
    String jarName = "SimpleTickets-Sponge-${project.version}.jar"
    println "Building: " + jarName
    archiveFileName = jarName

    // include these projects in the final jar
    dependencies {
        include project(':api')
        include project(':common')
    }
}

artifacts {
    archives shadowJar
}