This commit is contained in:
		
					parent
					
						
							
								2761ed8757
							
						
					
				
			
			
				commit
				
					
						9345efe1d4
					
				
			
		
					 5 changed files with 222 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -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 <sup><sub>P</sub></sup> \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Player, Long> lastFullReminder = new HashMap<>();
 | 
			
		||||
    //                              BAD
 | 
			
		||||
    private final Map<Player, Map<Material, Long>> lastUse = new HashMap<>();
 | 
			
		||||
    //                              BAD
 | 
			
		||||
    private final Map<Player, Map<Material, Long>> 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue