88 Commits
Author SHA1 Message Date
Christopher G cf43f85993 wip toppt performance improvements 2025-09-07 20:35:03 -05:00
Christopher f8006c5337 Update issue templates 2024-06-20 14:04:27 -05:00
CodedRed 52e989b6b0 fix possible resource leaks 2024-06-20 13:37:35 -05:00
CodedRed 393000c8db fix old builds of 1.20.6 not loading properly 2024-06-20 13:31:53 -05:00
CodedRed 0bfb51e9a1 Merge branch 'production' of https://github.com/CodedRed-Spigot/PlayTimes 2024-06-18 23:56:05 -05:00
CodedRed d8ae3aca1e optimize config 2024-06-18 23:38:01 -05:00
CodedRed d8245aa23f optimize onQuit listener 2024-06-18 23:37:45 -05:00
CodedRed cdead9f0f8 optimize hex util 2024-06-18 23:37:37 -05:00
CodedRed ff0670c65c add release script 2024-06-18 23:11:24 -05:00
CodedRed deb86b6772 Merge branch 'main' into production
1.6.2
2024-06-18 23:03:54 -05:00
CodedRed 7ab32d73ab update plugin to work in 1.20.6 2024-06-18 22:57:22 -05:00
CodedRed b3dfd53a37 fix uuid lookups 2024-06-18 22:53:17 -05:00
CodedRed ccc7e17383 finished updating all papi & database placeholders 2024-03-23 12:54:00 -05:00
CodedRed 53dbd2d991 updated papi placeholders 2024-03-10 20:43:54 -05:00
CodedRed 1407e43c6f add compatibility with java 8 2024-03-10 18:02:19 -05:00
CodedRed c45096360e config change 2024-02-20 19:51:35 -06:00
CodedRed 9bae99ce8d bump version -> 1.6 2024-02-19 17:13:36 -06:00
CodedRed e02e765154 fix offline player checks & add uuid-lookups 2024-02-19 17:00:37 -06:00
CodedRed a554819066 add reloadDatabase command 2024-02-19 15:22:49 -06:00
CodedRed cc3e835b49 handle afkchecker on reload 2024-02-19 15:22:38 -06:00
CodedRed 79a351fbfe run time command async 2024-02-19 15:07:02 -06:00
CodedRed 5133c031d1 hide vanished players from tab completion 2024-02-19 14:53:17 -06:00
CodedRed 526feb0aa5 change debug command -> version 2024-02-19 14:24:37 -06:00
CodedRed 82493b27f6 Message class 2024-02-19 14:20:15 -06:00
CodedRed c38bafb608 replace player classes with Message class 2024-02-19 14:20:06 -06:00
CodedRed bab7c374f1 fix offline player requests 2024-02-19 13:54:11 -06:00
CodedRed 2ef20ea5d1 add offline player values to map when requested 2024-02-19 13:53:53 -06:00
CodedRed 76c15ac56d update hashed time & database 2024-02-19 11:56:21 -06:00
CodedRed 19871bc0db database placeholder fix 2024-02-19 11:45:44 -06:00
CodedRed e0747b37f7 don't need this? 2024-01-02 17:56:29 -06:00
CodedRed 7ac62d1681 consistent config keys 2024-01-02 17:53:06 -06:00
CodedRed e257740aaa optimized afk manager & listeners 2024-01-02 17:45:00 -06:00
CodedRed 7d0f7c76cd global placeholder fixes 2024-01-02 17:10:45 -06:00
CodedRed 0e1f2146d6 toppt can track playtime or rawtime 2024-01-02 17:04:42 -06:00
CodedRed 02d75354df fix default afktime 2024-01-02 16:39:31 -06:00
CodedRed 36b53c17cd update debugger & defaults 2024-01-02 16:17:33 -06:00
CodedRed 534bbba9fc impl afktime placeholder 2024-01-02 16:01:20 -06:00
CodedRed 52800c3de0 placeholder changes 2024-01-02 15:14:27 -06:00
CodedRed 4c1219de2b wip afk database changes 2024-01-02 13:39:33 -06:00
CodedRed 88a40062c9 more resource file changes 2024-01-02 12:43:09 -06:00
CodedRed 931f192f95 afk config updates 2024-01-02 12:17:00 -06:00
CodedRed 6bc6e8395a impl afk tracker 2024-01-02 12:11:34 -06:00
CodedRed 4897fe11a8 toppt works with console now 2024-01-02 11:59:13 -06:00
CodedRed c28ce839ea impl debugger 2023-12-30 14:04:07 -06:00
CodedRed b38b080d4b implement rounded only-hour setting 2023-12-30 13:42:29 -06:00
CodedRed c279d60d88 update config.yml 2023-12-30 13:26:25 -06:00
CodedRed 5dc8a9c1a9 implement data purger 2023-11-25 19:54:01 -06:00
CodedRed 79c36751ca temp bug fix 2023-11-25 19:30:12 -06:00
CodedRed 7644a91756 bug fix 2023-11-25 19:12:42 -06:00
CodedRed 7abae06f54 various bug fixes 2023-11-25 18:47:48 -06:00
Christopher f58f182bc4 Update gradle.yml 2023-11-25 18:25:12 -06:00
CodedRed 7e7a16bcdf temp remove craftengine 2023-11-25 18:23:41 -06:00
Christopher 963ef56818 Create gradle.yml 2023-11-25 18:21:58 -06:00
CodedRed 9075dcd4ad ensure database is enabled 2023-11-25 18:18:31 -06:00
CodedRed 8300a4d2e3 add new placeholders to papi 2023-11-25 18:16:02 -06:00
CodedRed ab7178c3bb bump version 2023-11-25 18:15:50 -06:00
CodedRed 52d11b9958 implement total time placeholder 2023-11-25 18:09:59 -06:00
CodedRed dad538a890 config changes 2023-11-25 18:05:13 -06:00
CodedRed 30b43df049 format time placeholder 2023-11-25 17:54:55 -06:00
CodedRed 6e0043e197 proper stat grab 2023-11-25 17:51:57 -06:00
CodedRed 97cba3598a add timestamp field 2023-11-25 17:48:42 -06:00
CodedRed 32110fc2f2 database setting changes 2023-11-25 17:43:24 -06:00
CodedRed 8e5db3928f beta database build 2023-11-25 17:32:46 -06:00
CodedRed 1e7a5ffadb implemented dynamic placeholder 2023-11-25 16:36:22 -06:00
CodedRed 62a5966270 refactored offline/online player 2023-11-25 16:11:40 -06:00
CodedRed 47666f3a74 add db playtime to onJoin & onQuit 2023-11-25 15:42:05 -06:00
CodedRed 4ae41d91cb format 2023-11-25 15:34:55 -06:00
CodedRed ea39b43725 stat type refactor 2023-11-25 15:32:08 -06:00
CodedRed 7ab49c419d format 2023-11-25 15:28:25 -06:00
CodedRed ecea1640ab database refactoring 2023-11-25 15:24:07 -06:00
CodedRed cbc8253b4b [wip] db implementation 2023-11-22 17:35:04 -06:00
CodedRed e292e6e1b3 possible null exception bug fix 2023-11-22 14:53:16 -06:00
Christopher afe66090f9 Merge pull request #36 from CodedRed-Spigot/main
1.5.3
2023-10-02 14:01:59 -05:00
CodedRed fc8e2dc3c6 fixed update checker 2023-10-02 13:46:30 -05:00
CodedRed 1cbfe86785 1.5.3 Update
Changelog:
- Fixed plugin updater
- Fixed pt.others permission
- Implemented PAPI Placeholder: %PlayTimes_timesjoined%$
- Updated .gitignore
2023-10-02 13:23:08 -05:00
Christopher cbc8b632e0 Merge pull request #34 from CodedRed-Spigot/main
v1.5.2
2023-07-27 16:53:03 -05:00
CodedRed b4b899b031 version update 2023-07-27 16:40:33 -05:00
CodedRed 3f2fe03fff added old api back 2023-07-27 16:39:35 -05:00
Christopher 8630c14648 Merge pull request #32 from CodedRed-Spigot/main
v1.5.1
2023-07-12 21:19:42 -05:00
CodedRed 71917d811d fixed block command bug 2023-07-12 21:11:56 -05:00
CodedRed 185510320b text fix 2023-07-12 21:02:31 -05:00
CodedRed bf320aca14 version update 2023-07-12 20:58:52 -05:00
CodedRed 4497848cc5 removing this for now 2023-07-12 20:58:21 -05:00
CodedRed 8ff6cd3a33 updated version number 2023-07-12 20:57:53 -05:00
CodedRed ab497b8157 fixed bug again 2023-07-12 20:51:06 -05:00
CodedRed a13fdba083 another bug fix 2023-07-12 20:33:01 -05:00
CodedRed 044c4c8f13 wip last joined placeholder 2023-07-12 20:24:33 -05:00
CodedRed 2d6501dbfc potential bug fix for version issue 2023-07-12 20:22:38 -05:00
63 changed files with 4585 additions and 2303 deletions

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: ''
---
# Bug Report Template
Thank you for helping us improve PlayTimes by reporting a bug! Please fill out the form below to help us diagnose and fix the issue.
---
## Basic Information
**Minecraft Version:**
- Please run `/version` and provide the output here.
**PlayTimes Version:**
- Please run `/pt version` .
- **Screenshot**: Upload a screenshot of the `/pt version` output here.
---
## Describe the Bug
**Description:**
- A clear and concise description of what the bug is.
---
## Screenshots
**Screenshots:**
- If applicable, add screenshots to help explain your problem.
---
## Additional Context
**Additional Context:**
- Add any other context about the problem here.
---
For more information and updates, visit [PlayTimes on Spigot](https://www.spigotmc.org/resources/playtimes-track-player-playtime.58858/).
## Disclaimer
Please note that responses to bug reports may take up to 72 hours or longer. PlayTimes is a FREE and OPEN-SOURCE plugin, and we greatly appreciate community involvement in its development. As PlayTimes is a side project and hobby for CodedRed, updates may occasionally take longer than anticipated. We sincerely thank you for using PlayTimes and for your understanding and patience.
+40
View File
@@ -0,0 +1,40 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[SUGGESTION]"
labels: enhancement
assignees: CodedRedGIT
---
# Suggestion Template
Thank you for helping us improve PlayTimes by sharing your suggestions! Please fill out the form below to provide us with detailed information about your suggestion.
---
## Suggestion
**Description:**
- A clear and concise description of your suggestion.
**Benefits:**
- Describe how this suggestion would improve PlayTimes or enhance the user experience.
**Implementation:**
- If possible, provide any ideas on how this suggestion could be implemented.
---
## Additional Context
**Additional Context:**
- Add any other context or information about your suggestion here.
---
For more information and updates, visit [PlayTimes on Spigot](https://www.spigotmc.org/resources/playtimes-track-player-playtime.58858/).
## Disclaimer
Please note that responses to suggestions may take up to 72 hours or longer. PlayTimes is a FREE and OPEN-SOURCE plugin, and we greatly appreciate community involvement in its development. As PlayTimes is a side project and hobby for CodedRed, implementation of new features may occasionally take longer than anticipated. We sincerely thank you for using PlayTimes and for your understanding and patience.
+22
View File
@@ -0,0 +1,22 @@
name: Build Minecraft Plugin
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew build
+35
View File
@@ -5,11 +5,16 @@
*.ipr
*.iws
# Hide my todo txt file
/src/main/resources/todo.txt
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
bin
# JIRA plugin
atlassian-ide-plugin.xml
@@ -116,3 +121,33 @@ run/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# VS Code directories
.vscode/
# User workspace settings
.vscode/settings.json
# User workspace launch configurations
.vscode/launch.json
# User workspace tasks
.vscode/tasks.json
# Recommended extensions for this workspace
.vscode/extensions.json
# VS Code directories for Java development
.vscode-test/
# VS Code for Java standalone server
.vscode-server/
# Windows image file caches
iconcache_*.db
# Windows Installer directory
Installer/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
+12 -24
View File
@@ -1,38 +1,27 @@
plugins {
id 'java'
id 'dev.codedred.craftengine' version '1.0.0'
}
group = 'dev.codedred'
version = '1.5'
version = '1.6.2b'
repositories {
mavenLocal()
gradlePluginPortal()
maven {
name = 'spigotmc-repo'
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
maven {
name = 'sonatype'
url = 'https://oss.sonatype.org/content/groups/public/'
}
maven {
url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/'
}
maven { name = 'spigotmc-repo'; url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
maven { name = 'sonatype'; url = 'https://oss.sonatype.org/content/groups/public/' }
maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' }
}
dependencies {
implementation 'org.jetbrains:annotations:23.0.0'
compileOnly 'org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT'
compileOnly 'me.clip:placeholderapi:2.10.9'
compileOnly 'org.projectlombok:lombok:1.18.28'
annotationProcessor 'org.projectlombok:lombok:1.18.28'
}
def targetJavaVersion = 17
def targetJavaVersion = 8
java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
sourceCompatibility = javaVersion
@@ -40,12 +29,11 @@ java {
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
}
}
tasks.withType(JavaCompile).configureEach {
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
options.release = targetJavaVersion
}
options.encoding = 'UTF-8'
options.release = targetJavaVersion
}
processResources {
@@ -55,4 +43,4 @@ processResources {
filesMatching('plugin.yml') {
expand props
}
}
}
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
NEW_VERSION="1.6.2b"
git checkout main
git pull origin main
git tag -a $NEW_VERSION -m "Release version $NEW_VERSION"
git push origin $NEW_VERSION
git checkout production
git pull origin production
git merge main -m "Release $NEW_VERSION"
git push origin production
echo "Release $NEW_VERSION completed and merged into production."
read -p "Press any key to continue... "
@@ -1,6 +1,10 @@
package me.codedred.playtimes;
import java.util.*;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
import me.codedred.playtimes.models.Leaderboard;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
@@ -10,122 +14,257 @@ import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class Expansions extends PlaceholderExpansion {
@Override
public boolean canRegister() {
return true;
}
@Override
public boolean canRegister() {
return true;
}
@Override
public @NotNull String getAuthor() {
return "CodedRed";
}
@Override
public @NotNull String getAuthor() {
return "CodedRed";
}
@Override
public @NotNull String getIdentifier() {
return "PlayTimes";
}
@Override
public @NotNull String getIdentifier() {
return "PlayTimes";
}
@Override
public @NotNull String getVersion() {
return "1.5";
}
@Override
public @NotNull String getVersion() {
return "1.6.1";
}
@Override
public boolean persist() {
return true;
}
@Override
public boolean persist() {
return true;
}
@Override
public String onRequest(OfflinePlayer player, @NotNull String identifier) {
identifier = identifier.toLowerCase();
switch (identifier) {
case "playtime":
return getPlayerPlayTime(player);
case "uptime":
return StatManager.getInstance().getUptime();
case "joindate":
return StatManager.getInstance().getJoinDate(player.getUniqueId());
default:
return handleLeaderboardIdentifier(identifier);
}
}
@Override
public String onRequest(OfflinePlayer player, @NotNull String identifier) {
identifier = identifier.toLowerCase();
switch (identifier) {
case "playtime":
return getPlayerPlayTime(player);
case "uptime":
return StatManager.getInstance().getUptime();
case "joindate":
return StatManager.getInstance().getJoinDate(player.getUniqueId());
case "timesjoined":
return Long.toString(
StatManager
.getInstance()
.getPlayerStat(player.getUniqueId(), StatisticType.TIMES_JOINED)
);
case "rawtime":
return getRawtime(player);
case "afktime":
return getAfktime(player);
case "global_playtime":
return getGlobalPlaytime(player);
case "global_rawtime":
return getGlobalRawtime(player);
case "global_afktime":
return getGlobalAfktime(player);
default:
if (identifier.startsWith("rawtime_")) {
return getServerSpecificRawtime(player, identifier.substring(8));
}
if (identifier.startsWith("playtime_")) {
return getServerSpecificPlaytime(player, identifier.substring(9));
}
if (identifier.startsWith("afktime_")) {
return getServerSpecificAfktime(player, identifier.substring(8));
}
return handleLeaderboardIdentifier(identifier);
}
}
private String getPlayerPlayTime(OfflinePlayer player) {
StatManager stats = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
return timeManager.buildFormat(stats.getPlayerStat(player.getUniqueId(), StatisticType.PLAYTIME) / 20);
}
private String getServerSpecificRawtime(
OfflinePlayer player,
String serverId
) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
DatabaseManager dbManager = DatabaseManager.getInstance();
if (dbManager.hasTimeForServer(player.getUniqueId(), serverId)) {
Long rawtime = dbManager
.getTimeForServer(player.getUniqueId(), serverId)
.get("playtime");
return timeManager.buildFormat(rawtime != null ? rawtime : 0);
}
return timeManager.buildFormat(0);
}
return "N/A";
}
private String handleLeaderboardIdentifier(String identifier) {
if (identifier.contains("topname")) {
return getLeaderboardPlayerName(identifier);
} else if (identifier.contains("toptime")) {
return getLeaderboardPlayTime(identifier);
}
return null;
}
private String getServerSpecificPlaytime(
OfflinePlayer player,
String serverId
) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
DatabaseManager dbManager = DatabaseManager.getInstance();
if (dbManager.hasTimeForServer(player.getUniqueId(), serverId)) {
Map<String, Long> timeForServer = dbManager.getTimeForServer(
player.getUniqueId(),
serverId
);
Long playtime =
timeForServer.get("playtime") -
timeForServer.getOrDefault("afktime", 0L);
return timeManager.buildFormat(playtime != null ? playtime : 0);
}
return timeManager.buildFormat(0);
}
return "N/A";
}
private String getLeaderboardPlayerName(String identifier) {
Leaderboard board = new Leaderboard();
int val = parseLeaderboardPosition(identifier);
if (val == -1) {
return ChatColor.RED + "Invalid leaderboard position!";
}
private String getServerSpecificAfktime(
OfflinePlayer player,
String serverId
) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
DatabaseManager dbManager = DatabaseManager.getInstance();
if (dbManager.hasTimeForServer(player.getUniqueId(), serverId)) {
Long afktime = dbManager
.getTimeForServer(player.getUniqueId(), serverId)
.get("afktime");
return timeManager.buildFormat(afktime != null ? afktime : 0);
}
return timeManager.buildFormat(0);
}
return "N/A";
}
List<UUID> players = getTopPlayersUUID(board);
if (players.size() < val + 1)
return "N/A";
private String getGlobalPlaytime(OfflinePlayer player) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
Long globalPlaytime = DatabaseManager
.getInstance()
.getTotalPlaytime(player.getUniqueId());
return timeManager.buildFormat(
globalPlaytime != null ? globalPlaytime : 0
);
}
return "N/A";
}
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(players.get(val));
return targetPlayer.getName();
}
private String getGlobalRawtime(OfflinePlayer player) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
Long globalRawtime = DatabaseManager
.getInstance()
.getTotalRawtime(player.getUniqueId());
return timeManager.buildFormat(globalRawtime != null ? globalRawtime : 0);
}
return "N/A";
}
private String getLeaderboardPlayTime(String identifier) {
Leaderboard board = new Leaderboard();
int val = parseLeaderboardPosition(identifier);
if (val == -1) {
return ChatColor.RED + "Invalid leaderboard position!";
}
private String getGlobalAfktime(OfflinePlayer player) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
Long globalAfktime = DatabaseManager
.getInstance()
.getTotalAfktime(player.getUniqueId());
return timeManager.buildFormat(globalAfktime != null ? globalAfktime : 0);
}
return "N/A";
}
List<Integer> players = getTopPlayersPlayTime(board);
if (players.size() < val + 1)
return "N/A";
private String getPlayerPlayTime(OfflinePlayer player) {
StatManager stats = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
long afkTime = AFKManager.getInstance().getAFKTime(player.getUniqueId());
return timeManager.buildFormat(
stats.getPlayerStat(player.getUniqueId(), StatisticType.PLAYTIME) /
20 -
afkTime
);
}
return TimeManager.getInstance().buildFormat(players.get(val) / 20);
}
private String getRawtime(OfflinePlayer player) {
StatManager stats = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
return timeManager.buildFormat(
stats.getPlayerStat(player.getUniqueId(), StatisticType.PLAYTIME) / 20
);
}
private List<UUID> getTopPlayersUUID(Leaderboard board) {
List<UUID> players = new ArrayList<>();
for (Map.Entry<String, Integer> entry : board.getTopTen().entrySet())
players.add(UUID.fromString(entry.getKey()));
return players;
}
private String getAfktime(OfflinePlayer player) {
TimeManager timeManager = TimeManager.getInstance();
long afkTime = AFKManager.getInstance().getAFKTime(player.getUniqueId());
return timeManager.buildFormat(afkTime);
}
private List<Integer> getTopPlayersPlayTime(Leaderboard board) {
List<Integer> players = new ArrayList<>();
for (Map.Entry<String, Integer> entry : board.getTopTen().entrySet())
players.add(entry.getValue());
return players;
}
private String handleLeaderboardIdentifier(String identifier) {
if (identifier.contains("topname")) {
return getLeaderboardPlayerName(identifier);
} else if (identifier.contains("toptime")) {
return getLeaderboardPlayTime(identifier);
}
return null;
}
private int parseLeaderboardPosition(String identifier) {
String num = identifier.substring(7);
if (isInteger(num)) {
return (Integer.parseInt(num) < 1) ? 0 : Integer.parseInt(num) - 1;
}
return -1;
}
private String getLeaderboardPlayerName(String identifier) {
Leaderboard board = new Leaderboard();
int val = parseLeaderboardPosition(identifier);
if (val == -1) {
return ChatColor.RED + "Invalid leaderboard position!";
}
private boolean isInteger(String str) {
try {
Integer.parseInt(str);
return true;
} catch (Exception e) {
return false;
}
}
List<UUID> players = getTopPlayersUUID(board);
if (players.size() < val + 1) return "N/A";
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(players.get(val));
return targetPlayer.getName();
}
private String getLeaderboardPlayTime(String identifier) {
Leaderboard board = new Leaderboard();
int val = parseLeaderboardPosition(identifier);
if (val == -1) {
return ChatColor.RED + "Invalid leaderboard position!";
}
List<Integer> players = getTopPlayersPlayTime(board);
if (players.size() < val + 1) return "N/A";
return TimeManager.getInstance().buildFormat(players.get(val) / 20);
}
private List<UUID> getTopPlayersUUID(Leaderboard board) {
List<UUID> players = new ArrayList<>();
for (Map.Entry<String, Integer> entry : board
.getTopTen()
.entrySet()) players.add(UUID.fromString(entry.getKey()));
return players;
}
private List<Integer> getTopPlayersPlayTime(Leaderboard board) {
List<Integer> players = new ArrayList<>();
for (Map.Entry<String, Integer> entry : board
.getTopTen()
.entrySet()) players.add(entry.getValue());
return players;
}
private int parseLeaderboardPosition(String identifier) {
String num = identifier.substring(7);
if (isInteger(num)) {
return (Integer.parseInt(num) < 1) ? 0 : Integer.parseInt(num) - 1;
}
return -1;
}
private boolean isInteger(String str) {
try {
Integer.parseInt(str);
return true;
} catch (Exception e) {
return false;
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,9 +1,19 @@
package me.codedred.playtimes;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Objects;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.afk.listeners.onChat;
import me.codedred.playtimes.afk.listeners.onInteract;
import me.codedred.playtimes.afk.listeners.onJoinQuit;
import me.codedred.playtimes.afk.listeners.onMove;
import me.codedred.playtimes.commands.Time;
import me.codedred.playtimes.commands.TopTime;
import me.codedred.playtimes.commands.Uptime;
import me.codedred.playtimes.commands.completer.TimeTabCompleter;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
import me.codedred.playtimes.listeners.Join;
import me.codedred.playtimes.listeners.Quit;
import me.codedred.playtimes.server.ServerManager;
@@ -11,75 +21,150 @@ import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.util.Objects;
public class PlayTimes extends JavaPlugin {
@Override
public void onEnable() {
checkForUpdate();
@Override
public void onEnable() {
checkForUpdate();
ServerManager.getInstance().register();
StatManager.getInstance().registerStatistics();
TimeManager.getInstance().registerTimings();
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
Expansions exp = new Expansions();
exp.register();
getLogger().info("[PlayTimes] PlaceholdersAPI Hooked!");
}
ServerManager.getInstance().register();
StatManager.getInstance().registerStatistics();
TimeManager.getInstance().registerTimings();
registerEvents();
registerCommands();
loadDatabase();
@SuppressWarnings("unused")
Metrics metrics = new Metrics(this, 5289);
getLogger().info("[PlayTimes] Successfully loaded.");
}
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
Expansions exp = new Expansions();
exp.register();
getLogger().info("PlaceholdersAPI Hooked!");
}
@Override
public void onDisable() {
getLogger().info("PlayTimes shutting down");
}
registerEvents();
registerCommands();
private void registerEvents() {
PluginManager pm = getServer().getPluginManager();
pm.registerEvents(new Join(), this);
pm.registerEvents(new Quit(), this);
}
@SuppressWarnings("unused")
Metrics metrics = new Metrics(this, 5289);
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());
AFKManager.getInstance().startAFKChecker();
getLogger().info("Successfully loaded.");
}
}
@Override
public void onDisable() {
AFKManager.getInstance().endAFKChecker();
getLogger().info("PlayTimes shutting down");
}
private void checkForUpdate() {
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
UpdateChecker updater = new UpdateChecker(this, 58858);
try {
if (!updater.checkForUpdates()) {
getLogger().warning(ChatUtil.format("&eYou are using an older version of PlayTimes!"));
getLogger().info(ChatUtil.format("&eDownload the newest version here:"));
getLogger().info(ChatUtil.format("&bhttps://www.spigotmc.org/resources/58858/"));
} else {
getLogger().info("[PlayTimes] Plugin is up to date! - " + getDescription().getVersion());
}
} catch (IOException e) {
getLogger().warning("[PlayTimes] Could not check for updates!");
}
});
}
private void registerEvents() {
PluginManager pm = getServer().getPluginManager();
DataManager data = DataManager.getInstance();
pm.registerEvents(new Join(), this);
pm.registerEvents(new Quit(), this);
if (data.hasAfkEnabled()) {
pm.registerEvents(new onJoinQuit(), this);
FileConfiguration config = data.getConfig();
boolean cancelAfkOnMove = config.getBoolean(
"afk-settings.cancel-afk.on-player-move"
);
boolean cancelAfkOnChat = config.getBoolean(
"afk-settings.cancel-afk.on-player-chat"
);
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);
}
}
}
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();
}
}
}
private void checkForUpdate() {
Bukkit
.getScheduler()
.runTaskAsynchronously(
this,
() -> {
UpdateChecker updater = new UpdateChecker(this, 58858);
try {
if (updater.hasUpdatesAvailable()) {
getLogger()
.warning(
ChatUtil.format(
"You are using an older version of PlayTimes!"
)
);
getLogger()
.info(ChatUtil.format("Download the newest version here:"));
getLogger()
.info(
ChatUtil.format("https://www.spigotmc.org/resources/58858/")
);
} else {
getLogger()
.info(
"[PlayTimes] Plugin is up to date! - " +
getDescription().getVersion()
);
}
} catch (IOException e) {
getLogger().warning("[PlayTimes] Could not check for updates!");
}
}
);
}
}
@@ -1,54 +1,48 @@
package me.codedred.playtimes;
import org.bukkit.plugin.java.JavaPlugin;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Objects;
import javax.net.ssl.HttpsURLConnection;
import org.bukkit.plugin.java.JavaPlugin;
public class UpdateChecker {
private static final String UPDATE_URL = "https://api.spigotmc.org/legacy/update.php?resource=%d";
private static final String UPDATE_URL =
"https://api.spigotmc.org/legacy/update.php?resource=%d";
private final JavaPlugin plugin;
private final int projectId;
private String cachedVersion;
private final JavaPlugin plugin;
private final int projectId;
public UpdateChecker(JavaPlugin plugin, int projectId) {
this.plugin = Objects.requireNonNull(plugin, "Plugin cannot be null.");
this.projectId = projectId;
this.cachedVersion = plugin.getDescription().getVersion();
public UpdateChecker(JavaPlugin plugin, int projectId) {
this.plugin = Objects.requireNonNull(plugin, "Plugin cannot be null.");
this.projectId = projectId;
}
public boolean hasUpdatesAvailable() throws IOException {
String latestVersion;
URL url = new URL(String.format(UPDATE_URL, projectId));
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setUseCaches(true);
try (
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream())
)
) {
latestVersion = reader.readLine();
}
public boolean checkForUpdates() throws IOException {
String latestVersion = getCachedVersion();
if (latestVersion == null) {
URL url = new URL(String.format(UPDATE_URL, projectId));
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setUseCaches(true);
latestVersion = new BufferedReader(new InputStreamReader(connection.getInputStream()))
.readLine();
if (latestVersion == null || latestVersion.isEmpty()) {
throw new IOException("Failed to retrieve latest version.");
}
cachedVersion = latestVersion;
}
return !plugin.getDescription().getVersion().equals(cachedVersion);
if (latestVersion == null || latestVersion.isEmpty()) {
throw new IOException("Failed to retrieve latest version.");
}
public String getCachedVersion() {
return cachedVersion;
}
}
return !plugin.getDescription().getVersion().equals(latestVersion);
}
}
@@ -0,0 +1,140 @@
package me.codedred.playtimes.afk;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
import me.codedred.playtimes.utils.ChatUtil;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
public class AFKManager {
private static AFKManager instance = null;
private final HashMap<UUID, Long> lastActive = new HashMap<>();
private final HashMap<UUID, Long> afkTime = new HashMap<>();
private final FileConfiguration config;
private long afkThresholdMillis;
private boolean notifyOnAfk;
private boolean notifyOnBack;
private String onAfkMessage;
private String onBackMessage;
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
);
}
}
},
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<String, Long> 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;
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");
startAFKChecker();
}
}
@@ -0,0 +1,14 @@
package me.codedred.playtimes.afk.listeners;
import me.codedred.playtimes.afk.AFKManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
public class onChat implements Listener {
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
AFKManager.getInstance().updateActivity(event.getPlayer());
}
}
@@ -0,0 +1,14 @@
package me.codedred.playtimes.afk.listeners;
import me.codedred.playtimes.afk.AFKManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
public class onInteract implements Listener {
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
AFKManager.getInstance().updateActivity(event.getPlayer());
}
}
@@ -0,0 +1,20 @@
package me.codedred.playtimes.afk.listeners;
import me.codedred.playtimes.afk.AFKManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
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());
}
}
@@ -0,0 +1,17 @@
package me.codedred.playtimes.afk.listeners;
import me.codedred.playtimes.afk.AFKManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
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());
}
}
@@ -0,0 +1,36 @@
package me.codedred.playtimes.api;
import java.util.UUID;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import me.codedred.playtimes.time.TimeManager;
import org.bukkit.entity.Player;
public class TimelessPlayer {
private final UUID uuid;
private final StatManager stats;
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;
}
}
@@ -0,0 +1,90 @@
package me.codedred.playtimes.api;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import me.codedred.playtimes.models.Leaderboard;
import me.codedred.playtimes.statistics.StatManager;
public class TimelessServer {
public List<UUID> getTop10Players() {
final List<UUID> players = new ArrayList<>();
final Leaderboard board = new Leaderboard();
final Map<String, Integer> 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<Integer> getTop10Times() {
final List<Integer> players = new ArrayList<>();
final Leaderboard board = new Leaderboard();
final Map<String, Integer> 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<String, Integer> 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()
);
}
}
@@ -1,9 +1,12 @@
package me.codedred.playtimes.commands;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.player.OfflinePlayer;
import me.codedred.playtimes.player.OnlinePlayer;
import me.codedred.playtimes.models.Message;
import me.codedred.playtimes.server.ServerManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeConstants;
@@ -11,6 +14,7 @@ import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -19,161 +23,289 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Time implements CommandExecutor {
private final DataManager data = DataManager.getInstance();
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;
}
switch (args.length > 0 ? args[0].toLowerCase() : "") {
case "" -> handlePlayerCommand(sender);
case "reload" -> handleReloadCommand(sender);
case "top" -> handleTopCommand(sender);
case "block" -> handleBlockCommand(sender, args, true);
case "unblock" -> handleBlockCommand(sender, args, false);
case "debug" -> handleDebugCommand(sender);
case "help" -> handleHelpCommand(sender);
default -> handleOtherPlayerCommand(sender, args);
}
return true;
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;
}
private void handleHelpCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
// assuming the main command is "pt"
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Help ***"));
sender.sendMessage(ChatUtil.format("&e/pt: &7Displays your play time"));
sender.sendMessage(ChatUtil.format("&e/pt reload: &7Reloads the PlayTimes plugin configurations"));
sender.sendMessage(ChatUtil.format("&e/pt top: &7Shows the top players by play time"));
sender.sendMessage(ChatUtil.format("&e/pt block <player>: &7Blocks a player from leaderboards"));
sender.sendMessage(ChatUtil.format("&e/pt unblock <player>: &7Unblocks a player from leaderboards"));
sender.sendMessage(ChatUtil.format("&e/pt debug: &7Displays the debug info"));
sender.sendMessage(ChatUtil.format("&e/pt <player>: &7Displays the play time of <player>"));
String arg = args.length > 0 ? args[0].toLowerCase() : "";
switch (arg) {
case "":
handlePlayerCommand(sender);
break;
case "reload":
handleReloadCommand(sender);
break;
case "reloaddatabase":
handleReloadDatabaseCommand(sender);
break;
case "top":
handleTopCommand(sender);
break;
case "block":
handleBlockCommand(sender, args, true);
break;
case "unblock":
handleBlockCommand(sender, args, false);
break;
case "version":
handleVersionCommand(sender);
break;
case "help":
handleHelpCommand(sender);
break;
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 <player>: &7Displays the play time of <player>")
);
sender.sendMessage(
ChatUtil.format(
"&e/pt reload: &7Reloads the PlayTimes plugin configurations"
)
);
sender.sendMessage(
ChatUtil.format("&e/pt top: &7Shows the top players by play time")
);
sender.sendMessage(
ChatUtil.format(
"&e/pt block <player>: &7Blocks a player from leaderboards"
)
);
sender.sendMessage(
ChatUtil.format(
"&e/pt unblock <player>: &7Unblocks a player from leaderboards"
)
);
sender.sendMessage(
ChatUtil.format("&e/pt version: &7Displays the version info")
);
}
private void handleDebugCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
boolean isOnline = ServerManager.getInstance().isOnline();
String serverStatus = isOnline ? "Online" : "Offline";
private void handleVersionCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
boolean isOnline = ServerManager.getInstance().isOnline();
String serverStatus = isOnline ? "Online" : "Offline";
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Debug ***"));
sender.sendMessage(ChatColor.GOLD + "Server version: " + ChatColor.WHITE + Bukkit.getServer().getVersion() +
ChatColor.GOLD + "\nPlayTimes version: " + ChatColor.WHITE + JavaPlugin.getPlugin(PlayTimes.class).getDescription().getVersion() +
ChatColor.GOLD + "\nStatManager: " + ChatColor.WHITE + StatManager.getInstance().name +
ChatColor.GOLD + "\nServerStatus: " + ChatColor.WHITE + serverStatus);
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Version Info ***"));
sender.sendMessage(
ChatColor.GOLD +
"Server version: " +
ChatColor.WHITE +
Bukkit.getServer().getVersion() +
ChatColor.GOLD +
"\nPlayTimes version: " +
ChatColor.WHITE +
JavaPlugin.getPlugin(PlayTimes.class).getDescription().getVersion() +
ChatColor.GOLD +
"\nStat Manager: " +
ChatColor.WHITE +
StatManager.getInstance().name +
ChatColor.GOLD +
"\nServer Status: " +
ChatColor.WHITE +
serverStatus +
ChatColor.GOLD +
"\nDatabase connected: " +
ChatColor.WHITE +
DataManager.getInstance().hasDatabase() +
ChatColor.GOLD +
"\nAFK Status: " +
ChatColor.WHITE +
DataManager.getInstance().hasAfkEnabled() +
ChatColor.GOLD +
"\nUUID Lookup Type: " +
ChatColor.WHITE +
ServerManager.getInstance().lookupType()
);
}
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;
}
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;
}
OnlinePlayer player = new OnlinePlayer((Player) sender);
player.sendMessageToTarget();
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!"));
private void handleReloadCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
private void handleTopCommand(CommandSender sender) {
Bukkit.dispatchCommand(sender, "toppt");
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 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;
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 + " <player>"
)
);
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") + " <player>"
)
);
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;
}
if (args.length < 2) {
sender.sendMessage(ChatUtil.formatWithPrefix("&c&lIncorrect Usage&c, try &7/pt " + blockAction + " <player>"));
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;
}
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") + " <player>"));
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();
target =
Objects.requireNonNull(Bukkit.getPlayer(args[0])).getUniqueId();
}
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) {
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();
}
org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(target);
OfflinePlayer player = new OfflinePlayer(sender, target, offlinePlayer.getName());
player.sendMessageToTarget();
}
}.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class));
OfflinePlayer offlinePlayer = Bukkit
.getServer()
.getOfflinePlayer(target);
Message message = new Message(sender, target, offlinePlayer.getName());
message.sendMessageToTarget();
}
}
.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class));
}
}
@@ -1,6 +1,13 @@
package me.codedred.playtimes.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.models.Leaderboard;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.*;
@@ -9,90 +16,146 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import me.codedred.playtimes.models.Leaderboard;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
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;
}
@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;
}
if (!(sender instanceof Player player)) {
sender.sendMessage("Must be a player to run this command.");
return true;
}
DataManager data = DataManager.getInstance();
DataManager data = DataManager.getInstance();
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(),
System.currentTimeMillis() +
(data.getConfig().getInt("top-playtime.cooldown-seconds") * 1000L)
);
}
if (data.getConfig().getBoolean("top-playtime.enable-cooldown") && !player.hasPermission("pt.block-cooldown")) {
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(), System.currentTimeMillis() + (data.getConfig().getInt("top-playtime.cooldown-seconds") * 1000L));
}
generateAndSendLeaderboard(sender);
return true;
}
Leaderboard board = new Leaderboard();
Map<String, Integer> map = board.getTopTen();
private void generateAndSendLeaderboard(CommandSender sender) {
CompletableFuture.supplyAsync(() -> {
Leaderboard board = new Leaderboard();
Map<String, Integer> map = board.getTopTen();
if (map.isEmpty()) {
return null;
}
if (map.isEmpty()) {
sender.sendMessage(ChatUtil.format("&cRejoin the server to fill the leaderboard!"));
return true;
}
return generateLeaderboardMessages(map, sender);
}).thenAccept(messages -> {
Bukkit.getScheduler().runTask(getPlugin(), () -> {
if (messages == null) {
sender.sendMessage(
ChatUtil.format("&cRejoin the server to fill the leaderboard!")
);
return;
}
StatManager statManager = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
String header = ChatUtil.format(data.getConfig().getString("top-playtime.header"));
String footer = ChatUtil.format(data.getConfig().getString("top-playtime.footer"));
String content = data.getConfig().getString("top-playtime.content");
for (String message : messages) {
sender.sendMessage(message);
}
});
}).exceptionally(throwable -> {
throwable.printStackTrace();
Bukkit.getScheduler().runTask(getPlugin(), () -> {
sender.sendMessage(ChatUtil.format("&cError generating leaderboard!"));
});
return null;
});
}
if (ServerUtils.hasPAPI()) {
header = PAPIHolders.getHolders(player, header);
footer = PAPIHolders.getHolders(player, footer);
}
private List<String> generateLeaderboardMessages(Map<String, Integer> map, CommandSender sender) {
List<String> messages = new ArrayList<>();
DataManager data = DataManager.getInstance();
StatManager statManager = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
sender.sendMessage(header);
String header = ChatUtil.format(
data.getConfig().getString("top-playtime.header")
);
String footer = ChatUtil.format(
data.getConfig().getString("top-playtime.footer")
);
String contentTemplate = data.getConfig().getString("top-playtime.content");
for (int i = 0; i < map.size(); i++) {
UUID uuid = UUID.fromString(map.keySet().toArray()[i].toString());
org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
if (ServerUtils.hasPAPI() && sender instanceof Player) {
header = PAPIHolders.getHolders((Player) sender, header);
footer = PAPIHolders.getHolders((Player) sender, footer);
}
if (ServerUtils.hasPAPI()) {
content = PAPIHolders.getHolders(offlinePlayer, content);
}
messages.add(header);
String offlinePlayerName = offlinePlayer.getName();
String place = String.valueOf(i + 1);
String time = timeManager.buildFormat(map.get(offlinePlayer.getUniqueId().toString()) / 20);
String joinDate = statManager.getJoinDate(uuid);
int place = 1;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
UUID uuid = UUID.fromString(entry.getKey());
org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
if (time == null) {
continue;
}
String content = contentTemplate;
if (ServerUtils.hasPAPI()) {
content = PAPIHolders.getHolders(offlinePlayer, content);
}
sender.sendMessage(ChatUtil.format(content
.replace("%player%", offlinePlayerName)
.replace("%place%", place)
.replace("%time%", time)
.replace("%joindate%", joinDate)));
String defaultPlayerName = "Unknown";
String defaultJoinDate = "N/A";
String defaultTime = "0h 0m 0s";
content = data.getConfig().getString("top-playtime.content");
}
String offlinePlayerName = offlinePlayer.getName() != null
? offlinePlayer.getName()
: defaultPlayerName;
String time = timeManager.buildFormat(entry.getValue() / 20);
String joinDate = statManager.getJoinDate(uuid) != null
? statManager.getJoinDate(uuid)
: defaultJoinDate;
sender.sendMessage(footer);
return true;
}
if (time == null) {
time = defaultTime;
}
String formattedContent = content
.replace("%player%", offlinePlayerName)
.replace("%place%", String.valueOf(place))
.replace("%time%", time)
.replace("%joindate%", joinDate);
messages.add(ChatUtil.format(formattedContent));
place++;
}
messages.add(footer);
return messages;
}
private Plugin getPlugin() {
return Bukkit.getPluginManager().getPlugin("PlayTimes");
}
}
@@ -1,5 +1,6 @@
package me.codedred.playtimes.commands;
import java.util.List;
import me.clip.placeholderapi.PlaceholderAPI;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.statistics.StatManager;
@@ -12,37 +13,54 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Uptime implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, String cmdL, String[] args) {
if (!cmdL.equalsIgnoreCase("uptime") && !cmdL.equalsIgnoreCase("serveruptime") && !cmdL.equalsIgnoreCase("serverupt")) {
return false;
}
@Override
public boolean onCommand(
@NotNull CommandSender sender,
@NotNull Command cmd,
String cmdL,
String[] args
) {
if (
!cmdL.equalsIgnoreCase("uptime") &&
!cmdL.equalsIgnoreCase("serveruptime") &&
!cmdL.equalsIgnoreCase("serverupt")
) {
return false;
}
if (!sender.hasPermission("pt.uptime")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
if (!sender.hasPermission("pt.uptime")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
String uptime = StatManager.getInstance().getUptime();
List<String> messages = DataManager.getInstance().getConfig().getStringList("uptime.message");
String uptime = StatManager.getInstance().getUptime();
List<String> messages = DataManager
.getInstance()
.getConfig()
.getStringList("uptime.message");
for (String message : messages) {
if (ServerUtils.hasPAPI() && sender instanceof Player player) {
message = PlaceholderAPI.setPlaceholders(player, message);
}
for (String message : messages) {
if (ServerUtils.hasPAPI() && sender instanceof Player) {
Player player = (Player) sender;
message = PlaceholderAPI.setPlaceholders(player, message);
}
if (message.contains("{\"text\":")) {
String consoleCommand = "tellraw " + sender.getName() + " " + message.replace("%serveruptime%", uptime);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), consoleCommand);
} else {
sender.sendMessage(ChatUtil.format(message.replace("%serveruptime%", uptime)));
}
}
if (message.contains("{\"text\":")) {
String consoleCommand =
"tellraw " +
sender.getName() +
" " +
message.replace("%serveruptime%", uptime);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), consoleCommand);
} else {
sender.sendMessage(
ChatUtil.format(message.replace("%serveruptime%", uptime))
);
}
}
return true;
}
return true;
}
}
@@ -1,46 +1,63 @@
package me.codedred.playtimes.commands.completer;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class TimeTabCompleter implements TabCompleter {
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {
if (sender.hasPermission("pt.use")) {
completions.add("top");
completions.addAll(getOnlinePlayerNames());
}
if (sender.hasPermission("pt.block")) {
completions.add("block");
completions.add("unblock");
}
if (sender.hasPermission("pt.reload")) {
completions.add("reload");
completions.add("debug");
completions.add("help");
}
}
return completions;
@Override
public List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String alias,
String[] args
) {
List<String> 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");
}
}
private List<String> getOnlinePlayerNames() {
List<String> playerNames = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
playerNames.add(player.getName());
}
return playerNames;
return completions;
}
private List<String> getOnlinePlayerNames(CommandSender sender) {
List<String> 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;
}
return false;
}
}
@@ -1,61 +1,183 @@
package me.codedred.playtimes.data;
import me.codedred.playtimes.PlayTimes;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import me.codedred.playtimes.PlayTimes;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
public class CustomConfig {
private final String name;
private final PlayTimes plugin;
private FileConfiguration dataConfig = null;
private File dataConfigFile = null;
public CustomConfig(PlayTimes plugin, String fileName) {
this.plugin = plugin;
this.name = fileName;
saveDefaultConfig();
}
private final String name;
private final PlayTimes plugin;
private FileConfiguration dataConfig = null;
private File dataConfigFile = null;
public void reloadConfig() {
if (this.dataConfigFile == null)
this.dataConfigFile = new File(this.plugin.getDataFolder(), name);
public CustomConfig(PlayTimes plugin, String fileName) {
this.plugin = plugin;
this.name = fileName;
saveDefaultConfig();
}
this.dataConfig = YamlConfiguration.loadConfiguration(this.dataConfigFile);
public void reloadConfig() {
if (this.dataConfigFile == null) {
this.dataConfigFile = new File(this.plugin.getDataFolder(), name);
}
InputStream defConfigStream = this.plugin.getResource(name);
if (defConfigStream != null) {
YamlConfiguration defConfig = YamlConfiguration
.loadConfiguration(new InputStreamReader(defConfigStream));
this.dataConfig.setDefaults(defConfig);
}
}
this.dataConfig = YamlConfiguration.loadConfiguration(this.dataConfigFile);
public FileConfiguration getConfig() {
if (this.dataConfig == null)
reloadConfig();
return this.dataConfig;
}
if ("config.yml".equals(name)) checkAndAddDefaults();
public void saveConfig() {
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); }
}
try (InputStream defConfigStream = this.plugin.getResource(name)) {
if (defConfigStream != null) {
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);
}
}
public void saveDefaultConfig() {
if (this.dataConfigFile == null)
this.dataConfigFile = new File(this.plugin.getDataFolder(), name);
if (!this.dataConfigFile.exists())
this.plugin.saveResource(name, false);
}
private void checkAndAddDefaults() {
Map<String, Object> 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.name.second", "s");
requiredKeys.put("playtime.name.minute", "min ");
requiredKeys.put("playtime.name.hour", "hr ");
requiredKeys.put("playtime.name.day", "day ");
requiredKeys.put("playtime.name.seconds", "s");
requiredKeys.put("playtime.name.minutes", "mins ");
requiredKeys.put("playtime.name.hours", "hrs ");
requiredKeys.put("playtime.name.days", "days ");
requiredKeys.put("playtime.only-hours", false);
requiredKeys.put("playtime.show-seconds", true);
requiredKeys.put("playtime.show-days", true);
requiredKeys.put("playtime.round-numbers", true);
requiredKeys.put("afk-settings.enabled", false);
requiredKeys.put("afk-settings.threshold", 5);
requiredKeys.put("afk-settings.broadcast-afk.on-enter-afk", true);
requiredKeys.put("afk-settings.broadcast-afk.on-exit-afk", true);
requiredKeys.put(
"afk-settings.on-enter-afk-message",
"&eYou are now marked as AFK."
);
requiredKeys.put(
"afk-settings.on-exit-afk-message",
"&eYou are no longer AFK."
);
requiredKeys.put("afk-settings.cancel-afk.on-player-move", true);
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(
"top-playtime.header",
"&b******&9[&3&lPlayTime Leaderboards&9]&b*****"
);
requiredKeys.put(
"top-playtime.content",
"&5%place%) &9&l%player% &9&o- %time%"
);
requiredKeys.put(
"top-playtime.footer",
"&b****************************************"
);
requiredKeys.put("top-playtime.enable-cooldown", true);
requiredKeys.put("top-playtime.cooldown-seconds", 60);
requiredKeys.put("top-playtime.track-rawtime", false);
requiredKeys.put("date-format", "MM/dd/yyyy");
requiredKeys.put("messages.noPermission", "&cYou cannot run this command.");
requiredKeys.put("messages.player-not-found", "&cUnable to locate player.");
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.type", "default");
boolean changesMade = false;
for (Map.Entry<String, Object> 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();
return this.dataConfig;
}
public void saveConfig() {
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);
}
}
}
@@ -1,35 +1,68 @@
package me.codedred.playtimes.data;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
import me.codedred.playtimes.server.ServerManager;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
public class DataManager {
private static final DataManager instance = new DataManager();
private final CustomConfig data = new CustomConfig(JavaPlugin.getPlugin(PlayTimes.class), "data.yml");
private final CustomConfig cfg = new CustomConfig(JavaPlugin.getPlugin(PlayTimes.class), "config.yml");
private static final DataManager instance = new DataManager();
private final CustomConfig data = new CustomConfig(
JavaPlugin.getPlugin(PlayTimes.class),
"data.yml"
);
private final CustomConfig db = new CustomConfig(
JavaPlugin.getPlugin(PlayTimes.class),
"database.yml"
);
private final CustomConfig cfg = new CustomConfig(
JavaPlugin.getPlugin(PlayTimes.class),
"config.yml"
);
public static DataManager getInstance() {
return instance;
}
public static DataManager getInstance() {
return instance;
}
public FileConfiguration getConfig() {
return cfg.getConfig();
}
public FileConfiguration getConfig() {
return cfg.getConfig();
}
public FileConfiguration getData() {
return data.getConfig();
}
public FileConfiguration getData() {
return data.getConfig();
}
public void reloadAll() {
data.reloadConfig();
cfg.reloadConfig();
}
public FileConfiguration getDBConfig() {
return db.getConfig();
}
public void saveData() {
data.saveConfig();
}
public void saveData() {
data.saveConfig();
}
public void reloadAll() {
data.reloadConfig();
cfg.reloadConfig();
db.reloadConfig();
ServerManager.getInstance().updateLookupType();
if (hasAfkEnabled()) AFKManager.getInstance().reload();
}
public void reloadDatabase() {
db.reloadConfig();
}
public boolean hasDatabase() {
return (
getDBConfig().getBoolean("database-settings.enabled") &&
DatabaseManager.getInstance().isConnected()
);
}
public boolean hasAfkEnabled() {
return getConfig().getBoolean("afk-settings.enabled", false);
}
}
@@ -0,0 +1,10 @@
package me.codedred.playtimes.data.database.datasource;
import java.sql.Connection;
import java.sql.SQLException;
public interface DataSource {
Connection getConnection() throws SQLException;
void closeConnection() throws SQLException;
}
@@ -0,0 +1,56 @@
package me.codedred.playtimes.data.database.datasource.impl;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import lombok.val;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.datasource.DataSource;
public class MySQL implements DataSource {
private final String connectionUrl;
private final String user;
private final String password;
public MySQL(PlayTimes plugin) {
val config = DataManager
.getInstance()
.getDBConfig()
.getConfigurationSection("database-settings");
val host = config.getString("host");
val port = config.getString("port");
this.user = config.getString("user");
this.password = config.getString("password");
val database = config.getString("database");
val SSL = config.getBoolean("useSSL");
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();
}
}
@@ -0,0 +1,43 @@
package me.codedred.playtimes.data.database.datasource.impl;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import lombok.val;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.datasource.DataSource;
public class SQLite implements DataSource {
private Connection connection;
public SQLite(PlayTimes plugin) {
val config = DataManager.getInstance().getDBConfig();
val 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();
}
}
@@ -0,0 +1,233 @@
package me.codedred.playtimes.data.database.manager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import lombok.Getter;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.datasource.DataSource;
import me.codedred.playtimes.data.database.datasource.impl.MySQL;
import me.codedred.playtimes.data.database.datasource.impl.SQLite;
import me.codedred.playtimes.data.database.table.UsersTable;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import org.bukkit.Bukkit;
@Getter
public class DatabaseManager {
private static DatabaseManager instance;
private DataSource dataSource;
private UsersTable usersTable;
private boolean connected = false;
private final String serverId;
private Map<UUID, Map<String, Map<String, Long>>> 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;
}
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;
case "sqlite":
this.dataSource = new SQLite(PlayTimes.getPlugin(PlayTimes.class));
break;
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<String, Map<String, Long>> 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<>())
.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<String, Long> getTimeForServer(UUID uuid, String server) {
return userPlaytimes.get(uuid).get(server);
}
public Map<String, Long> getTimeForServer(UUID uuid) {
return getTimeForServer(uuid, serverId);
}
public Long getTotalRawtime(UUID uuid) {
Long rawtime = 0L;
Map<String, Map<String, Long>> userTimeData = userPlaytimes.get(uuid);
if (userTimeData != null) {
for (Map.Entry<String, Map<String, Long>> entry : userTimeData.entrySet()) {
String server = entry.getKey();
Map<String, Long> 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<String, Map<String, Long>> userTimeData = userPlaytimes.get(uuid);
if (userTimeData != null) {
for (Map.Entry<String, Map<String, Long>> entry : userTimeData.entrySet()) {
String server = entry.getKey();
Map<String, Long> 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<String, Map<String, Long>> userTimeData = userPlaytimes.get(uuid);
if (userTimeData != null) {
for (Map.Entry<String, Map<String, Long>> entry : userTimeData.entrySet()) {
String server = entry.getKey();
Map<String, Long> serverData = entry.getValue();
if (!server.equals(serverId)) {
totalPlaytime += serverData.getOrDefault("playtime", 0L);
totalAfktime += serverData.getOrDefault("afktime", 0L);
}
}
}
StatManager stats = StatManager.getInstance();
Long currentServerPlaytime =
stats.getPlayerStat(uuid, StatisticType.PLAYTIME) / 20;
long afkTime = AFKManager.getInstance().getAFKTime(uuid);
totalPlaytime += currentServerPlaytime;
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
)
);
this.usersTable.purgeOldPlaytimes(serverId, months);
}
}
@@ -0,0 +1,174 @@
package me.codedred.playtimes.data.database.table;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import me.codedred.playtimes.data.database.datasource.DataSource;
import me.codedred.playtimes.utils.Async;
import org.bukkit.Bukkit;
public class UsersTable {
private static final String TABLE_NAME = "playtimes";
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
);
try (
Connection conn = dataSource.getConnection();
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
);
try (
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(query)
) {
preparedStatement.setString(1, uuid);
preparedStatement.setString(2, serverId);
preparedStatement.setLong(3, playtime);
preparedStatement.setLong(4, afktime);
preparedStatement.setLong(5, playtime);
preparedStatement.setLong(6, afktime);
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
});
}
public Map<String, Map<String, Long>> getPlaytimesByUuid(UUID uuid) {
Map<String, Map<String, Long>> timesMap = new HashMap<>();
try (
Connection conn = dataSource.getConnection();
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");
Map<String, Long> timeData = new HashMap<>();
timeData.put("playtime", playtime);
timeData.put("afktime", afktime);
timesMap.put(serverId, timeData);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return timesMap;
}
public Map<String, Timestamp> getLastUpdatedTimesByUuid(UUID uuid) {
Map<String, Timestamp> 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.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.
*
* @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.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."
);
} catch (SQLException e) {
e.printStackTrace();
}
});
}
}
@@ -1,37 +1,79 @@
package me.codedred.playtimes.listeners;
import java.util.UUID;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.UUID;
public class Join implements Listener {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
DataManager data = DataManager.getInstance();
if (data.getData().contains("blocked." + event.getPlayer().getName().toLowerCase())) {
return;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
DataManager data = DataManager.getInstance();
UUID uuid = event.getPlayer().getUniqueId();
ConfigurationSection leaderboardSection = data.getData().getConfigurationSection("leaderboard");
// Database
if (data.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
dbManager.retrievePlaytime(uuid);
if (leaderboardSection == null) {
leaderboardSection = data.getData().createSection("leaderboard");
}
// 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
);
}
if (leaderboardSection.contains(uuid.toString())) {
return;
}
// Leaderboard
if (
data
.getData()
.contains("blocked." + event.getPlayer().getName().toLowerCase())
) {
return;
}
long time = StatManager.getInstance().getPlayerStat(uuid, StatisticType.PLAYTIME);
leaderboardSection.set(uuid.toString(), time);
data.saveData();
}
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();
}
}
@@ -1,28 +1,65 @@
package me.codedred.playtimes.listeners;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.UUID;
public class Quit implements Listener {
@EventHandler
public void onQuit(PlayerQuitEvent event) {
DataManager data = DataManager.getInstance();
String playerName = event.getPlayer().getName().toLowerCase();
if (data.getData().contains("blocked." + playerName)) {
return;
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
DataManager data = DataManager.getInstance();
StatManager statManager = StatManager.getInstance();
UUID uuid = event.getPlayer().getUniqueId();
long playtime = statManager.getPlayerStat(uuid, StatisticType.PLAYTIME);
data.getData().set("leaderboard." + uuid, playtime);
data.saveData();
}
// Database
if (data.hasDatabase()) {
CompletableFuture.runAsync(() -> {
DatabaseManager dbManager = DatabaseManager.getInstance();
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();
});
}
}
@@ -1,79 +1,123 @@
package me.codedred.playtimes.models;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import java.util.*;
public class Leaderboard {
final String LEADERBOARD = "leaderboard";
final String LEADERBOARD = "leaderboard";
private static final Map<String, Integer> cachedLeaderboard = java.util.Collections.synchronizedMap(new LinkedHashMap<>());
private static long lastCacheUpdate = 0;
private static final long CACHE_DURATION = 30000; // 30 seconds
/**
* 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<String, Integer> getTopTen() {
Map<String, Integer> 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)));
}
/**
* 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<String, Integer> getTopTen() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastCacheUpdate < CACHE_DURATION && !cachedLeaderboard.isEmpty()) {
return new LinkedHashMap<>(cachedLeaderboard);
}
Map<String, Integer> topTen = generateTopTen();
cachedLeaderboard.clear();
cachedLeaderboard.putAll(topTen);
lastCacheUpdate = currentTime;
return topTen;
}
/**
* Forces cache invalidation and regenerates the leaderboard
*/
public static void invalidateCache() {
lastCacheUpdate = 0;
cachedLeaderboard.clear();
}
private Map<String, Integer> generateTopTen() {
Map<String, Integer> allUsers = new LinkedHashMap<>();
DataManager data = DataManager.getInstance();
if (!data.getData().contains("leaderboard")) {
return allUsers;
}
for (String key : data
.getData()
.getConfigurationSection(LEADERBOARD)
.getKeys(false)) {
allUsers.put(
key,
Integer.valueOf(data.getData().getString(LEADERBOARD + "." + key))
);
}
List<Map.Entry<String, Integer>> list = new LinkedList<>(topTen.entrySet());
list.sort((o1, o2) -> o2.getValue() - o1.getValue());
Map<String, Integer> updatedUsers = updateAllUserTimes(allUsers);
List<Map.Entry<String, Integer>> list = new ArrayList<>(updatedUsers.entrySet());
list.sort(Map.Entry.<String, Integer>comparingByValue().reversed());
if (list.size() > 20) {
List<Map.Entry<String, Integer>> delList = list.subList(20, list.size());
if (list.size() > 20) {
List<Map.Entry<String, Integer>> delList = list.subList(20, list.size());
for (Map.Entry<String, Integer> key : delList) {
data.getData().set(LEADERBOARD + "." + key.getKey(), null);
}
data.saveData();
}
for (Map.Entry<String, Integer> key : delList) {
data.getData().set(LEADERBOARD + "." + key.getKey(), null);
}
data.saveData();
list = list.subList(0, 20);
}
if (list.size() > 9) {
list = list.subList(0, 10);
}
Map<String, Integer> topTen = new LinkedHashMap<>();
int count = Math.min(list.size(), 10);
for (int i = 0; i < count; i++) {
Map.Entry<String, Integer> entry = list.get(i);
topTen.put(entry.getKey(), entry.getValue());
}
topTen.clear();
return topTen;
}
for (Map.Entry<String, Integer> entry : list) {
topTen.put(entry.getKey(), entry.getValue());
}
/**
* Updates all users' times by querying the latest times from the StatManager.
*
* @param allUsers all users with their cached playtime data
* @return updated map with current playtime data
*/
private Map<String, Integer> updateAllUserTimes(Map<String, Integer> allUsers) {
StatManager statManager = StatManager.getInstance();
DataManager dataManager = DataManager.getInstance();
Map<String, Integer> updatedUsers = new LinkedHashMap<>();
updateTimes(topTen);
return topTen;
}
for (Map.Entry<String, Integer> entry : allUsers.entrySet()) {
String uuid = entry.getKey();
int latestTime = (int) statManager.getPlayerStat(
UUID.fromString(uuid),
StatisticType.PLAYTIME
);
/**
* 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
*/
private void updateTimes(Map<String, Integer> topTen) {
StatManager statManager = StatManager.getInstance();
List<Map.Entry<String, Integer>> list = new ArrayList<>(topTen.entrySet());
if (
!dataManager
.getConfig()
.getBoolean("top-playtime.track-rawtime", false) &&
dataManager.hasAfkEnabled()
) {
latestTime -=
AFKManager.getInstance().getAFKTime(UUID.fromString(uuid)) * 20;
}
for (Map.Entry<String, Integer> entry : list) {
String uuid = entry.getKey();
int latestTime = (int) statManager.getPlayerStat(UUID.fromString(uuid), StatisticType.PLAYTIME);
topTen.put(uuid, latestTime);
}
updatedUsers.put(uuid, latestTime);
}
list = new ArrayList<>(topTen.entrySet());
list.sort(Map.Entry.<String, Integer>comparingByValue().reversed());
topTen.clear();
for (int i = 0; i < Math.min(list.size(), 10); i++) {
Map.Entry<String, Integer> entry = list.get(i);
topTen.put(entry.getKey(), entry.getValue());
}
}
return updatedUsers;
}
}
@@ -0,0 +1,228 @@
package me.codedred.playtimes.models;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import me.codedred.playtimes.utils.PAPIHolders;
import me.codedred.playtimes.utils.ServerUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
public class Message {
private final CommandSender sender;
private final UUID targetUUID;
private final String targetName;
private List<String> message;
private final DataManager dataManager;
private final StatManager statManager;
private final TimeManager timeManager;
/**
* Constructor for Message.
* Initializes the message for the target player.
*
* @param sender The command sender.
* @param targetUUID The UUID 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;
this.dataManager = DataManager.getInstance();
this.statManager = StatManager.getInstance();
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.
*
* @return A list of formatted strings representing the message.
*/
private List<String> buildMessage() {
List<String> 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);
}
if (ServerUtils.hasPAPI()) {
builtMessage = applyPAPIPlaceholders(builtMessage);
}
return processMessageWithReplacements(
builtMessage,
prepareReplacements(rawTime, afkTime)
);
}
/**
* Applies PlaceholderAPI placeholders to the message if available.
*
* @param message The message list to process.
* @return A list of messages with placeholders applied.
*/
private List<String> applyPAPIPlaceholders(List<String> message) {
List<String> papiMessage = new ArrayList<>();
for (String msg : message) {
papiMessage.add(
PAPIHolders.getHolders(Bukkit.getOfflinePlayer(targetUUID), msg)
);
}
return papiMessage;
}
/**
* 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.
* @return A map of placeholder keys and their replacement values.
*/
private Map<String, String> prepareReplacements(long rawTime, long afkTime) {
Map<String, String> 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("%joindate%", statManager.getJoinDate(targetUUID));
if (dataManager.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
long globalPlaytime = dbManager.getTotalPlaytime(targetUUID);
long globalRawtime = dbManager.getTotalRawtime(targetUUID);
long globalAfktime = dbManager.getTotalAfktime(targetUUID);
replacements.put(
"%global_playtime%",
timeManager.buildFormat(globalPlaytime)
);
replacements.put(
"%global_rawtime%",
timeManager.buildFormat(globalRawtime)
);
replacements.put(
"%global_afktime%",
timeManager.buildFormat(globalAfktime)
);
}
return replacements;
}
/**
* Processes the message by applying the necessary replacements.
*
* @param message The original message.
* @param replacements The map of placeholders and their replacements.
* @return A list of processed messages.
*/
private List<String> processMessageWithReplacements(
List<String> message,
Map<String, String> replacements
) {
List<String> 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<String, Long> timeForServer = dbManager.getTimeForServer(
targetUUID,
serverId
);
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<String, String> 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).
*/
public void sendMessageToTarget() {
for (String msg : message) {
String formattedMsg = ChatUtil.format(msg);
if (formattedMsg.contains("{\"text\":")) {
Bukkit.dispatchCommand(
Bukkit.getConsoleSender(),
"tellraw " + sender.getName() + " " + formattedMsg
);
} else {
sender.sendMessage(formattedMsg);
}
}
}
}
@@ -1,70 +0,0 @@
package me.codedred.playtimes.player;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import me.codedred.playtimes.utils.PAPIHolders;
import me.codedred.playtimes.utils.ServerUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class OfflinePlayer {
private final CommandSender sender;
private final UUID target;
private final String name;
private List<String> message = new ArrayList<>();
public OfflinePlayer(CommandSender sender, UUID target, String name) {
this.target = target;
this.sender = sender;
this.name = name;
buildMessage();
}
private void buildMessage() {
DataManager dataManager = DataManager.getInstance();
StatManager statManager = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
message = dataManager.getConfig().getStringList("playtime.message");
long rawTime = statManager.getPlayerStat(target, StatisticType.PLAYTIME);
if (ServerUtils.hasPAPI()) {
List<String> papiMessage = new ArrayList<>();
for (String msg : message) {
papiMessage.add(PAPIHolders.getHolders(Bukkit.getOfflinePlayer(target), msg));
}
message = papiMessage;
}
String timeFormat = timeManager.buildFormat(rawTime / 20);
List<String> newMessage = new ArrayList<>();
for (String msg : message) {
msg = msg.replace("%time%", timeFormat);
msg = msg.replace("%player%", name);
msg = msg.replace("%timesjoined%", Long.toString(statManager.getPlayerStat(target, StatisticType.LEAVE)));
msg = msg.replace("%joindate%", statManager.getJoinDate(target));
newMessage.add(msg);
}
message = newMessage;
}
public void sendMessageToTarget() {
for (String msg : message) {
if (msg.contains("{\"text\":")) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender + " " + ChatUtil.format(msg));
} else {
sender.sendMessage(ChatUtil.format(msg));
}
}
}
}
@@ -1,99 +0,0 @@
package me.codedred.playtimes.player;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import me.codedred.playtimes.utils.PAPIHolders;
import me.codedred.playtimes.utils.ServerUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class OnlinePlayer {
private final Player target;
private final List<String> message;
private final DataManager dataManager;
private final StatManager statManager;
private final TimeManager timeManager;
public OnlinePlayer(Player target) {
this.target = target;
this.message = new ArrayList<>();
this.dataManager = DataManager.getInstance();
this.statManager = StatManager.getInstance();
this.timeManager = TimeManager.getInstance();
buildMessage();
}
private void buildMessage() {
long rawTime;
if (statManager.isLegacy()) {
rawTime = statManager.getPlayerStat(target.getUniqueId(), StatisticType.PLAYTIME);
} else {
rawTime = statManager.getStats().getOnlineStatistic(target, StatisticType.PLAYTIME);
}
message.addAll(dataManager.getConfig().getStringList("playtime.message"));
if (ServerUtils.hasPAPI()) {
List<String> papiMessage = new ArrayList<>();
for (String msg : message) {
papiMessage.add(PAPIHolders.getHolders(target, msg));
}
message.clear();
message.addAll(papiMessage);
}
if (message.isEmpty()) {
return;
}
Map<String, String> replacements = new HashMap<>();
String timeFormat = timeManager.buildFormat(rawTime / 20);
replacements.put("%time%", timeFormat);
replacements.put("%player%", target.getName());
replacements.put("%timesjoined%", Long.toString(statManager.getPlayerStat(target.getUniqueId(), StatisticType.LEAVE)));
replacements.put("%joindate%", statManager.getJoinDate(target.getUniqueId()));
Pattern pattern = Pattern.compile("%(\\w+)%");
List<String> newMessage = new ArrayList<>();
for (String msg : message) {
Matcher matcher = pattern.matcher(msg);
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
String replacement = replacements.get(matcher.group(1));
if (replacement != null) {
matcher.appendReplacement(sb, replacement);
}
}
matcher.appendTail(sb);
newMessage.add(sb.toString());
}
message.clear();
message.addAll(newMessage);
}
public void sendMessageToTarget() {
String playerName = target.getName();
for (String msg : message) {
String formattedMsg = ChatUtil.format(msg)
.replace("%time%", timeManager.buildFormat(StatManager.getInstance().getStats().getOnlineStatistic(target, StatisticType.PLAYTIME) / 20))
.replace("%player%", playerName)
.replace("%timesjoined%", Long.toString(StatManager.getInstance().getPlayerStat(target.getUniqueId(), StatisticType.LEAVE)))
.replace("%joindate%", StatManager.getInstance().getJoinDate(target.getUniqueId()));
if (formattedMsg.contains("{\"text\":")) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + target + " " + formattedMsg);
} else {
target.sendMessage(formattedMsg);
}
}
}
}
@@ -1,52 +1,95 @@
package me.codedred.playtimes.server;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import java.io.IOException;
import java.util.UUID;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
public class ServerManager {
private static final ServerManager instance = new ServerManager();
public static ServerManager getInstance() {
return instance;
private static final ServerManager instance = new ServerManager();
public enum LookUpTypes {
OFFLINE,
ONLINE,
}
public static ServerManager getInstance() {
return instance;
}
private ServerStatus status;
private LookUpTypes lookupType = LookUpTypes.ONLINE;
public void register() {
if (Bukkit.getOnlineMode()) {
status = new ServerOnline();
} else {
status = new ServerOffline();
}
cleanLeaderboard();
updateLookupType();
}
private ServerStatus status;
public ServerStatus getStatus() {
return status;
}
public void register() {
if (Bukkit.getOnlineMode())
status = new ServerOnline();
else
status = new ServerOffline();
cleanLeaderboard();
public UUID getUUID(String name) {
return getStatus().getUUID(name);
}
public String getName(UUID uuid) throws IOException {
return getStatus().getName(uuid);
}
public boolean isOnline() {
return getStatus().isOnline();
}
public LookUpTypes lookupType() {
return lookupType;
}
public void updateLookupType() {
DataManager dataManager = DataManager.getInstance();
boolean databaseEnabled = dataManager
.getDBConfig()
.getBoolean("database-settings.enabled");
boolean isSetToOffline = dataManager
.getConfig()
.getString("uuid-lookups.type")
.equalsIgnoreCase("offline");
boolean isSetToOnline = dataManager
.getConfig()
.getString("uuid-lookups.type")
.equalsIgnoreCase("online");
boolean serverOnline = isOnline();
if (isSetToOnline) lookupType = LookUpTypes.ONLINE; else if (
(!serverOnline && !databaseEnabled) || isSetToOffline
) {
lookupType = LookUpTypes.OFFLINE;
} else {
lookupType = LookUpTypes.ONLINE;
}
}
public ServerStatus getStatus() {
return status;
}
public UUID getUUID(String name) {
return getStatus().getUUID(name);
}
public String getName(UUID uuid) throws IOException {
return getStatus().getName(uuid);
}
public boolean isOnline() {
return getStatus().isOnline();
}
private void cleanLeaderboard() {
DataManager data = DataManager.getInstance();
if (!data.getData().contains("leaderboard"))
return;
for (String key : data.getData().getConfigurationSection("leaderboard").getKeys(false)) {
if (Bukkit.getServer().getOfflinePlayer(UUID.fromString(key)).getName() == null)
data.getData().set("leaderboard." + key, null);
}
data.saveData();
private void cleanLeaderboard() {
DataManager data = DataManager.getInstance();
if (!data.getData().contains("leaderboard")) return;
for (String key : data
.getData()
.getConfigurationSection("leaderboard")
.getKeys(false)) {
if (
Bukkit.getServer().getOfflinePlayer(UUID.fromString(key)).getName() ==
null
) data.getData().set("leaderboard." + key, null);
}
data.saveData();
}
}
@@ -2,8 +2,6 @@ package me.codedred.playtimes.server;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import me.codedred.playtimes.statistics.StatManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -11,39 +9,92 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import me.codedred.playtimes.server.ServerManager.LookUpTypes;
import me.codedred.playtimes.statistics.StatManager;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
public class ServerOffline implements ServerStatus {
@Override
public UUID getUUID(String name) {
try (InputStream is = new URL("https://api.mojang.com/users/profiles/minecraft/" + name).openStream()) {
BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
JsonObject rootobj = new Gson().fromJson(rd, JsonObject.class);
String fixedName = rootobj.get("name").getAsString();
UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + fixedName).getBytes(StandardCharsets.UTF_8));
if (StatManager.getInstance().hasJoinedBefore(uuid)) {
return null;
}
return uuid;
} catch (IOException e) {
return null;
}
@Override
public UUID getUUID(String name) {
UUID offlineUUID = null;
Player onlinePlayer = Bukkit.getServer().getPlayer(name);
if (onlinePlayer != null) {
offlineUUID = onlinePlayer.getUniqueId();
}
@Override
public boolean isOnline() {
return false;
}
@Override
public String getName(UUID uuid) throws IOException {
try (InputStream is = new URL("https://mcapi.ca/player/profile/" + uuid).openStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
JsonObject rootobj = new Gson().fromJson(rd, JsonObject.class);
return rootobj.get("name").getAsString();
} catch (IOException e) {
e.printStackTrace();
return "User Not Found";
if (offlineUUID == null) {
OfflinePlayer offlinePlayers[] = Bukkit.getServer().getOfflinePlayers();
int playerIndex = 0;
while ((playerIndex < offlinePlayers.length) && (offlineUUID == null)) {
if (offlinePlayers[playerIndex].getName().equalsIgnoreCase(name)) {
offlineUUID = offlinePlayers[playerIndex].getUniqueId();
}
playerIndex = playerIndex + 1;
}
}
if (offlineUUID != null) {
return offlineUUID;
} else {
try (
InputStream is = new URL(
"https://api.mojang.com/users/profiles/minecraft/" + name
)
.openStream()
) {
BufferedReader rd = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8)
);
JsonObject rootobj = new Gson().fromJson(rd, JsonObject.class);
UUID uuid;
if (ServerManager.getInstance().lookupType() == LookUpTypes.OFFLINE) {
String fixedName = rootobj.get("name").getAsString();
uuid =
UUID.nameUUIDFromBytes(
("OfflinePlayer:" + fixedName).getBytes(StandardCharsets.UTF_8)
);
} else {
String id = rootobj.get("id").getAsString();
String uuidWithDashes = id.replaceAll(
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
"$1-$2-$3-$4-$5"
);
uuid = UUID.fromString(uuidWithDashes);
}
if (!StatManager.getInstance().hasJoinedBefore(uuid)) {
return null;
}
return uuid;
} catch (IOException e) {
return null;
}
}
}
@Override
public boolean isOnline() {
return false;
}
@Override
public String getName(UUID uuid) throws IOException {
try (
InputStream is = new URL("https://mcapi.ca/player/profile/" + uuid)
.openStream();
BufferedReader rd = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8)
)
) {
JsonObject rootobj = new Gson().fromJson(rd, JsonObject.class);
return rootobj.get("name").getAsString();
} catch (IOException e) {
e.printStackTrace();
return "User Not Found";
}
}
}
@@ -1,10 +1,6 @@
package me.codedred.playtimes.server;
import com.google.gson.*;
import me.codedred.playtimes.statistics.StatManager;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -12,35 +8,43 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import me.codedred.playtimes.statistics.StatManager;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
public class ServerOnline implements ServerStatus {
@Override
public UUID getUUID(@NotNull String name) {
UUID uuid = Bukkit.getOfflinePlayer(name).getUniqueId();
if (StatManager.getInstance().hasJoinedBefore(uuid)) {
return null;
}
return uuid;
@Override
public UUID getUUID(@NotNull String name) {
UUID uuid = Bukkit.getServer().getOfflinePlayer(name).getUniqueId();
if (!StatManager.getInstance().hasJoinedBefore(uuid)) {
return null;
}
return uuid;
}
@Override
public boolean isOnline() {
return true;
@Override
public boolean isOnline() {
return true;
}
@Override
public String getName(UUID uuid) throws IOException {
try (
InputStream is = new URL("https://mcapi.ca/player/profile/" + uuid)
.openStream()
) {
BufferedReader rd = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8)
);
JsonElement root = JsonParser.parseReader(rd);
JsonObject rootObj = root.getAsJsonObject();
return rootObj.get("name").getAsString();
} catch (IOException e) {
e.printStackTrace();
return "User Not Found";
}
@Override
public String getName(UUID uuid) throws IOException {
try (InputStream is = new URL("https://mcapi.ca/player/profile/" + uuid).openStream()) {
BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
JsonElement root = JsonParser.parseReader(rd);
JsonObject rootObj = root.getAsJsonObject();
return rootObj.get("name").getAsString();
} catch (IOException e) {
e.printStackTrace();
return "User Not Found";
}
}
}
}
}
@@ -4,8 +4,9 @@ import java.io.IOException;
import java.util.UUID;
public interface ServerStatus {
public UUID getUUID(String name);
public UUID getUUID(String name);
public boolean isOnline();
public String getName(UUID uuid) throws IOException;
public boolean isOnline();
public String getName(UUID uuid) throws IOException;
}
@@ -2,62 +2,66 @@ package me.codedred.playtimes.statistics;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.FileReader;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.UUID;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class LegacyStats implements Stats {
@Override
public long getPlayerStatistic(UUID uuid, StatisticType type) {
File playerStatistics = new File(worldFolder, uuid + ".json");
@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);
if (playerStatistics.exists()) {
try {
JsonObject jsonObject = new Gson()
.fromJson(new FileReader(playerStatistics), JsonObject.class);
return switch (type) {
case PLAYTIME -> jsonObject.get("stat.playOneMinute").getAsLong();
case LEAVE -> jsonObject.get("stat.leaveGame").getAsLong();
case REST -> jsonObject.get("stat.timeSinceDeath").getAsLong();
};
} catch (Exception e) {
//e.printStackTrace();
}
switch (type) {
case PLAYTIME:
return jsonObject.get("stat.playOneMinute").getAsLong();
case TIMES_JOINED:
return jsonObject.get("stat.leaveGame").getAsLong();
}
return 0;
} 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")
);
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());
}
@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"));
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";
}
return "Never Joined";
}
}
@@ -2,81 +2,83 @@ package me.codedred.playtimes.statistics;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.FileReader;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.UUID;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
public class RisenStats implements Stats {
@Override
public long getPlayerStatistic(UUID uuid, StatisticType type) {
if (Bukkit.getPlayer(uuid) != null)
return getOnlineStatistic(Bukkit.getPlayer(uuid), type);
@Override
public long getPlayerStatistic(UUID uuid, StatisticType type) {
if (Bukkit.getPlayer(uuid) != null) return getOnlineStatistic(
Bukkit.getPlayer(uuid),
type
);
File playerStatistics = new File(worldFolder, uuid + ".json");
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");
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 LEAVE -> {
return passenger.get("minecraft:leave_game").getAsLong();
}
case REST -> {
return passenger.get("minecraft:time_since_rest").getAsLong();
}
}
} catch (Exception e) {
//e.printStackTrace();
}
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 long getOnlineStatistic(Player player, StatisticType type) {
return switch (type) {
case PLAYTIME -> player.getStatistic(Statistic.PLAY_ONE_MINUTE);
case REST -> player.getStatistic(Statistic.TIME_SINCE_REST);
case LEAVE -> player.getStatistic(Statistic.LEAVE_GAME) + 1;
};
@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")
);
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());
}
@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"));
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";
}
return "Never Joined";
}
}
@@ -2,72 +2,80 @@ package me.codedred.playtimes.statistics;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.FileReader;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.UUID;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
public class RisingStats implements Stats {
@Override
public long getPlayerStatistic(UUID uuid, StatisticType type) {
if (Bukkit.getPlayer(uuid) != null)
return getOnlineStatistic(Bukkit.getPlayer(uuid), type);
File playerStatistics = new File(worldFolder, uuid + ".json");
@Override
public long getPlayerStatistic(UUID uuid, StatisticType type) {
if (Bukkit.getPlayer(uuid) != null) return getOnlineStatistic(
Bukkit.getPlayer(uuid),
type
);
if (playerStatistics.exists()) {
try {
JsonObject jsonObject = new Gson().fromJson(new FileReader(playerStatistics), JsonObject.class);
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");
JsonObject pilot = (JsonObject) jsonObject.get("stats");
JsonObject passenger = (JsonObject) pilot.get("minecraft:custom");
return switch (type) {
case PLAYTIME -> passenger.get("minecraft:play_one_minute").getAsLong();
case LEAVE -> passenger.get("minecraft:leave_game").getAsLong();
case REST -> passenger.get("minecraft:time_since_rest").getAsLong();
};
} catch (Exception e) {
//e.printStackTrace();
}
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 long getOnlineStatistic(Player player, StatisticType type) {
return switch (type) {
case PLAYTIME -> player.getStatistic(Statistic.PLAY_ONE_MINUTE);
case REST -> player.getStatistic(Statistic.TIME_SINCE_REST);
case LEAVE -> player.getStatistic(Statistic.LEAVE_GAME) + 1;
};
@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")
);
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());
}
@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"));
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";
}
return "Never Joined";
}
}
@@ -1,67 +1,69 @@
package me.codedred.playtimes.statistics;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ServerUtils;
import java.lang.management.ManagementFactory;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ServerUtils;
public class StatManager {
private static final StatManager instance = new StatManager();
private static final StatManager instance = new StatManager();
public static StatManager getInstance() {
return instance;
public static StatManager getInstance() {
return instance;
}
private Stats stats;
private boolean legacy = false;
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";
}
}
private Stats stats;
private boolean legacy = false;
public Stats getStats() {
return stats;
}
public String name;
public long getPlayerStat(UUID uuid, StatisticType type) {
return getStats().getPlayerStatistic(uuid, type);
}
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 boolean hasJoinedBefore(UUID uuid) {
return getStats().hasJoinedBefore(uuid);
}
public Stats getStats() {
return stats;
}
public String getJoinDate(UUID uuid) {
return getStats().getJoinDate(uuid);
}
public long getPlayerStat(UUID uuid, StatisticType type) {
return getStats().getPlayerStatistic(uuid, type);
}
private void setLegacy() {
legacy = true;
}
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()));
}
public boolean isLegacy() {
return legacy;
}
public String getUptime() {
return TimeManager
.getInstance()
.buildFormat(
(int) TimeUnit.MILLISECONDS.toSeconds(
ManagementFactory.getRuntimeMXBean().getUptime()
)
);
}
}
@@ -1,11 +1,6 @@
package me.codedred.playtimes.statistics;
public enum StatisticType {
// todo add monthly/weekly/daily times
PLAYTIME,
LEAVE,
REST
PLAYTIME,
TIMES_JOINED,
}
@@ -1,17 +1,18 @@
package me.codedred.playtimes.statistics;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.File;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public interface Stats {
long getPlayerStatistic(UUID uuid, StatisticType type);
boolean hasJoinedBefore(UUID uuid);
String getJoinDate(UUID uuid);
long getOnlineStatistic(Player player, StatisticType type);
long getPlayerStatistic(UUID uuid, StatisticType type);
boolean hasJoinedBefore(UUID uuid);
String getJoinDate(UUID uuid);
long getOnlineStatistic(Player player, StatisticType type);
File worldFolder = new File(Bukkit.getServer().getWorlds().get(0).getWorldFolder(), "stats");
File worldFolder = new File(
Bukkit.getServer().getWorlds().get(0).getWorldFolder(),
"stats"
);
}
@@ -1,31 +1,63 @@
package me.codedred.playtimes.time;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.configuration.file.FileConfiguration;
public class TimeConstants {
private static String second, seconds, minute, minutes, hour, hours, day, days;
static {
reload();
}
private static String second, seconds, minute, minutes, hour, hours, day, days;
private static boolean rounded;
public static void reload() {
second = DataManager.getInstance().getConfig().getString("playtime.name.second");
seconds = DataManager.getInstance().getConfig().getString("playtime.name.seconds");
minute = DataManager.getInstance().getConfig().getString("playtime.name.minute");
minutes = DataManager.getInstance().getConfig().getString("playtime.name.minutes");
hour = DataManager.getInstance().getConfig().getString("playtime.name.hour");
hours = DataManager.getInstance().getConfig().getString("playtime.name.hours");
day = DataManager.getInstance().getConfig().getString("playtime.name.day");
days = DataManager.getInstance().getConfig().getString("playtime.name.days");
}
static {
reload();
}
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 void reload() {
FileConfiguration data = DataManager.getInstance().getConfig();
second = data.getString("playtime.name.second");
seconds = data.getString("playtime.name.seconds");
minute = data.getString("playtime.name.minute");
minutes = data.getString("playtime.name.minutes");
hour = data.getString("playtime.name.hour");
hours = data.getString("playtime.name.hours");
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;
}
}
@@ -5,43 +5,43 @@ import me.codedred.playtimes.time.formats.*;
public class TimeManager {
private static final TimeManager instance = new TimeManager();
private static final TimeManager instance = new TimeManager();
public static TimeManager getInstance() {
return instance;
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;
}
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();
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;
}
private Timings getTimings() {
return timings;
}
timings = new NativeFormat();
}
public String buildFormat(long time) {
return getTimings().buildFormat(time);
}
private Timings getTimings() {
return timings;
}
public String buildFormat(long time) {
return getTimings().buildFormat(time);
}
}
@@ -1,6 +1,5 @@
package me.codedred.playtimes.time;
public interface Timings {
String buildFormat(long time);
String buildFormat(long time);
}
@@ -5,62 +5,66 @@ import me.codedred.playtimes.time.Timings;
public class NativeFormat implements Timings {
@Override
public String buildFormat(long time) {
if (time < 60)
return formatSeconds(time);
@Override
public String buildFormat(long time) {
if (time < 60) return formatSeconds(time);
int minutes = (int) (time / 60);
int secondsLeft = (int) (time % 60);
int minutes = (int) (time / 60);
int secondsLeft = (int) (time % 60);
// Time less than 1 hour
if (minutes < 60)
return formatMinutes(minutes, secondsLeft);
// Time less than 1 hour
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);
// Time less than 1 Day
if (minutes < 1440) {
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());
}
// Time greater than 1 day
int days = minutes / 1440;
int minutesLeft = minutes % 1440;
return formatDays(days, minutesLeft, secondsLeft);
}
private String formatMinutes(int minutes, int secondsLeft) {
String format = minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes());
if (secondsLeft > 0)
format += formatSeconds(secondsLeft);
return format;
}
private String formatSeconds(long seconds) {
return (
seconds +
(seconds == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())
);
}
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);
return format;
}
private String formatMinutes(int minutes, int secondsLeft) {
String format =
minutes +
(minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes());
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;
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);
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;
}
}
@@ -5,42 +5,45 @@ import me.codedred.playtimes.time.Timings;
public class NoDays implements Timings {
@Override
public String buildFormat(long time) {
if (time < 60)
return formatSeconds(time);
@Override
public String buildFormat(long time) {
if (time < 60) return formatSeconds(time);
int minutes = (int) (time / 60);
int secondsLeft = (int) (time % 60);
int minutes = (int) (time / 60);
int secondsLeft = (int) (time % 60);
// Time less than 1 hour
if (minutes < 60)
return formatMinutes(minutes, secondsLeft);
// Time less than 1 hour
if (minutes < 60) return formatMinutes(minutes, secondsLeft);
// Greater than 1 hour, only in hours
int hours = minutes / 60;
int minutesLeft = minutes % 60;
// Greater than 1 hour, only in hours
int hours = minutes / 60;
int minutesLeft = minutes % 60;
return formatHours(hours, minutesLeft, secondsLeft);
}
return formatHours(hours, minutesLeft, secondsLeft);
}
private String formatSeconds(long seconds) {
return seconds + (seconds == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond());
}
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);
return format;
}
private String formatMinutes(int minutes, int secondsLeft) {
String format =
minutes +
(minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes());
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);
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);
return format;
}
}
@@ -5,29 +5,34 @@ import me.codedred.playtimes.time.Timings;
public class NoDaysNoSeconds implements Timings {
@Override
public String buildFormat(long time) {
if (time < 60)
return time + ( time == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond());
@Override
public String buildFormat(long time) {
if (time < 60) return (
time +
(time == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())
);
int minutes = (int) (time / 60);
int minutes = (int) (time / 60);
// Time less than 1 hour
if (minutes < 60)
return minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes());
// Time less than 1 hour
if (minutes < 60) return (
minutes +
(minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes())
);
String format;
String format;
int hours = minutes / 60;
format = hours + (hours == 1 ? TimeConstants.getHour() : TimeConstants.getHours());
int inMins = 60 * hours;
int left = minutes - inMins;
int hours = minutes / 60;
format =
hours + (hours == 1 ? TimeConstants.getHour() : TimeConstants.getHours());
int inMins = 60 * hours;
int left = minutes - inMins;
if (left >= 1)
format = format + left + (left == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes());
return format;
}
if (left >= 1) format =
format +
left +
(left == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes());
return format;
}
}
@@ -5,54 +5,59 @@ import me.codedred.playtimes.time.Timings;
public class NoSeconds implements Timings {
@Override
public String buildFormat(long time) {
if (time < 60)
return formatSeconds(time);
@Override
public String buildFormat(long time) {
if (time < 60) return formatSeconds(time);
int minutes = (int) (time / 60);
int minutes = (int) (time / 60);
// Time less than 1 hour
if (minutes < 60)
return formatMinutes(minutes);
// Time less than 1 hour
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);
// Time less than 1 Day
if (minutes < 1440) {
int hours = minutes / 60;
int minutesLeft = minutes % 60;
return formatHours(hours, minutesLeft);
}
private String formatSeconds(long seconds) {
return seconds + (seconds == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond());
}
// Time greater than 1 day
int days = minutes / 1440;
int minutesLeft = minutes % 1440;
return formatDays(days, minutesLeft);
}
private String formatMinutes(int minutes) {
return minutes + (minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes());
}
private String formatSeconds(long seconds) {
return (
seconds +
(seconds == 1 ? TimeConstants.getSeconds() : TimeConstants.getSecond())
);
}
private String formatHours(int hours, int minutesLeft) {
String format = hours + (hours == 1 ? TimeConstants.getHour() : TimeConstants.getHours());
if (minutesLeft >= 1)
format += formatMinutes(minutesLeft);
return format;
}
private String formatMinutes(int minutes) {
return (
minutes +
(minutes == 1 ? TimeConstants.getMinute() : TimeConstants.getMinutes())
);
}
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;
private String formatHours(int hours, int minutesLeft) {
String format =
hours + (hours == 1 ? TimeConstants.getHour() : TimeConstants.getHours());
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;
}
}
@@ -5,9 +5,17 @@ import me.codedred.playtimes.time.Timings;
public class OnlyHours implements Timings {
@Override
public String buildFormat(long time) {
return (Math.round((time/3600.0)*100d))/100d + TimeConstants.getHours();
@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();
}
}
@@ -0,0 +1,30 @@
package me.codedred.playtimes.utils;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ForkJoinPool;
import lombok.Getter;
public class Async {
@Getter
private static final ForkJoinPool worker = new ForkJoinPool(
8,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
(t, e) -> e.printStackTrace(),
false
);
public static CompletableFuture<Void> run(Runnable runnable) {
return CompletableFuture.runAsync(
() -> {
try {
runnable.run();
} catch (Exception exception) {
throw new CompletionException(exception);
}
},
worker
);
}
}
@@ -6,37 +6,52 @@ import org.bukkit.command.CommandSender;
public class ChatUtil {
private static final DataManager DATA_MANAGER = DataManager.getInstance();
private static final boolean IS_NEWER_VERSION = ServerUtils.isNewerVersion();
private static final DataManager DATA_MANAGER = DataManager.getInstance();
private static final boolean IS_NEWER_VERSION = ServerUtils.isNewerVersion();
public enum ChatTypes {
NO_PERMISSION,
PLAYER_NOT_FOUND,
PLAYER_NEVER_PLAYED
}
public enum ChatTypes {
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")));
case PLAYER_NOT_FOUND ->
sender.sendMessage(formatWithPrefix(DATA_MANAGER.getConfig().getString("messages.player-not-found")));
case PLAYER_NEVER_PLAYED ->
sender.sendMessage(formatWithPrefix(DATA_MANAGER.getConfig().getString("messages.player-never-joined")));
}
public static void errno(CommandSender sender, ChatTypes type) {
switch (type) {
case NO_PERMISSION:
sender.sendMessage(
format(DATA_MANAGER.getConfig().getString("messages.noPermission"))
);
break;
case PLAYER_NOT_FOUND:
sender.sendMessage(
formatWithPrefix(
DATA_MANAGER.getConfig().getString("messages.player-not-found")
)
);
break;
case PLAYER_NEVER_PLAYED:
sender.sendMessage(
formatWithPrefix(
DATA_MANAGER.getConfig().getString("messages.player-never-joined")
)
);
break;
}
}
public static String format(String msg) {
msg = ChatColor.translateAlternateColorCodes('&', msg);
if (IS_NEWER_VERSION)
msg = HexUtil.hex(msg);
return msg;
}
public static String format(String msg) {
msg = ChatColor.translateAlternateColorCodes('&', msg);
if (IS_NEWER_VERSION) msg = HexUtil.hex(msg);
return msg;
}
public static String formatWithPrefix(String msg) {
msg = ChatColor.translateAlternateColorCodes('&', DATA_MANAGER.getConfig().getString("prefix") + " " + msg);
if (IS_NEWER_VERSION)
msg = HexUtil.hex(msg);
return msg;
}
public static String formatWithPrefix(String msg) {
msg =
ChatColor.translateAlternateColorCodes(
'&',
DATA_MANAGER.getConfig().getString("prefix") + " " + msg
);
if (IS_NEWER_VERSION) msg = HexUtil.hex(msg);
return msg;
}
}
@@ -5,33 +5,31 @@ import java.util.UUID;
public class CoolDownUtil {
private CoolDownUtil() {
throw new IllegalStateException("Utility Class");
}
private CoolDownUtil() {
throw new IllegalStateException("Utility Class");
}
private static final HashMap<UUID, Long> 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;
remove(uuid);
}
return false;
}
private static final HashMap<UUID, Long> 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;
remove(uuid);
}
return false;
}
}
@@ -4,31 +4,34 @@ import net.md_5.bungee.api.ChatColor;
public class HexUtil {
private HexUtil() {
throw new IllegalStateException("Utility Class");
}
private HexUtil() {
throw new IllegalStateException("Utility Class");
}
public static String hex(String message) {
StringBuilder builder = new StringBuilder();
int index = 0;
public static String hex(String message) {
StringBuilder builder = new StringBuilder();
int length = message.length();
while (index < message.length()) {
int colorCodeStart = message.indexOf("<#", index);
if (colorCodeStart != -1) {
int colorCodeEnd = message.indexOf(">", colorCodeStart + 2);
if (colorCodeEnd != -1) {
builder.append(message, index, colorCodeStart);
String hexCode = message.substring(colorCodeStart + 2, colorCodeEnd);
ChatColor hexColor = ChatColor.of('#' + hexCode);
builder.append(hexColor);
index = colorCodeEnd + 1;
continue;
}
}
builder.append(message.substring(index));
break;
}
for (int i = 0; i < length; i++) {
if (
message.charAt(i) == '<' &&
i + 8 < length &&
message.charAt(i + 1) == '#'
) {
String hexCode = message.substring(i + 2, i + 8);
if (message.charAt(i + 8) == '>' && isValidHexCode(hexCode)) {
builder.append(ChatColor.of('#' + hexCode));
i += 8; // Skip the processed hex code and the '>'
continue;
}
}
builder.append(message.charAt(i));
}
return ChatColor.translateAlternateColorCodes('&', builder.toString());
}
}
return ChatColor.translateAlternateColorCodes('&', builder.toString());
}
private static boolean isValidHexCode(String hexCode) {
return hexCode.matches("^[a-fA-F0-9]{6}$");
}
}
@@ -6,29 +6,29 @@ import org.bukkit.entity.Player;
public class PAPIHolders {
private PAPIHolders() {
throw new IllegalStateException("Utility class");
}
private PAPIHolders() {
throw new IllegalStateException("Utility class");
}
/**
* Translates PAPI holders
*
* @param player the receiver of the message
* @param msg the message that is being checked/translated
* @return the fixed message with updated PAPI holders
*/
public static String getHolders(Player player, String msg) {
return PlaceholderAPI.setPlaceholders(player, msg);
}
/**
* Translates PAPI holders
*
* @param player the receiver of the message
* @param msg the message that is being checked/translated
* @return the fixed message with updated PAPI holders
*/
public static String getHolders(Player player, String msg) {
return PlaceholderAPI.setPlaceholders(player, msg);
}
/**
* Translates PAPI holders for offline players
*
* @param player the receiver of the message
* @param msg the message that is being checked/translated
* @return the fixed message with updated PAPI holders
*/
public static String getHolders(OfflinePlayer player, String msg) {
return PlaceholderAPI.setPlaceholders(player, msg);
}
/**
* Translates PAPI holders for offline players
*
* @param player the receiver of the message
* @param msg the message that is being checked/translated
* @return the fixed message with updated PAPI holders
*/
public static String getHolders(OfflinePlayer player, String msg) {
return PlaceholderAPI.setPlaceholders(player, msg);
}
}
@@ -1,48 +1,80 @@
package me.codedred.playtimes.utils;
import java.lang.reflect.Method;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.lang.reflect.Method;
public class ServerUtils {
private static Boolean isNewerVersion = null;
private static Boolean isNewerVersion = null;
private ServerUtils() {
throw new IllegalStateException("Utility Class");
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;
}
/**
* Checks if the server version is 1.17+
*
* @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();
}
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;
if (mcVersion == null) {
Bukkit
.getLogger()
.warning("PlayTimes failed to extract Minecraft version: " + version);
return false;
}
/**
* Checks if the server version is 1.17+
*
* @return true if so
*/
public static boolean isRisenVersion() {
String[] versionParts = Bukkit.getServer().getVersion().split("\\.");
int majorVersion = Integer.parseInt(versionParts[1]);
int minorVersion = Integer.parseInt(versionParts[2].replaceAll("[^0-9]", ""));
return majorVersion >= 17 && minorVersion >= 0;
}
String[] versionParts = mcVersion.split("\\.");
try {
int minorVersion = versionParts.length >= 2
? Integer.parseInt(versionParts[1])
: 0;
public static boolean hasPAPI() {
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null)
return DataManager.getInstance().getConfig().getBoolean("use-papi-placeholders");
return false;
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;
}
}
+61 -31
View File
@@ -1,27 +1,26 @@
# PlayTimes by CodedRed
# For support please ask here: https://discord.gg/7A9UA2n
# For assistance, please submit inquiries here: https://github.com/CodedRed-Spigot/PlayTimes/issues
# Prefix will show before 'player-not-found' & 'player-never-joined' messages
# The 'prefix' configures the introductory text for 'player-not-found' and 'player-never-joined' notifications.
prefix: "&7[&b&lPlayTimes&7]"
# Placeholders API info
# Only enable this if you are using PAPI holders in PlayTimes
# If you want to use PlayTimes placeholders in other plugins...
# Just download PAPI and it will *automatically* register
# Placeholder API Configuration
# Activate this only if using PlaceholderAPI with PlayTimes.
# To integrate PlayTimes placeholders into other plugins, simply install PlaceholderAPI; integration occurs seamlessly.
use-papi-placeholders: false
# PlayTimes format & message settings
# The name refers to the format. See spigot plugin page for format examples
# PlayTimes Formatting and Message Customization
# 'playtime.name' defines time unit labels. For formatting guidance, refer to our Spigot plugin page.
playtime:
## Built-in Placeholders:
## %player% %time% %timesjoined% %joindate%
## You may also use PAPI Placeholders
## Default Placeholders:
## %player%, %playtime%, %timesjoined%, %joindate%, %rawTime%, %afkTime%
## For database placeholders, visit: https://github.com/CodedRed-Spigot/PlayTimes/wiki/Bungee-Support
## PlaceholderAPI placeholders are also compatible.
message:
- "&b&m=======&b&l[%player%]&b&m======="
- "&aPlaytime:&f %time%"
- "&aTimes joined:&f %timesjoined%"
- "&aJoined Date:&f %joindate%"
- "&aPlaytime:&f %playtime%"
- "&aTimes Joined:&f %timesjoined%"
- "&aJoin Date:&f %joindate%"
- "&b&m============================"
name:
second: "s"
@@ -33,40 +32,71 @@ playtime:
hours: "hrs "
days: "days "
## only-hours - when true it will display playtime in hours (When true this will also disable show-seconds/days setting)
## show-seconds - when true it will display seconds
## show-days - when true it will display days
## 'only-hours' - Displays playtime solely in hours. Activating this overrides 'show-seconds' and 'show-days'.
## 'show-seconds' - Includes seconds in the display.
## 'show-days' - Includes days in the display.
only-hours: false
show-seconds: true
show-days: true
## 'round-numbers' - Rounds off the playtime to whole numbers when 'only-hours' is true.
round-numbers: true
# /Uptime command info - only placeholder is %serveruptime%
# AFK Tracker Configuration
# NOTE: When enabled, this will remove afk time from %playtime% placeholder
afk-settings:
enabled: true
## Time in minutes after which a player is considered AFK.
threshold: 5
## Notifications settings for AFK status changes.
broadcast-afk:
on-enter-afk: true
on-exit-afk: true
# Message to send to the player when they are marked as AFK.
on-enter-afk-message: "&eYou are now marked as AFK."
# Message to send to the player when they are no longer marked as AFK.
on-exit-afk-message: "&eYou are no longer AFK."
# Leave true unless you are experiencing lag/issues with one of the afk listeners.
cancel-afk:
on-player-move: true
on-player-chat: true
on-player-interact: true
# /Uptime Command Configuration - Use '%serveruptime%' to display server uptime.
uptime:
message:
- "&b&m============================"
- "&aServer Uptime:&f %serveruptime%"
- "&b&m============================"
# /toppt command info
## Built-in Placeholders:
## %player% %place% %time% %joindate%
## You may also use PAPI placeholders
# /toppt Command Settings
## Standard Placeholders:
## %player%, %place%, %time%, %joindate%
## PlaceholderAPI placeholders are also supported.
top-playtime:
header: "&b******&9[&3&lPlayTime Leaderboards&9]&b*****"
content: "&5%place%) &9&l%player% &9&o- %time%"
footer: "&b****************************************"
enable-cooldown: true
cooldown-seconds: 60
track-rawtime: false
# date-format can be changed to your liking.
# For more placeholders look here: https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html
# This is the join date format.
# 'date-format' - Customize this to change the format of join dates.
# Explore more formatting options at: https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html
date-format: "MM/dd/yyyy"
# Built in messages, may change to your liking :D
# Customizable Built-In Messages
messages:
noPermission: "&cYou cannot run this command."
player-not-found: "&cPlayer not found."
player-never-joined: "&cPlayer has never joined the server!"
cooldown: "&cYou cannot use this command for %timeleft% seconds!"
player-not-found: "&cUnable to locate player."
player-never-joined: "&cThis player has not joined the server."
cooldown: "&cCommand unavailable for %timeleft% seconds!"
# Do not change this unless you know what you're doing!
# This setting is for servers experiencing issues when looking up offline players.
# For more information, see the link below.
uuid-lookups:
what-does-this-do: https://github.com/CodedRed-Spigot/PlayTimes/wiki/UUID-LOOKUPS
type: "default"
+1
View File
@@ -0,0 +1 @@
# DO NOT ALTER THIS FILE.
+30
View File
@@ -0,0 +1,30 @@
database-settings:
enabled: false
type: "mysql" # change to 'sqlite' for SQLite
database: "playtimes"
host: "localhost"
port: 3306
user: "root"
password: "password"
useSSL: false
# IMPORTANT:
# 'serverId' must be UNIQUE to each server!
# If you have multiple servers running PlayTimes, ensure each config has a unique 'serverId'.
# The 'serverId' will also be used for placeholders, like %playtime_serverId% (e.g., %playtime_survival%).
# The placeholder will return the playtime on that specific server.
serverId: "survival"
# If enabled, upon starting your server it will purge all user info that is older than x months.
# NOTE: This will only purge data for the specific 'serverId', not for all servers!
purge-database:
enabled: false
months: 12
# Placeholders:
## For use in Plugin:
# %rawtime_serverId%
# %afktime_serverId%
# %playtime_serverId%
## For PAPI:
# %PlayTimes_rawtime_serverId%
# %PlayTimes_afktime_serverId%
# %PlayTimes_playtime_serverId%
+89
View File
@@ -0,0 +1,89 @@
# PlayTimes by CodedRed
# For assistance, please submit inquiries here: https://github.com/CodedRed-Spigot/PlayTimes/issues
# The 'prefix' configures the introductory text for 'player-not-found' and 'player-never-joined' notifications.
prefix: "&7[&b&lPlayTimes&7]"
# Placeholder API Configuration
# Activate this only if using PlaceholderAPI with PlayTimes.
# To integrate PlayTimes placeholders into other plugins, simply install PlaceholderAPI; integration occurs seamlessly.
use-papi-placeholders: false
# PlayTimes Formatting and Message Customization
# 'playtime.name' defines time unit labels. For formatting guidance, refer to our Spigot plugin page.
playtime:
## Default Placeholders:
## %player%, %playtime%, %timesjoined%, %joindate%, %rawTime%, %afkTime%
## For database placeholders, visit: https://github.com/CodedRed-Spigot/PlayTimes/wiki/Bungee-Support
## PlaceholderAPI placeholders are also compatible.
message:
- "&b&m=======&b&l[%player%]&b&m======="
- "&aPlaytime:&f %playtime%"
- "&aTimes Joined:&f %timesjoined%"
- "&aJoin Date:&f %joindate%"
- "&b&m============================"
name:
second: "s"
minute: "min "
hour: "hr "
day: "day "
seconds: "s"
minutes: "mins "
hours: "hrs "
days: "days "
## 'only-hours' - Displays playtime solely in hours. Activating this overrides 'show-seconds' and 'show-days'.
## 'show-seconds' - Includes seconds in the display.
## 'show-days' - Includes days in the display.
only-hours: false
show-seconds: true
show-days: true
## 'round-numbers' - Rounds off the playtime to whole numbers when 'only-hours' is true.
round-numbers: true
# AFK Tracker Configuration
# NOTE: When enabled, this will remove afk time from %time% placeholder
afk-settings:
enabled: true
## Time in minutes after which a player is considered AFK.
threshold: 2
## Notifications settings for AFK status changes.
notify-on-afk:
onAFK: true
onBackFromAFK: true
# Message to send to the player when they are marked as AFK.
afk-message: "&eYou are now marked as AFK."
# Message to send to the player when they are no longer marked as AFK.
back-from-afk-message: "&eYou are no longer AFK."
# /Uptime Command Configuration - Use '%serveruptime%' to display server uptime.
uptime:
message:
- "&b&m============================"
- "&aServer Uptime:&f %serveruptime%"
- "&b&m============================"
# /toppt Command Settings
## Standard Placeholders:
## %player%, %place%, %time%, %joindate%
## PlaceholderAPI placeholders are also supported.
top-playtime:
header: "&b******&9[&3&lPlayTime Leaderboards&9]&b*****"
content: "&5%place%) &9&l%player% &9&o- %time%"
footer: "&b****************************************"
enable-cooldown: true
cooldown-seconds: 60
# 'date-format' - Customize this to change the format of join dates.
# Explore more formatting options at: https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html
date-format: "MM/dd/yyyy"
# Customizable Built-In Messages
messages:
noPermission: "&cYou cannot run this command."
player-not-found: "&cUnable to locate player."
player-never-joined: "&cThis player has not joined the server."
cooldown: "&cCommand unavailable for %timeleft% seconds!"
+9 -12
View File
@@ -1,24 +1,21 @@
name: Playtimes
version: "${version}"
main: me.codedred.playtimes.PlayTimes
# Keeping 'Cmaaxx' as author for Metric purposes.
author: Cmaaxx
authors: [CodedRed]
version: 1.5
softdepend: [PlaceholderAPI]
description: Check Players PlayTime And More!
commands:
playtime:
description: Check your playtime!
usage: /playtime
permission: pt.use
aliases: [playt, playerinfo, pt]
description: "Check your playtime!"
usage: "/playtime"
uptime:
description: Check your servers uptime!
usage: /uptime
permission: pt.uptime
aliases: [serveruptime, serverupt]
description: "Check your servers uptime!"
usage: "/uptime"
topplaytime:
description: Check your servers uptime!
usage: /topplaytime
permission: pt.top
aliases: [topplayt, toppt]
aliases: [topplayt, toppt]
description: "Check your servers uptime!"
usage: "/topplaytime"