From 9345efe1d45716419785191db41b760ea37911c9 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Mon, 27 Jan 2025 08:45:52 +0100 Subject: [PATCH] Initial durability module --- README.md | 4 + .../java/eu/m724/tweaks/TweaksPlugin.java | 3 + .../module/durability/DurabilityCaches.java | 59 +++++++ .../module/durability/DurabilityModule.java | 151 ++++++++++++++++++ src/main/resources/plugin.yml | 5 + 5 files changed, 222 insertions(+) create mode 100644 src/main/java/eu/m724/tweaks/module/durability/DurabilityCaches.java create mode 100644 src/main/java/eu/m724/tweaks/module/durability/DurabilityModule.java diff --git a/README.md b/README.md index f748457..4fbf2f2 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,10 @@ Quickly kills (terminates) the server on trigger, via command or HTTP request. ### Swing through grass Self-explanatory +### Durability alert +Self-explanatory too. \ +For simplicity, there's no configuration. Control with `tweaks724.durabilityalert` + ### Utility commands - `/ping` - displays player ping P \ diff --git a/src/main/java/eu/m724/tweaks/TweaksPlugin.java b/src/main/java/eu/m724/tweaks/TweaksPlugin.java index 8c6430e..7da3a4a 100644 --- a/src/main/java/eu/m724/tweaks/TweaksPlugin.java +++ b/src/main/java/eu/m724/tweaks/TweaksPlugin.java @@ -14,6 +14,7 @@ import eu.m724.tweaks.module.auth.AuthModule; import eu.m724.tweaks.module.chat.ChatModule; import eu.m724.tweaks.module.door.DoorKnockModule; import eu.m724.tweaks.module.door.DoorOpenModule; +import eu.m724.tweaks.module.durability.DurabilityModule; import eu.m724.tweaks.module.full.FullModule; import eu.m724.tweaks.module.hardcore.HardcoreModule; import eu.m724.tweaks.module.killswitch.KillswitchModule; @@ -154,6 +155,8 @@ public class TweaksPlugin extends MStatsPlugin { TweaksModule.init(SwingModule.class); } + TweaksModule.init(DurabilityModule.class); + /* end modules */ if (config.metrics()) { diff --git a/src/main/java/eu/m724/tweaks/module/durability/DurabilityCaches.java b/src/main/java/eu/m724/tweaks/module/durability/DurabilityCaches.java new file mode 100644 index 0000000..4ab41d7 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/module/durability/DurabilityCaches.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2025 Minecon724 + * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file + * in the project root for the full license text. + */ + +package eu.m724.tweaks.module.durability; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; + +public class DurabilityCaches { + private final Map lastFullReminder = new HashMap<>(); + // BAD + private final Map> lastUse = new HashMap<>(); + // BAD + private final Map> lastPing = new HashMap<>(); + + boolean shouldFullRemind(Player player, long now) { + var lfr = lastFullReminder.getOrDefault(player, 0L); + + if (now - lfr > 300 * 1000) { + lastFullReminder.put(player, now); + return true; + } else if (now - lfr < 3 * 1000) { + return true; + } + + return false; + } + + boolean shouldRemind(Player player, ItemStack itemStack, long now) { + var lu = lastUse.computeIfAbsent(player, (k) -> new HashMap<>()).getOrDefault(itemStack.getType(), 0L); + + if (now - lu > 180 * 1000) { + lastUse.get(player).put(itemStack.getType(), now); + return true; + } else if (now - lu < 3 * 1000) { + return true; + } + + return false; + } + + boolean shouldPing(Player player, ItemStack itemStack, long now) { + var lp = lastPing.computeIfAbsent(player, (k) -> new HashMap<>()).getOrDefault(itemStack.getType(), 0L); + + if (now - lp > 60 * 1000) { + lastPing.get(player).put(itemStack.getType(), now); + return true; + } + + return false; + } +} diff --git a/src/main/java/eu/m724/tweaks/module/durability/DurabilityModule.java b/src/main/java/eu/m724/tweaks/module/durability/DurabilityModule.java new file mode 100644 index 0000000..f8bde5a --- /dev/null +++ b/src/main/java/eu/m724/tweaks/module/durability/DurabilityModule.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2025 Minecon724 + * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file + * in the project root for the full license text. + */ + +package eu.m724.tweaks.module.durability; + +import eu.m724.tweaks.DebugLogger; +import eu.m724.tweaks.module.TweaksModule; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.ComponentBuilder; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.scheduler.BukkitRunnable; + +import java.awt.Color; + +public class DurabilityModule extends TweaksModule implements Listener { + private final DurabilityCaches cache = new DurabilityCaches(); + + @Override + protected void onInit() { + registerEvents(this); + + new BukkitRunnable() { + @Override + public void run() { + Bukkit.getServer().getOnlinePlayers().forEach(p -> refreshBar(p)); + } + }.runTaskTimerAsynchronously(getPlugin(), 0, 40); + } + + @EventHandler + public void onPlayerItemDamage(PlayerItemDamageEvent event) { + refreshBar(event.getPlayer(), event.getItem(), event.getDamage()); + } + + private void refreshBar(Player player) { + refreshBar(player, null, -1); + } + + private void refreshBar(Player player, ItemStack justDamaged, int damage) { + var items = new ItemStack[] { + player.getInventory().getHelmet(), + player.getInventory().getChestplate(), + player.getInventory().getLeggings(), + player.getInventory().getBoots(), + player.getInventory().getItemInMainHand(), + player.getInventory().getItemInOffHand() + }; + + var builder = new ComponentBuilder(); + + var now = System.currentTimeMillis(); + var all = cache.shouldFullRemind(player, now); + + for (var itemStack : items) { + if (itemStack == null || !itemStack.hasItemMeta()) continue; + + if (itemStack.getItemMeta() instanceof Damageable meta) { + var target = itemStack.equals(justDamaged); + + var maxDurability = itemStack.getType().getMaxDurability(); + var durability = maxDurability - meta.getDamage() - (target ? damage : 0); + durability = Math.max(0, durability); + var percentage = (double) durability / maxDurability; + + var notify = durability < 30 && (durability < 10 || percentage < 0.1); + + var remind = cache.shouldRemind(player, itemStack, now); + var important = target && notify && cache.shouldPing(player, itemStack, now); + + DebugLogger.finer("%s's %s: %d / %d (%.2f%%)%s%s", player.getName(), itemStack.getType().name(), durability, maxDurability, percentage * 100, notify ? " notify" : "", important ? " important" : ""); + + if (notify || all || remind) { + var longName = remind || important; + var label = longName ? getMaterialLongName(itemStack.getType()) : getMaterialShortName(itemStack.getType()); + var labelColor = percentage > 0 ? matColor(itemStack.getType()) : ChatColor.DARK_RED; + + var percentageStr = (int) (percentage * 100) + "%"; + var percentageColor = mixColor(labelColor, ChatColor.DARK_RED, 1.0 - percentage * 10); + + builder.append(label + " ").color(labelColor); + builder.append(percentageStr + " ").color(percentageColor); + + if (important) { + player.playSound(player, Sound.BLOCK_ANVIL_PLACE, 0.5f, 1.5f); + player.sendTitle("", labelColor + label + " " + percentageColor + percentageStr, 5, 20, 5); + } + } + } + } + + var component = builder.create(); + if (component.length > 0) + player.spigot().sendMessage(ChatMessageType.ACTION_BAR, component); + } + + private String getMaterialLongName(Material material) { + var sp = material.name().split("_"); + var str = sp[sp.length - 1]; + return str.charAt(0) + str.substring(1).toLowerCase(); + } + + private String getMaterialShortName(Material material) { + return getMaterialLongName(material).substring(0, 2); + } + + private ChatColor mixColor(ChatColor from, ChatColor to, double percentage) { + percentage = Math.clamp(percentage, 0.0, 1.0); + + var diffR = to.getColor().getRed() - from.getColor().getRed(); + var diffG = to.getColor().getGreen() - from.getColor().getGreen(); + var diffB = to.getColor().getBlue() - from.getColor().getBlue(); + + var r = from.getColor().getRed() + (int) (diffR * percentage); + var g = from.getColor().getGreen() + (int) (diffG * percentage); + var b = from.getColor().getBlue() + (int) (diffB * percentage); + + return ChatColor.of(new Color(r, g, b)); + } + + private ChatColor matColor(Material material) { + var color = ChatColor.DARK_GRAY; + + if (material.name().startsWith("DIAMOND_")) { + color = ChatColor.AQUA; + } else if (material.name().startsWith("NETHERITE_")) { + color = ChatColor.DARK_PURPLE; + } else if (material.name().startsWith("IRON_")) { + color = ChatColor.WHITE; + } else if (material.name().startsWith("STONE_")) { + color = ChatColor.GRAY; + } else if (material.name().startsWith("WOODEN_")) { + color = ChatColor.DARK_GREEN; + } else if (material.name().startsWith("GOLDEN_")) { + color = ChatColor.GOLD; + } + + return color; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5b193b2..5a84f24 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -40,6 +40,9 @@ commands: servkill: description: Immediately stop the server permission: tweaks724.servkill + durabilityalert: + description: Durability alert toggle + permission: tweaks724.durabilityalert permissions: tweaks724.chatmanage: @@ -58,6 +61,8 @@ permissions: default: op tweaks724.servkill: default: false + tweaks724.durabilityalert: + default: true 7weaks724.ignore.this: description: "Internal, not for use. ${project.spigot.version}"