Improve messages & logs
This commit is contained in:
@@ -3,93 +3,219 @@ package dev.tatsi.reloadmc.smp.command;
|
||||
import dev.tatsi.reloadmc.smp.manager.DeathCounterManager;
|
||||
import dev.tatsi.reloadmc.smp.model.DeathRecord;
|
||||
import dev.tatsi.reloadmc.smp.model.PlayerDeathData;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DeathStatsCommand implements CommandExecutor {
|
||||
private final DeathCounterManager deathCounterManager;
|
||||
private static final int PAGE_SIZE = 3;
|
||||
|
||||
public DeathStatsCommand(DeathCounterManager deathCounterManager) {
|
||||
this.deathCounterManager = deathCounterManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
public boolean onCommand(@NotNull CommandSender sender, Command command, String label, String[] args) {
|
||||
|
||||
// Parse args: self vs target; optional page
|
||||
Player self = (sender instanceof Player p) ? p : null;
|
||||
|
||||
String targetName;
|
||||
UUID targetUuid = null;
|
||||
int page = 1;
|
||||
|
||||
if (args.length == 0) {
|
||||
// Show own stats if player, or usage if console
|
||||
if (sender instanceof Player) {
|
||||
showPlayerStats(sender, (Player) sender);
|
||||
} else {
|
||||
sender.sendMessage("§cUsage: /deathstats <player>");
|
||||
if (self == null) {
|
||||
sender.sendMessage(Component.text("Usage: /deathstats <player> [page]", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
targetName = self.getName();
|
||||
targetUuid = self.getUniqueId();
|
||||
} else if (args.length == 1) {
|
||||
// One arg: if numeric and sender is player -> page for self; else treat as player name
|
||||
if (isPositiveInt(args[0]) && self != null) {
|
||||
page = Integer.parseInt(args[0]);
|
||||
targetName = self.getName();
|
||||
targetUuid = self.getUniqueId();
|
||||
} else {
|
||||
targetName = args[0];
|
||||
Player online = Bukkit.getPlayerExact(targetName);
|
||||
if (online != null) {
|
||||
targetUuid = online.getUniqueId();
|
||||
targetName = online.getName(); // normalize case
|
||||
} else {
|
||||
// search offline data
|
||||
targetUuid = findUuidByNameInsensitive(targetName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
targetName = args[0];
|
||||
if (isPositiveInt(args[1])) page = Integer.parseInt(args[1]);
|
||||
Player online = Bukkit.getPlayerExact(targetName);
|
||||
if (online != null) {
|
||||
targetUuid = online.getUniqueId();
|
||||
targetName = online.getName();
|
||||
} else {
|
||||
targetUuid = findUuidByNameInsensitive(targetName);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetUuid == null) {
|
||||
sender.sendMessage(
|
||||
Component.text("Player '", NamedTextColor.RED)
|
||||
.append(Component.text(targetName, NamedTextColor.WHITE))
|
||||
.append(Component.text("' not found or has no death records.", NamedTextColor.RED))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show stats for specified player
|
||||
String targetPlayerName = args[0];
|
||||
Player targetPlayer = Bukkit.getPlayer(targetPlayerName);
|
||||
|
||||
if (targetPlayer == null) {
|
||||
// Try to find offline player data
|
||||
UUID targetUuid = null;
|
||||
for (PlayerDeathData data : deathCounterManager.getAllPlayerData().values()) {
|
||||
if (data.getPlayerName().equalsIgnoreCase(targetPlayerName)) {
|
||||
targetUuid = data.getPlayerUuid();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetUuid == null) {
|
||||
sender.sendMessage("§cPlayer '" + targetPlayerName + "' not found or has no death records.");
|
||||
return true;
|
||||
}
|
||||
|
||||
showPlayerStatsByUuid(sender, targetUuid, targetPlayerName);
|
||||
} else {
|
||||
showPlayerStats(sender, targetPlayer);
|
||||
}
|
||||
|
||||
showPlayerStatsByUuid(sender, targetUuid, targetName, page, label);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showPlayerStats(CommandSender sender, Player player) {
|
||||
showPlayerStatsByUuid(sender, player.getUniqueId(), player.getName());
|
||||
}
|
||||
|
||||
private void showPlayerStatsByUuid(CommandSender sender, UUID playerUuid, String playerName) {
|
||||
private void showPlayerStatsByUuid(CommandSender sender, UUID playerUuid, String playerName, int page, String label) {
|
||||
PlayerDeathData deathData = deathCounterManager.getPlayerDeathData(playerUuid);
|
||||
|
||||
if (deathData == null || deathData.getDeathCount() == 0) {
|
||||
sender.sendMessage("§a" + playerName + " has no recorded deaths. Lucky!");
|
||||
int total = (deathData == null) ? 0 : deathData.getDeathCount();
|
||||
if (total == 0) {
|
||||
sender.sendMessage(
|
||||
Component.text(playerName, NamedTextColor.RED, TextDecoration.BOLD)
|
||||
.append(Component.text(" has no recorded deaths.", NamedTextColor.WHITE))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage("§6=== Death Statistics for " + playerName + " ===");
|
||||
sender.sendMessage("§eTotalDeaths: §c" + deathData.getDeathCount());
|
||||
// Header: === Death Statistics of <red bold>name</red bold> (total) ===
|
||||
Component header = Component.text("===", NamedTextColor.DARK_GRAY)
|
||||
.append(Component.text(" Death Statistics of ", NamedTextColor.WHITE))
|
||||
.append(Component.text(playerName, NamedTextColor.RED, TextDecoration.BOLD))
|
||||
.append(Component.text(" (", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.text(String.valueOf(total), NamedTextColor.RED))
|
||||
.append(Component.text(") ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.text("===", NamedTextColor.DARK_GRAY));
|
||||
sender.sendMessage(header);
|
||||
|
||||
List<DeathRecord> deaths = deathData.getDeaths();
|
||||
if (deaths.size() > 0) {
|
||||
sender.sendMessage("§eRecent Deaths:");
|
||||
int showCount = Math.min(5, deaths.size()); // Show last 5 deaths
|
||||
for (int i = deaths.size() - showCount; i < deaths.size(); i++) {
|
||||
DeathRecord death = deaths.get(i);
|
||||
sender.sendMessage(String.format("§7- §f%s §7at §e%.1f, %.1f, %.1f §7in §b%s §7(%s)",
|
||||
death.getDeathReason(),
|
||||
death.getX(), death.getY(), death.getZ(),
|
||||
death.getWorld(),
|
||||
death.getTimestamp().replace("T", " ")
|
||||
));
|
||||
}
|
||||
int totalPages = (int) Math.ceil(total / (double) PAGE_SIZE);
|
||||
page = Math.max(1, Math.min(page, totalPages));
|
||||
|
||||
if (deaths.size() > 5) {
|
||||
sender.sendMessage("§7... and " + (deaths.size() - 5) + " more deaths.");
|
||||
// We want latest first. Data assumed chronological -> iterate from end backwards
|
||||
int endIdxExclusive = deaths.size(); // last element is newest
|
||||
int startNewestIndex = endIdxExclusive - 1; // newest index
|
||||
int startIndex = startNewestIndex - (PAGE_SIZE * (page - 1));
|
||||
int endIndex = Math.max(startNewestIndex - (PAGE_SIZE * page) + 1, 0);
|
||||
|
||||
// Display items from startIndex down to endIndex inclusive
|
||||
DateTimeFormatter outFmt = DateTimeFormatter.ofPattern("dd.MM.yy HH:mm", Locale.ROOT);
|
||||
|
||||
for (int i = startIndex; i >= endIndex && i >= 0 && i < deaths.size(); i--) {
|
||||
DeathRecord d = deaths.get(i);
|
||||
|
||||
String ts = formatTimestampShort(d.getTimestamp(), outFmt);
|
||||
String world = d.getWorld();
|
||||
String coords = (int) d.getX() + ", " + (int) d.getY() + ", " + (int) d.getZ();
|
||||
String reason = safeReason(d.getDeathReason());
|
||||
|
||||
// » [dd-MM-yy HH:mm] (world) X,Y,Z – reason
|
||||
Component line = Component.text("» ", NamedTextColor.GRAY) // prefix
|
||||
.append(Component.text("[", NamedTextColor.GRAY))
|
||||
.append(Component.text(ts, NamedTextColor.WHITE))
|
||||
.append(Component.text("] ", NamedTextColor.GRAY))
|
||||
.append(Component.text("(" + world + ") ", NamedTextColor.GRAY))
|
||||
.append(Component.text(coords, NamedTextColor.WHITE))
|
||||
.append(Component.text(" – ", NamedTextColor.GRAY))
|
||||
.append(Component.text(reason, NamedTextColor.WHITE));
|
||||
sender.sendMessage(line);
|
||||
}
|
||||
|
||||
// Footer with paging hint
|
||||
if (totalPages > 1) {
|
||||
String nextHint;
|
||||
if (sender instanceof Player p && p.getName().equalsIgnoreCase(playerName)) {
|
||||
// self
|
||||
nextHint = "/" + "deathstats" + " " + (page + 1);
|
||||
} else {
|
||||
nextHint = "/" + "deathstats" + " " + playerName + " " + (page + 1);
|
||||
}
|
||||
Component footer = Component.text("(Page " + page + "/" + totalPages + ")", NamedTextColor.GRAY);
|
||||
if (page < totalPages) {
|
||||
footer = footer.append(Component.text(" • next: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(nextHint, NamedTextColor.WHITE));
|
||||
}
|
||||
sender.sendMessage(footer);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPositiveInt(String s) {
|
||||
try {
|
||||
int v = Integer.parseInt(s);
|
||||
return v > 0;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private UUID findUuidByNameInsensitive(String name) {
|
||||
return deathCounterManager.getAllPlayerData().values().stream()
|
||||
.filter(d -> d.getPlayerName() != null && d.getPlayerName().equalsIgnoreCase(name))
|
||||
.map(PlayerDeathData::getPlayerUuid)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private String safeReason(String reason) {
|
||||
if (reason == null) return "unknown";
|
||||
String r = reason.trim();
|
||||
return r.isEmpty() ? "unknown" : r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse various ISO-like strings and format as "yy-MM-dd HH:mm" (no seconds).
|
||||
* Falls back to the raw string if parsing fails.
|
||||
*/
|
||||
private String formatTimestampShort(String raw, DateTimeFormatter outFmt) {
|
||||
if (raw == null || raw.isBlank()) return "??-??-?? ??:??";
|
||||
try {
|
||||
// Try OffsetDateTime (e.g., 2025-09-01T12:34:56Z)
|
||||
return outFmt.format(OffsetDateTime.parse(raw).atZoneSameInstant(ZoneId.systemDefault()));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
// Try ZonedDateTime
|
||||
return outFmt.format(ZonedDateTime.parse(raw).withZoneSameInstant(ZoneId.systemDefault()));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
// Try LocalDateTime (assume system zone)
|
||||
return outFmt.format(LocalDateTime.parse(raw).atZone(ZoneId.systemDefault()));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// Fallback: best-effort trim seconds if present like "2025-09-01 12:34:56"
|
||||
int tIdx = raw.indexOf(':');
|
||||
if (tIdx > 0) {
|
||||
int lastColon = raw.lastIndexOf(':');
|
||||
if (lastColon > tIdx) {
|
||||
// remove last :ss
|
||||
return raw.substring(0, lastColon);
|
||||
}
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
private void showPlayerStats(CommandSender sender, Player player) {
|
||||
showPlayerStatsByUuid(sender, player.getUniqueId(), player.getName(), 1, "deathstats");
|
||||
}
|
||||
}
|
||||
@@ -23,37 +23,39 @@ public class PlayerDeathListener implements Listener {
|
||||
Player player = event.getEntity();
|
||||
Location deathLocation = player.getLocation();
|
||||
|
||||
// Original-Death-Message (kann den Spielernamen enthalten)
|
||||
// Vanilla death message, may include player name
|
||||
String raw = event.getDeathMessage();
|
||||
if (raw == null || raw.trim().isEmpty()) {
|
||||
raw = "died";
|
||||
}
|
||||
|
||||
// Heuristik: Spielernamen vorne entfernen, falls vorhanden
|
||||
String name = player.getName();
|
||||
String reason = raw.trim();
|
||||
if (reason.startsWith(name)) {
|
||||
|
||||
// Strip player name if it's at the beginning
|
||||
if (reason.toLowerCase().startsWith(name.toLowerCase())) {
|
||||
reason = reason.substring(name.length()).trim();
|
||||
}
|
||||
|
||||
// Ensure reason is not empty
|
||||
if (reason.isEmpty()) {
|
||||
reason = "died";
|
||||
}
|
||||
|
||||
// Death record speichern
|
||||
// Store clean reason (without player name)
|
||||
DeathRecord deathRecord = new DeathRecord(
|
||||
deathLocation.getX(),
|
||||
deathLocation.getY(),
|
||||
deathLocation.getZ(),
|
||||
deathLocation.getWorld().getName(),
|
||||
raw // originaler Grund für Historie
|
||||
reason
|
||||
);
|
||||
deathCounterManager.addDeath(player.getUniqueId(), name, deathRecord);
|
||||
|
||||
// Schlanke Chat-Nachricht: Roter, fetter Spielername + Grund in Grau
|
||||
// Broadcast custom death message: red bold name + reason
|
||||
Component message = Component.text(name, NamedTextColor.RED, TextDecoration.BOLD)
|
||||
.append(Component.text(" " + reason, NamedTextColor.GRAY));
|
||||
|
||||
// Setze die Death-Message (Paper/Adventure)
|
||||
event.deathMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,75 +21,78 @@ public class DeathCounterManager {
|
||||
private final JavaPlugin plugin;
|
||||
private final File dataFile;
|
||||
private final Gson gson;
|
||||
private final Map<UUID, PlayerDeathData> playerDeathData;
|
||||
|
||||
public DeathCounterManager(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.dataFile = new File(plugin.getDataFolder(), "death_counter.json");
|
||||
this.gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
this.playerDeathData = new HashMap<>();
|
||||
|
||||
// Create plugin data folder if it doesn't exist
|
||||
// Ensure data folder exists
|
||||
if (!plugin.getDataFolder().exists()) {
|
||||
plugin.getDataFolder().mkdirs();
|
||||
}
|
||||
|
||||
loadData();
|
||||
// Ensure file exists (empty JSON) to simplify reads
|
||||
if (!dataFile.exists()) {
|
||||
saveAll(new HashMap<>());
|
||||
}
|
||||
}
|
||||
|
||||
public void addDeath(UUID playerUuid, String playerName, DeathRecord deathRecord) {
|
||||
PlayerDeathData data = playerDeathData.computeIfAbsent(playerUuid,
|
||||
Map<UUID, PlayerDeathData> all = loadAll();
|
||||
|
||||
PlayerDeathData data = all.computeIfAbsent(playerUuid,
|
||||
uuid -> new PlayerDeathData(uuid, playerName));
|
||||
// keep latest known name
|
||||
if (playerName != null && !playerName.isBlank() && !playerName.equals(data.getPlayerName())) {
|
||||
data.setPlayerName(playerName);
|
||||
}
|
||||
|
||||
data.addDeath(deathRecord);
|
||||
saveData();
|
||||
saveAll(all);
|
||||
|
||||
plugin.getLogger().info(String.format("Recorded death for %s: %s", playerName, deathRecord));
|
||||
}
|
||||
|
||||
public PlayerDeathData getPlayerDeathData(UUID playerUuid) {
|
||||
return playerDeathData.get(playerUuid);
|
||||
Map<UUID, PlayerDeathData> all = loadAll();
|
||||
return all.get(playerUuid);
|
||||
}
|
||||
|
||||
public int getDeathCount(UUID playerUuid) {
|
||||
PlayerDeathData data = playerDeathData.get(playerUuid);
|
||||
PlayerDeathData data = getPlayerDeathData(playerUuid);
|
||||
return data != null ? data.getDeathCount() : 0;
|
||||
}
|
||||
|
||||
public Map<UUID, PlayerDeathData> getAllPlayerData() {
|
||||
return new HashMap<>(playerDeathData);
|
||||
// return a copy
|
||||
return new HashMap<>(loadAll());
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
private Map<UUID, PlayerDeathData> loadAll() {
|
||||
if (!dataFile.exists()) {
|
||||
plugin.getLogger().info("Death counter data file not found, starting with empty data.");
|
||||
return;
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
try (FileReader reader = new FileReader(dataFile)) {
|
||||
Type type = new TypeToken<Map<UUID, PlayerDeathData>>() {
|
||||
}.getType();
|
||||
Map<UUID, PlayerDeathData> loadedData = gson.fromJson(reader, type);
|
||||
|
||||
if (loadedData != null) {
|
||||
playerDeathData.putAll(loadedData);
|
||||
plugin.getLogger().info(String.format("Loaded death data for %d players.", loadedData.size()));
|
||||
}
|
||||
Map<UUID, PlayerDeathData> loaded = gson.fromJson(reader, type);
|
||||
return (loaded != null) ? loaded : new HashMap<>();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to load death counter data", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveData() {
|
||||
private void saveAll(Map<UUID, PlayerDeathData> data) {
|
||||
try (FileWriter writer = new FileWriter(dataFile)) {
|
||||
gson.toJson(playerDeathData, writer);
|
||||
gson.toJson(data, writer);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to save death counter data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
saveData();
|
||||
plugin.getLogger().info("Death counter data saved on shutdown.");
|
||||
// nothing to do here
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import java.util.UUID;
|
||||
|
||||
public class PlayerDeathData {
|
||||
private final UUID playerUuid;
|
||||
private final String playerName;
|
||||
private String playerName; // not final anymore
|
||||
private final List<DeathRecord> deaths;
|
||||
|
||||
public PlayerDeathData(UUID playerUuid, String playerName) {
|
||||
@@ -43,6 +43,11 @@ public class PlayerDeathData {
|
||||
return new ArrayList<>(deaths); // Return copy to prevent external modification
|
||||
}
|
||||
|
||||
// Setter for playerName
|
||||
public void setPlayerName(String playerName) {
|
||||
this.playerName = playerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PlayerDeathData{uuid=%s, name='%s', deathCount=%d}",
|
||||
|
||||
@@ -18,7 +18,8 @@ import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DeathCounterManagerTest {
|
||||
@@ -39,41 +40,35 @@ class DeathCounterManagerTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Setup mock plugin
|
||||
// Use a fresh temporary data folder per test
|
||||
dataFolder = tempDir.toFile();
|
||||
when(mockPlugin.getDataFolder()).thenReturn(dataFolder);
|
||||
when(mockPlugin.getLogger()).thenReturn(mockLogger);
|
||||
|
||||
// Test data
|
||||
// Lenient to avoid UnnecessaryStubbingException in tests that don't hit the logger
|
||||
lenient().when(mockPlugin.getLogger()).thenReturn(mockLogger);
|
||||
|
||||
testPlayerUuid = UUID.randomUUID();
|
||||
testPlayerName = "TestPlayer";
|
||||
|
||||
// Create DeathCounterManager instance
|
||||
deathCounterManager = new DeathCounterManager(mockPlugin);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructor_CreatesDataFolderIfNotExists() {
|
||||
// Given: A new temp directory that doesn't exist
|
||||
File newDataFolder = new File(tempDir.toFile(), "newFolder");
|
||||
when(mockPlugin.getDataFolder()).thenReturn(newDataFolder);
|
||||
|
||||
// When: Creating a new DeathCounterManager
|
||||
new DeathCounterManager(mockPlugin);
|
||||
|
||||
// Then: The data folder should be created
|
||||
assertTrue(newDataFolder.exists());
|
||||
assertTrue(newDataFolder.exists(), "Expected data folder to be created by constructor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddDeath_NewPlayer() {
|
||||
// Given: A new death record
|
||||
DeathRecord deathRecord = new DeathRecord(100.0, 64.0, 200.0, "world", "Fell from a high place");
|
||||
|
||||
// When: Adding a death for a new player
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, deathRecord);
|
||||
|
||||
// Then: Player data should be created and death should be recorded
|
||||
PlayerDeathData playerData = deathCounterManager.getPlayerDeathData(testPlayerUuid);
|
||||
assertNotNull(playerData);
|
||||
assertEquals(testPlayerUuid, playerData.getPlayerUuid());
|
||||
@@ -85,15 +80,12 @@ class DeathCounterManagerTest {
|
||||
|
||||
@Test
|
||||
void testAddDeath_ExistingPlayer() {
|
||||
// Given: A player with existing death data
|
||||
DeathRecord firstDeath = new DeathRecord(100.0, 64.0, 200.0, "world", "Fell from a high place");
|
||||
DeathRecord secondDeath = new DeathRecord(150.0, 70.0, 250.0, "nether", "Burned to death");
|
||||
|
||||
// When: Adding multiple deaths for the same player
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, firstDeath);
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, secondDeath);
|
||||
|
||||
// Then: Both deaths should be recorded
|
||||
PlayerDeathData playerData = deathCounterManager.getPlayerDeathData(testPlayerUuid);
|
||||
assertNotNull(playerData);
|
||||
assertEquals(2, playerData.getDeathCount());
|
||||
@@ -102,44 +94,34 @@ class DeathCounterManagerTest {
|
||||
|
||||
@Test
|
||||
void testGetPlayerDeathData_NonExistentPlayer() {
|
||||
// Given: A UUID that doesn't exist in the data
|
||||
UUID nonExistentUuid = UUID.randomUUID();
|
||||
|
||||
// When: Getting player data for non-existent player
|
||||
PlayerDeathData result = deathCounterManager.getPlayerDeathData(nonExistentUuid);
|
||||
|
||||
// Then: Should return null
|
||||
assertNull(result);
|
||||
assertNull(result, "Expected null for non-existent player UUID");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDeathCount_ExistingPlayer() {
|
||||
// Given: A player with death data
|
||||
DeathRecord deathRecord = new DeathRecord(100.0, 64.0, 200.0, "world", "Fell from a high place");
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, deathRecord);
|
||||
|
||||
// When: Getting death count
|
||||
int deathCount = deathCounterManager.getDeathCount(testPlayerUuid);
|
||||
|
||||
// Then: Should return correct count
|
||||
assertEquals(1, deathCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDeathCount_NonExistentPlayer() {
|
||||
// Given: A UUID that doesn't exist in the data
|
||||
UUID nonExistentUuid = UUID.randomUUID();
|
||||
|
||||
// When: Getting death count for non-existent player
|
||||
int deathCount = deathCounterManager.getDeathCount(nonExistentUuid);
|
||||
|
||||
// Then: Should return 0
|
||||
assertEquals(0, deathCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllPlayerData() {
|
||||
// Given: Multiple players with death data
|
||||
UUID player1Uuid = UUID.randomUUID();
|
||||
UUID player2Uuid = UUID.randomUUID();
|
||||
DeathRecord death1 = new DeathRecord(100.0, 64.0, 200.0, "world", "Fell from a high place");
|
||||
@@ -148,29 +130,25 @@ class DeathCounterManagerTest {
|
||||
deathCounterManager.addDeath(player1Uuid, "Player1", death1);
|
||||
deathCounterManager.addDeath(player2Uuid, "Player2", death2);
|
||||
|
||||
// When: Getting all player data
|
||||
Map<UUID, PlayerDeathData> allData = deathCounterManager.getAllPlayerData();
|
||||
|
||||
// Then: Should return copy of all data
|
||||
assertEquals(2, allData.size());
|
||||
assertTrue(allData.containsKey(player1Uuid));
|
||||
assertTrue(allData.containsKey(player2Uuid));
|
||||
|
||||
// Verify it's a copy (modifying returned map shouldn't affect internal data)
|
||||
// verify defensive copy
|
||||
allData.clear();
|
||||
assertEquals(2, deathCounterManager.getAllPlayerData().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDataPersistence_SaveAndLoad() throws IOException {
|
||||
// Given: Some death data
|
||||
DeathRecord deathRecord = new DeathRecord(100.0, 64.0, 200.0, "world", "Fell from a high place");
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, deathRecord);
|
||||
|
||||
// When: Creating a new DeathCounterManager (which should load existing data)
|
||||
// Recreate manager -> should load existing data from disk
|
||||
DeathCounterManager newManager = new DeathCounterManager(mockPlugin);
|
||||
|
||||
// Then: Data should be loaded correctly
|
||||
PlayerDeathData loadedData = newManager.getPlayerDeathData(testPlayerUuid);
|
||||
assertNotNull(loadedData);
|
||||
assertEquals(testPlayerUuid, loadedData.getPlayerUuid());
|
||||
@@ -180,35 +158,37 @@ class DeathCounterManagerTest {
|
||||
|
||||
@Test
|
||||
void testLoadData_EmptyFile() throws IOException {
|
||||
// Given: An empty data file
|
||||
File dataFile = new File(dataFolder, "death_counter.json");
|
||||
// Create an empty file
|
||||
dataFile.createNewFile();
|
||||
assertTrue(dataFile.exists());
|
||||
File dataFile = new File(dataFolder, "death_counter.json");
|
||||
assertTrue(dataFile.delete() || !dataFile.exists());
|
||||
assertTrue(dataFolder.exists() || dataFolder.mkdirs());
|
||||
assertTrue(dataFile.createNewFile(), "Expected to create empty data file");
|
||||
|
||||
// When: Creating a new DeathCounterManager
|
||||
DeathCounterManager newManager = new DeathCounterManager(mockPlugin);
|
||||
|
||||
// Then: Should handle empty data gracefully
|
||||
assertEquals(0, newManager.getAllPlayerData().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShutdown_SavesData() {
|
||||
// Given: Some death data
|
||||
void testShutdown_Noop() {
|
||||
// Add some data
|
||||
DeathRecord deathRecord = new DeathRecord(100.0, 64.0, 200.0, "world", "Fell from a high place");
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, deathRecord);
|
||||
|
||||
// When: Calling shutdown
|
||||
deathCounterManager.shutdown();
|
||||
File dataFile = new File(dataFolder, "death_counter.json");
|
||||
long beforeLen = dataFile.length();
|
||||
long beforeMod = dataFile.lastModified();
|
||||
|
||||
// Then: Should log shutdown message
|
||||
verify(mockLogger).info("Death counter data saved on shutdown.");
|
||||
// Should not throw and should not alter the file
|
||||
assertDoesNotThrow(() -> deathCounterManager.shutdown());
|
||||
|
||||
// Manager does not touch the file on shutdown -> length & timestamp should remain unchanged
|
||||
assertEquals(beforeLen, dataFile.length(), "shutdown() should not change data file size");
|
||||
assertEquals(beforeMod, dataFile.lastModified(), "shutdown() should not modify data file timestamp");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeathRecordDetails() {
|
||||
// Given: A death record with specific details
|
||||
double x = 123.45;
|
||||
double y = 67.89;
|
||||
double z = 234.56;
|
||||
@@ -218,11 +198,10 @@ class DeathCounterManagerTest {
|
||||
DeathRecord deathRecord = new DeathRecord(x, y, z, world, reason);
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, deathRecord);
|
||||
|
||||
// When: Retrieving the death record
|
||||
PlayerDeathData playerData = deathCounterManager.getPlayerDeathData(testPlayerUuid);
|
||||
assertNotNull(playerData);
|
||||
DeathRecord retrievedRecord = playerData.getDeaths().get(0);
|
||||
|
||||
// Then: All details should be preserved
|
||||
assertEquals(x, retrievedRecord.getX(), 0.001);
|
||||
assertEquals(y, retrievedRecord.getY(), 0.001);
|
||||
assertEquals(z, retrievedRecord.getZ(), 0.001);
|
||||
@@ -233,18 +212,16 @@ class DeathCounterManagerTest {
|
||||
|
||||
@Test
|
||||
void testMultipleDeathsPreserveOrder() {
|
||||
// Given: Multiple deaths added in sequence
|
||||
DeathRecord death1 = new DeathRecord(100.0, 64.0, 200.0, "world", "First death");
|
||||
DeathRecord death2 = new DeathRecord(150.0, 70.0, 250.0, "nether", "Second death");
|
||||
DeathRecord death3 = new DeathRecord(200.0, 80.0, 300.0, "end", "Third death");
|
||||
|
||||
// When: Adding deaths in sequence
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, death1);
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, death2);
|
||||
deathCounterManager.addDeath(testPlayerUuid, testPlayerName, death3);
|
||||
|
||||
// Then: Deaths should be preserved in order
|
||||
PlayerDeathData playerData = deathCounterManager.getPlayerDeathData(testPlayerUuid);
|
||||
assertNotNull(playerData);
|
||||
assertEquals(3, playerData.getDeathCount());
|
||||
assertEquals("First death", playerData.getDeaths().get(0).getDeathReason());
|
||||
assertEquals("Second death", playerData.getDeaths().get(1).getDeathReason());
|
||||
|
||||
Reference in New Issue
Block a user