diff --git a/bootstrap.sh b/bootstrap.sh index 7e3516b..46a1e94 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,4 +1,4 @@ #!/usr/bin/bash # Fetch all submodule content. -git submodule update --force --recursive --init --remote +git submodule update --force --recursive --init diff --git a/build.gradle.kts b/build.gradle.kts index 5db50f1..0edc9eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,52 +1,63 @@ +/* ------------------------------ Plugins ------------------------------ */ plugins { - id("java") // Tell gradle this is a java project. - id("java-library") // Import helper for source-based libraries. - id("com.diffplug.spotless") version "7.0.4" // Import auto-formatter. - id("com.gradleup.shadow") version "8.3.6" // Import shadow API. - eclipse // Import eclipse plugin for IDE integration. + id("java") // Import Java plugin. + id("java-library") // Import Java Library plugin. + id("com.diffplug.spotless") version "7.0.4" // Import Spotless plugin. + id("com.gradleup.shadow") version "8.3.6" // Import Shadow plugin. + id("checkstyle") // Import Checkstyle plugin. + eclipse // Import Eclipse plugin. + kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin. } +/* --------------------------- JDK / Kotlin ---------------------------- */ java { - // Declare java version. - sourceCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility. + toolchain { // Select Java toolchain. + languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17. + vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE. + } } +kotlin { jvmToolchain(17) } + +/* ----------------------------- Metadata ------------------------------ */ group = "net.trueog.firstplayed-og" // Declare bundle identifier. version = "3.0" // Declare plugin version (will be in .jar). val apiVersion = "1.19" // Declare minecraft server target version. +/* ----------------------------- Resources ----------------------------- */ tasks.named("processResources") { val props = mapOf("version" to version, "apiVersion" to apiVersion) - inputs.properties(props) // Indicates to rerun if version changes. - filesMatching("plugin.yml") { expand(props) } - from("LICENSE") { // Bundle license into .jars. - into("/") - } + from("LICENSE") { into("/") } // Bundle licenses into jarfiles. } +/* ---------------------------- Repos ---------------------------------- */ repositories { - mavenCentral() - gradlePluginPortal() - maven { url = uri("https://repo.purpurmc.org/snapshots") } - maven { url = uri("https://jitpack.io") } + mavenCentral() // Import the Maven Central Maven Repository. + gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository. + maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository. + maven { url = uri("https://jitpack.io") } // Import the Jitpack Maven Repository. } +/* ---------------------- Java project deps ---------------------------- */ dependencies { - compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare purpur API version to be packaged. - compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.2.3") // Import MiniPlaceholders API. - implementation("io.lettuce:lettuce-core:6.7.1.RELEASE") - compileOnlyApi(project(":libs:Utilities-OG")) + compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged. + compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.2.3") // Import the MiniPlaceholders API. + implementation("io.lettuce:lettuce-core:6.7.1.RELEASE") // Import the Lettuce API for keydb. + compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source). } +/* ---------------------- Reproducible jars ---------------------------- */ tasks.withType().configureEach { // Ensure reproducible .jars isPreserveFileTimestamps = false isReproducibleFileOrder = true } +/* ----------------------------- Shadow -------------------------------- */ tasks.shadowJar { relocate("io.lettuce", "net.trueog.shadow.io.lettuce") exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed. @@ -54,34 +65,42 @@ tasks.shadowJar { minimize() } -tasks.build { - dependsOn(tasks.spotlessApply) - dependsOn(tasks.shadowJar) -} +tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only. -tasks.jar { archiveClassifier.set("part") } +tasks.build { dependsOn(tasks.spotlessApply, tasks.shadowJar) } // Build depends on spotless and shadow. +/* --------------------------- Javac opts ------------------------------- */ tasks.withType().configureEach { - options.compilerArgs.add("-parameters") - options.compilerArgs.add("-Xlint:deprecation") // Triggers deprecation warning messages. - options.encoding = "UTF-8" - options.isFork = true -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - vendor = JvmVendorSpec.GRAAL_VM - } + options.compilerArgs.add("-parameters") // Enable reflection for java code. + options.isFork = true // Run javac in its own process. + options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages. + options.encoding = "UTF-8" // Use UTF-8 file encoding. } +/* ----------------------------- Auto Formatting ------------------------ */ spotless { java { - removeUnusedImports() - palantirJavaFormat() + eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting. + leadingTabsToSpaces() // Convert leftover leading tabs to spaces. + removeUnusedImports() // Remove imports that aren't being called. } kotlinGradle { - ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } - target("build.gradle.kts", "settings.gradle.kts") + ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting. + target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format. } } + +checkstyle { + toolVersion = "10.18.1" // Declare checkstyle version to use. + configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file. + isIgnoreFailures = true // Don't fail the build if checkstyle does not pass. + isShowViolations = true // Show the violations in any IDE with the checkstyle plugin. +} + +tasks.named("compileJava") { + dependsOn("spotlessApply") // Run spotless before compiling with the JDK. +} + +tasks.named("spotlessCheck") { + dependsOn("spotlessApply") // Run spotless before checking if spotless ran. +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..4c3ab95 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/formatter/eclipse-java-formatter.xml b/config/formatter/eclipse-java-formatter.xml new file mode 100644 index 0000000..a3e01a0 --- /dev/null +++ b/config/formatter/eclipse-java-formatter.xml @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/libs/Utilities-OG b/libs/Utilities-OG index ed7f99d..4b73071 160000 --- a/libs/Utilities-OG +++ b/libs/Utilities-OG @@ -1 +1 @@ -Subproject commit ed7f99d354cf6cf2eff115cfbb2772c6be23af14 +Subproject commit 4b730712ad072426072dbb3d4ecfcee6f7883fd5 diff --git a/settings.gradle.kts b/settings.gradle.kts index eb42e65..09aac43 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,12 +1,13 @@ rootProject.name = "FirstPlayed-OG" -// Run the bootstrap at configuration time. -val process = ProcessBuilder("sh", "bootstrap.sh").directory(rootDir).start() - -val exitValue = process.waitFor() - -if (exitValue != 0) { - throw GradleException("bootstrap.sh failed with exit code $exitValue") +ProcessBuilder("sh", "bootstrap.sh").directory(rootDir).inheritIO().start().let { + if (it.waitFor() != 0) throw GradleException("bootstrap.sh failed") } -include("libs:Utilities-OG") +file("libs") + .listFiles() + ?.filter { it.isDirectory && !it.name.startsWith(".") } + ?.forEach { dir -> + include(":libs:${dir.name}") + project(":libs:${dir.name}").projectDir = dir + } diff --git a/src/main/java/plugin/CommandManager.java b/src/main/java/plugin/CommandManager.java index 13015b8..5862ea3 100644 --- a/src/main/java/plugin/CommandManager.java +++ b/src/main/java/plugin/CommandManager.java @@ -19,8 +19,7 @@ import org.bukkit.entity.Player; public class CommandManager implements CommandExecutor, TabExecutor { // Enable the conversion of text from config.yml to objects. - public FileConfiguration config = - Bukkit.getPluginManager().getPlugin("FirstPlayed-OG").getConfig(); + public FileConfiguration config = Bukkit.getPluginManager().getPlugin("FirstPlayed-OG").getConfig(); // Command execution event handler extending bukkit's CommandManager. @Override @@ -39,7 +38,8 @@ public class CommandManager implements CommandExecutor, TabExecutor { // If no username is specified after /firstplayed, do this. if (args.length == 0 && sender instanceof Player) { - // With no parameters, plugin will assume player wants information about themselves. + // With no parameters, plugin will assume player wants information about + // themselves. sendOwnInfo(sender); // Healthy exit status. @@ -52,7 +52,8 @@ public class CommandManager implements CommandExecutor, TabExecutor { // Get the string that the user put in after /firstplayed. String target = args[0]; - // If the username that was specified is the command sender's own username, do this... + // If the username that was specified is the command sender's own username, do + // this... if (sender.getName().equalsIgnoreCase(target)) { // Send the player their own join message. @@ -68,25 +69,30 @@ public class CommandManager implements CommandExecutor, TabExecutor { // Healthy exit status. return true; + } } // If the player passed too many arguments. else { - // Create a colored too many arguments error message using the TextComponent API. - String tooManyArgumentsError = - config.getString("prefix") + config.getString("too_many_arguments_error"); - TextComponent tooManyArgumentsContainer = - LegacyComponentSerializer.legacyAmpersand().deserialize(tooManyArgumentsError); + // Create a colored too many arguments error message using the TextComponent + // API. + String tooManyArgumentsError = config.getString("prefix") + + config.getString("too_many_arguments_error"); + TextComponent tooManyArgumentsContainer = LegacyComponentSerializer.legacyAmpersand() + .deserialize(tooManyArgumentsError); // Send error to command runner. sender.sendMessage(tooManyArgumentsContainer); // Command Failed (will show /usage). return false; + } + } + } // Runs when player specifies no arguments. @@ -101,11 +107,11 @@ public class CommandManager implements CommandExecutor, TabExecutor { // Format the player's own join information using the TextComponent API. String ownInfo = config.getString("prefix") + config.getString("message_me") + date; - TextComponent ownInfoContainer = - LegacyComponentSerializer.legacyAmpersand().deserialize(ownInfo); + TextComponent ownInfoContainer = LegacyComponentSerializer.legacyAmpersand().deserialize(ownInfo); // Send the player their own join information in chat. sender.sendMessage(ownInfoContainer); + } // Runs when player specifies one argument. @@ -113,7 +119,8 @@ public class CommandManager implements CommandExecutor, TabExecutor { // If offline, do slow lookup. Always returns an object, never null. OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(target); - // Manually check if player is real because offlinePlayer is always a valid Object. + // Manually check if player is real because offlinePlayer is always a valid + // Object. if (offlinePlayer.hasPlayedBefore()) { // Get join data from world files. @@ -123,10 +130,10 @@ public class CommandManager implements CommandExecutor, TabExecutor { String date = new SimpleDateFormat(config.getString("date_format")).format(new Date(timestamp)); // Format the player's requested join information using the TextComponent API. - String requestedInfo = - config.getString("prefix") + "&a" + target + " " + config.getString("message_other") + date; - TextComponent requestedInfoContainer = - LegacyComponentSerializer.legacyAmpersand().deserialize(requestedInfo); + String requestedInfo = config.getString("prefix") + "&a" + target + " " + config.getString("message_other") + + date; + TextComponent requestedInfoContainer = LegacyComponentSerializer.legacyAmpersand() + .deserialize(requestedInfo); // Send the player their requested join information in chat. sender.sendMessage(requestedInfoContainer); @@ -135,12 +142,14 @@ public class CommandManager implements CommandExecutor, TabExecutor { // Format the player not found error using the TextComponent API. String invalidPlayerError = config.getString("prefix") + config.getString("invalid_player_error") + target; - TextComponent invalidPlayerErrorContainer = - LegacyComponentSerializer.legacyAmpersand().deserialize(invalidPlayerError); + TextComponent invalidPlayerErrorContainer = LegacyComponentSerializer.legacyAmpersand() + .deserialize(invalidPlayerError); // Notify command sender that the player they specified does not exist. sender.sendMessage(invalidPlayerErrorContainer); + } + } // Tab completion (online players only for performance reasons). @@ -152,7 +161,8 @@ public class CommandManager implements CommandExecutor, TabExecutor { // Make an empty list of strings to store potential players for tab completion. List players = new ArrayList(); - // Optimized java 5 style Collections loop, not list or array loop, for efficiency and compliance with + // Optimized java 5 style Collections loop, not list or array loop, for + // efficiency and compliance with // bukkit API. for (Player player : Bukkit.getOnlinePlayers()) { @@ -161,14 +171,19 @@ public class CommandManager implements CommandExecutor, TabExecutor { // Feeds individual player to list on each run of loop. players.add(player.getName()); + } + } // Completed list of online players to tab complete. return players; + } // Show all options. return new ArrayList<>(); + } + } diff --git a/src/main/java/plugin/FirstPlayed.java b/src/main/java/plugin/FirstPlayed.java index a87b53a..3d86ff4 100644 --- a/src/main/java/plugin/FirstPlayed.java +++ b/src/main/java/plugin/FirstPlayed.java @@ -21,12 +21,16 @@ public class FirstPlayed extends JavaPlugin { // Save config.yml and reload settings. saveDefaultConfig(); reloadConfig(); + } - // Runs plugin asynchronously so multiple players can use it at once efficiently. + // Runs plugin asynchronously so multiple players can use it at once + // efficiently. public BukkitTask runTaskAsynchronously(final Runnable run) { // Schedule Processes. return this.getServer().getScheduler().runTaskAsynchronously(this, run); + } + }