diff --git a/build.gradle.kts b/build.gradle.kts index 22f9fe4..390736a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,86 +1,120 @@ +/* ------------------------------ 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. id("io.freefair.lombok") version "8.13.1" // Automatic lombok support. } +extra["kotlinAttribute"] = Attribute.of("kotlin-tag", Boolean::class.javaObjectType) + +val kotlinAttribute: Attribute by rootProject.extra + +/* --------------------------- 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. + } } -group = "dev.codedred" // Declare bundle identifier. +kotlin { jvmToolchain(17) } + +/* ----------------------------- Metadata ------------------------------ */ +group = "net.trueog.playtimes-og" // Declare bundle identifier. version = "1.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://oss.sonatype.org/content/groups/public/") } + 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. } +/* ---------------------- Java project deps ---------------------------- */ dependencies { - compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare purpur API version to be packaged. + 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. - compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG API. + compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source). } +apply(from = "eclipse.gradle.kts") // Import eclipse classpath support script. + +/* ---------------------- Reproducible jars ---------------------------- */ tasks.withType().configureEach { // Ensure reproducible .jars isPreserveFileTimestamps = false isReproducibleFileOrder = true } +/* ----------------------------- Shadow -------------------------------- */ tasks.shadowJar { exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed. archiveClassifier.set("") // Use empty string instead of null. 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. +} + +/* ------------------------------ Eclipse SHIM ------------------------- */ + +// This can't be put in eclipse.gradle.kts because Gradle is weird. +subprojects { + apply(plugin = "java-library") + apply(plugin = "eclipse") + eclipse.project.name = "${project.name}-${rootProject.name}" + tasks.withType().configureEach { archiveBaseName.set("${project.name}-${rootProject.name}") } +} 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/eclipse.gradle.kts b/eclipse.gradle.kts new file mode 100644 index 0000000..b0dffb3 --- /dev/null +++ b/eclipse.gradle.kts @@ -0,0 +1,151 @@ +/* This is free and unencumbered software released into the public domain */ + +import org.gradle.plugins.ide.eclipse.model.EclipseModel +import java.nio.file.Files +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +val kotlinAttribute: Attribute by rootProject.extra + +val kotlinPluginProjects = configurations +.getByName("compileClasspath") +.allDependencies + .filterIsInstance() + .filter { it -> it.attributes.getAttribute(kotlinAttribute) != null } + .map { it.path } +kotlinPluginProjects.forEach { evaluationDependsOn(it) } + +val ideLibs: Configuration by +configurations.creating { + isCanBeResolved = true + isCanBeConsumed = false +} + +configure { + project { + name = "Template-OG-Plugin" + } + classpath { + plusConfigurations.add(ideLibs) + } +} + +val ideLibDir = layout.buildDirectory.dir("ide-libs") +val hashRegex = Regex("-[0-9a-fA-F]{10}(?=\\.jar$)") // Black magic. + +val copyTasks = mutableListOf>() + +kotlinPluginProjects.forEach { path -> + val sub = project(path) + val shadowJarProv = sub.tasks.named("shadowJar") + val copyTask = + tasks.register("ideCopy${sub.name.replaceFirstChar(Char::titlecase)}") { + dependsOn(shadowJarProv) + from(shadowJarProv) + into(ideLibDir) + rename { it.replace(hashRegex, "") } // Remove git commit hash from jarfile. + } + copyTasks += copyTask +} + +/* Ensure the jars exist before .classpath is generated */ +tasks.named("eclipseClasspath").configure { dependsOn(copyTasks) } + +/* Supply those jars to the ideLibs configuration (after evaluation) */ +afterEvaluate { dependencies { add("ideLibs", fileTree(ideLibDir) { include("*.jar") }) } } + +val injectIdeLibs = + tasks.register("injectIdeLibs") { + dependsOn("eclipse") // run after all eclipse files are generated + doLast { + val cpFile = file(".classpath") + if (!cpFile.exists()) return@doLast + + val jars = fileTree(ideLibDir.get()) { include("*.jar") }.files.sortedBy { it.name } + if (jars.isEmpty()) return@doLast + + // Parse DOM + val dbf = DocumentBuilderFactory.newInstance() + val doc = dbf.newDocumentBuilder().parse(cpFile) + val root = doc.documentElement + + // Helper to see if an entry already exists + fun exists(path: String): Boolean = + root.getElementsByTagName("classpathentry").let { list -> + (0 until list.length).any { i -> + val n = list.item(i) + val kind = n.attributes?.getNamedItem("kind")?.nodeValue + val p = n.attributes?.getNamedItem("path")?.nodeValue + kind == "lib" && p == path + } + } + + // Remove any entry that points to ide-libs dir (folder or jars) to avoid dupes + val toRemove = mutableListOf() + val list = root.getElementsByTagName("classpathentry") + val dirPath = ideLibDir.get().asFile.absolutePath + for (i in 0 until list.length) { + val n = list.item(i) + val kind = n.attributes?.getNamedItem("kind")?.nodeValue + val p = n.attributes?.getNamedItem("path")?.nodeValue ?: "" + if (kind == "lib" && (p == dirPath || p.startsWith("$dirPath/"))) { + toRemove += n + } + } + toRemove.forEach { root.removeChild(it) } + + // Append our jar entries LAST + jars.forEach { f -> + val abs = f.absolutePath + if (!exists(abs)) { + val entry = doc.createElement("classpathentry") + entry.setAttribute("kind", "lib") + entry.setAttribute("path", abs) + root.appendChild(entry) + } + } + + // Write back pretty + val tf = + TransformerFactory.newInstance().newTransformer().apply { + setOutputProperty(OutputKeys.INDENT, "yes") + setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1") + setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no") + setOutputProperty(OutputKeys.ENCODING, "UTF-8") + } + Files.newBufferedWriter(cpFile.toPath()).use { w -> tf.transform(DOMSource(doc), StreamResult(w)) } + } + } + +tasks.named("eclipse").configure { finalizedBy(injectIdeLibs) } + +val eclipseModel = extensions.getByType() + +fun Project.addResolvableEclipseConfigs() { + val jarAttr = + objects.named(org.gradle.api.attributes.LibraryElements::class, org.gradle.api.attributes.LibraryElements.JAR) + val apiAttr = objects.named(org.gradle.api.attributes.Usage::class, org.gradle.api.attributes.Usage.JAVA_API) + + val compileOnlyRes = + configurations.create("eclipseCompileOnly") { + extendsFrom(configurations.getByName("compileOnly")) + isCanBeResolved = true + isCanBeConsumed = false + attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr) + attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr) + } + val compileOnlyApiRes = + configurations.create("eclipseCompileOnlyApi") { + extendsFrom(configurations.getByName("compileOnlyApi")) + isCanBeResolved = true + isCanBeConsumed = false + attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr) + attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr) + } + eclipseModel.classpath.plusConfigurations.addAll(listOf(compileOnlyRes, compileOnlyApiRes)) +} + +addResolvableEclipseConfigs() 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 18a33b0..ed87991 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,19 +1,13 @@ -pluginManagement { - repositories { - mavenLocal() - gradlePluginPortal() - } -} - rootProject.name = "PlayTimes-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/me/codedred/playtimes/PlayTimes.java b/src/main/java/me/codedred/playtimes/PlayTimes.java index 9cc60d6..31f0f96 100644 --- a/src/main/java/me/codedred/playtimes/PlayTimes.java +++ b/src/main/java/me/codedred/playtimes/PlayTimes.java @@ -35,15 +35,19 @@ public class PlayTimes extends JavaPlugin { AFKManager.getInstance().startAFKChecker(); getLogger().info("Successfully loaded."); + } @Override public void onDisable() { + AFKManager.getInstance().endAFKChecker(); getLogger().info("PlayTimes shutting down"); + } private void registerEvents() { + PluginManager pm = getServer().getPluginManager(); DataManager data = DataManager.getInstance(); @@ -51,6 +55,7 @@ public class PlayTimes extends JavaPlugin { pm.registerEvents(new Quit(), this); if (data.hasAfkEnabled()) { + pm.registerEvents(new onJoinQuit(), this); FileConfiguration config = data.getConfig(); @@ -59,41 +64,66 @@ public class PlayTimes extends JavaPlugin { boolean cancelAfkOnInteract = config.getBoolean("afk-settings.cancel-afk.on-player-interact"); if (cancelAfkOnMove || cancelAfkOnChat || cancelAfkOnInteract) { - if (cancelAfkOnMove) pm.registerEvents(new onMove(), this); - if (cancelAfkOnChat) pm.registerEvents(new onChat(), this); - if (cancelAfkOnInteract) pm.registerEvents(new onInteract(), this); + + if (cancelAfkOnMove) + pm.registerEvents(new onMove(), this); + if (cancelAfkOnChat) + pm.registerEvents(new onChat(), this); + if (cancelAfkOnInteract) + pm.registerEvents(new onInteract(), this); + } + } + } private void registerCommands() { + Objects.requireNonNull(getCommand("playtime")).setExecutor(new Time()); Objects.requireNonNull(getCommand("uptime")).setExecutor(new Uptime()); Objects.requireNonNull(getCommand("topplaytime")).setExecutor(new TopTime()); Objects.requireNonNull(this.getCommand("pt")).setTabCompleter(new TimeTabCompleter()); + } public void loadDatabase() { + if (DataManager.getInstance().getDBConfig().getBoolean("database-settings.enabled")) { + getLogger().info("Connecting to Database..."); DatabaseManager databaseManager = DatabaseManager.getInstance(); try { + if (databaseManager.getDataSource().getConnection() != null) { + try { + databaseManager.getDataSource().closeConnection(); + } catch (Exception e) { + getLogger().warning("Error while trying to close Database connection.."); + } + databaseManager.load(); if (DataManager.getInstance().getDBConfig().getBoolean("purge-database.enabled")) databaseManager.purgeOldPlaytimeData(); + } + } catch (SQLException e) { + getLogger().severe("Error loading the database: " + e.getMessage()); e.printStackTrace(); + } + } + } + } diff --git a/src/main/java/me/codedred/playtimes/afk/AFKManager.java b/src/main/java/me/codedred/playtimes/afk/AFKManager.java index aaab100..afd81d5 100644 --- a/src/main/java/me/codedred/playtimes/afk/AFKManager.java +++ b/src/main/java/me/codedred/playtimes/afk/AFKManager.java @@ -30,88 +30,122 @@ public class AFKManager { private BukkitTask afkCheckerTask = null; private AFKManager() { + config = DataManager.getInstance().getConfig(); afkThresholdMillis = config.getLong("afk-settings.threshold") * 60L * 1000L; notifyOnAfk = config.getBoolean("afk-settings.broadcast-afk.on-enter-afk"); notifyOnBack = config.getBoolean("afk-settings.broadcast-afk.on-exit-afk"); onAfkMessage = config.getString("afk-settings.on-enter-afk-message"); onBackMessage = config.getString("afk-settings.on-exit-afk-message"); + } public static AFKManager getInstance() { + if (instance == null) { + instance = new AFKManager(); + } + return instance; + } public void updateActivity(Player player) { + boolean wasAFK = isAFK(player); lastActive.put(player.getUniqueId(), System.currentTimeMillis()); if (wasAFK && notifyOnBack) { + player.sendMessage(ChatUtil.format(onBackMessage)); + } + } public boolean isAFK(Player player) { + return (lastActive.containsKey(player.getUniqueId()) && System.currentTimeMillis() - lastActive.get(player.getUniqueId()) > afkThresholdMillis); + } public void startAFKChecker() { + if (afkCheckerTask == null || afkCheckerTask.isCancelled()) { - afkCheckerTask = Bukkit.getScheduler() - .runTaskTimerAsynchronously( - PlayTimes.getPlugin(PlayTimes.class), - () -> { - for (Player player : Bukkit.getOnlinePlayers()) { - if (isAFK(player)) { - if (!afkTime.containsKey(player.getUniqueId()) && notifyOnAfk) { - player.sendMessage(ChatUtil.format(onAfkMessage)); - } - afkTime.put( - player.getUniqueId(), - afkTime.getOrDefault( - player.getUniqueId(), - getDefaultAfkTime(player.getUniqueId())) - + 1); - } + + afkCheckerTask = Bukkit.getScheduler().runTaskTimerAsynchronously(PlayTimes.getPlugin(PlayTimes.class), + () -> + { + + for (Player player : Bukkit.getOnlinePlayers()) { + + if (isAFK(player)) { + + if (!afkTime.containsKey(player.getUniqueId()) && notifyOnAfk) { + + player.sendMessage(ChatUtil.format(onAfkMessage)); + } - }, - 20L, - 20L); + + afkTime.put(player.getUniqueId(), afkTime.getOrDefault(player.getUniqueId(), + getDefaultAfkTime(player.getUniqueId())) + 1); + + } + + } + + }, 20L, 20L); + } + } public void endAFKChecker() { + if (afkCheckerTask != null) { + afkCheckerTask.cancel(); afkCheckerTask = null; + } + } private Long getDefaultAfkTime(UUID uuid) { + DataManager dataManager = DataManager.getInstance(); if (dataManager.hasDatabase() && DatabaseManager.getInstance().hasTimeForServer(uuid)) { + Map timeMap = DatabaseManager.getInstance().getTimeForServer(uuid); return timeMap != null ? timeMap.getOrDefault("afktime", 0L) : 0L; + } else { + return dataManager.getData().getLong("afktime." + uuid, 0L); + } + } public void removePlayer(UUID uuid) { + lastActive.remove(uuid); afkTime.remove(uuid); + } public long getAFKTime(UUID uuid) { + return afkTime.getOrDefault(uuid, getDefaultAfkTime(uuid)); + } public void reload() { + endAFKChecker(); FileConfiguration config = DataManager.getInstance().getConfig(); afkThresholdMillis = config.getLong("afk-settings.threshold") * 60L * 1000L; @@ -120,5 +154,7 @@ public class AFKManager { onAfkMessage = config.getString("afk-settings.on-enter-afk-message"); onBackMessage = config.getString("afk-settings.on-exit-afk-message"); startAFKChecker(); + } + } diff --git a/src/main/java/me/codedred/playtimes/afk/listeners/onChat.java b/src/main/java/me/codedred/playtimes/afk/listeners/onChat.java index 0de5b34..9e30ce4 100644 --- a/src/main/java/me/codedred/playtimes/afk/listeners/onChat.java +++ b/src/main/java/me/codedred/playtimes/afk/listeners/onChat.java @@ -9,6 +9,9 @@ public class onChat implements Listener { @EventHandler public void onPlayerChat(AsyncChatEvent event) { + AFKManager.getInstance().updateActivity(event.getPlayer()); + } + } diff --git a/src/main/java/me/codedred/playtimes/afk/listeners/onInteract.java b/src/main/java/me/codedred/playtimes/afk/listeners/onInteract.java index 3f8a9eb..8b29f98 100644 --- a/src/main/java/me/codedred/playtimes/afk/listeners/onInteract.java +++ b/src/main/java/me/codedred/playtimes/afk/listeners/onInteract.java @@ -9,6 +9,9 @@ public class onInteract implements Listener { @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { + AFKManager.getInstance().updateActivity(event.getPlayer()); + } + } diff --git a/src/main/java/me/codedred/playtimes/afk/listeners/onJoinQuit.java b/src/main/java/me/codedred/playtimes/afk/listeners/onJoinQuit.java index 007438a..c0ff46b 100644 --- a/src/main/java/me/codedred/playtimes/afk/listeners/onJoinQuit.java +++ b/src/main/java/me/codedred/playtimes/afk/listeners/onJoinQuit.java @@ -10,11 +10,16 @@ public class onJoinQuit implements Listener { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { + AFKManager.getInstance().updateActivity(event.getPlayer()); + } @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { + AFKManager.getInstance().removePlayer(event.getPlayer().getUniqueId()); + } + } diff --git a/src/main/java/me/codedred/playtimes/afk/listeners/onMove.java b/src/main/java/me/codedred/playtimes/afk/listeners/onMove.java index d4843a3..0a4417c 100644 --- a/src/main/java/me/codedred/playtimes/afk/listeners/onMove.java +++ b/src/main/java/me/codedred/playtimes/afk/listeners/onMove.java @@ -9,9 +9,15 @@ public class onMove implements Listener { @EventHandler public void onPlayerMove(PlayerMoveEvent event) { + if (event.getFrom().getBlock().equals(event.getTo().getBlock())) { + return; + } + AFKManager.getInstance().updateActivity(event.getPlayer()); + } + } diff --git a/src/main/java/me/codedred/playtimes/api/TimelessPlayer.java b/src/main/java/me/codedred/playtimes/api/TimelessPlayer.java index 3f393bb..0f3a339 100644 --- a/src/main/java/me/codedred/playtimes/api/TimelessPlayer.java +++ b/src/main/java/me/codedred/playtimes/api/TimelessPlayer.java @@ -13,22 +13,31 @@ public class TimelessPlayer { private final TimeManager timings; public TimelessPlayer(final Player player) { + this.uuid = player.getUniqueId(); this.stats = StatManager.getInstance(); this.timings = TimeManager.getInstance(); + } public TimelessPlayer(final UUID player) { + this.uuid = player; this.stats = StatManager.getInstance(); this.timings = TimeManager.getInstance(); + } public String getPlayTime() { + return this.timings.buildFormat(this.stats.getPlayerStat(this.uuid, StatisticType.PLAYTIME) / 20L); + } public long getRawPlayTimeInSeconds() { + return this.stats.getPlayerStat(this.uuid, StatisticType.PLAYTIME) / 20L; + } + } diff --git a/src/main/java/me/codedred/playtimes/api/TimelessServer.java b/src/main/java/me/codedred/playtimes/api/TimelessServer.java index d9dac9b..8b48c65 100644 --- a/src/main/java/me/codedred/playtimes/api/TimelessServer.java +++ b/src/main/java/me/codedred/playtimes/api/TimelessServer.java @@ -12,78 +12,114 @@ import me.codedred.playtimes.statistics.StatManager; public class TimelessServer { public List getTop10Players() { + final List players = new ArrayList<>(); final Leaderboard board = new Leaderboard(); final Map map = board.getTopTen(); final Object[] array = map.keySet().toArray(); for (int i = 0; i < map.size(); ++i) { + players.add(UUID.fromString(array[i].toString())); + } + return players; + } public List getTop10Times() { + final List players = new ArrayList<>(); final Leaderboard board = new Leaderboard(); final Map map = board.getTopTen(); final Object[] array = map.values().toArray(); for (int i = 0; i < map.size(); ++i) { + players.add(Integer.parseInt(array[i].toString()) / 20); + } + return players; + } public Map getTopMap() { + final Leaderboard board = new Leaderboard(); return board.getTopTen(); + } public UUID getNumberOne() { + return this.getTop10Players().get(0); + } public UUID getNumberTwo() { + return this.getTop10Players().get(1); + } public UUID getNumberThree() { + return this.getTop10Players().get(2); + } public UUID getNumberFour() { + return this.getTop10Players().get(3); + } public UUID getNumberFive() { + return this.getTop10Players().get(4); + } public UUID getNumberSix() { + return this.getTop10Players().get(5); + } public UUID getNumberSeven() { + return this.getTop10Players().get(6); + } public UUID getNumberEight() { + return this.getTop10Players().get(7); + } public UUID getNumberNine() { + return this.getTop10Players().get(8); + } public UUID getNumberTen() { + return this.getTop10Players().get(9); + } public String getUptime() { + return StatManager.getInstance().getUptime(); + } public long getUptimeInSeconds() { - return TimeUnit.MILLISECONDS.toSeconds( - ManagementFactory.getRuntimeMXBean().getUptime()); + + return TimeUnit.MILLISECONDS.toSeconds(ManagementFactory.getRuntimeMXBean().getUptime()); + } + } diff --git a/src/main/java/me/codedred/playtimes/commands/Time.java b/src/main/java/me/codedred/playtimes/commands/Time.java index 110b291..0384f6e 100644 --- a/src/main/java/me/codedred/playtimes/commands/Time.java +++ b/src/main/java/me/codedred/playtimes/commands/Time.java @@ -28,14 +28,18 @@ public class Time implements CommandExecutor { private final DataManager data = DataManager.getInstance(); public boolean onCommand(CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { + if (!sender.hasPermission("pt.use")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return true; + } String arg = args.length > 0 ? args[0].toLowerCase() : ""; switch (arg) { + case "": handlePlayerCommand(sender); break; @@ -63,16 +67,22 @@ public class Time implements CommandExecutor { default: handleOtherPlayerCommand(sender, args); break; + } return true; + } private void handleHelpCommand(CommandSender sender) { + if (!sender.hasPermission("pt.reload")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return; + } + sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Help ***")); sender.sendMessage(ChatUtil.format("&e/pt: &7Displays your play time")); sender.sendMessage(ChatUtil.format("&e/pt : &7Displays the play time of ")); @@ -81,187 +91,236 @@ public class Time implements CommandExecutor { sender.sendMessage(ChatUtil.format("&e/pt block : &7Blocks a player from leaderboards")); sender.sendMessage(ChatUtil.format("&e/pt unblock : &7Unblocks a player from leaderboards")); sender.sendMessage(ChatUtil.format("&e/pt version: &7Displays the version info")); + } private void handleVersionCommand(CommandSender sender) { + if (!sender.hasPermission("pt.reload")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return; + } + sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Version Info ***")); if (sender instanceof Player) { - UtilitiesOG.trueogMessage( - (Player) sender, - "&6" + "Server version: " - + "&f" - + Bukkit.getServer().getVersion() - + "&6" - + "\nPlayTimes version: " - + "&f" - + "v1.0" - + "&6" - + "\nStat Manager: " - + "&f" - + StatManager.getInstance().name - + "&6" - + "\nDatabase connected: " - + "&f" - + DataManager.getInstance().hasDatabase() - + "&6" - + "\nAFK Status: " - + "&f" - + DataManager.getInstance().hasAfkEnabled()); + + UtilitiesOG.trueogMessage((Player) sender, + "&6" + "Server version: " + "&f" + Bukkit.getServer().getVersion() + "&6" + "\nPlayTimes version: " + + "&f" + "v1.0" + "&6" + "\nStat Manager: " + "&f" + StatManager.getInstance().name + "&6" + + "\nDatabase connected: " + "&f" + DataManager.getInstance().hasDatabase() + "&6" + + "\nAFK Status: " + "&f" + DataManager.getInstance().hasAfkEnabled()); + } else { - UtilitiesOG.logToConsole( - "[PlayTimes-OG] ", - "&6" + "Server version: " - + "&f" - + Bukkit.getServer().getVersion() - + "&6" - + "\nPlayTimes version: " - + "&f" - + "v1.0" - + "&6" - + "\nStat Manager: " - + "&f" - + StatManager.getInstance().name - + "&6" - + "\nDatabase connected: " - + "&f" - + DataManager.getInstance().hasDatabase() - + "&6" - + "\nAFK Status: " - + "&f" - + DataManager.getInstance().hasAfkEnabled()); + + UtilitiesOG.logToConsole("[PlayTimes-OG] ", + "&6" + "Server version: " + "&f" + Bukkit.getServer().getVersion() + "&6" + "\nPlayTimes version: " + + "&f" + "v1.0" + "&6" + "\nStat Manager: " + "&f" + StatManager.getInstance().name + "&6" + + "\nDatabase connected: " + "&f" + DataManager.getInstance().hasDatabase() + "&6" + + "\nAFK Status: " + "&f" + DataManager.getInstance().hasAfkEnabled()); + } + } private void handlePlayerCommand(CommandSender sender) { + if (!(sender instanceof Player)) { + StatManager stats = StatManager.getInstance(); sender.sendMessage(ChatUtil.format("&cThe console has been up for " + stats.getUptime())); return; + } new BukkitRunnable() { + @Override public void run() { + Player player = (Player) sender; Message message = new Message(sender, player.getUniqueId(), player.getName()); message.sendMessageToTarget(); + } + }.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class)); + } private void handleReloadCommand(CommandSender sender) { + if (!sender.hasPermission("pt.reload")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return; + } + data.reloadAll(); TimeConstants.reload(); TimeManager.getInstance().registerTimings(); sender.sendMessage(ChatUtil.format("&aPlayTimes Configurations Reloaded!")); if (DataManager.getInstance().hasDatabase()) { + sender.sendMessage(ChatUtil.format("&aTrying to reload the Database settings?")); sender.sendMessage(ChatUtil.format(" &e&oTry, /pt reloadDatabase")); + } + } private void handleReloadDatabaseCommand(CommandSender sender) { + if (!sender.hasPermission("pt.reload")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return; + } + data.reloadDatabase(); PlayTimes.getPlugin(PlayTimes.class).loadDatabase(); sender.sendMessage(ChatUtil.format("&aDatabase Reloaded.")); sender.sendMessage(ChatUtil.format("&oIf you are still encountering issues try restarting your server.")); + } private void handleTopCommand(CommandSender sender) { + Bukkit.dispatchCommand(sender, "toppt"); + } private void handleBlockCommand(CommandSender sender, String[] args, boolean block) { + String blockAction = block ? "block" : "unblock"; if (!sender.hasPermission("pt.block")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return; + } + if (args.length < 2) { + sender.sendMessage( ChatUtil.formatWithPrefix("&c&lIncorrect Usage&c, try &7/pt " + blockAction + " ")); return; + } + String playerName = args[1]; String blockedKey = "blocked." + playerName.toLowerCase(); if (block == data.getData().contains(blockedKey)) { + sender.sendMessage(ChatUtil.formatWithPrefix("&cUser is already " + blockAction + "ed!")); sender.sendMessage( ChatUtil.formatWithPrefix("&c&oMaybe try, &7/pt " + (block ? "unblock" : "block") + " ")); return; + } + UUID uuid; if (Bukkit.getPlayer(playerName) == null) { + try { + uuid = ServerManager.getInstance().getUUID(playerName.toLowerCase()); + } catch (NullPointerException e) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND); return; + } + } else { + uuid = Objects.requireNonNull(Bukkit.getPlayer(playerName)).getUniqueId(); + } + if (uuid == null) { + sender.sendMessage(ChatUtil.formatWithPrefix("&c&lUser not found.")); return; + } + if (block) { + data.getData().set(blockedKey, uuid.toString()); if (data.getData().contains("leaderboard." + uuid)) { + data.getData().set("leaderboard." + uuid, null); + } + sender.sendMessage(ChatUtil.formatWithPrefix("&aUser &c&lBLOCKED &afrom leaderboards!")); + } else { + data.getData().set(blockedKey, null); sender.sendMessage(ChatUtil.formatWithPrefix("&aUser &b&lUNBLOCKED &afrom leaderboards!")); + } + data.saveData(); + } private void handleOtherPlayerCommand(CommandSender sender, String[] args) { + if (!sender.hasPermission("pt.others")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return; + } + new BukkitRunnable() { + @Override public void run() { + Pattern p = Pattern.compile("[^a-z0-9_]", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(args[0]); boolean b = m.find(); if (b) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND); return; + } UUID target; if (Bukkit.getPlayer(args[0]) == null) { + target = ServerManager.getInstance().getUUID(args[0]); if (target == null) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND); return; + } + } else { + target = Objects.requireNonNull(Bukkit.getPlayer(args[0])).getUniqueId(); + } OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(target); Message message = new Message(sender, target, offlinePlayer.getName()); message.sendMessageToTarget(); + } + }.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class)); + } + } diff --git a/src/main/java/me/codedred/playtimes/commands/TopTime.java b/src/main/java/me/codedred/playtimes/commands/TopTime.java index 8932fcb..cae6867 100644 --- a/src/main/java/me/codedred/playtimes/commands/TopTime.java +++ b/src/main/java/me/codedred/playtimes/commands/TopTime.java @@ -21,34 +21,43 @@ public class TopTime implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { + if (!sender.hasPermission("pt.top")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return true; + } DataManager data = DataManager.getInstance(); - if (data.getConfig().getBoolean("top-playtime.enable-cooldown") - && !sender.hasPermission("pt.block-cooldown") - && sender instanceof Player) { + if (data.getConfig().getBoolean("top-playtime.enable-cooldown") && !sender.hasPermission("pt.block-cooldown") + && sender instanceof Player) + { + Player player = (Player) sender; if (CoolDownUtil.contains(player.getUniqueId())) { + String cooldownMessage = Objects.requireNonNull(data.getConfig().getString("messages.cooldown")) .replace("%timeleft%", Integer.toString(CoolDownUtil.left(player.getUniqueId()))); sender.sendMessage(ChatUtil.formatWithPrefix(cooldownMessage)); return true; + } - CoolDownUtil.add( - player.getUniqueId(), + + CoolDownUtil.add(player.getUniqueId(), System.currentTimeMillis() + (data.getConfig().getInt("top-playtime.cooldown-seconds") * 1000L)); + } Leaderboard board = new Leaderboard(); Map map = board.getTopTen(); if (map.isEmpty()) { + sender.sendMessage(ChatUtil.format("&cRejoin the server to fill the leaderboard!")); return true; + } StatManager statManager = StatManager.getInstance(); @@ -60,6 +69,7 @@ public class TopTime implements CommandExecutor { sender.sendMessage(header); for (int i = 0; i < map.size(); i++) { + UUID uuid = UUID.fromString(map.keySet().toArray()[i].toString()); org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); @@ -69,25 +79,27 @@ public class TopTime implements CommandExecutor { String offlinePlayerName = offlinePlayer.getName() != null ? offlinePlayer.getName() : defaultPlayerName; String place = String.valueOf(i + 1); - String time = - timeManager.buildFormat(map.get(offlinePlayer.getUniqueId().toString()) / 20); + String time = timeManager.buildFormat(map.get(offlinePlayer.getUniqueId().toString()) / 20); String joinDate = statManager.getJoinDate(uuid) != null ? statManager.getJoinDate(uuid) : defaultJoinDate; if (time == null) { + time = defaultTime; + } - String formattedContent = content.replace("%player%", offlinePlayerName) - .replace("%place%", place) - .replace("%time%", time) - .replace("%joindate%", joinDate); + String formattedContent = content.replace("%player%", offlinePlayerName).replace("%place%", place) + .replace("%time%", time).replace("%joindate%", joinDate); sender.sendMessage(ChatUtil.format(formattedContent)); content = data.getConfig().getString("top-playtime.content"); + } sender.sendMessage(footer); return true; + } + } diff --git a/src/main/java/me/codedred/playtimes/commands/Uptime.java b/src/main/java/me/codedred/playtimes/commands/Uptime.java index 1842e6d..30843cb 100644 --- a/src/main/java/me/codedred/playtimes/commands/Uptime.java +++ b/src/main/java/me/codedred/playtimes/commands/Uptime.java @@ -15,17 +15,22 @@ public class Uptime implements CommandExecutor { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, String cmdL, String[] args) { + // command must be uptime,serveruptime,or serverupt - if (!cmdL.equalsIgnoreCase("uptime") - && !cmdL.equalsIgnoreCase("serveruptime") - && !cmdL.equalsIgnoreCase("serverupt")) { + if (!cmdL.equalsIgnoreCase("uptime") && !cmdL.equalsIgnoreCase("serveruptime") + && !cmdL.equalsIgnoreCase("serverupt")) + { + return false; + } // check for permission if (!sender.hasPermission("pt.uptime")) { + ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION); return true; + } // get the uptime @@ -34,24 +39,30 @@ public class Uptime implements CommandExecutor { // iterate through the messages and replace the placeholders for (String message : messages) { + // 1: replace them using placeholder api // TODO: use MMAPI /* - if (ServerUtils.hasPAPI() && sender instanceof Player) { - Player player = (Player) sender; - message = PlaceholderAPI.setPlaceholders(player, message); - } - */ + * if (ServerUtils.hasPAPI() && sender instanceof Player) { Player player = + * (Player) sender; message = PlaceholderAPI.setPlaceholders(player, message); } + */ // 2. replace them using java string methods if (message.contains("{\"text\":") && sender instanceof Player) { + String consoleCommand = "tellraw " + sender.getName() + " " + message.replace("%serveruptime%", uptime); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), consoleCommand); + // 3. if all else fails dont send name } else if (sender instanceof Player) { + sender.sendMessage(ChatUtil.format(message.replace("%serveruptime%", uptime))); + } + } return true; + } + } diff --git a/src/main/java/me/codedred/playtimes/commands/completer/TimeTabCompleter.java b/src/main/java/me/codedred/playtimes/commands/completer/TimeTabCompleter.java index 80691a8..4041b95 100644 --- a/src/main/java/me/codedred/playtimes/commands/completer/TimeTabCompleter.java +++ b/src/main/java/me/codedred/playtimes/commands/completer/TimeTabCompleter.java @@ -13,43 +13,70 @@ import org.jetbrains.annotations.NotNull; public class TimeTabCompleter implements TabCompleter { @Override - public List onTabComplete( - @NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, + String[] args) + { + List completions = new ArrayList<>(); if (args.length == 1) { + if (sender.hasPermission("pt.use")) { + completions.add("top"); completions.addAll(getOnlinePlayerNames(sender)); + } + if (sender.hasPermission("pt.block")) { + completions.add("block"); completions.add("unblock"); + } + if (sender.hasPermission("pt.reload")) { + completions.add("reload"); completions.add("version"); completions.add("help"); + } + } return completions; + } private List getOnlinePlayerNames(CommandSender sender) { + List playerNames = new ArrayList<>(); for (Player player : Bukkit.getOnlinePlayers()) { + if (!isVanished(player) || sender.equals(player) || sender.hasPermission("pt.seevanished")) { + playerNames.add(player.getName()); + } + } + return playerNames; + } private boolean isVanished(Player player) { + for (MetadataValue meta : player.getMetadata("vanished")) { - if (meta.asBoolean()) return true; + + if (meta.asBoolean()) + return true; + } + return false; + } + } diff --git a/src/main/java/me/codedred/playtimes/data/CustomConfig.java b/src/main/java/me/codedred/playtimes/data/CustomConfig.java index c6f249c..4f7773f 100644 --- a/src/main/java/me/codedred/playtimes/data/CustomConfig.java +++ b/src/main/java/me/codedred/playtimes/data/CustomConfig.java @@ -19,43 +19,54 @@ public class CustomConfig { private File dataConfigFile = null; public CustomConfig(PlayTimes plugin, String fileName) { + this.plugin = plugin; this.name = fileName; saveDefaultConfig(); + } public void reloadConfig() { + if (this.dataConfigFile == null) { + this.dataConfigFile = new File(this.plugin.getDataFolder(), name); + } this.dataConfig = YamlConfiguration.loadConfiguration(this.dataConfigFile); - if ("config.yml".equals(name)) checkAndAddDefaults(); + if ("config.yml".equals(name)) + checkAndAddDefaults(); try (InputStream defConfigStream = this.plugin.getResource(name)) { + if (defConfigStream != null) { - YamlConfiguration defConfig = - YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream)); + + YamlConfiguration defConfig = YamlConfiguration + .loadConfiguration(new InputStreamReader(defConfigStream)); this.dataConfig.setDefaults(defConfig); + } + } catch (IOException e) { + this.plugin.getLogger().log(Level.SEVERE, "Error reading default config", e); + } + } private void checkAndAddDefaults() { + Map requiredKeys = new LinkedHashMap<>(); requiredKeys.put("prefix", "&7[&b&lPlayTimes&7]"); requiredKeys.put("use-papi-placeholders", false); - requiredKeys.put("playtime.message", new String[] { - "&b&m=======&b&l[%player%]&b&m=======", - "&aPlaytime:&f %playtime%", - "&aTimes Joined:&f %timesjoined%", - "&aJoin Date:&f %joindate%", - "&b&m============================", - }); + requiredKeys.put("playtime.message", + new String[] + { "&b&m=======&b&l[%player%]&b&m=======", "&aPlaytime:&f %playtime%", "&aTimes Joined:&f %timesjoined%", + "&aJoin Date:&f %joindate%", "&b&m============================", }); requiredKeys.put("playtime.name.second", "s"); requiredKeys.put("playtime.name.minute", "min "); @@ -80,9 +91,10 @@ public class CustomConfig { requiredKeys.put("afk-settings.cancel-afk.on-player-chat", true); requiredKeys.put("afk-settings.cancel-afk.on-player-interact", true); - requiredKeys.put("uptime.message", new String[] { - "&b&m============================", "&aServer Uptime:&f %serveruptime%", "&b&m============================", - }); + requiredKeys.put("uptime.message", + new String[] + { "&b&m============================", "&aServer Uptime:&f %serveruptime%", + "&b&m============================", }); requiredKeys.put("top-playtime.header", "&b******&9[&3&lPlayTime Leaderboards&9]&b*****"); requiredKeys.put("top-playtime.content", "&5%place%) &9&l%player% &9&o- %time%"); @@ -98,44 +110,69 @@ public class CustomConfig { requiredKeys.put("messages.player-never-joined", "&cThis player has not joined the server."); requiredKeys.put("messages.cooldown", "&cCommand unavailable for %timeleft% seconds!"); - requiredKeys.put( - "uuid-lookups.what-does-this-do", "https://github.com/CodedRed-Spigot/PlayTimes/wiki/UUID-LOOKUPS"); + requiredKeys.put("uuid-lookups.what-does-this-do", + "https://github.com/CodedRed-Spigot/PlayTimes/wiki/UUID-LOOKUPS"); requiredKeys.put("uuid-lookups.type", "default"); boolean changesMade = false; for (Map.Entry entry : requiredKeys.entrySet()) { + if (!this.dataConfig.contains(entry.getKey())) { + this.dataConfig.set(entry.getKey(), entry.getValue()); changesMade = true; + } + } if (changesMade) { + plugin.getLogger().info("Missing configuration keys detected in 'Config.yml'. Adding them now."); saveConfig(); + } + } public FileConfiguration getConfig() { - if (this.dataConfig == null) reloadConfig(); + + if (this.dataConfig == null) + reloadConfig(); return this.dataConfig; + } public void saveConfig() { - if (this.dataConfig == null || this.dataConfigFile == null) return; + + if (this.dataConfig == null || this.dataConfigFile == null) + return; try { + getConfig().save(this.dataConfigFile); + } catch (IOException ex) { + this.plugin.getLogger().log(Level.SEVERE, ex, () -> "Could not save config to " + this.dataConfigFile); + } + } public void saveDefaultConfig() { + if (this.dataConfigFile == null) { + this.dataConfigFile = new File(this.plugin.getDataFolder(), name); + } + if (!this.dataConfigFile.exists()) { + this.plugin.saveResource(name, false); + } + } + } diff --git a/src/main/java/me/codedred/playtimes/data/DataManager.java b/src/main/java/me/codedred/playtimes/data/DataManager.java index a5da74a..d43fd8c 100644 --- a/src/main/java/me/codedred/playtimes/data/DataManager.java +++ b/src/main/java/me/codedred/playtimes/data/DataManager.java @@ -14,42 +14,61 @@ public class DataManager { private final CustomConfig cfg = new CustomConfig(JavaPlugin.getPlugin(PlayTimes.class), "config.yml"); public static DataManager getInstance() { + return instance; + } public FileConfiguration getConfig() { + return cfg.getConfig(); + } public FileConfiguration getData() { + return data.getConfig(); + } public FileConfiguration getDBConfig() { + return db.getConfig(); + } public void saveData() { + data.saveConfig(); + } public void reloadAll() { + data.reloadConfig(); cfg.reloadConfig(); db.reloadConfig(); - if (hasAfkEnabled()) AFKManager.getInstance().reload(); + if (hasAfkEnabled()) + AFKManager.getInstance().reload(); + } public void reloadDatabase() { + db.reloadConfig(); + } public boolean hasDatabase() { - return (getDBConfig().getBoolean("database-settings.enabled") - && DatabaseManager.getInstance().isConnected()); + + return (getDBConfig().getBoolean("database-settings.enabled") && DatabaseManager.getInstance().isConnected()); + } public boolean hasAfkEnabled() { + return getConfig().getBoolean("afk-settings.enabled", false); + } + } diff --git a/src/main/java/me/codedred/playtimes/data/database/datasource/DataSource.java b/src/main/java/me/codedred/playtimes/data/database/datasource/DataSource.java index 97f4d3d..f811e91 100644 --- a/src/main/java/me/codedred/playtimes/data/database/datasource/DataSource.java +++ b/src/main/java/me/codedred/playtimes/data/database/datasource/DataSource.java @@ -4,7 +4,9 @@ import java.sql.Connection; import java.sql.SQLException; public interface DataSource { + Connection getConnection() throws SQLException; void closeConnection() throws SQLException; + } diff --git a/src/main/java/me/codedred/playtimes/data/database/datasource/impl/MySQL.java b/src/main/java/me/codedred/playtimes/data/database/datasource/impl/MySQL.java index 57aeafa..8b2a283 100644 --- a/src/main/java/me/codedred/playtimes/data/database/datasource/impl/MySQL.java +++ b/src/main/java/me/codedred/playtimes/data/database/datasource/impl/MySQL.java @@ -14,6 +14,7 @@ public class MySQL implements DataSource { private final String password; public MySQL(PlayTimes plugin) { + var config = DataManager.getInstance().getDBConfig().getConfigurationSection("database-settings"); String host = config.getString("host"); String port = config.getString("port"); @@ -22,24 +23,33 @@ public class MySQL implements DataSource { String database = config.getString("database"); boolean SSL = config.getBoolean("useSSL"); - this.connectionUrl = - String.format("jdbc:mysql://%s:%s/%s?autoReconnect=true&useSSL=" + SSL, host, port, database); + this.connectionUrl = String.format("jdbc:mysql://%s:%s/%s?autoReconnect=true&useSSL=" + SSL, host, port, + database); try { + Class.forName("com.mysql.cj.jdbc.Driver"); plugin.getLogger().info("Successfully connected to database."); + } catch (ClassNotFoundException e) { + plugin.getLogger().severe("MySQL JDBC Driver not found: " + e.getMessage()); + } + } @Override public Connection getConnection() throws SQLException { + return DriverManager.getConnection(connectionUrl, user, password); + } @Override public void closeConnection() throws SQLException { + // connection.close(); } + } diff --git a/src/main/java/me/codedred/playtimes/data/database/datasource/impl/SQLite.java b/src/main/java/me/codedred/playtimes/data/database/datasource/impl/SQLite.java index 548d376..91366b9 100644 --- a/src/main/java/me/codedred/playtimes/data/database/datasource/impl/SQLite.java +++ b/src/main/java/me/codedred/playtimes/data/database/datasource/impl/SQLite.java @@ -12,26 +12,37 @@ public class SQLite implements DataSource { private Connection connection; public SQLite(PlayTimes plugin) { + var config = DataManager.getInstance().getDBConfig(); var database = config.getString("database"); try { + Class.forName("org.sqlite.JDBC"); String url = "jdbc:sqlite:" + plugin.getDataFolder() + "/" + database + ".db"; this.connection = DriverManager.getConnection(url); plugin.getLogger().info("Successfully connected to database."); + } catch (SQLException | ClassNotFoundException exception) { + plugin.getLogger().severe("ERROR! Database failed to connect. Please check your config.yml and try again."); + } + } @Override public Connection getConnection() { + return connection; + } @Override public void closeConnection() throws SQLException { + connection.close(); + } + } diff --git a/src/main/java/me/codedred/playtimes/data/database/manager/DatabaseManager.java b/src/main/java/me/codedred/playtimes/data/database/manager/DatabaseManager.java index 738fb8c..13828e5 100644 --- a/src/main/java/me/codedred/playtimes/data/database/manager/DatabaseManager.java +++ b/src/main/java/me/codedred/playtimes/data/database/manager/DatabaseManager.java @@ -29,25 +29,36 @@ public class DatabaseManager { private Map>> userPlaytimes = new HashMap<>(); private DatabaseManager() { + serverId = DataManager.getInstance().getDBConfig().getString("database-settings.serverId"); setupDataSource(); + } public static DatabaseManager getInstance() { + if (instance == null) { + instance = new DatabaseManager(); + } + return instance; + } public DataSource getDataSource() { + return this.dataSource; + } private void setupDataSource() { + String type = DataManager.getInstance().getDBConfig().getString("database-settings.type"); switch (type.toLowerCase()) { + case "mysql": this.dataSource = new MySQL(PlayTimes.getPlugin(PlayTimes.class)); break; @@ -57,127 +68,187 @@ public class DatabaseManager { default: throw new IllegalStateException( "Unexpected database type: " + type + ". Accepted Values: 'mysql', 'sqlite'"); + } try { + connected = dataSource != null && dataSource.getConnection() != null; + } catch (SQLException e) { + Bukkit.getServer().getLogger().warning("[PlayTimes] Error thrown from DatabaseManager!"); + } + } public boolean isConnected() { + return connected; + } public void load() { + if (isConnected()) { + this.usersTable = new UsersTable(dataSource); this.usersTable.createTable(); + } else { + Bukkit.getServer().getLogger().warning("[PlayTimes] Couldn't connect to Database!"); + } + } private UsersTable getUsersTable() { + if (this.usersTable == null) { + this.usersTable = new UsersTable(dataSource); + } + return this.usersTable; + } public boolean retrievePlaytime(UUID uuid) { + Map> timeMap = getUsersTable().getPlaytimesByUuid(uuid); if (timeMap != null && !timeMap.isEmpty()) { + userPlaytimes.put(uuid, timeMap); return true; + } + return false; + } public void updatePlaytime(UUID uuid, Long playtime, Long afktime) { + getUsersTable().insertOrUpdate(uuid.toString(), serverId, playtime, afktime); - userPlaytimes - .computeIfAbsent(uuid, k -> new HashMap<>()) - .computeIfAbsent(serverId, k -> new HashMap<>()) + userPlaytimes.computeIfAbsent(uuid, k -> new HashMap<>()).computeIfAbsent(serverId, k -> new HashMap<>()) .put("playtime", playtime); userPlaytimes.get(uuid).get(serverId).put("afktime", afktime); + } public boolean hasTimeForServer(UUID uuid, String server) { + if (!userPlaytimes.containsKey(uuid)) { + return retrievePlaytime(uuid); + } + return (userPlaytimes.containsKey(uuid) && userPlaytimes.get(uuid).containsKey(server)); + } public boolean hasTimeForServer(UUID uuid) { + return (hasTimeForServer(uuid, serverId)); + } public Map getTimeForServer(UUID uuid, String server) { + return userPlaytimes.get(uuid).get(server); + } public Map getTimeForServer(UUID uuid) { + return getTimeForServer(uuid, serverId); + } public Long getTotalRawtime(UUID uuid) { + Long rawtime = 0L; Map> userTimeData = userPlaytimes.get(uuid); if (userTimeData != null) { + for (Map.Entry> entry : userTimeData.entrySet()) { + String server = entry.getKey(); Map serverData = entry.getValue(); if (!server.equals(serverId)) { + Long individualRawtime = serverData.getOrDefault("playtime", 0L); rawtime += individualRawtime; + } + } + } + StatManager stats = StatManager.getInstance(); Long currentServerRawtime = stats.getPlayerStat(uuid, StatisticType.PLAYTIME) / 20; rawtime += currentServerRawtime; return rawtime; + } public Long getTotalAfktime(UUID uuid) { + Long afktime = 0L; Map> userTimeData = userPlaytimes.get(uuid); if (userTimeData != null) { + for (Map.Entry> entry : userTimeData.entrySet()) { + String server = entry.getKey(); Map serverData = entry.getValue(); if (!server.equals(serverId)) { + Long individualAfktime = serverData.getOrDefault("afktime", 0L); afktime += individualAfktime; + } + } + } + long currentServerAfkTime = AFKManager.getInstance().getAFKTime(uuid); afktime += currentServerAfkTime; return afktime; + } // Calculate the total playtime after subtracting total afktime public Long getTotalPlaytime(UUID uuid) { + Long totalPlaytime = 0L; Long totalAfktime = 0L; Map> userTimeData = userPlaytimes.get(uuid); if (userTimeData != null) { + for (Map.Entry> entry : userTimeData.entrySet()) { + String server = entry.getKey(); Map serverData = entry.getValue(); if (!server.equals(serverId)) { + totalPlaytime += serverData.getOrDefault("playtime", 0L); totalAfktime += serverData.getOrDefault("afktime", 0L); + } + } + } StatManager stats = StatManager.getInstance(); @@ -188,17 +259,21 @@ public class DatabaseManager { totalAfktime += afkTime; return totalPlaytime - totalAfktime; + } public void purgeOldPlaytimeData() { + Bukkit.getLogger().info("[PlayTimes] Initiating purge of outdated playtime data..."); int months = DataManager.getInstance().getDBConfig().getInt("purge-database.months", 12); Bukkit.getLogger() .info(String.format( - "[PlayTimes] Purging playtime data for server ID '%s' that is older than %d months.", - serverId, months)); + "[PlayTimes] Purging playtime data for server ID '%s' that is older than %d months.", serverId, + months)); this.usersTable.purgeOldPlaytimes(serverId, months); + } + } diff --git a/src/main/java/me/codedred/playtimes/data/database/table/UsersTable.java b/src/main/java/me/codedred/playtimes/data/database/table/UsersTable.java index 16d5cd9..d4a698f 100644 --- a/src/main/java/me/codedred/playtimes/data/database/table/UsersTable.java +++ b/src/main/java/me/codedred/playtimes/data/database/table/UsersTable.java @@ -18,36 +18,45 @@ public class UsersTable { private final DataSource dataSource; public UsersTable(DataSource dataSource) { + this.dataSource = dataSource; + } public void createTable() { - String createTableSql = String.format( - "CREATE TABLE IF NOT EXISTS `%s` (" + "`uniqueId` VARCHAR(36) NOT NULL," - + "`serverId` VARCHAR(255) NOT NULL," - + "`playtime` BIGINT NOT NULL DEFAULT 0," - + "`afktime` BIGINT NOT NULL DEFAULT 0," - + "`lastUpdated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," - + "PRIMARY KEY (`uniqueId`, `serverId`))", - TABLE_NAME); + + String createTableSql = String.format("CREATE TABLE IF NOT EXISTS `%s` (" + "`uniqueId` VARCHAR(36) NOT NULL," + + "`serverId` VARCHAR(255) NOT NULL," + "`playtime` BIGINT NOT NULL DEFAULT 0," + + "`afktime` BIGINT NOT NULL DEFAULT 0," + + "`lastUpdated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," + + "PRIMARY KEY (`uniqueId`, `serverId`))", TABLE_NAME); try (Connection conn = dataSource.getConnection(); - PreparedStatement preparedStatement = conn.prepareStatement(createTableSql)) { + PreparedStatement preparedStatement = conn.prepareStatement(createTableSql)) + { + preparedStatement.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } public void insertOrUpdate(String uuid, String serverId, long playtime, long afktime) { + Async.run(() -> { - String query = String.format( - "INSERT INTO `%s` (`uniqueId`, `serverId`, `playtime`, `afktime`) VALUES (?, ?, ?, ?) " - + "ON DUPLICATE KEY UPDATE `playtime` = ?, `afktime` = ?", - TABLE_NAME); + + String query = String + .format("INSERT INTO `%s` (`uniqueId`, `serverId`, `playtime`, `afktime`) VALUES (?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE `playtime` = ?, `afktime` = ?", TABLE_NAME); try (Connection conn = dataSource.getConnection(); - PreparedStatement preparedStatement = conn.prepareStatement(query)) { + PreparedStatement preparedStatement = conn.prepareStatement(query)) + { + preparedStatement.setString(1, uuid); preparedStatement.setString(2, serverId); preparedStatement.setLong(3, playtime); @@ -56,22 +65,32 @@ public class UsersTable { preparedStatement.setLong(6, afktime); preparedStatement.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } public Map> getPlaytimesByUuid(UUID uuid) { + Map> timesMap = new HashMap<>(); try (Connection conn = dataSource.getConnection(); - PreparedStatement preparedStatement = - conn.prepareStatement(String.format("SELECT * FROM `%s` WHERE `uniqueId` = ?", TABLE_NAME))) { + PreparedStatement preparedStatement = conn + .prepareStatement(String.format("SELECT * FROM `%s` WHERE `uniqueId` = ?", TABLE_NAME))) + { + preparedStatement.setString(1, uuid.toString()); try (ResultSet resultSet = preparedStatement.executeQuery()) { + while (resultSet.next()) { + String serverId = resultSet.getString("serverId"); long playtime = resultSet.getLong("playtime"); long afktime = resultSet.getLong("afktime"); @@ -81,64 +100,87 @@ public class UsersTable { timeData.put("afktime", afktime); timesMap.put(serverId, timeData); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } return timesMap; + } public Map getLastUpdatedTimesByUuid(UUID uuid) { + Map lastUpdatedTimes = new HashMap<>(); String query = String.format("SELECT `serverId`, `lastUpdated` FROM `%s` WHERE `uniqueId` = ?", TABLE_NAME); try (Connection conn = dataSource.getConnection(); - PreparedStatement preparedStatement = conn.prepareStatement(query)) { + PreparedStatement preparedStatement = conn.prepareStatement(query)) + { + preparedStatement.setString(1, uuid.toString()); try (ResultSet resultSet = preparedStatement.executeQuery()) { + while (resultSet.next()) { + String serverId = resultSet.getString("serverId"); Timestamp lastUpdated = resultSet.getTimestamp("lastUpdated"); lastUpdatedTimes.put(serverId, lastUpdated); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } return lastUpdatedTimes; + } /** - * Deletes rows from the database where the last update was more than the specified number of months ago. + * Deletes rows from the database where the last update was more than the + * specified number of months ago. * * @param serverId The server ID to match rows. * @param months The number of months to check for old data. */ public void purgeOldPlaytimes(String serverId, int months) { + Async.run(() -> { + String query = String.format( "DELETE FROM `%s` WHERE `serverId` = ? AND `lastUpdated` < (NOW() - INTERVAL ? MONTH)", TABLE_NAME); try (Connection conn = dataSource.getConnection(); - PreparedStatement preparedStatement = conn.prepareStatement(query)) { + PreparedStatement preparedStatement = conn.prepareStatement(query)) + { + preparedStatement.setString(1, serverId); preparedStatement.setInt(2, months); int deletedRows = preparedStatement.executeUpdate(); - Bukkit.getLogger() - .info("[PlayTimes] Deleted " + deletedRows - + " rows for serverId " - + serverId - + " older than " - + months - + " months."); + Bukkit.getLogger().info("[PlayTimes] Deleted " + deletedRows + " rows for serverId " + serverId + + " older than " + months + " months."); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + } diff --git a/src/main/java/me/codedred/playtimes/listeners/Join.java b/src/main/java/me/codedred/playtimes/listeners/Join.java index 136c153..5b5f61f 100644 --- a/src/main/java/me/codedred/playtimes/listeners/Join.java +++ b/src/main/java/me/codedred/playtimes/listeners/Join.java @@ -17,48 +17,59 @@ public class Join implements Listener { @EventHandler public void onJoin(PlayerJoinEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); DataManager data = DataManager.getInstance(); // Database if (data.hasDatabase()) { + DatabaseManager dbManager = DatabaseManager.getInstance(); dbManager.retrievePlaytime(uuid); // hacky bug fix that will update data again if database query was too slow. // this bug only arises when switching servers super fast! - Bukkit.getScheduler() - .runTaskLaterAsynchronously( - PlayTimes.getPlugin(PlayTimes.class), - () -> { - dbManager.retrievePlaytime(uuid); - }, - 20L * 30); + Bukkit.getScheduler().runTaskLaterAsynchronously(PlayTimes.getPlugin(PlayTimes.class), () -> { + + dbManager.retrievePlaytime(uuid); + + }, 20L * 30); + } // Leaderboard if (data.getData().contains("blocked." + event.getPlayer().getName().toLowerCase())) { + return; + } ConfigurationSection leaderboardSection = data.getData().getConfigurationSection("leaderboard"); if (leaderboardSection == null) { + leaderboardSection = data.getData().createSection("leaderboard"); + } if (leaderboardSection.contains(uuid.toString())) { + return; + } long time = StatManager.getInstance().getPlayerStat(uuid, StatisticType.PLAYTIME); if (!data.getConfig().getBoolean("top-playtime.track-rawtime", false) && data.hasAfkEnabled()) { + time -= AFKManager.getInstance().getAFKTime(event.getPlayer().getUniqueId()) * 20; + } leaderboardSection.set(uuid.toString(), time); data.saveData(); + } + } diff --git a/src/main/java/me/codedred/playtimes/listeners/Quit.java b/src/main/java/me/codedred/playtimes/listeners/Quit.java index d8c6048..d15ac7e 100644 --- a/src/main/java/me/codedred/playtimes/listeners/Quit.java +++ b/src/main/java/me/codedred/playtimes/listeners/Quit.java @@ -15,42 +15,58 @@ public class Quit implements Listener { @EventHandler public void onQuit(PlayerQuitEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); DataManager data = DataManager.getInstance(); // Database if (data.hasDatabase()) { + CompletableFuture.runAsync(() -> { + DatabaseManager dbManager = DatabaseManager.getInstance(); - dbManager.updatePlaytime( - uuid, + dbManager.updatePlaytime(uuid, StatManager.getInstance().getPlayerStat(uuid, StatisticType.PLAYTIME) / 20, AFKManager.getInstance().getAFKTime(uuid)); + }); + } else { + // Save AFK time in data.yml CompletableFuture.runAsync(() -> { + data.getData().set("afktime." + uuid, AFKManager.getInstance().getAFKTime(uuid)); data.saveData(); + }); + } // Leaderboard String playerName = event.getPlayer().getName().toLowerCase(); if (data.getData().contains("blocked." + playerName)) { + return; + } // Run leaderboard update CompletableFuture.runAsync(() -> { + StatManager statManager = StatManager.getInstance(); long playtime = statManager.getPlayerStat(uuid, StatisticType.PLAYTIME); if (!data.getConfig().getBoolean("top-playtime.track-rawtime", false) && data.hasAfkEnabled()) { + playtime -= AFKManager.getInstance().getAFKTime(uuid) * 20; + } data.getData().set("leaderboard." + uuid, playtime); data.saveData(); + }); + } + } diff --git a/src/main/java/me/codedred/playtimes/models/Leaderboard.java b/src/main/java/me/codedred/playtimes/models/Leaderboard.java index 5661aad..ab43c84 100644 --- a/src/main/java/me/codedred/playtimes/models/Leaderboard.java +++ b/src/main/java/me/codedred/playtimes/models/Leaderboard.java @@ -11,66 +11,91 @@ public class Leaderboard { final String LEADERBOARD = "leaderboard"; /** - * Grabs the uuids from the data file, sorts them, then removes redundant uuids and updates the top 10 + * Grabs the uuids from the data file, sorts them, then removes redundant uuids + * and updates the top 10 * * @return listing of top 10 players */ public Map getTopTen() { + Map topTen = new LinkedHashMap<>(); DataManager data = DataManager.getInstance(); if (!data.getData().contains("leaderboard")) { + return topTen; + } + for (String key : data.getData().getConfigurationSection(LEADERBOARD).getKeys(false)) { + topTen.put(key, Integer.valueOf(data.getData().getString(LEADERBOARD + "." + key))); + } List> list = new LinkedList<>(topTen.entrySet()); list.sort((o1, o2) -> o2.getValue() - o1.getValue()); if (list.size() > 20) { + List> delList = list.subList(20, list.size()); for (Map.Entry key : delList) { + data.getData().set(LEADERBOARD + "." + key.getKey(), null); + } + data.saveData(); + } if (list.size() > 9) { + list = list.subList(0, 10); + } topTen.clear(); for (Map.Entry entry : list) { + topTen.put(entry.getKey(), entry.getValue()); + } updateTimes(topTen); return topTen; + } /** - * Updates the top ten times by querying the latest times from the StatManager and sorting the list. + * Updates the top ten times by querying the latest times from the StatManager + * and sorting the list. * - * @param topTen the current leaderboard, with UUIDs and their corresponding time + * @param topTen the current leaderboard, with UUIDs and their corresponding + * time */ private void updateTimes(Map topTen) { + StatManager statManager = StatManager.getInstance(); DataManager dataManager = DataManager.getInstance(); List> list = new ArrayList<>(topTen.entrySet()); for (Map.Entry entry : list) { + String uuid = entry.getKey(); int latestTime = (int) statManager.getPlayerStat(UUID.fromString(uuid), StatisticType.PLAYTIME); if (!dataManager.getConfig().getBoolean("top-playtime.track-rawtime", false) - && dataManager.hasAfkEnabled()) { + && dataManager.hasAfkEnabled()) + { + latestTime -= AFKManager.getInstance().getAFKTime(UUID.fromString(uuid)) * 20; + } topTen.put(uuid, latestTime); + } list = new ArrayList<>(topTen.entrySet()); @@ -79,8 +104,12 @@ public class Leaderboard { topTen.clear(); for (int i = 0; i < Math.min(list.size(), 10); i++) { + Map.Entry entry = list.get(i); topTen.put(entry.getKey(), entry.getValue()); + } + } + } diff --git a/src/main/java/me/codedred/playtimes/models/Message.java b/src/main/java/me/codedred/playtimes/models/Message.java index 02a5363..2334ab5 100644 --- a/src/main/java/me/codedred/playtimes/models/Message.java +++ b/src/main/java/me/codedred/playtimes/models/Message.java @@ -30,14 +30,14 @@ public class Message { private final TimeManager timeManager; /** - * Constructor for Message. - * Initializes the message for the target player. + * Constructor for Message. Initializes the message for the target player. * - * @param sender The command sender. + * @param sender The command sender. * @param targetUUID The UUID of the offline player. - * @param targetName The name of the offline player. + * @param targetName The name of the offline player. */ public Message(CommandSender sender, UUID targetUUID, String targetName) { + this.sender = sender; this.targetUUID = targetUUID; this.targetName = targetName; @@ -47,46 +47,56 @@ public class Message { this.timeManager = TimeManager.getInstance(); this.message = buildMessage(); + } /** - * Builds the message to be sent to the sender about the target player. - * This involves fetching playtime statistics, formatting them, - * and applying any necessary replacements and PlaceholderAPI placeholders. + * Builds the message to be sent to the sender about the target player. This + * involves fetching playtime statistics, formatting them, and applying any + * necessary replacements and PlaceholderAPI placeholders. * * @return A list of formatted strings representing the message. */ private List buildMessage() { + List builtMessage = dataManager.getConfig().getStringList("playtime.message"); long rawTime = statManager.getPlayerStat(targetUUID, StatisticType.PLAYTIME); long afkTime = AFKManager.getInstance().getAFKTime(targetUUID); if (dataManager.hasDatabase()) { + DatabaseManager dbManager = DatabaseManager.getInstance(); dbManager.updatePlaytime(targetUUID, rawTime / 20, afkTime); + } return processMessageWithReplacements(builtMessage, prepareReplacements(rawTime, afkTime)); + } + /** * Prepares a map of replacements to be used in the message. * - * @param rawTime The playtime statistic to format and include in the replacements. - * @param afkTime The afktime statistic to format and include in the replacements. + * @param rawTime The playtime statistic to format and include in the + * replacements. + * @param afkTime The afktime statistic to format and include in the + * replacements. * @return A map of placeholder keys and their replacement values. */ private Map prepareReplacements(long rawTime, long afkTime) { + Map replacements = new HashMap<>(); replacements.put("%time%", timeManager.buildFormat(rawTime / 20 - afkTime)); replacements.put("%playtime%", timeManager.buildFormat(rawTime / 20 - afkTime)); replacements.put("%rawtime%", timeManager.buildFormat(rawTime / 20)); replacements.put("%afktime%", timeManager.buildFormat(afkTime)); replacements.put("%player%", targetName); - replacements.put( - "%timesjoined%", Long.toString(statManager.getPlayerStat(targetUUID, StatisticType.TIMES_JOINED))); + replacements.put("%timesjoined%", + Long.toString(statManager.getPlayerStat(targetUUID, StatisticType.TIMES_JOINED))); replacements.put("%joindate%", statManager.getJoinDate(targetUUID)); if (dataManager.hasDatabase()) { + DatabaseManager dbManager = DatabaseManager.getInstance(); long globalPlaytime = dbManager.getTotalPlaytime(targetUUID); @@ -96,9 +106,11 @@ public class Message { replacements.put("%global_playtime%", timeManager.buildFormat(globalPlaytime)); replacements.put("%global_rawtime%", timeManager.buildFormat(globalRawtime)); replacements.put("%global_afktime%", timeManager.buildFormat(globalAfktime)); + } return replacements; + } /** @@ -109,58 +121,82 @@ public class Message { * @return A list of processed messages. */ private List processMessageWithReplacements(List message, Map replacements) { + List newMessage = new ArrayList<>(); boolean hasDatabase = dataManager.hasDatabase(); DatabaseManager dbManager = hasDatabase ? DatabaseManager.getInstance() : null; Pattern pattern = Pattern.compile("%(rawtime|afktime|playtime)_(\\w+)%"); for (String msg : message) { + StringBuffer sb = new StringBuffer(); if (hasDatabase && dbManager != null) { + Matcher matcher = pattern.matcher(msg); while (matcher.find()) { + String placeholderType = matcher.group(1); String serverId = matcher.group(2); Long timeValue = 0L; if (dbManager.hasTimeForServer(targetUUID, serverId)) { + Map timeForServer = dbManager.getTimeForServer(targetUUID, serverId); - timeValue = timeForServer.getOrDefault( - placeholderType.equals("rawtime") ? "playtime" : placeholderType, 0L); + timeValue = timeForServer + .getOrDefault(placeholderType.equals("rawtime") ? "playtime" : placeholderType, 0L); if (placeholderType.equals("playtime")) { + timeValue -= timeForServer.getOrDefault("afktime", 0L); + } + } matcher.appendReplacement(sb, timeManager.buildFormat(timeValue)); + } + matcher.appendTail(sb); msg = sb.toString(); + } for (Map.Entry entry : replacements.entrySet()) { + msg = msg.replace(entry.getKey(), entry.getValue() != null ? entry.getValue() : entry.getKey()); + } newMessage.add(msg); + } return newMessage; + } /** - * Sends the formatted message to the sender. - * This method determines the appropriate method to send the message - * (e.g., standard chat message or JSON formatted tellraw command). + * Sends the formatted message to the sender. This method determines the + * appropriate method to send the message (e.g., standard chat message or JSON + * formatted tellraw command). */ public void sendMessageToTarget() { + for (String msg : message) { + TextComponent formattedMsg = ChatUtil.format(msg); if (formattedMsg.content().contains("{\"text\":")) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender.getName() + " " + formattedMsg); + } else { + sender.sendMessage(formattedMsg); + } + } + } + } diff --git a/src/main/java/me/codedred/playtimes/server/ServerManager.java b/src/main/java/me/codedred/playtimes/server/ServerManager.java index 83bdaed..a2c2b0c 100644 --- a/src/main/java/me/codedred/playtimes/server/ServerManager.java +++ b/src/main/java/me/codedred/playtimes/server/ServerManager.java @@ -9,28 +9,47 @@ public class ServerManager { private static final ServerManager instance = new ServerManager(); public static ServerManager getInstance() { + return instance; + } @Nullable public UUID getUUID(String name) { + if (Bukkit.getPlayer(name) != null) { + return Bukkit.getPlayer(name).getUniqueId(); + } else if (Bukkit.getOfflinePlayer(name) != null) { + return Bukkit.getOfflinePlayer(name).getUniqueId(); + } else { + return null; + } + } @Nullable public String getName(UUID uuid) { + if (Bukkit.getPlayer(uuid) != null) { + return Bukkit.getPlayer(uuid).getName(); + } else if (Bukkit.getOfflinePlayer(uuid) != null) { + return Bukkit.getOfflinePlayer(uuid).getName(); + } else { + return null; + } + } + } diff --git a/src/main/java/me/codedred/playtimes/server/ServerStatus.java b/src/main/java/me/codedred/playtimes/server/ServerStatus.java index 7cb1ede..4b7fda1 100644 --- a/src/main/java/me/codedred/playtimes/server/ServerStatus.java +++ b/src/main/java/me/codedred/playtimes/server/ServerStatus.java @@ -4,9 +4,11 @@ import java.io.IOException; import java.util.UUID; public interface ServerStatus { + public UUID getUUID(String name); public boolean isOnline(); public String getName(UUID uuid) throws IOException; + } diff --git a/src/main/java/me/codedred/playtimes/statistics/LegacyStats.java b/src/main/java/me/codedred/playtimes/statistics/LegacyStats.java index 9d44d6d..de38aa2 100644 --- a/src/main/java/me/codedred/playtimes/statistics/LegacyStats.java +++ b/src/main/java/me/codedred/playtimes/statistics/LegacyStats.java @@ -15,51 +15,72 @@ public class LegacyStats implements Stats { @Override public long getPlayerStatistic(UUID uuid, StatisticType type) { + File playerStatistics = new File(worldFolder, uuid + ".json"); if (playerStatistics.exists()) { + try { + JsonObject jsonObject = new Gson().fromJson(new FileReader(playerStatistics), JsonObject.class); switch (type) { + case PLAYTIME: return jsonObject.get("stat.playOneMinute").getAsLong(); case TIMES_JOINED: return jsonObject.get("stat.leaveGame").getAsLong(); + } + } catch (Exception e) { + // e.printStackTrace(); } + } + return 0; + } @Override public long getOnlineStatistic(Player player, StatisticType type) { + return getPlayerStatistic(player.getUniqueId(), type); + } @Override public boolean hasJoinedBefore(UUID uuid) { + File playerStatistics = new File(worldFolder, uuid + ".json"); return playerStatistics.exists(); + } @Override public String getJoinDate(UUID uuid) { + Player player = Bukkit.getPlayer(uuid); - SimpleDateFormat simpleDateFormat = - new SimpleDateFormat(DataManager.getInstance().getConfig().getString("date-format")); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat( + DataManager.getInstance().getConfig().getString("date-format")); Calendar calendar = Calendar.getInstance(); if (player == null) { + calendar.setTimeInMillis(Bukkit.getOfflinePlayer(uuid).getFirstPlayed()); return simpleDateFormat.format(calendar.getTime()); + } else if (player.hasPlayedBefore()) { + calendar.setTimeInMillis(player.getFirstPlayed()); return simpleDateFormat.format(calendar.getTime()); + } return "Never Joined"; + } + } diff --git a/src/main/java/me/codedred/playtimes/statistics/RisenStats.java b/src/main/java/me/codedred/playtimes/statistics/RisenStats.java index 0eed1f5..ac834ec 100644 --- a/src/main/java/me/codedred/playtimes/statistics/RisenStats.java +++ b/src/main/java/me/codedred/playtimes/statistics/RisenStats.java @@ -16,63 +16,87 @@ public class RisenStats implements Stats { @Override public long getPlayerStatistic(UUID uuid, StatisticType type) { - if (Bukkit.getPlayer(uuid) != null) return getOnlineStatistic(Bukkit.getPlayer(uuid), type); + + if (Bukkit.getPlayer(uuid) != null) + return getOnlineStatistic(Bukkit.getPlayer(uuid), type); File playerStatistics = new File(worldFolder, uuid + ".json"); if (playerStatistics.exists()) { + try { + JsonObject jsonObject = new Gson().fromJson(new FileReader(playerStatistics), JsonObject.class); JsonObject pilot = (JsonObject) jsonObject.get("stats"); JsonObject passenger = (JsonObject) pilot.get("minecraft:custom"); switch (type) { + case PLAYTIME: if (passenger.get("minecraft:play_time") == null) return passenger.get("minecraft:play_one_minute").getAsLong(); return passenger.get("minecraft:play_time").getAsLong(); case TIMES_JOINED: return passenger.get("minecraft:leave_game").getAsLong(); + } + } catch (Exception e) { + // e.printStackTrace(); } + } + return 0; + } @Override public long getOnlineStatistic(Player player, StatisticType type) { + switch (type) { + case PLAYTIME: return player.getStatistic(Statistic.PLAY_ONE_MINUTE); case TIMES_JOINED: return player.getStatistic(Statistic.LEAVE_GAME) + 1; default: return 0; + } + } @Override public boolean hasJoinedBefore(UUID uuid) { + File playerStatistics = new File(worldFolder, uuid + ".json"); return playerStatistics.exists(); + } @Override public String getJoinDate(UUID uuid) { + Player player = Bukkit.getPlayer(uuid); - SimpleDateFormat simpleDateFormat = - new SimpleDateFormat(DataManager.getInstance().getConfig().getString("date-format")); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat( + DataManager.getInstance().getConfig().getString("date-format")); Calendar calendar = Calendar.getInstance(); if (player == null) { + calendar.setTimeInMillis(Bukkit.getOfflinePlayer(uuid).getFirstPlayed()); return simpleDateFormat.format(calendar.getTime()); + } else if (player.hasPlayedBefore()) { + calendar.setTimeInMillis(player.getFirstPlayed()); return simpleDateFormat.format(calendar.getTime()); + } return "Never Joined"; + } + } diff --git a/src/main/java/me/codedred/playtimes/statistics/RisingStats.java b/src/main/java/me/codedred/playtimes/statistics/RisingStats.java index f246797..e38446f 100644 --- a/src/main/java/me/codedred/playtimes/statistics/RisingStats.java +++ b/src/main/java/me/codedred/playtimes/statistics/RisingStats.java @@ -16,61 +16,85 @@ public class RisingStats implements Stats { @Override public long getPlayerStatistic(UUID uuid, StatisticType type) { - if (Bukkit.getPlayer(uuid) != null) return getOnlineStatistic(Bukkit.getPlayer(uuid), type); + + if (Bukkit.getPlayer(uuid) != null) + return getOnlineStatistic(Bukkit.getPlayer(uuid), type); File playerStatistics = new File(worldFolder, uuid + ".json"); if (playerStatistics.exists()) { + try { + JsonObject jsonObject = new Gson().fromJson(new FileReader(playerStatistics), JsonObject.class); JsonObject pilot = (JsonObject) jsonObject.get("stats"); JsonObject passenger = (JsonObject) pilot.get("minecraft:custom"); switch (type) { + case PLAYTIME: return passenger.get("minecraft:play_one_minute").getAsLong(); case TIMES_JOINED: return passenger.get("minecraft:leave_game").getAsLong(); + } + } catch (Exception e) { + // e.printStackTrace(); } + } + return 0; + } @Override public long getOnlineStatistic(Player player, StatisticType type) { + switch (type) { + case PLAYTIME: return player.getStatistic(Statistic.PLAY_ONE_MINUTE); case TIMES_JOINED: return player.getStatistic(Statistic.LEAVE_GAME) + 1; default: return 0; + } + } @Override public boolean hasJoinedBefore(UUID uuid) { + File playerStatistics = new File(worldFolder, uuid + ".json"); return playerStatistics.exists(); + } @Override public String getJoinDate(UUID uuid) { + Player player = Bukkit.getPlayer(uuid); - SimpleDateFormat simpleDateFormat = - new SimpleDateFormat(DataManager.getInstance().getConfig().getString("date-format")); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat( + DataManager.getInstance().getConfig().getString("date-format")); Calendar calendar = Calendar.getInstance(); if (player == null) { + calendar.setTimeInMillis(Bukkit.getOfflinePlayer(uuid).getFirstPlayed()); return simpleDateFormat.format(calendar.getTime()); + } else if (player.hasPlayedBefore()) { + calendar.setTimeInMillis(player.getFirstPlayed()); return simpleDateFormat.format(calendar.getTime()); + } return "Never Joined"; + } + } diff --git a/src/main/java/me/codedred/playtimes/statistics/StatManager.java b/src/main/java/me/codedred/playtimes/statistics/StatManager.java index 4a9c65c..4290402 100644 --- a/src/main/java/me/codedred/playtimes/statistics/StatManager.java +++ b/src/main/java/me/codedred/playtimes/statistics/StatManager.java @@ -11,7 +11,9 @@ public class StatManager { private static final StatManager instance = new StatManager(); public static StatManager getInstance() { + return instance; + } private Stats stats; @@ -20,45 +22,68 @@ public class StatManager { public String name; public void registerStatistics() { + if (ServerUtils.isRisenVersion()) { + stats = new RisenStats(); name = "Risen"; + } else if (ServerUtils.isNewerVersion()) { + stats = new RisingStats(); name = "Rising"; + } else { + stats = new LegacyStats(); setLegacy(); name = "Legacy"; + } + } public Stats getStats() { + return stats; + } public long getPlayerStat(UUID uuid, StatisticType type) { + return getStats().getPlayerStatistic(uuid, type); + } public boolean hasJoinedBefore(UUID uuid) { + return getStats().hasJoinedBefore(uuid); + } public String getJoinDate(UUID uuid) { + return getStats().getJoinDate(uuid); + } private void setLegacy() { + legacy = true; + } public boolean isLegacy() { + return legacy; + } public String getUptime() { - return TimeManager.getInstance().buildFormat((int) TimeUnit.MILLISECONDS.toSeconds( - ManagementFactory.getRuntimeMXBean().getUptime())); + + return TimeManager.getInstance() + .buildFormat((int) TimeUnit.MILLISECONDS.toSeconds(ManagementFactory.getRuntimeMXBean().getUptime())); + } + } diff --git a/src/main/java/me/codedred/playtimes/statistics/StatisticType.java b/src/main/java/me/codedred/playtimes/statistics/StatisticType.java index c18e632..fb6b1b9 100644 --- a/src/main/java/me/codedred/playtimes/statistics/StatisticType.java +++ b/src/main/java/me/codedred/playtimes/statistics/StatisticType.java @@ -1,6 +1,5 @@ package me.codedred.playtimes.statistics; public enum StatisticType { - PLAYTIME, - TIMES_JOINED, + PLAYTIME, TIMES_JOINED, } diff --git a/src/main/java/me/codedred/playtimes/statistics/Stats.java b/src/main/java/me/codedred/playtimes/statistics/Stats.java index 8fcb3cc..c1f0fb3 100644 --- a/src/main/java/me/codedred/playtimes/statistics/Stats.java +++ b/src/main/java/me/codedred/playtimes/statistics/Stats.java @@ -6,6 +6,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; public interface Stats { + long getPlayerStatistic(UUID uuid, StatisticType type); boolean hasJoinedBefore(UUID uuid); @@ -15,4 +16,5 @@ public interface Stats { long getOnlineStatistic(Player player, StatisticType type); File worldFolder = new File(Bukkit.getServer().getWorlds().get(0).getWorldFolder(), "stats"); + } diff --git a/src/main/java/me/codedred/playtimes/time/TimeConstants.java b/src/main/java/me/codedred/playtimes/time/TimeConstants.java index 65942b5..27b81b6 100644 --- a/src/main/java/me/codedred/playtimes/time/TimeConstants.java +++ b/src/main/java/me/codedred/playtimes/time/TimeConstants.java @@ -9,10 +9,13 @@ public class TimeConstants { private static boolean rounded; static { + reload(); + } public static void reload() { + FileConfiguration data = DataManager.getInstance().getConfig(); second = data.getString("playtime.name.second"); seconds = data.getString("playtime.name.seconds"); @@ -23,41 +26,61 @@ public class TimeConstants { day = data.getString("playtime.name.day"); days = data.getString("playtime.name.days"); rounded = data.getBoolean("playtime.round-numbers"); + } public static String getSecond() { + return second; + } public static String getSeconds() { + return seconds; + } public static String getMinute() { + return minute; + } public static String getMinutes() { + return minutes; + } public static String getHour() { + return hour; + } public static String getHours() { + return hours; + } public static String getDay() { + return day; + } public static String getDays() { + return days; + } public static boolean getRounded() { + return rounded; + } + } diff --git a/src/main/java/me/codedred/playtimes/time/TimeManager.java b/src/main/java/me/codedred/playtimes/time/TimeManager.java index ab70de2..f21b12f 100644 --- a/src/main/java/me/codedred/playtimes/time/TimeManager.java +++ b/src/main/java/me/codedred/playtimes/time/TimeManager.java @@ -8,40 +8,59 @@ public class TimeManager { private static final TimeManager instance = new TimeManager(); public static TimeManager getInstance() { + return instance; + } private Timings timings; public void registerTimings() { + DataManager data = DataManager.getInstance(); if (data.getConfig().getBoolean("playtime.only-hours")) { + timings = new OnlyHours(); return; + } if (!data.getConfig().getBoolean("playtime.show-seconds")) { + if (!data.getConfig().getBoolean("playtime.show-days")) { + timings = new NoDaysNoSeconds(); return; + } + timings = new NoSeconds(); return; + } + if (!data.getConfig().getBoolean("playtime.show-days")) { + timings = new NoDays(); return; + } timings = new NativeFormat(); + } private Timings getTimings() { + return timings; + } public String buildFormat(long time) { + return getTimings().buildFormat(time); + } + } diff --git a/src/main/java/me/codedred/playtimes/time/Timings.java b/src/main/java/me/codedred/playtimes/time/Timings.java index 4868830..e84ed5b 100644 --- a/src/main/java/me/codedred/playtimes/time/Timings.java +++ b/src/main/java/me/codedred/playtimes/time/Timings.java @@ -1,5 +1,7 @@ package me.codedred.playtimes.time; public interface Timings { + String buildFormat(long time); + } diff --git a/src/main/java/me/codedred/playtimes/time/formats/NativeFormat.java b/src/main/java/me/codedred/playtimes/time/formats/NativeFormat.java index c7bbe64..697adcb 100644 --- a/src/main/java/me/codedred/playtimes/time/formats/NativeFormat.java +++ b/src/main/java/me/codedred/playtimes/time/formats/NativeFormat.java @@ -7,55 +7,80 @@ public class NativeFormat implements Timings { @Override public String buildFormat(long time) { - if (time < 60) return formatSeconds(time); + + if (time < 60) + return formatSeconds(time); int minutes = (int) (time / 60); int secondsLeft = (int) (time % 60); // Time less than 1 hour - if (minutes < 60) return formatMinutes(minutes, secondsLeft); + if (minutes < 60) + return formatMinutes(minutes, secondsLeft); // Time less than 1 Day if (minutes < 1440) { + int hours = minutes / 60; int minutesLeft = minutes % 60; return formatHours(hours, minutesLeft, secondsLeft); + } // Time greater than 1 day int days = minutes / 1440; int minutesLeft = minutes % 1440; return formatDays(days, minutesLeft, secondsLeft); + } private String formatSeconds(long seconds) { + return (seconds + (seconds == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())); + } private String formatMinutes(int minutes, int secondsLeft) { + String format = minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes()); - if (secondsLeft > 0) format += formatSeconds(secondsLeft); + if (secondsLeft > 0) + format += formatSeconds(secondsLeft); return format; + } private String formatHours(int hours, int minutesLeft, int secondsLeft) { + String format = hours + (hours == 1 ? TimeConstants.getHour() : TimeConstants.getHours()); - if (minutesLeft >= 1) format += formatMinutes(minutesLeft, secondsLeft); - else if (secondsLeft > 0) format += formatSeconds(secondsLeft); + if (minutesLeft >= 1) + format += formatMinutes(minutesLeft, secondsLeft); + else if (secondsLeft > 0) + format += formatSeconds(secondsLeft); return format; + } private String formatDays(int days, int minutesLeft, int secondsLeft) { + String format = days + (days == 1 ? TimeConstants.getDay() : TimeConstants.getDays()); if (minutesLeft >= 60) { + int hours = minutesLeft / 60; int minutesAfterHours = minutesLeft % 60; format += formatHours(hours, minutesAfterHours, secondsLeft); + } else if (minutesLeft >= 1) { + format += formatMinutes(minutesLeft, secondsLeft); + } else if (secondsLeft > 0) { + format += formatSeconds(secondsLeft); + } + return format; + } + } diff --git a/src/main/java/me/codedred/playtimes/time/formats/NoDays.java b/src/main/java/me/codedred/playtimes/time/formats/NoDays.java index a5d64f1..0e65aac 100644 --- a/src/main/java/me/codedred/playtimes/time/formats/NoDays.java +++ b/src/main/java/me/codedred/playtimes/time/formats/NoDays.java @@ -7,35 +7,49 @@ public class NoDays implements Timings { @Override public String buildFormat(long time) { - if (time < 60) return formatSeconds(time); + + if (time < 60) + return formatSeconds(time); int minutes = (int) (time / 60); int secondsLeft = (int) (time % 60); // Time less than 1 hour - if (minutes < 60) return formatMinutes(minutes, secondsLeft); + if (minutes < 60) + return formatMinutes(minutes, secondsLeft); // Greater than 1 hour, only in hours int hours = minutes / 60; int minutesLeft = minutes % 60; return formatHours(hours, minutesLeft, secondsLeft); + } private String formatSeconds(long seconds) { + return (seconds + (seconds == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())); + } private String formatMinutes(int minutes, int secondsLeft) { + String format = minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes()); - if (secondsLeft > 0) format += formatSeconds(secondsLeft); + if (secondsLeft > 0) + format += formatSeconds(secondsLeft); return format; + } private String formatHours(int hours, int minutesLeft, int secondsLeft) { + String format = hours + (hours == 1 ? TimeConstants.getHour() : TimeConstants.getHours()); - if (minutesLeft >= 1) format += formatMinutes(minutesLeft, secondsLeft); - else if (secondsLeft > 0) format += formatSeconds(secondsLeft); + if (minutesLeft >= 1) + format += formatMinutes(minutesLeft, secondsLeft); + else if (secondsLeft > 0) + format += formatSeconds(secondsLeft); return format; + } + } diff --git a/src/main/java/me/codedred/playtimes/time/formats/NoDaysNoSeconds.java b/src/main/java/me/codedred/playtimes/time/formats/NoDaysNoSeconds.java index 05a7566..7ee5134 100644 --- a/src/main/java/me/codedred/playtimes/time/formats/NoDaysNoSeconds.java +++ b/src/main/java/me/codedred/playtimes/time/formats/NoDaysNoSeconds.java @@ -7,12 +7,15 @@ public class NoDaysNoSeconds implements Timings { @Override public String buildFormat(long time) { - if (time < 60) return (time + (time == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())); + + if (time < 60) + return (time + (time == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())); int minutes = (int) (time / 60); // Time less than 1 hour - if (minutes < 60) return (minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes())); + if (minutes < 60) + return (minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes())); String format; @@ -21,8 +24,11 @@ public class NoDaysNoSeconds implements Timings { int inMins = 60 * hours; int left = minutes - inMins; - if (left >= 1) format = format + left + (left == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes()); + if (left >= 1) + format = format + left + (left == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes()); return format; + } + } diff --git a/src/main/java/me/codedred/playtimes/time/formats/NoSeconds.java b/src/main/java/me/codedred/playtimes/time/formats/NoSeconds.java index 71374b0..81101f5 100644 --- a/src/main/java/me/codedred/playtimes/time/formats/NoSeconds.java +++ b/src/main/java/me/codedred/playtimes/time/formats/NoSeconds.java @@ -7,49 +7,70 @@ public class NoSeconds implements Timings { @Override public String buildFormat(long time) { - if (time < 60) return formatSeconds(time); + + if (time < 60) + return formatSeconds(time); int minutes = (int) (time / 60); // Time less than 1 hour - if (minutes < 60) return formatMinutes(minutes); + if (minutes < 60) + return formatMinutes(minutes); // Time less than 1 Day if (minutes < 1440) { + int hours = minutes / 60; int minutesLeft = minutes % 60; return formatHours(hours, minutesLeft); + } // Time greater than 1 day int days = minutes / 1440; int minutesLeft = minutes % 1440; return formatDays(days, minutesLeft); + } private String formatSeconds(long seconds) { + return (seconds + (seconds == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())); + } private String formatMinutes(int minutes) { + return (minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes())); + } private String formatHours(int hours, int minutesLeft) { + String format = hours + (hours == 1 ? TimeConstants.getHour() : TimeConstants.getHours()); - if (minutesLeft >= 1) format += formatMinutes(minutesLeft); + if (minutesLeft >= 1) + format += formatMinutes(minutesLeft); return format; + } private String formatDays(int days, int minutesLeft) { + String format = days + (days == 1 ? TimeConstants.getDay() : TimeConstants.getDays()); if (minutesLeft >= 60) { + int hours = minutesLeft / 60; int minutesAfterHours = minutesLeft % 60; format += formatHours(hours, minutesAfterHours); + } else if (minutesLeft >= 1) { + format += formatMinutes(minutesLeft); + } + return format; + } + } diff --git a/src/main/java/me/codedred/playtimes/time/formats/OnlyHours.java b/src/main/java/me/codedred/playtimes/time/formats/OnlyHours.java index 727efea..a84f74d 100644 --- a/src/main/java/me/codedred/playtimes/time/formats/OnlyHours.java +++ b/src/main/java/me/codedred/playtimes/time/formats/OnlyHours.java @@ -7,15 +7,20 @@ public class OnlyHours implements Timings { @Override public String buildFormat(long time) { + boolean round = TimeConstants.getRounded(); double hours = time / 3600.0; if (round) { + int roundedHours = (int) Math.round(hours); return roundedHours + TimeConstants.getHours(); + } hours = (Math.round(hours * 100d)) / 100d; return hours + TimeConstants.getHours(); + } + } diff --git a/src/main/java/me/codedred/playtimes/utils/Async.java b/src/main/java/me/codedred/playtimes/utils/Async.java index a693449..3270126 100644 --- a/src/main/java/me/codedred/playtimes/utils/Async.java +++ b/src/main/java/me/codedred/playtimes/utils/Async.java @@ -8,18 +8,25 @@ import lombok.Getter; public class Async { @Getter - private static final ForkJoinPool worker = - new ForkJoinPool(8, ForkJoinPool.defaultForkJoinWorkerThreadFactory, (t, e) -> e.printStackTrace(), false); + private static final ForkJoinPool worker = new ForkJoinPool(8, ForkJoinPool.defaultForkJoinWorkerThreadFactory, + (t, e) -> e.printStackTrace(), false); public static CompletableFuture run(Runnable runnable) { - return CompletableFuture.runAsync( - () -> { - try { - runnable.run(); - } catch (Exception exception) { - throw new CompletionException(exception); - } - }, - worker); + + return CompletableFuture.runAsync(() -> { + + try { + + runnable.run(); + + } catch (Exception exception) { + + throw new CompletionException(exception); + + } + + }, worker); + } + } diff --git a/src/main/java/me/codedred/playtimes/utils/ChatUtil.java b/src/main/java/me/codedred/playtimes/utils/ChatUtil.java index e19edca..d0224bf 100644 --- a/src/main/java/me/codedred/playtimes/utils/ChatUtil.java +++ b/src/main/java/me/codedred/playtimes/utils/ChatUtil.java @@ -10,14 +10,13 @@ public class ChatUtil { private static final DataManager DATA_MANAGER = DataManager.getInstance(); public enum ChatTypes { - NO_PERMISSION, - PLAYER_NOT_FOUND, - PLAYER_NEVER_PLAYED, + NO_PERMISSION, PLAYER_NOT_FOUND, PLAYER_NEVER_PLAYED, } public static void errno(CommandSender sender, ChatTypes type) { switch (type) { + case NO_PERMISSION: sender.sendMessage(format(DATA_MANAGER.getConfig().getString("messages.noPermission"))); break; @@ -28,16 +27,21 @@ public class ChatUtil { sender.sendMessage( formatWithPrefix(DATA_MANAGER.getConfig().getString("messages.player-never-joined"))); break; + } + } public static TextComponent format(String msg) { return UtilitiesOG.trueogColorize(msg); + } public static TextComponent formatWithPrefix(String msg) { return UtilitiesOG.trueogColorize(DATA_MANAGER.getConfig().getString("prefix") + msg); + } + } diff --git a/src/main/java/me/codedred/playtimes/utils/CoolDownUtil.java b/src/main/java/me/codedred/playtimes/utils/CoolDownUtil.java index 2ac780b..5339442 100644 --- a/src/main/java/me/codedred/playtimes/utils/CoolDownUtil.java +++ b/src/main/java/me/codedred/playtimes/utils/CoolDownUtil.java @@ -6,28 +6,43 @@ import java.util.UUID; public class CoolDownUtil { private CoolDownUtil() { + throw new IllegalStateException("Utility Class"); + } private static final HashMap cooldowns = new HashMap<>(); public static void remove(UUID uuid) { + cooldowns.remove(uuid); + } public static void add(UUID uuid, long time) { + cooldowns.put(uuid, time); + } public static int left(UUID uuid) { + return (int) (cooldowns.get(uuid) / 1000 - (System.currentTimeMillis() / 1000)); + } public static boolean contains(UUID uuid) { + if (cooldowns.containsKey(uuid)) { - if (cooldowns.get(uuid) > System.currentTimeMillis()) return true; + + if (cooldowns.get(uuid) > System.currentTimeMillis()) + return true; remove(uuid); + } + return false; + } + } diff --git a/src/main/java/me/codedred/playtimes/utils/HexUtil.java b/src/main/java/me/codedred/playtimes/utils/HexUtil.java index e3549af..9bd20c5 100644 --- a/src/main/java/me/codedred/playtimes/utils/HexUtil.java +++ b/src/main/java/me/codedred/playtimes/utils/HexUtil.java @@ -7,6 +7,7 @@ public class HexUtil { private HexUtil() { throw new IllegalStateException("Utility Class"); + } public static String hex(String message) { @@ -24,18 +25,23 @@ public class HexUtil { i += 8; continue; + } + } builder.append(message.charAt(i)); + } return TextUtils.processColorCodes(builder.toString()); + } private static boolean isValidHexCode(String hexCode) { return hexCode.matches("^[a-fA-F0-9]{6}$"); + } private static String toLegacyHex(String hexCode) { @@ -45,8 +51,11 @@ public class HexUtil { for (char c : hexCode.toCharArray()) { legacy.append('&').append(c); + } return legacy.toString(); + } + } diff --git a/src/main/java/me/codedred/playtimes/utils/ServerUtils.java b/src/main/java/me/codedred/playtimes/utils/ServerUtils.java index 0318b4b..b162e83 100644 --- a/src/main/java/me/codedred/playtimes/utils/ServerUtils.java +++ b/src/main/java/me/codedred/playtimes/utils/ServerUtils.java @@ -10,20 +10,31 @@ public class ServerUtils { private static Boolean isNewerVersion = null; private ServerUtils() { + throw new IllegalStateException("Utility Class"); + } public static boolean isNewerVersion() { + if (isNewerVersion == null) { + try { + Class classMaterial = Material.class; Method method = classMaterial.getDeclaredMethod("matchMaterial", String.class, boolean.class); isNewerVersion = (method != null); + } catch (ReflectiveOperationException ex) { + isNewerVersion = false; + } + } + return isNewerVersion; + } /** @@ -32,35 +43,48 @@ public class ServerUtils { * @return true if so */ public static boolean isRisenVersion() { + String version = Bukkit.getServer().getVersion(); String mcVersion = null; if (version.contains("(MC:")) { + int startIndex = version.indexOf("(MC:") + 5; int endIndex = version.indexOf(")", startIndex); mcVersion = version.substring(startIndex, endIndex).trim(); + } if (mcVersion == null) { + Bukkit.getLogger().warning("PlayTimes failed to extract Minecraft version: " + version); return false; + } String[] versionParts = mcVersion.split("\\."); try { + int minorVersion = versionParts.length >= 2 ? Integer.parseInt(versionParts[1]) : 0; return minorVersion >= 17; + } catch (NumberFormatException e) { + Bukkit.getLogger().warning("PlayTimes failed to parse Minecraft version: " + mcVersion); return false; + } + } public static boolean hasPAPI() { + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) return DataManager.getInstance().getConfig().getBoolean("use-papi-placeholders"); return false; + } + }