Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions PlayerHeadHunt.iml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
<configuration>
<autoDetectTypes>
<platformType>PAPER</platformType>
<platformType>ADVENTURE</platformType>
<platformType>SPIGOT</platformType>
</autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion>
</configuration>
Expand Down
34 changes: 34 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.apache.hc</pattern>
<shadedPattern> org.modularsoft.PlayerHeadHunt.shaded.org.apache.hc</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>

<resources>
Expand Down Expand Up @@ -99,6 +120,19 @@
<artifactId>snakeyaml</artifactId>
<version>2.0</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2</version>
</dependency>

<dependency>
<groupId>net.luckperms</groupId>
<artifactId>api</artifactId>
<version>5.4</version>
<scope>provided</scope>
</dependency>
</dependencies>

</project>
104 changes: 82 additions & 22 deletions src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
package org.modularsoft.PlayerHeadHunt;

import lombok.Getter;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.util.Tristate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class HeadQuery {
private final YamlFileManager yamlFileManager;
private final LuckPerms luckPerms;

public HeadQuery(YamlFileManager yamlFileManager) {
this.yamlFileManager = yamlFileManager;

// Get the LuckPerms provider from the Bukkit services manager
RegisteredServiceProvider<LuckPerms> provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class);
if (provider != null) {
this.luckPerms = provider.getProvider();
} else {
throw new IllegalStateException("LuckPerms API is not available!");
}
}

public record HeadHunter(@Getter String name, @Getter int headsCollected) { }
Expand Down Expand Up @@ -150,29 +163,76 @@ public boolean addNewHunter(Player player) {
return true;
}

