36 Commits
Author SHA1 Message Date
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
38 changed files with 1434 additions and 713 deletions

No files matched your search

+3 -1
View File
@@ -5,6 +5,9 @@
*.ipr
*.iws
# Hide my todo txt file
/src/main/resources/todo.txt
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
@@ -119,7 +122,6 @@ run/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# VS Code directories
.vscode/
+9 -22
View File
@@ -2,38 +2,26 @@ plugins {
id 'java'
}
group = 'dev.codedred'
version = '1.5.4'
version = '1.6.2'
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.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
@@ -41,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 {
@@ -56,4 +43,4 @@ processResources {
filesMatching('plugin.yml') {
expand props
}
}
}
@@ -2,6 +2,7 @@ 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;
@@ -32,7 +33,7 @@ public class Expansions extends PlaceholderExpansion {
@Override
public @NotNull String getVersion() {
return "1.5.4";
return "1.6.1";
}
@Override
@@ -56,29 +57,44 @@ public class Expansions extends PlaceholderExpansion {
.getInstance()
.getPlayerStat(player.getUniqueId(), StatisticType.TIMES_JOINED)
);
// New cases for %PlayTimes_db_serverId% and %PlayTimes_total%
case "total":
return getTotalPlaytime(player);
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("db_")) {
return getServerSpecificPlaytime(player, identifier.substring(3));
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 getTotalPlaytime(OfflinePlayer player) {
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled")
) {
private String getServerSpecificRawtime(
OfflinePlayer player,
String serverId
) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
Long totalPlaytime = DatabaseManager
.getInstance()
.getTotalPlayTime(player.getUniqueId());
return timeManager.buildFormat(totalPlaytime != null ? totalPlaytime : 0);
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";
}
@@ -87,22 +103,89 @@ public class Expansions extends PlaceholderExpansion {
OfflinePlayer player,
String serverId
) {
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled")
) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
Long playtime = DatabaseManager
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 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";
}
private String getGlobalPlaytime(OfflinePlayer player) {
if (DataManager.getInstance().hasDatabase()) {
TimeManager timeManager = TimeManager.getInstance();
Long globalPlaytime = DatabaseManager
.getInstance()
.getPlayTimeForServer(player.getUniqueId(), serverId);
return timeManager.buildFormat(playtime != null ? playtime : 0);
.getTotalPlaytime(player.getUniqueId());
return timeManager.buildFormat(
globalPlaytime != null ? globalPlaytime : 0
);
}
return "N/A";
}
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 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";
}
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
);
}
private String getRawtime(OfflinePlayer player) {
StatManager stats = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
return timeManager.buildFormat(
@@ -110,6 +193,12 @@ public class Expansions extends PlaceholderExpansion {
);
}
private String getAfktime(OfflinePlayer player) {
TimeManager timeManager = TimeManager.getInstance();
long afkTime = AFKManager.getInstance().getAFKTime(player.getUniqueId());
return timeManager.buildFormat(afkTime);
}
private String handleLeaderboardIdentifier(String identifier) {
if (identifier.contains("topname")) {
return getLeaderboardPlayerName(identifier);
@@ -3,6 +3,11 @@ 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;
@@ -16,6 +21,7 @@ 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;
@@ -24,8 +30,6 @@ public class PlayTimes extends JavaPlugin {
@Override
public void onEnable() {
checkForUpdate();
DataManager dataManager = DataManager.getInstance();
dataManager.reloadAll();
ServerManager.getInstance().register();
StatManager.getInstance().registerStatistics();
@@ -44,18 +48,44 @@ public class PlayTimes extends JavaPlugin {
@SuppressWarnings("unused")
Metrics metrics = new Metrics(this, 5289);
AFKManager.getInstance().startAFKChecker();
getLogger().info("Successfully loaded.");
}
@Override
public void onDisable() {
AFKManager.getInstance().endAFKChecker();
getLogger().info("PlayTimes shutting down");
}
private void registerEvents() {
PluginManager pm = getServer().getPluginManager();
DataManager data = DataManager.getInstance();
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() {
@@ -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());
}
}
@@ -6,8 +6,7 @@ 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;
@@ -15,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;
@@ -38,16 +38,38 @@ public class Time implements CommandExecutor {
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);
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;
}
@@ -58,6 +80,9 @@ public class Time implements CommandExecutor {
}
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"
@@ -77,14 +102,11 @@ public class Time implements CommandExecutor {
)
);
sender.sendMessage(
ChatUtil.format("&e/pt debug: &7Displays the debug info")
);
sender.sendMessage(
ChatUtil.format("&e/pt <player>: &7Displays the play time of <player>")
ChatUtil.format("&e/pt version: &7Displays the version info")
);
}
private void handleDebugCommand(CommandSender sender) {
private void handleVersionCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
@@ -92,7 +114,7 @@ public class Time implements CommandExecutor {
boolean isOnline = ServerManager.getInstance().isOnline();
String serverStatus = isOnline ? "Online" : "Offline";
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Debug ***"));
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Version Info ***"));
sender.sendMessage(
ChatColor.GOLD +
"Server version: " +
@@ -103,13 +125,25 @@ public class Time implements CommandExecutor {
ChatColor.WHITE +
JavaPlugin.getPlugin(PlayTimes.class).getDescription().getVersion() +
ChatColor.GOLD +
"\nStatManager: " +
"\nStat Manager: " +
ChatColor.WHITE +
StatManager.getInstance().name +
ChatColor.GOLD +
"\nServerStatus: " +
"\nServer Status: " +
ChatColor.WHITE +
serverStatus
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()
);
}
@@ -121,8 +155,20 @@ public class Time implements CommandExecutor {
);
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) {
@@ -134,6 +180,27 @@ public class Time implements CommandExecutor {
TimeConstants.reload();
TimeManager.getInstance().registerTimings();
sender.sendMessage(ChatUtil.format("&aPlayTimes Configurations Reloaded!"));
if (DataManager.getInstance().hasDatabase()) {
sender.sendMessage(
ChatUtil.format("&aTrying to reload the Database settings?")
);
sender.sendMessage(ChatUtil.format(" &e&oTry, /pt reloadDatabase"));
}
}
private void handleReloadDatabaseCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
data.reloadDatabase();
PlayTimes.getPlugin(PlayTimes.class).loadDatabase();
sender.sendMessage(ChatUtil.format("&aDatabase Reloaded."));
sender.sendMessage(
ChatUtil.format(
"&oIf you are still encountering issues try restarting your server."
)
);
}
private void handleTopCommand(CommandSender sender) {
@@ -232,15 +299,11 @@ public class Time implements CommandExecutor {
Objects.requireNonNull(Bukkit.getPlayer(args[0])).getUniqueId();
}
org.bukkit.OfflinePlayer offlinePlayer = Bukkit
OfflinePlayer offlinePlayer = Bukkit
.getServer()
.getOfflinePlayer(target);
OfflinePlayer player = new OfflinePlayer(
sender,
target,
offlinePlayer.getName()
);
player.sendMessageToTarget();
Message message = new Message(sender, target, offlinePlayer.getName());
message.sendMessageToTarget();
}
}
.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class));
@@ -29,17 +29,14 @@ public class TopTime implements CommandExecutor {
return true;
}
if (!(sender instanceof Player player)) {
sender.sendMessage("Must be a player to run this command.");
return true;
}
DataManager data = DataManager.getInstance();
if (
data.getConfig().getBoolean("top-playtime.enable-cooldown") &&
!player.hasPermission("pt.block-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"))
@@ -78,8 +75,8 @@ public class TopTime implements CommandExecutor {
String content = data.getConfig().getString("top-playtime.content");
if (ServerUtils.hasPAPI()) {
header = PAPIHolders.getHolders(player, header);
footer = PAPIHolders.getHolders(player, footer);
header = PAPIHolders.getHolders((Player) sender, header);
footer = PAPIHolders.getHolders((Player) sender, footer);
}
sender.sendMessage(header);
@@ -42,7 +42,8 @@ public class Uptime implements CommandExecutor {
.getStringList("uptime.message");
for (String message : messages) {
if (ServerUtils.hasPAPI() && sender instanceof Player player) {
if (ServerUtils.hasPAPI() && sender instanceof Player) {
Player player = (Player) sender;
message = PlaceholderAPI.setPlaceholders(player, message);
}
@@ -7,6 +7,7 @@ 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;
public class TimeTabCompleter implements TabCompleter {
@@ -23,7 +24,7 @@ public class TimeTabCompleter implements TabCompleter {
if (args.length == 1) {
if (sender.hasPermission("pt.use")) {
completions.add("top");
completions.addAll(getOnlinePlayerNames());
completions.addAll(getOnlinePlayerNames(sender));
}
if (sender.hasPermission("pt.block")) {
completions.add("block");
@@ -31,7 +32,7 @@ public class TimeTabCompleter implements TabCompleter {
}
if (sender.hasPermission("pt.reload")) {
completions.add("reload");
completions.add("debug");
completions.add("version");
completions.add("help");
}
}
@@ -39,11 +40,24 @@ public class TimeTabCompleter implements TabCompleter {
return completions;
}
private List<String> getOnlinePlayerNames() {
private List<String> getOnlinePlayerNames(CommandSender sender) {
List<String> playerNames = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
playerNames.add(player.getName());
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;
}
}
@@ -4,6 +4,8 @@ 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;
@@ -23,11 +25,14 @@ public class CustomConfig {
}
public void reloadConfig() {
if (this.dataConfigFile == null) this.dataConfigFile =
new File(this.plugin.getDataFolder(), name);
if (this.dataConfigFile == null) {
this.dataConfigFile = new File(this.plugin.getDataFolder(), name);
}
this.dataConfig = YamlConfiguration.loadConfiguration(this.dataConfigFile);
if (name == "config.yml") checkAndAddDefaults();
InputStream defConfigStream = this.plugin.getResource(name);
if (defConfigStream != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
@@ -37,6 +42,113 @@ public class CustomConfig {
}
}
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;
@@ -57,8 +169,11 @@ public class CustomConfig {
}
public void saveDefaultConfig() {
if (this.dataConfigFile == null) this.dataConfigFile =
new File(this.plugin.getDataFolder(), name);
if (!this.dataConfigFile.exists()) this.plugin.saveResource(name, false);
if (this.dataConfigFile == null) {
this.dataConfigFile = new File(this.plugin.getDataFolder(), name);
}
if (!this.dataConfigFile.exists()) {
this.plugin.saveResource(name, false);
}
}
}
@@ -1,6 +1,9 @@
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;
@@ -44,5 +47,22 @@ public class DataManager {
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);
}
}
@@ -6,11 +6,14 @@ 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
@@ -20,10 +23,17 @@ public class DatabaseManager {
private DataSource dataSource;
private UsersTable usersTable;
private boolean connected = false;
private final String serverId;
private Map<UUID, Map<String, Long>> userPlaytimes = new HashMap<>();
private Map<UUID, Map<String, Map<String, Long>>> userPlaytimes = new HashMap<>();
private DatabaseManager() {
serverId =
DataManager
.getInstance()
.getDBConfig()
.getString("database-settings.serverId");
setupDataSource();
}
@@ -54,17 +64,30 @@ public class DatabaseManager {
". 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() {
try {
if (dataSource.getConnection() != null) {
this.usersTable = new UsersTable(dataSource);
this.usersTable.createTable();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
if (isConnected()) {
this.usersTable = new UsersTable(dataSource);
this.usersTable.createTable();
} else {
Bukkit
.getServer()
.getLogger()
.warning("[PlayTimes] Couldn't connect to Database!");
}
}
@@ -75,49 +98,115 @@ public class DatabaseManager {
return this.usersTable;
}
// Called onPlayerJoin
public void retrievePlayTime(UUID uuid) {
Map<String, Long> timeMap = getUsersTable().getPlaytimesByUuid(uuid);
userPlaytimes.put(uuid, timeMap);
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;
}
// Called onPlayerQuit
public void updatePlaytime(UUID uuid, Long playtime) {
public void updatePlaytime(UUID uuid, Long playtime, Long afktime) {
getUsersTable()
.insertOrUpdate(
uuid.toString(),
DataManager
.getInstance()
.getDBConfig()
.getString("database-settings.serverId"),
playtime
);
userPlaytimes.remove(uuid);
.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);
}
// Given the serverId returns the playtime from that server
public Long getPlayTimeForServer(UUID uuid, String server) {
if (
public boolean hasTimeForServer(UUID uuid, String server) {
if (!userPlaytimes.containsKey(uuid)) {
return retrievePlaytime(uuid);
}
return (
userPlaytimes.containsKey(uuid) &&
userPlaytimes.get(uuid).containsKey(server)
) {
return userPlaytimes.get(uuid).get(server);
}
return null;
);
}
// %PlayTimes_total%
public Long getTotalPlayTime(UUID uuid) {
Long playtime = 0L;
Map<String, Long> userPlaytimeMap = userPlaytimes.get(uuid);
public boolean hasTimeForServer(UUID uuid) {
return (hasTimeForServer(uuid, serverId));
}
if (userPlaytimeMap != null) {
for (Long individualPlaytime : userPlaytimeMap.values()) {
playtime += individualPlaytime;
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);
}
}
}
return playtime;
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() {
@@ -125,10 +214,6 @@ public class DatabaseManager {
.getLogger()
.info("[PlayTimes] Initiating purge of outdated playtime data...");
String serverId = DataManager
.getInstance()
.getDBConfig()
.getString("database-settings.serverId");
int months = DataManager
.getInstance()
.getDBConfig()
@@ -27,6 +27,7 @@ public class UsersTable {
"`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
@@ -44,11 +45,16 @@ public class UsersTable {
}
}
public void insertOrUpdate(String uuid, String serverId, long playtime) {
public void insertOrUpdate(
String uuid,
String serverId,
long playtime,
long afktime
) {
Async.run(() -> {
String query = String.format(
"INSERT INTO `%s` (`uniqueId`, `serverId`, `playtime`) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE `playtime` = ?",
"INSERT INTO `%s` (`uniqueId`, `serverId`, `playtime`, `afktime`) VALUES (?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE `playtime` = ?, `afktime` = ?",
TABLE_NAME
);
@@ -59,7 +65,9 @@ public class UsersTable {
preparedStatement.setString(1, uuid);
preparedStatement.setString(2, serverId);
preparedStatement.setLong(3, playtime);
preparedStatement.setLong(4, playtime);
preparedStatement.setLong(4, afktime);
preparedStatement.setLong(5, playtime);
preparedStatement.setLong(6, afktime);
preparedStatement.executeUpdate();
} catch (SQLException e) {
@@ -68,8 +76,8 @@ public class UsersTable {
});
}
public Map<String, Long> getPlaytimesByUuid(UUID uuid) {
Map<String, Long> playtimes = new HashMap<>();
public Map<String, Map<String, Long>> getPlaytimesByUuid(UUID uuid) {
Map<String, Map<String, Long>> timesMap = new HashMap<>();
try (
Connection conn = dataSource.getConnection();
@@ -83,14 +91,20 @@ public class UsersTable {
while (resultSet.next()) {
String serverId = resultSet.getString("serverId");
long playtime = resultSet.getLong("playtime");
playtimes.put(serverId, 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 playtimes;
return timesMap;
}
public Map<String, Timestamp> getLastUpdatedTimesByUuid(UUID uuid) {
@@ -2,6 +2,7 @@ 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;
@@ -20,9 +21,9 @@ public class Join implements Listener {
DataManager data = DataManager.getInstance();
// Database
if (data.getDBConfig().getBoolean("database-settings.enabled")) {
if (data.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
dbManager.retrievePlayTime(uuid);
dbManager.retrievePlaytime(uuid);
// hacky bug fix that will update data again if database query was too slow.
// this bug only arises when switching servers super fast!
@@ -31,7 +32,7 @@ public class Join implements Listener {
.runTaskLaterAsynchronously(
PlayTimes.getPlugin(PlayTimes.class),
() -> {
dbManager.retrievePlayTime(uuid);
dbManager.retrievePlaytime(uuid);
},
20L * 30
);
@@ -61,6 +62,16 @@ public class Join implements Listener {
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,6 +1,7 @@
package me.codedred.playtimes.listeners;
import java.util.UUID;
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;
@@ -17,7 +18,7 @@ public class Quit implements Listener {
DataManager data = DataManager.getInstance();
// Database
if (data.getDBConfig().getBoolean("database-settings.enabled")) {
if (data.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
dbManager.updatePlaytime(
uuid,
@@ -27,8 +28,18 @@ public class Quit implements Listener {
event.getPlayer().getUniqueId(),
StatisticType.PLAYTIME
) /
20
20,
AFKManager.getInstance().getAFKTime(event.getPlayer().getUniqueId())
);
} else {
// save afktime in data.yml
data
.getData()
.set(
"afktime." + uuid,
AFKManager.getInstance().getAFKTime(event.getPlayer().getUniqueId())
);
data.saveData();
}
// Leaderboard
@@ -39,8 +50,16 @@ public class Quit implements Listener {
StatManager statManager = StatManager.getInstance();
long playtime = statManager.getPlayerStat(uuid, StatisticType.PLAYTIME);
data.getData().set("leaderboard." + uuid, playtime);
if (
!data.getConfig().getBoolean("top-playtime.track-rawtime", false) &&
data.hasAfkEnabled()
) {
playtime -=
AFKManager.getInstance().getAFKTime(event.getPlayer().getUniqueId()) *
20;
}
data.getData().set("leaderboard." + uuid, playtime);
data.saveData();
}
}
@@ -1,6 +1,7 @@
package me.codedred.playtimes.models;
import java.util.*;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
@@ -63,6 +64,7 @@ public class Leaderboard {
*/
private void updateTimes(Map<String, Integer> topTen) {
StatManager statManager = StatManager.getInstance();
DataManager dataManager = DataManager.getInstance();
List<Map.Entry<String, Integer>> list = new ArrayList<>(topTen.entrySet());
for (Map.Entry<String, Integer> entry : list) {
@@ -71,6 +73,17 @@ public class Leaderboard {
UUID.fromString(uuid),
StatisticType.PLAYTIME
);
if (
!dataManager
.getConfig()
.getBoolean("top-playtime.track-rawtime", false) &&
dataManager.hasAfkEnabled()
) {
latestTime -=
AFKManager.getInstance().getAFKTime(UUID.fromString(uuid)) * 20;
}
topTen.put(uuid, latestTime);
}
@@ -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,193 +0,0 @@
package me.codedred.playtimes.player;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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 OfflinePlayer {
private final CommandSender sender;
private final UUID target;
private final String name;
private List<String> message;
/**
* Constructor for OfflinePlayer.
* Initializes the message for the offline player.
*
* @param sender The command sender.
* @param target The UUID of the offline player.
* @param name The name of the offline player.
*/
public OfflinePlayer(CommandSender sender, UUID target, String name) {
this.sender = sender;
this.target = target;
this.name = name;
this.message = buildMessage();
}
/**
* Builds the message to be sent to the sender about the offline 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() {
DataManager dataManager = DataManager.getInstance();
StatManager statManager = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
List<String> builtMessage = dataManager
.getConfig()
.getStringList("playtime.message");
long rawTime = statManager.getPlayerStat(target, StatisticType.PLAYTIME);
if (ServerUtils.hasPAPI()) {
builtMessage = applyPAPIPlaceholders(builtMessage);
}
return processMessageWithReplacements(
builtMessage,
prepareReplacements(rawTime, statManager, timeManager)
);
}
/**
* 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(target), 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 statManager The StatManager instance to fetch additional statistics.
* @param timeManager The TimeManager instance to format time.
* @return A map of placeholder keys and their replacement values.
*/
private Map<String, String> prepareReplacements(
long rawTime,
StatManager statManager,
TimeManager timeManager
) {
Map<String, String> replacements = new HashMap<>();
replacements.put("%time%", timeManager.buildFormat(rawTime / 20));
replacements.put("%player%", name);
replacements.put(
"%timesjoined%",
Long.toString(
statManager.getPlayerStat(target, StatisticType.TIMES_JOINED)
)
);
replacements.put("%joindate%", statManager.getJoinDate(target));
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled")
) {
replacements.put("%PlayTimes_db_serverId%", "DYNAMIC");
replacements.put(
"%PlayTimes_total%",
timeManager.buildFormat(
DatabaseManager.getInstance().getTotalPlayTime(target)
)
);
}
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 dbEnabled = DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled");
TimeManager timeManager = TimeManager.getInstance();
for (String msg : message) {
for (Map.Entry<String, String> entry : replacements.entrySet()) {
if (entry.getKey().equals("%PlayTimes_db_serverId%") && dbEnabled) {
// Handle dynamic replacement
Pattern pattern = Pattern.compile("%PlayTimes_db_(\\w+)%");
Matcher matcher = pattern.matcher(msg);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String serverId = matcher.group(1);
Long playTime = DatabaseManager
.getInstance()
.getPlayTimeForServer(target, serverId);
matcher.appendReplacement(
sb,
playTime != null
? timeManager.buildFormat(playTime)
: timeManager.buildFormat(0)
);
}
matcher.appendTail(sb);
msg = sb.toString();
} else {
// Replace static placeholders
msg = msg.replace(entry.getKey(), entry.getValue());
}
}
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,200 +0,0 @@
package me.codedred.playtimes.player;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.entity.Player;
public class OnlinePlayer {
private final Player target;
private final List<String> message;
private final DataManager dataManager;
private final StatManager statManager;
private final TimeManager timeManager;
/**
* Constructor for OnlinePlayer.
* Initializes the required managers and builds the message for the player.
*
* @param target The player for whom this OnlinePlayer instance is created.
*/
public OnlinePlayer(Player target) {
this.target = target;
this.dataManager = DataManager.getInstance();
this.statManager = StatManager.getInstance();
this.timeManager = TimeManager.getInstance();
this.message = buildMessage();
}
/**
* Builds the message to be sent to the 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 = new ArrayList<>();
long rawTime = getPlaytimeStat();
builtMessage.addAll(
dataManager.getConfig().getStringList("playtime.message")
);
if (ServerUtils.hasPAPI()) {
builtMessage = applyPAPIPlaceholders(builtMessage);
}
return processMessageWithReplacements(
builtMessage,
prepareReplacements(rawTime)
);
}
/**
* Retrieves the playtime statistic for the player.
*
* @return The raw playtime statistic.
*/
private long getPlaytimeStat() {
return statManager.isLegacy()
? statManager.getPlayerStat(target.getUniqueId(), StatisticType.PLAYTIME)
: statManager
.getStats()
.getOnlineStatistic(target, StatisticType.PLAYTIME);
}
/**
* 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(target, 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.
* @return A map of placeholder keys and their replacement values.
*/
private Map<String, String> prepareReplacements(long rawTime) {
Map<String, String> replacements = new HashMap<>();
replacements.put("%time%", timeManager.buildFormat(rawTime / 20));
replacements.put("%player%", target.getName());
replacements.put(
"%timesjoined%",
Long.toString(
statManager.getPlayerStat(
target.getUniqueId(),
StatisticType.TIMES_JOINED
)
)
);
replacements.put(
"%joindate%",
statManager.getJoinDate(target.getUniqueId())
);
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled")
) {
replacements.put("%PlayTimes_db_serverId%", "DYNAMIC");
replacements.put(
"%PlayTimes_total%",
timeManager.buildFormat(
DatabaseManager.getInstance().getTotalPlayTime(target.getUniqueId())
)
);
}
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 dbEnabled = DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled");
for (String msg : message) {
Matcher matcher = Pattern.compile("%PlayTimes_db_(\\w+)%").matcher(msg);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
if (dbEnabled) {
String serverId = matcher.group(1);
Long playTime = DatabaseManager
.getInstance()
.getPlayTimeForServer(target.getUniqueId(), serverId);
matcher.appendReplacement(
sb,
playTime != null
? timeManager.buildFormat(playTime)
: timeManager.buildFormat(0)
);
}
}
matcher.appendTail(sb);
msg = sb.toString();
// Replace other static placeholders
for (Map.Entry<String, String> entry : replacements.entrySet()) {
msg = msg.replace(entry.getKey(), entry.getValue());
}
newMessage.add(msg);
}
return newMessage;
}
/**
* Sends the formatted message to the target player.
* 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 " + target.getName() + " " + formattedMsg
);
} else {
target.sendMessage(formattedMsg);
}
}
}
}
@@ -9,16 +9,26 @@ public class ServerManager {
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();
if (Bukkit.getOnlineMode()) {
status = new ServerOnline();
} else {
status = new ServerOffline();
}
cleanLeaderboard();
updateLookupType();
}
public ServerStatus getStatus() {
@@ -37,6 +47,37 @@ public class ServerManager {
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;
}
}
private void cleanLeaderboard() {
DataManager data = DataManager.getInstance();
if (!data.getData().contains("leaderboard")) return;
@@ -9,32 +9,70 @@ 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)) {
UUID offlineUUID = null;
Player onlinePlayer = Bukkit.getServer().getPlayer(name);
if (onlinePlayer != null) {
offlineUUID = onlinePlayer.getUniqueId();
}
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;
}
return uuid;
} catch (IOException e) {
return null;
}
}
@@ -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";
}
}
}
}
}
@@ -22,10 +22,12 @@ public class LegacyStats implements Stats {
JsonObject jsonObject = new Gson()
.fromJson(new FileReader(playerStatistics), JsonObject.class);
return switch (type) {
case PLAYTIME -> jsonObject.get("stat.playOneMinute").getAsLong();
case TIMES_JOINED -> jsonObject.get("stat.leaveGame").getAsLong();
};
switch (type) {
case PLAYTIME:
return jsonObject.get("stat.playOneMinute").getAsLong();
case TIMES_JOINED:
return jsonObject.get("stat.leaveGame").getAsLong();
}
} catch (Exception e) {
//e.printStackTrace();
}
@@ -51,6 +53,7 @@ public class LegacyStats implements Stats {
DataManager.getInstance().getConfig().getString("date-format")
);
Calendar calendar = Calendar.getInstance();
if (player == null) {
calendar.setTimeInMillis(Bukkit.getOfflinePlayer(uuid).getFirstPlayed());
return simpleDateFormat.format(calendar.getTime());
@@ -58,6 +61,7 @@ public class LegacyStats implements Stats {
calendar.setTimeInMillis(player.getFirstPlayed());
return simpleDateFormat.format(calendar.getTime());
}
return "Never Joined";
}
}
@@ -22,25 +22,21 @@ public class RisenStats implements Stats {
);
File playerStatistics = new File(worldFolder, uuid + ".json");
if (playerStatistics.exists()) {
try {
JsonObject jsonObject = new Gson()
.fromJson(new FileReader(playerStatistics), JsonObject.class);
JsonObject pilot = (JsonObject) jsonObject.get("stats");
JsonObject passenger = (JsonObject) pilot.get("minecraft:custom");
switch (type) {
case PLAYTIME -> {
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 -> {
case TIMES_JOINED:
return passenger.get("minecraft:leave_game").getAsLong();
}
}
} catch (Exception e) {
//e.printStackTrace();
@@ -51,10 +47,14 @@ public class RisenStats implements Stats {
@Override
public long getOnlineStatistic(Player player, StatisticType type) {
return switch (type) {
case PLAYTIME -> player.getStatistic(Statistic.PLAY_ONE_MINUTE);
case TIMES_JOINED -> player.getStatistic(Statistic.LEAVE_GAME) + 1;
};
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
@@ -70,6 +70,7 @@ public class RisenStats implements Stats {
DataManager.getInstance().getConfig().getString("date-format")
);
Calendar calendar = Calendar.getInstance();
if (player == null) {
calendar.setTimeInMillis(Bukkit.getOfflinePlayer(uuid).getFirstPlayed());
return simpleDateFormat.format(calendar.getTime());
@@ -77,6 +78,7 @@ public class RisenStats implements Stats {
calendar.setTimeInMillis(player.getFirstPlayed());
return simpleDateFormat.format(calendar.getTime());
}
return "Never Joined";
}
}
@@ -20,24 +20,21 @@ public class RisingStats implements Stats {
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");
return switch (type) {
case PLAYTIME -> passenger
.get("minecraft:play_one_minute")
.getAsLong();
case TIMES_JOINED -> passenger
.get("minecraft:leave_game")
.getAsLong();
};
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();
}
@@ -47,10 +44,14 @@ public class RisingStats implements Stats {
@Override
public long getOnlineStatistic(Player player, StatisticType type) {
return switch (type) {
case PLAYTIME -> player.getStatistic(Statistic.PLAY_ONE_MINUTE);
case TIMES_JOINED -> player.getStatistic(Statistic.LEAVE_GAME) + 1;
};
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
@@ -66,6 +67,7 @@ public class RisingStats implements Stats {
DataManager.getInstance().getConfig().getString("date-format")
);
Calendar calendar = Calendar.getInstance();
if (player == null) {
calendar.setTimeInMillis(Bukkit.getOfflinePlayer(uuid).getFirstPlayed());
return simpleDateFormat.format(calendar.getTime());
@@ -73,6 +75,7 @@ public class RisingStats implements Stats {
calendar.setTimeInMillis(player.getFirstPlayed());
return simpleDateFormat.format(calendar.getTime());
}
return "Never Joined";
}
}
@@ -42,7 +42,7 @@ public class StatManager {
}
public boolean hasJoinedBefore(UUID uuid) {
return !getStats().hasJoinedBefore(uuid);
return getStats().hasJoinedBefore(uuid);
}
public String getJoinDate(UUID uuid) {
@@ -1,31 +1,28 @@
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;
private static boolean rounded;
static {
reload();
}
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");
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() {
@@ -59,4 +56,8 @@ public class TimeConstants {
public static String getDays() {
return days;
}
public static boolean getRounded() {
return rounded;
}
}
@@ -7,8 +7,15 @@ public class OnlyHours implements Timings {
@Override
public String buildFormat(long time) {
return (
(Math.round((time / 3600.0) * 100d)) / 100d + TimeConstants.getHours()
);
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();
}
}
@@ -17,19 +17,25 @@ public class ChatUtil {
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")
)
);
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;
}
}
+60 -42
View File
@@ -1,27 +1,26 @@
# PlayTimes by CodedRed
# For support please ask here: https://github.com/CodedRed-Spigot/PlayTimes/issues
# 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%
## Database placeholders info found here: https://github.com/CodedRed-Spigot/PlayTimes/wiki/Bungee-Support
## 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,52 +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!"
# TODO: Decide if I will be added this or not:
# This is an optional command for you to allow you to display more information.
# permission: pt.more & pt.more.others
playtime-more-command:
enabled: false
command: "more" # the actual command usage: /pt more
message:
- "&b&m=======&b&l[%player%'s Info']&b&m======="
- "&aCreative PlayTime:&f %PlayTimes_db_creative%"
- "&aSurvival PlayTime:&f %PlayTimes_db_survival%"
- "&2Total:&f %PlayTimes_total%"
- "&b&m============================"
# 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.
+10 -1
View File
@@ -10,7 +10,7 @@ database-settings:
# 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 %PlayTimes_db_serverId% (e.g., %PlayTimes_db_survival%).
# 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"
@@ -19,3 +19,12 @@ database-settings:
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!"
+10 -12
View File
@@ -1,24 +1,22 @@
name: Playtimes
version: "${version}"
main: me.codedred.playtimes.PlayTimes
# Keeping 'Cmaaxx' as author for Metric purposes.
api-version: 1.19
author: Cmaaxx
authors: [CodedRed]
version: '${version}'
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"