Skip to content

16. Rebuilding From Scratch ​

This document is a guide to reconstructing the entire AppelTools plugin suite using only this reference. It assumes you have the technology stack installed (Java 25, Gradle 9+, MySQL 8+, Velocity 4.2, Paper 26.3).

16.1 Prerequisites ​

ToolVersion
JavaOpenJDK 25
Gradle8.x
MySQL8.x
Paper1.21.11 (latest)
Velocity3.5.0
LuckPerms5.5 (latest)
Vault1.7
PremiumVanish2.9.18

16.2 Bootstrap Order ​

  1. Database: Create the MySQL database appelcraft and a user with full privileges on it.
  2. Common module: Build first; produces AppelTools-Common.jar. This is included in both platform jars via implementation project(':common').
  3. Velocity module: Build second; deploys AppelTools-Velocity.jar to /plugins/.
  4. Paper module: Build third; deploys AppelTools-Paper.jar to each /plugins/ directory.
  5. Verify: Start Velocity, then each Paper node. The DB schema migrates on first connect.

16.3 Module Skeletons ​

16.3.1 common/build.gradle ​

groovy
import org.apache.tools.ant.filters.ReplaceTokens
import java.nio.charset.StandardCharsets

plugins {
    id 'java-library'
    id 'com.gradleup.shadow' version '9.3.2'
}

java { withSourcesJar() }
group = "${rootProject.group}.common"
version = rootProject.version
base { archivesName.set("${rootProject.name}-Common") }

tasks.register('replaceTokens', Copy) {
    from("src/main/java") {
        include "**/*.java"
        filteringCharset = 'UTF-8'
        filter(ReplaceTokens, tokens: [
                'version'    : project.version,
                'description': project.description ?: "",
                'author'     : rootProject.ext.get("author").toString()
        ])
    }
    into "${layout.buildDirectory.get()}/filteredSrc"
}

compileJava {
    dependsOn replaceTokens
    source = fileTree("${layout.buildDirectory.get()}/filteredSrc")
    options.encoding = StandardCharsets.UTF_8.name()
    options.release.set(21)
}