public List<HeadHunter> getBestHunters(int topHunters) {
Map<String, Object> data = yamlFileManager.getData();
List<HeadHunter> bestHunters = new ArrayList<>();
private boolean isPlayerExcluded(UUID uuid, String username) {
return luckPerms.getUserManager().loadUser(uuid)
.thenApply(user -> {
if (user == null) {
Bukkit.getLogger().warning("LuckPerms failed to load user for UUID: " + uuid);
return false; // Include the player if user data cannot be loaded
}

Tristate permissionResult = user.getCachedData()
.getPermissionData()
.checkPermission("playerheadhunt.leaderboard.exclude");

// Only exclude if the permission is explicitly TRUE
boolean isExcluded = permissionResult == Tristate.TRUE;

data.forEach((key, value) -> {
if (value instanceof Map) {
Map<String, Object> playerData = (Map<String, Object>) value;
Bukkit.getLogger().info("Player " + username + " exclusion status: " + isExcluded);
return isExcluded;
}).join();
}

private Optional<HeadHunter> processPlayerData(String uuidStr, Map<String, Object> playerData) {
UUID uuid;
try {
uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) {
Bukkit.getLogger().warning("Invalid UUID string: " + uuidStr);
return Optional.empty();
}

String username = (String) playerData.get("username");
Object headsCollectedObj = playerData.get("headsCollected");
String username = (String) playerData.get("username");
Object headsCollectedObj = playerData.get("headsCollected");

if (username == null || !(headsCollectedObj instanceof List<?> headsCollected)) {
Bukkit.getLogger().warning("Invalid data for user " + uuidStr + ": username=" + username + ", headsCollected=" + headsCollectedObj);
return Optional.empty();
}

// Validate that headsCollected is a list
if (username != null && headsCollectedObj instanceof List<?>) {
int headsCollectedCount = ((List<?>) headsCollectedObj).size();
bestHunters.add(new HeadHunter(username, headsCollectedCount));
}
Bukkit.getLogger().info("Processing player: " + username + ", Heads Collected: " + headsCollected.size());

if (isPlayerExcluded(uuid, username)) {
return Optional.empty();
}

return Optional.of(new HeadHunter(username, headsCollected.size()));
}

public CompletableFuture<List<HeadHunter>> getBestHunters(int topHunters) {
Map<String, Object> data = yamlFileManager.getData();
List<CompletableFuture<Optional<HeadHunter>>> futures = new ArrayList<>();

for (Map.Entry<String, Object> entry : data.entrySet()) {
String uuidStr = entry.getKey();
Map<String, Object> playerData = (Map<String, Object>) entry.getValue();

if (playerData == null) {
Bukkit.getLogger().warning("Missing playerData for UUID: " + uuidStr);
continue;
}
});

// Sort hunters by the number of heads collected in descending order
bestHunters.sort((a, b) -> Integer.compare(b.headsCollected(), a.headsCollected()));
CompletableFuture<Optional<HeadHunter>> future = CompletableFuture.supplyAsync(() -> processPlayerData(uuidStr, playerData));
futures.add(future);
}

// Return the top hunters
return bestHunters.subList(0, Math.min(topHunters, bestHunters.size()));
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.filter(Optional::isPresent)
.map(Optional::get)
.sorted((a, b) -> Integer.compare(b.headsCollected(), a.headsCollected()))
.limit(topHunters)
.collect(Collectors.toList()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ public void onEnable() {
// Command Registry
Objects.requireNonNull(getCommand("heads")).setExecutor(new heads(this, headChatController));
Objects.requireNonNull(getCommand("leaderboard")).setExecutor(new leaderboard(this, headChatController, headQuery)); // Register leaderboard command
Objects.requireNonNull(getCommand("debugheadhunt")).setExecutor(new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery));
Objects.requireNonNull(getCommand("debugheadhunt")).setExecutor(
new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery)
);
Objects.requireNonNull(getCommand("debugheadhunt")).setTabCompleter(
new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery)
);

// Plugin Load Message
console.sendMessage(ChatColor.GREEN + getDescription().getName() + " is now enabled.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.modularsoft.PlayerHeadHunt.*;
import org.modularsoft.PlayerHeadHunt.helpers.WebhookUtil;

public class debugheadhunt implements CommandExecutor {
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class debugheadhunt implements CommandExecutor, TabCompleter {
private final PlayerHeadHuntMain plugin;
private final HeadChatController headChatController;
private final HeadHatController headHatController;
private final HeadScoreboardController scoreboardController;
private final HeadWorldController headWorldController;
private final HeadQuery headQuery;
private final WebhookUtil webhookUtil;

public debugheadhunt(PlayerHeadHuntMain plugin,
HeadChatController headChatController,
Expand All @@ -27,42 +35,60 @@ public debugheadhunt(PlayerHeadHuntMain plugin,
this.scoreboardController = scoreboardController;
this.headWorldController = headWorldController;
this.headQuery = headQuery;
this.webhookUtil = new WebhookUtil(plugin);
}

@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(plugin.config().getLangNotAPlayer());
return true;
}

if (!sender.hasPermission("playerheadhunt.debug") || !sender.isOp()) {
sender.sendMessage(plugin.config().getLangInsufficientPermissions());
return true;
}

if (args.length == 0) {
sender.sendMessage("Usage: /debugheadhunt <clearheads|countheads>");
sender.sendMessage("Usage: /debugheadhunt <clearheads|countheads|firewebhook>");
return true;
}

switch (args[0].toLowerCase()) {
case "clearheads" -> {
if (!headQuery.clearHeads(player)) {
sender.sendMessage("No heads to clear.");
return true;
if (sender instanceof Player player) {
if (!headQuery.clearHeads(player)) {
sender.sendMessage("No heads to clear.");
return true;
}
headChatController.playerClearedTheirHeadsResponse(player);
headHatController.clearHelmet(player);
scoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player));
sender.sendMessage("Heads cleared successfully.");
} else {
sender.sendMessage("The 'clearheads' command can only be executed by a player.");
}
headChatController.playerClearedTheirHeadsResponse(player);
headHatController.clearHelmet(player);
scoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player));
sender.sendMessage("Heads cleared successfully.");
}
case "countheads" -> {
headWorldController.countHeadsInRegion();
sender.sendMessage("Heads counted successfully.");
}
default -> sender.sendMessage("Invalid subcommand. Use: clearheads or countheads.");
case "firewebhook" -> {
try {
String webhookUrl = plugin.getConfig().getString("DISCORD.WEBHOOKURL");
webhookUtil.sendLeaderboardWebhook(webhookUrl, headQuery);
sender.sendMessage("Webhook fired successfully.");
} catch (Exception e) {
sender.sendMessage("Failed to fire webhook: " + e.getMessage());
e.printStackTrace();
}
}
default -> sender.sendMessage("Invalid subcommand. Use: clearheads, countheads, or firewebhook.");
}
return true;
}

@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
if (args.length == 1) {
return Arrays.asList("clearheads", "countheads", "firewebhook");
}
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.modularsoft.PlayerHeadHunt.commands;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
Expand All @@ -14,24 +15,34 @@
public class leaderboard implements CommandExecutor {
private final PlayerHeadHuntMain plugin;
private final HeadChatController headChatController;
private final HeadQuery headQuery; // Add HeadQuery instance
private final HeadQuery headQuery;

public leaderboard(PlayerHeadHuntMain plugin, HeadChatController headChatController, HeadQuery headQuery) {
this.plugin = plugin;
this.headChatController = headChatController;
this.headQuery = headQuery; // Initialize HeadQuery
this.headQuery = headQuery;
}

@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String s, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(plugin.config().getLangNotAPlayer());
return true;
if (sender instanceof Player player) {
// Handle the command for players
headQuery.getBestHunters(5).thenAccept(bestHunters -> {
// Must run sync to safely interact with Bukkit API
Bukkit.getScheduler().runTask(plugin, () -> {
headChatController.showLeaderBoardResponse(player, bestHunters);
});
});
} else {
// Handle the command for the console
headQuery.getBestHunters(5).thenAccept(bestHunters -> {
// Log the leaderboard to the console
plugin.getServer().getConsoleSender().sendMessage("Top 5 Hunters:");
for (int i = 0; i < bestHunters.size(); i++) {
plugin.getServer().getConsoleSender().sendMessage((i + 1) + ". " + bestHunters.get(i));
}
});
}

// Use the HeadQuery instance to call getBestHunters
List<HeadQuery.HeadHunter> bestHunters = headQuery.getBestHunters(5);
headChatController.showLeaderBoardResponse(player, bestHunters);
return true;
}
}
Loading