Consistently format heads, add hover display

This commit is contained in:
NotAlexNoyle committed 2026-04-21 12:27:31 -04:00
1 parent 2008150b11
commit 1ca9aaed36
15 files changed
+507 -350

No files matched your search

@@ -19,11 +19,14 @@ import com.tcoded.playerbountiesplus.hook.currency.DiamondBankOGHook;
import com.tcoded.playerbountiesplus.hook.logging.CoreProtectHook;
import com.tcoded.playerbountiesplus.hook.currency.EconomyHook;
import com.tcoded.playerbountiesplus.hook.team.TeamHook;
import com.tcoded.playerbountiesplus.listener.BountyHeadLabelListener;
import com.tcoded.playerbountiesplus.listener.BountyHeadListener;
import com.tcoded.playerbountiesplus.listener.BountyIncreasePromptListener;
import com.tcoded.playerbountiesplus.listener.DeathListener;
import com.tcoded.playerbountiesplus.listener.GuiProtectionListener;
import com.tcoded.playerbountiesplus.manager.BountyDataManager;
import com.tcoded.playerbountiesplus.util.BountyHeadFormatter;
import com.tcoded.playerbountiesplus.util.BountyHeadMetadata;
import com.tcoded.playerbountiesplus.util.LangUtil;
import net.luckperms.api.LuckPerms;
@@ -49,6 +52,8 @@ public final class PlayerBountiesOG extends JavaPlugin {
// Utilities.
private LangUtil langUtil;
private BountyHeadFormatter bountyHeadFormatter;
private BountyHeadMetadata bountyHeadMetadata;
// Managers.
private BountyDataManager bountyDataManager;
@@ -67,6 +72,7 @@ public final class PlayerBountiesOG extends JavaPlugin {
private CoreProtectHook coreProtectHook;
private BountyIncreasePromptListener bountyIncreasePromptListener;
private BountyHeadLabelListener bountyHeadLabelListener;
public PlayerBountiesOG() {
@@ -121,6 +127,8 @@ public final class PlayerBountiesOG extends JavaPlugin {
// Utils.
this.reloadLang();
this.bountyHeadFormatter = new BountyHeadFormatter(this.diamondBankAPI, this.luckPerms);
this.bountyHeadMetadata = new BountyHeadMetadata(this);
// Managers.
this.bountyDataManager = new BountyDataManager(this);
@@ -158,12 +166,12 @@ public final class PlayerBountiesOG extends JavaPlugin {
}
// Listeners.
this.getServer().getPluginManager().registerEvents(new DeathListener(this, this.diamondBankAPI, this.luckPerms),
this);
this.getServer().getPluginManager().registerEvents(new DeathListener(this), this);
this.bountyIncreasePromptListener = new BountyIncreasePromptListener(this);
this.getServer().getPluginManager().registerEvents(
new BountyHeadListener(this, this.diamondBankAPI, this.luckPerms, this.coreProtectHook), this);
this.getServer().getPluginManager().registerEvents(new BountyHeadListener(this), this);
this.bountyHeadLabelListener = new BountyHeadLabelListener(this);
this.getServer().getPluginManager().registerEvents(this.bountyHeadLabelListener, this);
this.getServer().getPluginManager().registerEvents(this.bountyIncreasePromptListener, this);
this.getServer().getPluginManager().registerEvents(new GuiProtectionListener(this), this);
@@ -188,6 +196,12 @@ public final class PlayerBountiesOG extends JavaPlugin {
@Override
public void onDisable() {
if (this.bountyHeadLabelListener != null) {
this.bountyHeadLabelListener.shutdown();
}
HandlerList.unregisterAll(this);
UtilitiesOG.logToConsole(getPrefix(), "PlayerBounties-OG has been shut down successfully!");
@@ -236,6 +250,18 @@ public final class PlayerBountiesOG extends JavaPlugin {
}
public BountyHeadFormatter getBountyHeadFormatter() {
return this.bountyHeadFormatter;
}
public BountyHeadMetadata getBountyHeadMetadata() {
return this.bountyHeadMetadata;
}
public BountyIncreasePromptListener getBountyIncreasePromptListener() {
return this.bountyIncreasePromptListener;
@@ -75,14 +75,15 @@ public class BountyAddCmd {
}
final OfflinePlayer target = Bukkit.getOfflinePlayer(targetNameArg);
if (target.getUniqueId() == null) {
if (target.getUniqueId() != null) {
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.add.player-not-found"));
return true;
return addBounty(plugin, player, target.getUniqueId(), target.getName(), amount, true);
}
return addBounty(plugin, player, target.getUniqueId(), target.getName(), amount, true);
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.add.player-not-found"));
return true;
}
@@ -247,13 +248,7 @@ public class BountyAddCmd {
final RegisteredServiceProvider<LuckPerms> provider = plugin.getServer().getServicesManager()
.getRegistration(LuckPerms.class);
if (provider == null) {
return null;
}
return provider.getProvider();
return provider == null ? null : provider.getProvider();
}
@@ -312,13 +307,7 @@ public class BountyAddCmd {
final String cleaned = StringUtils.trimToEmpty(formatted).replaceAll("(?i)(?:\\s*(?:<reset>|[&§]r))+$", "")
.replaceAll("\\s*>$", "");
if (cleaned.isBlank()) {
return fallbackName;
}
return cleaned;
return cleaned.isBlank() ? fallbackName : cleaned;
}
@@ -297,13 +297,7 @@ public class BountySetCmd {
final RegisteredServiceProvider<LuckPerms> provider = plugin.getServer().getServicesManager()
.getRegistration(LuckPerms.class);
if (provider == null) {
return null;
}
return provider.getProvider();
return provider == null ? null : provider.getProvider();
}
@@ -2,7 +2,6 @@ package com.tcoded.playerbountiesplus.command.bounty;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -15,10 +14,8 @@ import org.bukkit.entity.Player;
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
import com.tcoded.playerbountiesplus.gui.MainBountyGui;
import com.tcoded.playerbountiesplus.util.BountyHeadFormatter;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.model.user.User;
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
import net.trueog.utilitiesog.UtilitiesOG;
@@ -45,6 +42,8 @@ public class BountyTopCmd {
private static List<MainBountyGui.BountyGuiEntry> buildGuiEntries(PlayerBountiesOG plugin) {
final BountyHeadFormatter formatter = plugin.getBountyHeadFormatter();
final Set<Map.Entry<UUID, Double>> bountiesSet = plugin.getBountyDataManager().getBounties().entrySet();
final List<Map.Entry<UUID, Double>> bounties = new ArrayList<>(bountiesSet);
@@ -68,7 +67,7 @@ public class BountyTopCmd {
}
final String displayName = resolveLuckPermsDisplayName(plugin.getLuckPerms(), entry.getKey(), targetName);
final String displayName = formatter.resolvePlayerDisplay(entry.getKey(), targetName);
entries.add(new MainBountyGui.BountyGuiEntry(entry.getKey(), targetName, displayName, entry.getValue()));
@@ -78,84 +77,12 @@ public class BountyTopCmd {
}
private static String resolveLuckPermsDisplayName(LuckPerms luckPerms, UUID playerId, String playerName) {
final String safeName = StringUtils.defaultIfBlank(playerName, "Unknown Player");
if (luckPerms == null) {
return "&f" + safeName;
}
final User user = luckPerms.getUserManager().getUser(playerId);
if (user == null) {
return "&f" + safeName;
}
final CachedMetaData meta = user.getCachedData().getMetaData();
final String prefix = StringUtils.trim(StringUtils.defaultString(meta.getPrefix()).replace('§', '&'));
final String suffix = StringUtils.trim(StringUtils.defaultString(meta.getSuffix()).replace('§', '&'));
final boolean metaContainsName = containsVisibleName(prefix, safeName) || containsVisibleName(suffix, safeName);
final StringBuilder out = new StringBuilder();
if (!prefix.isBlank()) {
out.append(prefix).append(' ');
}
if (!metaContainsName) {
out.append(safeName);
}
if (!suffix.isBlank()) {
out.append(' ').append(suffix);
}
return sanitizeDisplay(out.toString(), safeName);
}
private static String sanitizeDisplay(String formatted, String fallbackName) {
final String cleaned = StringUtils.trimToEmpty(formatted).replaceAll("(?i)(?:\\s*(?:<reset>|[&§]r))+$", "")
.replaceAll("\\s*>$", "");
if (cleaned.isBlank()) {
return fallbackName;
}
return cleaned;
}
private static boolean containsVisibleName(String text, String playerName) {
if (text == null || playerName == null || playerName.isBlank()) {
return false;
}
final String normalizedText = StringUtils.lowerCase(UtilitiesOG.stripFormatting(text), Locale.ROOT);
final String normalizedName = StringUtils.lowerCase(playerName, Locale.ROOT);
return StringUtils.contains(normalizedText, normalizedName);
}
private static void sendTextTopList(PlayerBountiesOG plugin, DiamondBankAPIJava diamondBankAPI,
CommandSender sender)
{
final BountyHeadFormatter formatter = plugin.getBountyHeadFormatter();
final Set<Map.Entry<UUID, Double>> bountiesSet = plugin.getBountyDataManager().getBounties().entrySet();
final List<Map.Entry<UUID, Double>> bounties = new ArrayList<>(bountiesSet);
@@ -184,8 +111,7 @@ public class BountyTopCmd {
.shardsToDiamonds(diamondBankAPI.diamondsToShards(entry.getValue()));
strb.append("&7 - ");
final String displayName = resolveLuckPermsDisplayName(plugin.getLuckPerms(), entry.getKey(),
targetName);
final String displayName = formatter.resolvePlayerDisplay(entry.getKey(), targetName);
strb.append(displayName);
strb.append(": &b");
@@ -390,13 +390,7 @@ public class MainBountyGui extends GUIBase {
private String safeName(String name) {
if (name == null || name.isBlank()) {
return "Unknown Player";
}
return name;
return name == null || name.isBlank() ? "Unknown Player" : name;
}
@@ -113,4 +113,4 @@ public class CoreProtectHook {
}
}
}
@@ -30,13 +30,8 @@ public abstract class AbstractTeamHook implements TeamHook {
public String getAuthor() {
final List<String> authors = this.teamPlugin.getPluginMeta().getAuthors();
if (authors.isEmpty()) {
return "N/A";
}
return authors.get(0);
return authors.isEmpty() ? "N/A" : authors.get(0);
}
@@ -35,13 +35,7 @@ public class SimpleClansHook extends AbstractTeamHook {
}
if (clan2 == null) {
return false;
}
return clan1 == clan2;
return clan2 == null ? false : clan1 == clan2;
}
@@ -0,0 +1,196 @@
package com.tcoded.playerbountiesplus.listener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Skull;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.RayTraceResult;
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
import com.tcoded.playerbountiesplus.util.BountyHeadData;
public final class BountyHeadLabelListener implements Listener {
private static final long UPDATE_PERIOD_TICKS = 2L;
private final PlayerBountiesOG plugin;
private final Map<HeadLabelKey, ArmorStand> activeLabels = new HashMap<>();
private final BukkitTask tickTask;
public BountyHeadLabelListener(PlayerBountiesOG plugin) {
this.plugin = plugin;
this.tickTask = plugin.getServer().getScheduler().runTaskTimer(plugin, this::tick, UPDATE_PERIOD_TICKS,
UPDATE_PERIOD_TICKS);
}
public void shutdown() {
if (tickTask != null) {
tickTask.cancel();
}
clearLabels();
}
private void tick() {
if (!isHoverModeEnabled()) {
clearLabels();
return;
}
final Set<HeadLabelKey> visibleTargets = new HashSet<>();
for (Player player : plugin.getServer().getOnlinePlayers()) {
final HeadTarget target = findTargetedHead(player);
if (target == null) {
continue;
}
visibleTargets.add(target.key());
upsertLabel(target);
}
final Set<HeadLabelKey> staleLabels = new HashSet<>(activeLabels.keySet());
staleLabels.removeAll(visibleTargets);
staleLabels.forEach(this::removeLabel);
}
private HeadTarget findTargetedHead(Player player) {
final double maxRange = Math.max(1D, plugin.getConfig().getDouble("bounty-head-label-range", 8D));
final RayTraceResult rayTrace = player.getWorld().rayTraceBlocks(player.getEyeLocation(),
player.getEyeLocation().getDirection(), maxRange, FluidCollisionMode.NEVER, true);
if (rayTrace == null) {
return null;
}
final Block hitBlock = rayTrace.getHitBlock();
if (hitBlock == null || !isHeadMaterial(hitBlock.getType())) {
return null;
}
final BlockState state = hitBlock.getState();
if (!(state instanceof Skull skull)) {
return null;
}
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(skull.getPersistentDataContainer());
if (headData == null) {
return null;
}
return new HeadTarget(HeadLabelKey.fromBlock(hitBlock), hitBlock.getLocation(), headData);
}
private void upsertLabel(HeadTarget target) {
final ArmorStand existing = activeLabels.get(target.key());
if (existing != null && existing.isValid()) {
existing.customName(plugin.getBountyHeadFormatter().buildHoverLabel(target.data()));
return;
}
final Location labelLocation = target.location().add(0.5D, 0.85D, 0.5D);
final ArmorStand label = target.location().getWorld().spawn(labelLocation, ArmorStand.class, stand -> {
stand.setInvisible(true);
stand.setGravity(false);
stand.setMarker(true);
stand.setSmall(true);
stand.setInvulnerable(true);
stand.setSilent(true);
stand.setPersistent(false);
stand.setBasePlate(false);
stand.setArms(false);
stand.customName(plugin.getBountyHeadFormatter().buildHoverLabel(target.data()));
stand.setCustomNameVisible(true);
});
activeLabels.put(target.key(), label);
}
private void removeLabel(HeadLabelKey key) {
final ArmorStand stand = activeLabels.remove(key);
if (stand != null && stand.isValid()) {
stand.remove();
}
}
private void clearLabels() {
final Set<HeadLabelKey> keys = new HashSet<>(activeLabels.keySet());
keys.forEach(this::removeLabel);
}
private boolean isHoverModeEnabled() {
return StringUtils.equalsIgnoreCase(plugin.getConfig().getString("bounty-head-label-mode", "hover"), "hover");
}
private static boolean isHeadMaterial(Material material) {
return material == Material.PLAYER_HEAD || material == Material.PLAYER_WALL_HEAD;
}
private record HeadTarget(HeadLabelKey key, Location location, BountyHeadData data) {
}
private record HeadLabelKey(UUID worldId, int x, int y, int z) {
private static HeadLabelKey fromBlock(Block block) {
final World world = block.getWorld();
return new HeadLabelKey(world.getUID(), block.getX(), block.getY(), block.getZ());
}
}
}
@@ -1,13 +1,6 @@
package com.tcoded.playerbountiesplus.listener;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.BlockState;
import org.bukkit.block.Skull;
import org.bukkit.entity.Player;
@@ -23,38 +16,20 @@ import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
import com.tcoded.playerbountiesplus.hook.logging.CoreProtectHook;
import net.kyori.adventure.text.TextComponent;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.model.user.User;
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
import net.trueog.utilitiesog.UtilitiesOG;
import com.tcoded.playerbountiesplus.util.BountyHeadData;
public class BountyHeadListener implements Listener {
private final DiamondBankAPIJava diamondBankAPI;
private final NamespacedKey targetKey;
private final NamespacedKey targetUuidKey;
private final NamespacedKey amountKey;
private final LuckPerms luckPerms;
private final PlayerBountiesOG plugin;
private final CoreProtectHook coreProtectHook;
public BountyHeadListener(PlayerBountiesOG plugin, DiamondBankAPIJava diamondBankAPI, LuckPerms luckPerms,
CoreProtectHook coreProtectHook)
{
public BountyHeadListener(PlayerBountiesOG plugin) {
this.diamondBankAPI = diamondBankAPI;
this.targetKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_NAME_KEY);
this.targetUuidKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_UUID_KEY);
this.amountKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_AMOUNT_KEY);
this.luckPerms = luckPerms;
this.coreProtectHook = coreProtectHook;
this.plugin = plugin;
this.coreProtectHook = plugin.getCoreProtectHook();
}
@@ -74,11 +49,8 @@ public class BountyHeadListener implements Listener {
}
final String targetName = itemMeta.getPersistentDataContainer().get(targetKey, PersistentDataType.STRING);
final String targetUuid = itemMeta.getPersistentDataContainer().get(targetUuidKey, PersistentDataType.STRING);
final Double bountyAmount = itemMeta.getPersistentDataContainer().get(amountKey, PersistentDataType.DOUBLE);
if (targetName == null || targetUuid == null || bountyAmount == null) {
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(itemMeta.getPersistentDataContainer());
if (headData == null) {
return;
@@ -91,10 +63,7 @@ public class BountyHeadListener implements Listener {
}
final PersistentDataContainer blockData = skull.getPersistentDataContainer();
blockData.set(targetKey, PersistentDataType.STRING, targetName);
blockData.set(targetUuidKey, PersistentDataType.STRING, targetUuid);
blockData.set(amountKey, PersistentDataType.DOUBLE, bountyAmount);
plugin.getBountyHeadMetadata().write(skull.getPersistentDataContainer(), headData);
skull.update(true, false);
if (this.coreProtectHook != null) {
@@ -122,11 +91,8 @@ public class BountyHeadListener implements Listener {
}
final String targetName = skull.getPersistentDataContainer().get(targetKey, PersistentDataType.STRING);
final String targetUuid = skull.getPersistentDataContainer().get(targetUuidKey, PersistentDataType.STRING);
final Double bountyAmount = skull.getPersistentDataContainer().get(amountKey, PersistentDataType.DOUBLE);
if (targetName == null || targetUuid == null || bountyAmount == null) {
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(skull.getPersistentDataContainer());
if (headData == null) {
return;
@@ -135,10 +101,8 @@ public class BountyHeadListener implements Listener {
final ItemStack drop = new ItemStack(Material.PLAYER_HEAD, 1);
final SkullMeta meta = (SkullMeta) drop.getItemMeta();
meta.setPlayerProfile(skull.getPlayerProfile());
applyBountyData(meta, targetName, targetUuid, bountyAmount);
plugin.getBountyHeadFormatter().applyHeadMeta(meta, headData, skull.getPlayerProfile(),
plugin.getBountyHeadMetadata());
drop.setItemMeta(meta);
if (this.coreProtectHook != null) {
@@ -232,150 +196,16 @@ public class BountyHeadListener implements Listener {
}
private void sendBountyHeadActionbar(Player player, PersistentDataContainer dataContainer) {
private void sendBountyHeadActionbar(Player player, org.bukkit.persistence.PersistentDataContainer dataContainer) {
final String targetName = dataContainer.get(targetKey, PersistentDataType.STRING);
final String targetUuid = dataContainer.get(targetUuidKey, PersistentDataType.STRING);
final Double bountyAmount = dataContainer.get(amountKey, PersistentDataType.DOUBLE);
if (targetName == null || targetUuid == null || bountyAmount == null) {
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(dataContainer);
if (headData == null) {
return;
}
final UUID uuid = parseUuid(targetUuid);
final String playerDisplay = formatLuckPermsDisplay(uuid, targetName);
final String diamonds = formatDiamonds(bountyAmount);
player.sendActionBar(
UtilitiesOG.trueogColorize("&c" + playerDisplay + "'s Head &7- &b" + diamonds + " &bDiamonds"));
}
private void applyBountyData(SkullMeta meta, String targetName, String targetUuid, double bountyAmount) {
meta.displayName(UtilitiesOG.trueogColorize("&c" + targetName + "'s Head"));
final UUID uuid = parseUuid(targetUuid);
final String playerDisplay = formatLuckPermsDisplay(uuid, targetName);
final List<TextComponent> lore = new ArrayList<>();
lore.add(UtilitiesOG.trueogColorize("&6Player: " + playerDisplay));
lore.add(UtilitiesOG.trueogColorize("&cBeheaded for: &b" + formatDiamonds(bountyAmount) + " &bDiamonds"));
meta.lore(lore);
meta.getPersistentDataContainer().set(targetKey, PersistentDataType.STRING, targetName);
meta.getPersistentDataContainer().set(targetUuidKey, PersistentDataType.STRING, targetUuid);
meta.getPersistentDataContainer().set(amountKey, PersistentDataType.DOUBLE, bountyAmount);
}
private UUID parseUuid(String uuidString) {
if (uuidString == null || uuidString.isBlank()) {
return null;
}
try {
return UUID.fromString(uuidString);
} catch (IllegalArgumentException ignored) {
return null;
}
}
private String formatLuckPermsDisplay(UUID targetUuid, String fallbackName) {
final String playerName = StringUtils.defaultIfBlank(fallbackName, "Unknown Player");
if (targetUuid == null || luckPerms == null) {
return "&f" + playerName;
}
final User user = luckPerms.getUserManager().getUser(targetUuid);
if (user == null) {
return "&f" + playerName;
}
final CachedMetaData meta = user.getCachedData().getMetaData();
final String prefix = StringUtils.trim(StringUtils.defaultString(meta.getPrefix()).replace('§', '&'));
final String suffix = StringUtils.trim(StringUtils.defaultString(meta.getSuffix()).replace('§', '&'));
final boolean metaContainsName = containsVisibleName(prefix, playerName)
|| containsVisibleName(suffix, playerName);
final StringBuilder out = new StringBuilder();
if (!prefix.isBlank()) {
out.append(prefix).append(' ');
}
if (!metaContainsName) {
out.append(playerName);
}
if (!suffix.isBlank()) {
out.append(' ').append(suffix);
}
return sanitizeDisplay(out.toString(), playerName);
}
private static String sanitizeDisplay(String formatted, String fallbackName) {
final String cleaned = StringUtils.trimToEmpty(formatted).replaceAll("(?i)(?:\\s*(?:<reset>|[&§]r))+$", "")
.replaceAll("\\s*>$", "");
if (cleaned.isBlank()) {
return fallbackName;
}
return cleaned;
}
private static boolean containsVisibleName(String text, String playerName) {
if (text == null || playerName == null || playerName.isBlank()) {
return false;
}
final String normalizedText = StringUtils.lowerCase(UtilitiesOG.stripFormatting(text), Locale.ROOT);
final String normalizedName = StringUtils.lowerCase(playerName, Locale.ROOT);
return StringUtils.contains(normalizedText, normalizedName);
}
private String formatDiamonds(double diamonds) {
try {
return diamondBankAPI.shardsToDiamonds(diamondBankAPI.diamondsToShards(diamonds));
} catch (RuntimeException runtimeException) {
return String.valueOf(diamonds);
}
player.sendActionBar(plugin.getBountyHeadFormatter().buildActionBar(headData));
}
@@ -6,7 +6,6 @@ import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
@@ -16,20 +15,16 @@ import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.persistence.PersistentDataType;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
import com.tcoded.playerbountiesplus.event.BountyClaimEvent;
import com.tcoded.playerbountiesplus.hook.team.TeamHook;
import com.tcoded.playerbountiesplus.manager.BountyDataManager;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import com.tcoded.playerbountiesplus.util.BountyHeadData;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.model.user.User;
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
import net.trueog.utilitiesog.UtilitiesOG;
public class DeathListener implements Listener {
@@ -39,14 +34,12 @@ public class DeathListener implements Listener {
public static final String BOUNTY_HEAD_AMOUNT_KEY = "bounty_head_diamonds";
private final PlayerBountiesOG plugin;
private final DiamondBankAPIJava diamondBankAPI;
private final LuckPerms luckPerms;
public DeathListener(PlayerBountiesOG plugin, DiamondBankAPIJava diamondBankAPI, LuckPerms luckPerms) {
public DeathListener(PlayerBountiesOG plugin) {
this.plugin = plugin;
this.diamondBankAPI = diamondBankAPI;
this.luckPerms = luckPerms;
this.luckPerms = plugin.getLuckPerms();
}
@@ -214,15 +207,7 @@ public class DeathListener implements Listener {
private String formatDiamonds(double diamonds) {
try {
return diamondBankAPI.shardsToDiamonds(diamondBankAPI.diamondsToShards(diamonds));
} catch (RuntimeException runtimeException) {
return String.valueOf(diamonds);
}
return plugin.getBountyHeadFormatter().formatDiamonds(diamonds);
}
@@ -272,22 +257,10 @@ public class DeathListener implements Listener {
final ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1);
final SkullMeta headMeta = (SkullMeta) head.getItemMeta();
final BountyHeadData headData = new BountyHeadData(victim.getUniqueId(), victim.getName(), bounty);
final PlayerProfile profile = Bukkit.createProfile(victim.getUniqueId());
headMeta.setPlayerProfile(profile);
headMeta.displayName(UtilitiesOG.trueogColorize("&c" + victim.getName() + "'s Head"));
final String bountyDisplay = formatDiamonds(bounty);
headMeta.lore(java.util.List.of(
Component.text("Target: ", NamedTextColor.GRAY)
.append(Component.text(victim.getName(), NamedTextColor.WHITE)),
Component.text("Beheaded for: ", NamedTextColor.GRAY)
.append(Component.text(bountyDisplay + " Diamonds", NamedTextColor.AQUA))));
headMeta.getPersistentDataContainer().set(new NamespacedKey(plugin, BOUNTY_HEAD_NAME_KEY),
PersistentDataType.STRING, victim.getName());
headMeta.getPersistentDataContainer().set(new NamespacedKey(plugin, BOUNTY_HEAD_UUID_KEY),
PersistentDataType.STRING, victim.getUniqueId().toString());
headMeta.getPersistentDataContainer().set(new NamespacedKey(plugin, BOUNTY_HEAD_AMOUNT_KEY),
PersistentDataType.DOUBLE, bounty);
plugin.getBountyHeadFormatter().applyHeadMeta(headMeta, headData, profile, plugin.getBountyHeadMetadata());
head.setItemMeta(headMeta);
event.getDrops().add(head);
@@ -0,0 +1,6 @@
package com.tcoded.playerbountiesplus.util;
import java.util.UUID;
public record BountyHeadData(UUID targetUuid, String targetName, double bountyAmount) {
}
@@ -0,0 +1,167 @@
package com.tcoded.playerbountiesplus.util;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.inventory.meta.SkullMeta;
import com.destroystokyo.paper.profile.PlayerProfile;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.model.user.User;
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
import net.trueog.utilitiesog.UtilitiesOG;
public final class BountyHeadFormatter {
private final DiamondBankAPIJava diamondBankAPI;
private final LuckPerms luckPerms;
public BountyHeadFormatter(DiamondBankAPIJava diamondBankAPI, LuckPerms luckPerms) {
this.diamondBankAPI = diamondBankAPI;
this.luckPerms = luckPerms;
}
public void applyHeadMeta(SkullMeta meta, BountyHeadData data, PlayerProfile profile, BountyHeadMetadata metadata) {
if (profile != null) {
meta.setPlayerProfile(profile);
}
meta.displayName(buildHeadDisplayName(data));
meta.lore(buildHeadLore(data));
metadata.write(meta.getPersistentDataContainer(), data);
}
public TextComponent buildHeadDisplayName(BountyHeadData data) {
return UtilitiesOG
.trueogColorize("&cBounty Head: &r" + resolvePlayerDisplay(data.targetUuid(), data.targetName()));
}
public List<TextComponent> buildHeadLore(BountyHeadData data) {
final String display = resolvePlayerDisplay(data.targetUuid(), data.targetName());
final String bounty = formatDiamonds(data.bountyAmount());
return List.of(UtilitiesOG.trueogColorize("&6Target: " + display),
UtilitiesOG.trueogColorize("&bBounty: &f" + bounty + " &bDiamonds"));
}
public TextComponent buildActionBar(BountyHeadData data) {
return UtilitiesOG
.trueogColorize("&cBounty Head: &r" + resolvePlayerDisplay(data.targetUuid(), data.targetName())
+ " &7- &b" + formatDiamonds(data.bountyAmount()) + " &bDiamonds");
}
public Component buildHoverLabel(BountyHeadData data) {
return UtilitiesOG.trueogColorize(resolvePlayerDisplay(data.targetUuid(), data.targetName()) + " &7- &b"
+ formatDiamonds(data.bountyAmount()) + " &bDiamonds");
}
public String resolvePlayerDisplay(UUID targetUuid, String fallbackName) {
final String playerName = StringUtils.defaultIfBlank(fallbackName, "Unknown Player");
if (targetUuid == null || luckPerms == null) {
return "&f" + playerName;
}
final User user = luckPerms.getUserManager().getUser(targetUuid);
if (user == null) {
return "&f" + playerName;
}
final CachedMetaData meta = user.getCachedData().getMetaData();
final String prefix = StringUtils.trim(StringUtils.defaultString(meta.getPrefix()).replace('§', '&'));
final String suffix = StringUtils.trim(StringUtils.defaultString(meta.getSuffix()).replace('§', '&'));
final boolean metaContainsName = containsVisibleName(prefix, playerName)
|| containsVisibleName(suffix, playerName);
final StringBuilder out = new StringBuilder();
if (!prefix.isBlank()) {
out.append(prefix).append(' ');
}
if (!metaContainsName) {
out.append(playerName);
}
if (!suffix.isBlank()) {
out.append(' ').append(suffix);
}
return sanitizeDisplay(out.toString(), playerName);
}
public String formatDiamonds(double diamonds) {
if (diamondBankAPI == null) {
return String.valueOf(diamonds);
}
try {
return diamondBankAPI.shardsToDiamonds(diamondBankAPI.diamondsToShards(diamonds));
} catch (RuntimeException runtimeException) {
return String.valueOf(diamonds);
}
}
private static String sanitizeDisplay(String formatted, String fallbackName) {
final String cleaned = StringUtils.trimToEmpty(formatted).replaceAll("(?i)(?:\\s*(?:<reset>|[&§]r))+$", "")
.replaceAll("\\s*>$", "");
return cleaned.isBlank() ? fallbackName : cleaned;
}
private static boolean containsVisibleName(String text, String playerName) {
if (text == null || playerName == null || playerName.isBlank()) {
return false;
}
final String normalizedText = StringUtils.lowerCase(UtilitiesOG.stripFormatting(text), Locale.ROOT);
final String normalizedName = StringUtils.lowerCase(playerName, Locale.ROOT);
return StringUtils.contains(normalizedText, normalizedName);
}
}
@@ -0,0 +1,59 @@
package com.tcoded.playerbountiesplus.util;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
import com.tcoded.playerbountiesplus.listener.DeathListener;
public final class BountyHeadMetadata {
private final NamespacedKey targetKey;
private final NamespacedKey targetUuidKey;
private final NamespacedKey amountKey;
public BountyHeadMetadata(PlayerBountiesOG plugin) {
this.targetKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_NAME_KEY);
this.targetUuidKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_UUID_KEY);
this.amountKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_AMOUNT_KEY);
}
public BountyHeadData read(PersistentDataContainer dataContainer) {
final String targetName = dataContainer.get(targetKey, PersistentDataType.STRING);
final String targetUuid = dataContainer.get(targetUuidKey, PersistentDataType.STRING);
final Double bountyAmount = dataContainer.get(amountKey, PersistentDataType.DOUBLE);
if (StringUtils.isBlank(targetName) || StringUtils.isBlank(targetUuid) || bountyAmount == null) {
return null;
}
try {
return new BountyHeadData(UUID.fromString(targetUuid), targetName, bountyAmount);
} catch (IllegalArgumentException ignored) {
return null;
}
}
public void write(PersistentDataContainer dataContainer, BountyHeadData data) {
dataContainer.set(targetKey, PersistentDataType.STRING, data.targetName());
dataContainer.set(targetUuidKey, PersistentDataType.STRING, data.targetUuid().toString());
dataContainer.set(amountKey, PersistentDataType.DOUBLE, data.bountyAmount());
}
}
+8
View File
@@ -36,6 +36,14 @@ bounty-claimed-announce: true
# Chance (0-100) that claiming a bounty drops the victim's player head.
bounty-head-drop-chance: 50.0
# How placed bounty heads should display extra labels.
# none = disabled
# hover = show a temporary floating label while a player aims at the head
bounty-head-label-mode: hover
# Maximum range in blocks for detecting a looked-at bounty head.
bounty-head-label-range: 8.0
# If we should announce that the bounty was placed.
bounty-placed-announce: true