89 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
Christopher 3435ac4fdd Update README.md 2023-07-11 16:20:22 -05:00
64 changed files with 4592 additions and 2304 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
+7 -1
View File
@@ -22,7 +22,13 @@
## About The Project
PlayTimes is your ideal plugin for gamified competition! It enables players to compete for the most time played, with leaderboards for showing off achievements. Beyond player time, you can also monitor server uptime. Future plans include introducing PlayTime Rewards, Ranks, and Shop! Start using PlayTimes today to add an exciting dimension to your server.
Introducing PlayTimes, the ultimate plugin for your server! Elevate the gaming experience by allowing players to showcase their skills and compete for top positions on the prestigious playtime leaderboards. Not only can you effortlessly monitor the playtime of online and offline players, but you can also keep track of the server's uptime for maximum efficiency. But that's not all! Brace yourself for an array of exciting features that are on the horizon, including PlayTime Rewards, PlayTime Ranks, and the immersive PlayTime Shop. Get ready to take your server to the next level with PlayTimes!
<img src="https://imgur.com/KebViUJ.gif"> <img src="https://imgur.com/v6ErIo9.gif">
<img src="https://imgur.com/VGZ0iuV.gif"> <img src="https://imgur.com/f4A5LRY.gif">
<img src="https://imgur.com/kOy8YSx.gif"> <img src="https://imgur.com/gNYsU00.gif">
## Usage
+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"