Appearance
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
| Tool | Version |
|---|---|
| Java | OpenJDK 25 |
| Gradle | 8.x |
| MySQL | 8.x |
| Paper | 1.21.11 (latest) |
| Velocity | 3.5.0 |
| LuckPerms | 5.5 (latest) |
| Vault | 1.7 |
| PremiumVanish | 2.9.18 |
16.2 Bootstrap Order
- Database: Create the MySQL database
appelcraftand a user with full privileges on it. - Common module: Build first; produces
AppelTools-Common.jar. This is included in both platform jars viaimplementation project(':common'). - Velocity module: Build second; deploys
AppelTools-Velocity.jarto/plugins/. - Paper module: Build third; deploys
AppelTools-Paper.jarto each/plugins/directory. - 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 on40001),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
- Create MySQL DB.
- Clone
AppelTools-Commonskeleton from §5. - Build common:
./gradlew :common:build. - Clone Paper skeleton from §6.
- Build paper:
./gradlew :paper:build. VerifyAppelTools-Paper.jarexists. - Clone Velocity skeleton from §7.
- Build velocity:
./gradlew :velocity:build. VerifyAppelTools-Velocity.jarexists. - Deploy
AppelTools-Velocity.jarto/plugins/. Start Velocity. Schema migrates. - Deploy
AppelTools-Paper.jarto each paper node. Start each. Schema migrates (no-op since velocity already did). - Test: join a paper server; check
/balance. Test/msgfrom one server to another. Test/banfrom velocity.
16.8 Common Pitfalls
- No
LuckPerms: startup throws. Install first. - No
Vault: paper throws. Install first. - Wrong MySQL credentials: Hikari fails. Check logs.
serverUuidcollides: should not happen (random UUID); resetdata.ymlif it does.- Plugin messages dropped: ensure
paper-plugin.ymlchannels include allappeltools:*channels. - Discord bot token invalid: bot doesn't start, but plugin still works; embeds are dropped.
16.9 Verification
After deployment, verify:
/balancereturns your starting balance./pay <otherPlayer> 100works and updates balances./tpa <otherPlayer>and clickable accept/deny works.- From velocity:
/ban Steve griefand observe Steve is kicked. /report Steve x-rayposts a Discord embed (if Discord enabled)./mt onputs the proxy into maintenance; non-bypass players cannot connect./rs 5mschedules 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 inModuleManager, add permission node. - New message: add
MessageKeytoMessages.java; default will be written on first start. - New DB column: update DAO + spec;
SchemaMigratorwill emitMODIFYon next start. - New plugin-messaging channel: add to
PluginChannels.javaconstants and update listeners in both modules. - New Discord embed: add factory method to
Embeds.java.
16.11 Reference
- For the full feature catalogue, see 01-project-overview.md.
- For build commands, see 02-build-system.md.
- For module wiring, see 03-architecture.md.
- For schema DDL, see 04-database-schema.md.
- For class-by-class reference, see 05-common-module.md, 06-paper-module.md, 07-velocity-module.md.
- For plugin-messaging protocol, see 08-messaging.md.
- For permissions, see 09-permissions.md.
- For Discord embeds, see 10-discord.md.
- For plugin updater, see 11-updater.md.
- For chat filter, see 12-chat-filter.md.
- For cross-server flows, see 13-cross-server.md.
- For the config keys, see 14-configs.md.
- For JAR resources, see 15-resources.md.
- For file index, see 17-file-index.md.