dependencies {
    compileOnly 'com.google.code.gson:gson:2.15.0'
    implementation 'com.google.guava:guava:33.8.0-jre'
    compileOnly 'net.kyori:adventure-api:4.26.1'
    compileOnly 'net.kyori:adventure-text-minimessage:4.26.1'
    compileOnly 'de.exlll:configlib-core:4.8.1'
    compileOnly 'net.luckperms:api:5.5'
    compileOnly 'com.mysql:mysql-connector-j:26.10.0'
    compileOnly 'com.zaxxer:HikariCP:7.1.0'
    compileOnly 'org.slf4j:slf4j-api:2.0.20'
    compileOnly('net.dv8tion:JDA:6.7.0') { exclude module: 'opus-java'; exclude module: 'tink' }
    testImplementation 'org.junit.jupiter:junit-jupiter:6.1.3'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

shadowJar { archiveClassifier.set('') }
build { dependsOn shadowJar }

16.3.2 paper/build.gradle ​

groovy
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.sftp.SFTPClient
import net.schmizz.sshj.transport.verification.PromiscuousVerifier

buildscript {
    repositories { mavenCentral() }
    dependencies { classpath 'com.hierynomus:sshj:0.40.0' }
}

plugins {
    id 'java'
    id 'com.gradleup.shadow' version '9.3.2'
}

group = "${rootProject.group}.paper"
version = rootProject.version
base { archivesName.set("${rootProject.name}-Paper") }

dependencies {
    implementation project(':common')
    compileOnly 'ch.qos.logback:logback-classic:1.6.6'
    compileOnly 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT'
    compileOnly('com.github.MilkBowl:VaultAPI:1.7') { exclude group: 'org.bukkit', module: 'bukkit' }
    compileOnly 'com.github.LeonMangler:PremiumVanishAPI:2.9.18-2'
    compileOnly 'de.exlll:configlib-core:4.8.1'
    compileOnly 'net.luckperms:api:5.5'
    compileOnly('net.dv8tion:JDA:6.7.0') { exclude module: 'opus-java'; exclude module: 'tink' }
    implementation 'dev.rollczi:litecommands-bukkit:3.11.0'
    testImplementation 'org.junit.jupiter:junit-jupiter:6.1.3'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks {
    compileJava {
        options.encoding = 'UTF-8'
        options.release.set(21)
        options.compilerArgs.add("-parameters")
    }
    processResources {
        filesMatching("paper-plugin.yml") {
            expand([
                    'version'    : project.version,
                    'description': project.description ?: "",
                    'author'     : rootProject.ext.get("author").toString()
            ])
        }
    }
    shadowJar {
        archiveClassifier.set('')
        manifest { attributes 'Main-Class': "${rootProject.group}.paper.AppelToolsPaperLoader" }
        relocate 'dev.rollczi.litecommands', "${rootProject.group}.libs.litecommands"
        dependsOn project(':common').tasks.shadowJar
    }
    build { dependsOn shadowJar }
}

16.3.3 velocity/build.gradle ​

groovy
plugins {
    id 'java'
    id 'com.gradleup.shadow' version '9.3.2'
}

group = "${rootProject.group}.velocity"
version = rootProject.version
base { archivesName.set("${rootProject.name}-Velocity") }

dependencies {
    implementation project(':common')
    compileOnly 'com.velocitypowered:velocity-api:4.2.0'
    compileOnly 'com.velocitypowered:velocity-proxy:4.2.0'
    annotationProcessor 'com.velocitypowered:velocity-api:4.2.0'
    compileOnly 'net.luckperms:api:5.5'
    compileOnly 'io.github.4drian3d:vpacketevents-api:1.2.0'
    compileOnly 'de.exlll:configlib-core:4.8.1'
    implementation 'javax.annotation:javax.annotation-api:1.3.2'
    implementation 'com.mysql:mysql-connector-j:26.10.0'
    implementation 'com.zaxxer:HikariCP:7.1.0'
    implementation 'dev.rollczi:litecommands-velocity:3.11.0'
    implementation 'dev.rollczi:litecommands-jda:3.11.0'
    implementation('net.dv8tion:JDA:6.7.0') { exclude module: 'opus-java'; exclude module: 'tink' }
    testImplementation 'org.junit.jupiter:junit-jupiter:6.1.3'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks {
    compileJava {
        options.encoding = 'UTF-8'
        options.release.set(21)
        options.compilerArgs.add("-parameters")
    }
    shadowJar {
        archiveClassifier.set('')
        manifest { attributes 'Main-Class': "${rootProject.group}.velocity.AppelToolsVelocity" }
        relocate 'com.mysql', "${rootProject.group}.libs.mysql"
        relocate 'com.zaxxer.hikari', "${rootProject.group}.libs.hikari"
        relocate 'dev.rollczi.litecommands', "${rootProject.group}.libs.litecommands"
        relocate 'net.dv8tion.jda', "${rootProject.group}.libs.jda"
        dependsOn project(':common').tasks.shadowJar
    }
    build { dependsOn shadowJar }
}

16.4 Common Module Class Skeletons ​

Every class in 05-common-module.md needs to exist in the appropriate package. Below is a stub layout:

Constants.java ​

java
package me.Maussee.AppelTools.common;

public final class Constants {
    public static final String VERSION = "@version@";
    public static final String DESCRIPTION = "@description@";
    public static final String AUTHOR = "@author@";
    public static final String MINECRAFT_VERSION = "1.21";
    public static final int DATABASE_VERSION = 4;
    public static final int CURRENT_PUNISHMENT_SCHEMA_VERSION = 2;
    private Constants() {}
}

Database/Database.java ​

  • See §5.3 for the full API.
  • Key methods: setup(DatabaseConfig), runQuery, runUpdate (with retry on 40001), shutdown.

Database/SchemaMigrator.java ​

  • See §5.3.

Database/DAO/* ​

  • One class per DAO; see §5.4.

Punishments/* ​

  • Punishment, PermPunishment, TempPunishment, UndonePunishment, PunishmentType, PunishmentChecker.

Types/* ​

  • ServerType, Prefix, Permissions (full list in 09-permissions.md), Party, PluginChannels, DefaultOptions, RegexFilter, CancellableDuration.

Types/Data/* ​

  • ATPlayerData, ATPlayerServerData, ATServerData, ATHome, ATFriendRequest, ATStaffInventory, Report, PluginUpdateConfig.

Types/Query/* ​

  • QueryKey, QueryFilter, SingleQueryFilter, MultipleQueryFilter.

Config/* ​

  • ConfigKey, TypeTokens, ModuleAdapter, ModulesAdapter, Locale, MessageKey, MessageMigrations, YamlStore.

Adapters/* ​

  • AppelToolsAdapter, ATUserAdapter, KeyAdapter, MessageKeyAdapter, PlayerManager.

Logger/* ​

  • AppelLogger, SLF4JLogger.

Scheduler/* ​

  • SchedulerAdapter, ScheduledTaskAdapter.

Discord/* ​

  • BaseDiscordBot, Embeds.

Utils/* ​

  • CommonUtils, TextUtils, FileUtils, UUIDFetcher, PluginUpdater (with 6 inner provider classes), ModuleManagerAdapter, Exceptions/StartupException, Exceptions/ModuleNotFoundException.

Queue/* ​

  • SignedResult, QueuedData, SignedQueue.

16.5 Paper Module Skeleton ​

AppelToolsPaperLoader ​

java
package me.Maussee.AppelTools.paper;
public class AppelToolsPaperLoader extends PluginLoader {
    @Override public JavaPlugin createPlugin(...) {
        return new AppelToolsPaperBootstrap(...);
    }
}

AppelToolsPaperBootstrap ​

java
@Plugin(name = "AppelTools", ...)
public class AppelToolsPaperBootstrap extends JavaPlugin {
    private AppelToolsPaper instance;
    @Override public void onEnable() { instance = new AppelToolsPaper(this); instance.enable(); }
    @Override public void onDisable() { instance.shutdown(); }
}

AppelToolsPaper ​

Implements AppelToolsAdapter. Implements the lifecycle in §3.7.

Subpackages ​

  • Types/ATPPlayer, ATPConsole.
  • Commands/Economy/{Balance, Pay, TogglePay, Economy, Baltop}Command.
  • Commands/Gamemode/{Gamemode, Creative, Survival, Adventure, Spectator}Command.
  • Commands/Home/{Home, SetHome, DelHome, DelHomeConfirm, RenameHome}Command.
  • Commands/Speed/{Speed, WalkingSpeed, FlyingSpeed, Fly}Command.
  • Commands/Teleport/{Teleport, TeleportHere, TeleportPos, Back, TeleportToggle, TeleportAuto, TeleportAsk, TeleportAHere, TeleportAccept, TeleportDeny, TeleportACancel}Command.
  • Commands/NotesGuiCommand, Commands/AppelToolsPaperCommand.
  • Commands/Helpers/{Arguments, Contexts, Handler}/....
  • Config/{Config, Messages, Module}.java.
  • Discord/DiscordBot.java, Discord/Commands/SearchCommand.java.
  • Economy/VaultEconomyProvider.java.
  • Events/{PlayerJoin, PlayerQuit, StaffFreeze, StaffMode, StaffTool, PlayerChat}Listener.java.
  • Utils/Managers/{ATPPlayerManager, CommandRegistrar, ModuleManager, StaffInventoryManager, PluginUpdaterManager, BaltopCache}.java.
  • Utils/Scheduler/{BukkitSchedulerAdapter, BukkitScheduledTaskAdapter}.java.
  • MessageFormatter.java, PluginChecker.java.

16.6 Velocity Module Skeleton ​

velocity-plugin.json ​

json
{
  "id": "appeltoolsvelocity",
  "name": "AppelTools",
  "version": "${version}",
  "description": "${description}",
  "author": "${author}",
  "dependencies": [{"id": "luckperms"}]
}

AppelToolsVelocityBootstrap ​

java
@Plugin(id = "appeltools", ...)
public class AppelToolsVelocityBootstrap {
    @Inject private Logger logger;
    @Inject private ProxyServer server;
    @Inject private PluginManager pluginManager;
    @Inject private @DataDirectory Path dataDir;

    private AppelToolsVelocity instance;

    @Subscribe
    public void onProxyInitialization(ProxyInitializeEvent event) {
        instance = new AppelToolsVelocity(logger, server, pluginManager, dataDir);
        instance.onProxyInitialization();
    }
    @Subscribe
    public void onProxyShutdown(ProxyShutdownEvent event) {
        instance.onProxyShutdown();
    }
}

AppelToolsVelocity ​

Implements AppelToolsAdapter.

Subpackages ​

  • Types/{ATVPlayer, ATVConsole}.java.
  • Commands/Punishment/{Ban, TempBan, Unban, IPBan, TempIPBan, IPUnban, Kick, IPKick, Mute, TempMute, Unmute, IPMute, TempIPMute, IPUnmute, History}Command.java.
  • Commands/Friends/FriendsCommand.java.
  • Commands/Party/{Party, PartyChat}Command.java.
  • Commands/Message/{Message, Reply, Ignore, Unignore, MessageToggle}Command.java.
  • Commands/Report/{Report, Reports}Command.java.
  • Commands/Staff/{StaffChat, StaffChatToggle, StaffModeToggle, StaffNotificationsToggle, StaffSpyToggle, StaffList, VersionList, Broadcast, StaffNotes}Command.java.
  • Commands/Other/{Maintenance, RestartServer, Servers, AppelToolsVelocity}Command.java.
  • Commands/Helpers/{Arguments, Contexts, Handler}/....
  • Config/{Config, Messages, Module, Modules}.java.
  • Discord/{DiscordBot, StaffNotificationService}.java.
  • Events/{PlayerJoin, PlayerQuit, PlayerServerSwitch, PluginMessage, StaffChat, Chat, ProxyPing, PreLogin, Login}Listener.java.
  • Punishments/PunishmentManager.java.
  • Utils/Managers/{ATVPlayerManager, CommandRegistrar, ModuleManager, PluginUpdaterManager, FriendManager, AnnouncementManager, RestartManager}.java.
  • Utils/Scheduler/{VelocitySchedulerAdapter, VelocityScheduledTaskAdapter}.java.

16.7 Step-by-Step Build Order ​

  1. Create MySQL DB.
  2. Clone AppelTools-Common skeleton from §5.
  3. Build common: ./gradlew :common:build.
  4. Clone Paper skeleton from §6.
  5. Build paper: ./gradlew :paper:build. Verify AppelTools-Paper.jar exists.
  6. Clone Velocity skeleton from §7.
  7. Build velocity: ./gradlew :velocity:build. Verify AppelTools-Velocity.jar exists.
  8. Deploy AppelTools-Velocity.jar to /plugins/. Start Velocity. Schema migrates.
  9. Deploy AppelTools-Paper.jar to each paper node. Start each. Schema migrates (no-op since velocity already did).
  10. Test: join a paper server; check /balance. Test /msg from one server to another. Test /ban from velocity.

16.8 Common Pitfalls ​

  • No LuckPerms: startup throws. Install first.
  • No Vault: paper throws. Install first.
  • Wrong MySQL credentials: Hikari fails. Check logs.
  • serverUuid collides: should not happen (random UUID); reset data.yml if it does.
  • Plugin messages dropped: ensure paper-plugin.yml channels include all appeltools:* channels.
  • Discord bot token invalid: bot doesn't start, but plugin still works; embeds are dropped.

16.9 Verification ​

After deployment, verify:

  1. /balance returns your starting balance.
  2. /pay <otherPlayer> 100 works and updates balances.
  3. /tpa <otherPlayer> and clickable accept/deny works.
  4. From velocity: /ban Steve grief and observe Steve is kicked.
  5. /report Steve x-ray posts a Discord embed (if Discord enabled).
  6. /mt on puts the proxy into maintenance; non-bypass players cannot connect.
  7. /rs 5m schedules a restart; broadcasts appear at 1m and 5/4/3/2/1s marks.

16.10 Extension Points ​

  • New command: create class with @Command, @Execute, @Permission, register in ModuleManager, add permission node.
  • New message: add MessageKey to Messages.java; default will be written on first start.
  • New DB column: update DAO + spec; SchemaMigrator will emit MODIFY on next start.
  • New plugin-messaging channel: add to PluginChannels.java constants and update listeners in both modules.
  • New Discord embed: add factory method to Embeds.java.

16.11 Reference ​

Built with VitePress.