3 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
5 changed files with 209 additions and 54 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.
@@ -31,9 +31,13 @@ public class UpdateChecker {
connection.setReadTimeout(5000);
connection.setUseCaches(true);
latestVersion =
new BufferedReader(new InputStreamReader(connection.getInputStream()))
.readLine();
try (
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream())
)
) {
latestVersion = reader.readLine();
}
if (latestVersion == null || latestVersion.isEmpty()) {
throw new IOException("Failed to retrieve latest version.");
@@ -1,8 +1,11 @@
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;
@@ -13,6 +16,7 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
public class TopTime implements CommandExecutor {
@@ -54,37 +58,69 @@ public class TopTime implements CommandExecutor {
);
}
Leaderboard board = new Leaderboard();
Map<String, Integer> map = board.getTopTen();
generateAndSendLeaderboard(sender);
return true;
}
if (map.isEmpty()) {
sender.sendMessage(
ChatUtil.format("&cRejoin the server to fill the leaderboard!")
);
return true;
}
private void generateAndSendLeaderboard(CommandSender sender) {
CompletableFuture.supplyAsync(() -> {
Leaderboard board = new Leaderboard();
Map<String, Integer> map = board.getTopTen();
if (map.isEmpty()) {
return null;
}
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;
}
for (String message : messages) {
sender.sendMessage(message);
}
});
}).exceptionally(throwable -> {
throwable.printStackTrace();
Bukkit.getScheduler().runTask(getPlugin(), () -> {
sender.sendMessage(ChatUtil.format("&cError generating leaderboard!"));
});
return null;
});
}
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();
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");
String contentTemplate = data.getConfig().getString("top-playtime.content");
if (ServerUtils.hasPAPI()) {
if (ServerUtils.hasPAPI() && sender instanceof Player) {
header = PAPIHolders.getHolders((Player) sender, header);
footer = PAPIHolders.getHolders((Player) sender, footer);
}
sender.sendMessage(header);
messages.add(header);
for (int i = 0; i < map.size(); i++) {
UUID uuid = UUID.fromString(map.keySet().toArray()[i].toString());
int place = 1;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
UUID uuid = UUID.fromString(entry.getKey());
org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
String content = contentTemplate;
if (ServerUtils.hasPAPI()) {
content = PAPIHolders.getHolders(offlinePlayer, content);
}
@@ -96,10 +132,7 @@ public class TopTime implements CommandExecutor {
String offlinePlayerName = offlinePlayer.getName() != null
? offlinePlayer.getName()
: defaultPlayerName;
String place = String.valueOf(i + 1);
String time = timeManager.buildFormat(
map.get(offlinePlayer.getUniqueId().toString()) / 20
);
String time = timeManager.buildFormat(entry.getValue() / 20);
String joinDate = statManager.getJoinDate(uuid) != null
? statManager.getJoinDate(uuid)
: defaultJoinDate;
@@ -110,16 +143,19 @@ public class TopTime implements CommandExecutor {
String formattedContent = content
.replace("%player%", offlinePlayerName)
.replace("%place%", place)
.replace("%place%", String.valueOf(place))
.replace("%time%", time)
.replace("%joindate%", joinDate);
sender.sendMessage(ChatUtil.format(formattedContent));
content = data.getConfig().getString("top-playtime.content");
messages.add(ChatUtil.format(formattedContent));
place++;
}
sender.sendMessage(footer);
return true;
messages.add(footer);
return messages;
}
private Plugin getPlugin() {
return Bukkit.getPluginManager().getPlugin("PlayTimes");
}
}
@@ -1,6 +1,7 @@
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;
@@ -9,6 +10,10 @@ import me.codedred.playtimes.statistics.StatisticType;
public class 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
@@ -16,23 +21,50 @@ public class Leaderboard {
* @return listing of top 10 players
*/
public Map<String, Integer> getTopTen() {
Map<String, Integer> topTen = new LinkedHashMap<>();
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 topTen;
return allUsers;
}
for (String key : data
.getData()
.getConfigurationSection(LEADERBOARD)
.getKeys(false)) {
topTen.put(
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());
@@ -41,33 +73,32 @@ public class Leaderboard {
data.getData().set(LEADERBOARD + "." + key.getKey(), null);
}
data.saveData();
list = list.subList(0, 20);
}
if (list.size() > 9) {
list = list.subList(0, 10);
}
topTen.clear();
for (Map.Entry<String, Integer> entry : list) {
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());
}
updateTimes(topTen);
return topTen;
}
/**
* Updates the top ten times by querying the latest times from the StatManager and sorting the list.
* Updates all users' times by querying the latest times from the StatManager.
*
* @param topTen the current leaderboard, with UUIDs and their corresponding time
* @param allUsers all users with their cached playtime data
* @return updated map with current playtime data
*/
private void updateTimes(Map<String, Integer> topTen) {
private Map<String, Integer> updateAllUserTimes(Map<String, Integer> allUsers) {
StatManager statManager = StatManager.getInstance();
DataManager dataManager = DataManager.getInstance();
List<Map.Entry<String, Integer>> list = new ArrayList<>(topTen.entrySet());
Map<String, Integer> updatedUsers = new LinkedHashMap<>();
for (Map.Entry<String, Integer> entry : list) {
for (Map.Entry<String, Integer> entry : allUsers.entrySet()) {
String uuid = entry.getKey();
int latestTime = (int) statManager.getPlayerStat(
UUID.fromString(uuid),
@@ -84,17 +115,9 @@ public class Leaderboard {
AFKManager.getInstance().getAFKTime(UUID.fromString(uuid)) * 20;
}
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;
}
}