diff --git a/.gradle/8.8/executionHistory/executionHistory.bin b/.gradle/8.8/executionHistory/executionHistory.bin index ea62791..71f9e97 100644 Binary files a/.gradle/8.8/executionHistory/executionHistory.bin and b/.gradle/8.8/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.8/executionHistory/executionHistory.lock b/.gradle/8.8/executionHistory/executionHistory.lock index c821e11..bf8b0e4 100644 Binary files a/.gradle/8.8/executionHistory/executionHistory.lock and b/.gradle/8.8/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.8/fileHashes/fileHashes.bin b/.gradle/8.8/fileHashes/fileHashes.bin index fa77287..2f68423 100644 Binary files a/.gradle/8.8/fileHashes/fileHashes.bin and b/.gradle/8.8/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.8/fileHashes/fileHashes.lock b/.gradle/8.8/fileHashes/fileHashes.lock index 863398f..182eb95 100644 Binary files a/.gradle/8.8/fileHashes/fileHashes.lock and b/.gradle/8.8/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.8/fileHashes/resourceHashesCache.bin b/.gradle/8.8/fileHashes/resourceHashesCache.bin index b061068..da2c5a1 100644 Binary files a/.gradle/8.8/fileHashes/resourceHashesCache.bin and b/.gradle/8.8/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index adecda2..fde4444 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin index b1be63c..45a82f4 100644 Binary files a/build/tmp/compileJava/previous-compilation-data.bin and b/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/src/main/java/me/trouper/armorsmp/data/Unique.java b/src/main/java/me/trouper/armorsmp/data/Unique.java index 0c6ac1f..6673dd3 100644 --- a/src/main/java/me/trouper/armorsmp/data/Unique.java +++ b/src/main/java/me/trouper/armorsmp/data/Unique.java @@ -1,25 +1,24 @@ package me.trouper.armorsmp.data; import io.github.itzispyder.pdk.plugin.builders.ItemBuilder; +import io.github.itzispyder.pdk.utils.misc.SoundPlayer; import io.github.itzispyder.pdk.utils.raytracers.CustomDisplayRaytracer; import me.trouper.armorsmp.ArmorSMP; +import me.trouper.armorsmp.utils.Display; import me.trouper.armorsmp.utils.Text; import me.trouper.armorsmp.utils.Verbose; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.World; -import org.bukkit.block.Block; +import me.trouper.armorsmp.utils.WorldUtils; +import org.bukkit.*; import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.AreaEffectCloud; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; +import org.bukkit.entity.*; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Stream; @@ -38,8 +37,8 @@ public enum Unique { .customModelData(2) .build(), "Netherite Helmet", (p) -> { - p.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE,21,1,true,false,false)); - p.addPotionEffect(new PotionEffect(PotionEffectType.HEALTH_BOOST,25,3,true,false,false)); + p.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE,21,0,true,false,false)); + p.addPotionEffect(new PotionEffect(PotionEffectType.HEALTH_BOOST,25,2,true,false,false)); }, (p) -> { }, 50), CHESTPLATE(ItemBuilder.create() @@ -58,7 +57,6 @@ public enum Unique { p.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH,21,0,true,false,false)); p.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE,21,0,true,false,false)); }, (p) -> { - // TODO: Test Dragon's Breath World world = p.getWorld(); Location eyeLocation = p.getEyeLocation(); @@ -76,7 +74,6 @@ public enum Unique { cloud.setParticle(Particle.DRAGON_BREATH); cloud.addCustomEffect(PotionEffectType.INSTANT_DAMAGE.createEffect(1,1),true); cloud.setOwnerUniqueId(p.getUniqueId()); - }, 50), LEGGINGS(ItemBuilder.create() .material(Material.NETHERITE_LEGGINGS) @@ -94,6 +91,7 @@ public enum Unique { p.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE,21,0,true,false,false)); }, (p) -> { // TODO: Test Shield + Stream toKnockBack = p.getNearbyEntities(10,10,10).stream().filter(target -> { if (!(target instanceof Player v)) return false; boolean tooClose = target.getLocation().distance(p.getLocation()) < 10; @@ -106,9 +104,37 @@ public enum Unique { direction.normalize(); direction.multiply(2); direction.setY(direction.getY() + 0.5); - + target.setVelocity(direction); })); + AtomicBoolean cancelShield = new AtomicBoolean(); + Bukkit.getScheduler().runTaskLater(ArmorSMP.getInstance(),(task)->{ + cancelShield.set(true); + },120); + Bukkit.getScheduler().runTaskTimer(ArmorSMP.getInstance(), (task) -> { + if (cancelShield.get()) task.cancel(); + World w = p.getWorld(); + Particle.DustOptions dust = new Particle.DustOptions(Color.AQUA, 0.5F); + Display.sphere(p.getEyeLocation(), 5.0, 0.5, 0.5, point -> { + w.spawnParticle(Particle.DUST, point, 1, 0, 0, 0, 0, dust); + }); + + List targets = new ArrayList<>(w.getNearbyEntities(p.getLocation(), 10, 10, 10, entity -> + entity instanceof LivingEntity living && !living.isDead() && living != p && living.getLocation().distance(p.getLocation()) < 5 + )); + + targets.forEach(target -> { + if (target instanceof LivingEntity living) { + SoundPlayer blockSound = new SoundPlayer(target.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1, 1); + Vector direction = target.getLocation().toVector().subtract(p.getEyeLocation().toVector()).normalize(); + double strength = 2.0; + double verticalMultiplier = 0.5; + living.setVelocity(direction.multiply(strength).setY(verticalMultiplier)); + blockSound.playWithin(10); + w.spawnParticle(Particle.POOF,target.getLocation().clone().add(0,1,0),10,0.2,1,0.2,0.1); + } + }); + }, 0,2); }, 45), BOOTS(ItemBuilder.create() .material(Material.NETHERITE_BOOTS) @@ -175,14 +201,14 @@ public enum Unique { private final String canonical; private final Consumer passiveAbility; private final Consumer ability; - private final int abilityCooldownTicks; + private final int abilityCooldownSeconds; - Unique(ItemStack inGame, String canonical, Consumer passiveAbility, Consumer ability, int abilityCooldownTicks) { + Unique(ItemStack inGame, String canonical, Consumer passiveAbility, Consumer ability, int abilityCooldownSeconds) { this.inGame = inGame; this.canonical = canonical; this.passiveAbility = passiveAbility; this.ability = ability; - this.abilityCooldownTicks = abilityCooldownTicks; + this.abilityCooldownSeconds = abilityCooldownSeconds; } public ItemStack getInGameItem() { @@ -191,7 +217,7 @@ public enum Unique { public static boolean isUnique(ItemStack i) { for (Unique value : values()) { - if (value.getInGameItem().isSimilar(i)) return true; + if (WorldUtils.isSimilar(value.getInGameItem(),i)) return true; if (i.getType().equals(Material.DRAGON_EGG)) return true; } return false; @@ -201,7 +227,7 @@ public enum Unique { Verbose.send("Matching Unique, Item Type: %s",i.getType()); Unique match = null; for (Unique value : values()) { - if (value.getInGameItem().isSimilar(i)) { + if (WorldUtils.isSimilar(value.getInGameItem(),i)) { match = value; Verbose.send("Matched with, Unique: %s",match); } @@ -225,7 +251,7 @@ public enum Unique { return passiveAbility; } - public int getAbilityCooldownTicks() { - return abilityCooldownTicks; + public int getAbilityCooldownSeconds() { + return abilityCooldownSeconds; } } diff --git a/src/main/java/me/trouper/armorsmp/data/io/Config.java b/src/main/java/me/trouper/armorsmp/data/io/Config.java index 63b8633..b44a146 100644 --- a/src/main/java/me/trouper/armorsmp/data/io/Config.java +++ b/src/main/java/me/trouper/armorsmp/data/io/Config.java @@ -5,12 +5,14 @@ import me.trouper.armorsmp.ArmorSMP; import me.trouper.armorsmp.utils.Verbose; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Config implements JsonSerializable { public boolean debugMode = false; + public List debuggerExclusions = new ArrayList<>(); @Override public File getFile() { diff --git a/src/main/java/me/trouper/armorsmp/server/commands/AbilityCommand.java b/src/main/java/me/trouper/armorsmp/server/commands/AbilityCommand.java index 51d01e2..8c20cf5 100644 --- a/src/main/java/me/trouper/armorsmp/server/commands/AbilityCommand.java +++ b/src/main/java/me/trouper/armorsmp/server/commands/AbilityCommand.java @@ -14,7 +14,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import java.util.Arrays; -import java.util.Objects; import java.util.UUID; @CommandRegistry(value = "ability", printStackTrace = true, playersOnly = true) @@ -39,7 +38,7 @@ public class AbilityCommand implements CustomCommand { return; } piece.getAbility().accept(p); - abilityCooldown.addCooldown(Pair.of(piece,p.getUniqueId()),piece.getAbilityCooldownTicks() * 50L); + abilityCooldown.addCooldown(Pair.of(piece,p.getUniqueId()),piece.getAbilityCooldownSeconds() * 1000L); Text.sendMessage(false, Text.Pallet.SUCCESS,sender,"Successfully used your {0}'s ability!",piece.getCanonical()); } diff --git a/src/main/java/me/trouper/armorsmp/server/commands/AdminCommand.java b/src/main/java/me/trouper/armorsmp/server/commands/AdminCommand.java index 65e6a29..e0c7085 100644 --- a/src/main/java/me/trouper/armorsmp/server/commands/AdminCommand.java +++ b/src/main/java/me/trouper/armorsmp/server/commands/AdminCommand.java @@ -11,6 +11,7 @@ import me.trouper.armorsmp.data.io.Config; import me.trouper.armorsmp.data.Unique; import me.trouper.armorsmp.server.crafting.ArmorUpgrade; import me.trouper.armorsmp.utils.Text; +import me.trouper.armorsmp.utils.Verbose; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.Command; @@ -30,6 +31,7 @@ public class AdminCommand implements CustomCommand { } String sub = args.get(0).toString(); switch (sub) { + case "debug" -> handleDebug(sender,args); case "reload" -> handleReload(sender,args); case "change" -> handleChange(sender, args); case "give" -> handleGive(sender, args); @@ -39,29 +41,93 @@ public class AdminCommand implements CustomCommand { default -> Text.sendError(sender, "Error: Valid sub-commands are: [change, give, toggle, reset, remove]"); } } - + @Override public void dispatchCompletions(CommandSender commandSender, Command command, String label, CompletionBuilder b) { b.then( + b.arg("reload") + ).then( + b.arg("debug") + .then( + b.arg("toggle") + ) + .then( + b.arg("include","exclude") + .then( + b.arg("Class.method"))) + ).then( b.arg("change") - .then(b.argEnum(ArmorTier.class).then(b.argOnlinePlayers())) + .then( + b.argEnum(ArmorTier.class).then( + b.argOnlinePlayers())) ).then( b.arg("give") - .then(b.arg("upgrader") - .then(b.argOnlinePlayers())) + .then( + b.arg("upgrader") + .then( + b.argOnlinePlayers())) .then(b.arg("unique") - .then(b.argEnum(Unique.class) - .then(b.argOnlinePlayers()))) + .then( + b.argEnum(Unique.class) + .then( + b.argOnlinePlayers()))) ).then( b.arg("reset") .then(b.arg("mace")) ).then( b.arg("remove") - .then(b.arg("unique") - .then(b.argEnum(Unique.class) - .then(b.argOnlinePlayers()))) - ).then(b.arg("toggle") - .then(b.arg("end","nether","mace","debug"))); + .then( + b.arg("unique") + .then( + b.argEnum(Unique.class) + .then( + b.argOnlinePlayers()))) + ).then( + b.arg("toggle") + .then( + b.arg("end","nether","mace"))); + } + + private void handleDebug(CommandSender sender, Args args) { + if (args.getSize() < 2) { + Text.sendError(sender, "Usage: /armorsmp debug "); + return; + } + + final String sub = args.get(1).toString(); + Config config = ArmorSMP.getInstance().getManager().io.config; + + switch (sub) { + case "toggle" -> { + boolean result = false; + config.debugMode = result = !config.debugMode; + config.save(); + + Text.sendMessage(false, Text.Pallet.SUCCESS,sender,"Toggled debug mode {0}.",result ? "on" : "off"); + } + case "exclude" -> { + if (args.getSize() < 3) { + Text.sendError(sender, "Usage: /armorsmp debug exclude "); + return; + } + final String exclusion = args.get(2).toString(); + config.debuggerExclusions.add(exclusion); + config.save(); + + Text.sendMessage(false, Text.Pallet.SUCCESS, sender, "Excluded {0} from the debugger.", exclusion); + } + case "include" -> { + if (args.getSize() < 3) { + Text.sendError(sender, "Usage: /armorsmp debug include "); + return; + } + final String exclusion = args.get(2).toString(); + config.debuggerExclusions.remove(exclusion); + config.save(); + + Text.sendMessage(false, Text.Pallet.SUCCESS, sender, "Removed exclusion for {0} on the debugger.", exclusion); + } + } } private void handleReload(CommandSender sender, Args args) { @@ -170,11 +236,6 @@ public class AdminCommand implements CustomCommand { config.save(); feature = "Mace Crafting"; } - case "debug" -> { - config.debugMode = result = !config.debugMode; - config.save(); - feature = "Debug Mode"; - } default -> { Text.sendMessage(false, Text.Pallet.ERROR, sender, "Error: {0} is not a valid feature.", feature); @@ -225,7 +286,7 @@ public class AdminCommand implements CustomCommand { return; } - ArmorSMP.getInstance().getManager().io.storage.uniques.owners.remove(piece); + ArmorSMP.getInstance().getManager().uniques.dropUnique(target,piece); ArmorSMP.getInstance().getManager().armor.queueUpdate(target, true); ArmorSMP.getInstance().getManager().uniques.queueUpdate(target); diff --git a/src/main/java/me/trouper/armorsmp/server/events/JoinEvent.java b/src/main/java/me/trouper/armorsmp/server/events/JoinEvent.java index 8fa4b5f..76542fb 100644 --- a/src/main/java/me/trouper/armorsmp/server/events/JoinEvent.java +++ b/src/main/java/me/trouper/armorsmp/server/events/JoinEvent.java @@ -31,17 +31,22 @@ public class JoinEvent implements CustomListener { private void handleUpdates(Player p) { Verbose.send("Checking for updates needed on %s",p.getName()); final Map armorCache = ArmorSMP.getInstance().getManager().io.storage.armorUpdateCache; + final Set uniquesCache = ArmorSMP.getInstance().getManager().io.storage.uniqueUpdateCache; if (!ArmorSMP.getInstance().getManager().armor.verifyArmor(p)) { Verbose.send("Updating armor"); ArmorSMP.getInstance().getManager().armor.queueUpdate(p,armorCache.getOrDefault(p.getUniqueId().toString(),true)); } + if (!ArmorSMP.getInstance().getManager().uniques.verifyUniques(p)) { + Verbose.send("Updating uniques"); + ArmorSMP.getInstance().getManager().uniques.queueUpdate(p); + } if (armorCache.containsKey(p.getUniqueId().toString())) { Verbose.send("Updating armor from cache"); ArmorSMP.getInstance().getManager().armor.queueUpdate(p,armorCache.getOrDefault(p.getUniqueId().toString(),true)); ArmorSMP.getInstance().getManager().io.storage.armorUpdateCache.remove(p.getUniqueId().toString()); ArmorSMP.getInstance().getManager().io.storage.save(); } - final Set uniquesCache = ArmorSMP.getInstance().getManager().io.storage.uniqueUpdateCache; + if (uniquesCache.contains(p.getUniqueId().toString())) { Verbose.send("Updating uniques"); ArmorSMP.getInstance().getManager().uniques.queueUpdate(p); diff --git a/src/main/java/me/trouper/armorsmp/server/systems/ArmorBackend.java b/src/main/java/me/trouper/armorsmp/server/systems/ArmorBackend.java index 2d8a339..df019b8 100644 --- a/src/main/java/me/trouper/armorsmp/server/systems/ArmorBackend.java +++ b/src/main/java/me/trouper/armorsmp/server/systems/ArmorBackend.java @@ -182,7 +182,6 @@ public class ArmorBackend { || unique.equals(Unique.BOOTS) ) { storage.uniques.owners.remove(unique); - //target.getLocation().getWorld().dropItem(target.getLocation(),unique.getInGameItem()); } if (unique.equals(Unique.CHESTPLATE)) { target.getLocation().getWorld().dropItem(target.getLocation(),new ItemStack(Material.DRAGON_EGG)); diff --git a/src/main/java/me/trouper/armorsmp/server/systems/UniquesBackend.java b/src/main/java/me/trouper/armorsmp/server/systems/UniquesBackend.java index a75e641..90b331f 100644 --- a/src/main/java/me/trouper/armorsmp/server/systems/UniquesBackend.java +++ b/src/main/java/me/trouper/armorsmp/server/systems/UniquesBackend.java @@ -9,7 +9,10 @@ import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; public class UniquesBackend { @@ -58,7 +61,7 @@ public class UniquesBackend { public void dropUniqueItems(Player p) { storage.uniques.owners.forEach((unique,owner)->{ - if (unique.equals(Unique.MACE) || unique.equals(Unique.SWORD) || unique.equals(Unique.AXE) && owner.equals(p.getUniqueId().toString()) && !p.getInventory().contains(unique.getInGameItem())) { + if (unique.equals(Unique.MACE) || unique.equals(Unique.SWORD) || unique.equals(Unique.AXE) && owner.equals(p.getUniqueId().toString())) { storage.uniques.owners.remove(unique); } }); @@ -66,9 +69,37 @@ public class UniquesBackend { } public void applyPersistence() { + Bukkit.getOnlinePlayers().forEach(this::applyEffects); + } + + public void applyEffects(Player p) { storage.uniques.owners.forEach((unique, owner) -> { - Player p = Bukkit.getPlayer(owner); - if (p != null && p.isOnline()) unique.getPassiveAbility().accept(p); + if (p != null && owner.equals(p.getUniqueId().toString()) && p.isOnline()) { + unique.getPassiveAbility().accept(p); + } }); } + + public boolean verifyUniques(Player p) { + AtomicBoolean success = new AtomicBoolean(false); + List checks = new ArrayList<>(); + ArmorSMP.getInstance().getManager().io.storage.uniques.owners.forEach(((unique, id) -> { + if (unique.equals(Unique.HELMET) + || unique.equals(Unique.CHESTPLATE) + || unique.equals(Unique.LEGGINGS) + || unique.equals(Unique.BOOTS) + ) return; + if (id.equals(p.getUniqueId().toString())) { + Verbose.send(1, "They own the unique, checking if they have it: ", p.getInventory().contains(unique.getInGameItem())); + if (p.getInventory().contains(unique.getInGameItem())) { + Verbose.send(1, "They have it"); + checks.add(true); + } else { + Verbose.send(1, "They don't have it"); + checks.add(false); + } + } + })); + return !checks.contains(false); + } } diff --git a/src/main/java/me/trouper/armorsmp/utils/Display.java b/src/main/java/me/trouper/armorsmp/utils/Display.java index 86dfe9c..205c4a8 100644 --- a/src/main/java/me/trouper/armorsmp/utils/Display.java +++ b/src/main/java/me/trouper/armorsmp/utils/Display.java @@ -17,6 +17,25 @@ import java.util.function.Function; public class Display implements Global { + + public static void sphere(Location center, double radius, double verticalStep, double horizontalSpacing, Consumer action) { + for (double yOffset = -radius; yOffset <= radius; yOffset += verticalStep) { + double horizontalRadius = Math.sqrt(radius * radius - yOffset * yOffset); + + if (horizontalRadius < 0.1) continue; + + double circumference = 2 * Math.PI * horizontalRadius; + int points = Math.max(4, (int) (circumference / horizontalSpacing)); + double angleStep = 360.0 / points; + + for (double theta = 0; theta < 360; theta += angleStep) { + double x = Math.cos(Math.toRadians(theta)) * horizontalRadius; + double z = Math.sin(Math.toRadians(theta)) * horizontalRadius; + Location point = center.clone().add(x, yOffset, z); + action.accept(point); + } + } + } public static final Function> PARTICLE_FACTORY = particle -> l -> l.getWorld().spawnParticle(particle, l, 1, 0, 0, 0, 0); public static final BiFunction> DUST_PARTICLE_FACTORY = (color, thickness) -> { Particle.DustOptions dust = new Particle.DustOptions(color, thickness); diff --git a/src/main/java/me/trouper/armorsmp/utils/Verbose.java b/src/main/java/me/trouper/armorsmp/utils/Verbose.java index 8a10237..cb64992 100644 --- a/src/main/java/me/trouper/armorsmp/utils/Verbose.java +++ b/src/main/java/me/trouper/armorsmp/utils/Verbose.java @@ -4,8 +4,13 @@ import me.trouper.armorsmp.ArmorSMP; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -public class Verbose { +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +public class Verbose { + public static void send(int backtrace, String message, Object... args) { if (!ArmorSMP.getInstance().getManager().io.config.debugMode) return; String callerInfo = "Unknown Caller"; @@ -23,7 +28,9 @@ public class Verbose { callerInfo = className + "." + caller.getMethodName(); } - + if (ArmorSMP.getInstance().getManager().io.config.debuggerExclusions.contains(callerInfo)) { + return; + } } String formattedMessage = message.formatted(args); @@ -52,8 +59,10 @@ public class Verbose { } else { callerInfo = className + "." + caller.getMethodName(); } - - + + if (ArmorSMP.getInstance().getManager().io.config.debuggerExclusions.contains(callerInfo)) { + return; + } } String formattedMessage = message.formatted(args); diff --git a/src/main/java/me/trouper/armorsmp/utils/WorldUtils.java b/src/main/java/me/trouper/armorsmp/utils/WorldUtils.java index b52fdb5..c74a7f1 100644 --- a/src/main/java/me/trouper/armorsmp/utils/WorldUtils.java +++ b/src/main/java/me/trouper/armorsmp/utils/WorldUtils.java @@ -2,14 +2,15 @@ package me.trouper.armorsmp.utils; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; public class WorldUtils { public static boolean notDroppable(ItemStack i) { - return isArmor(i) && !isUnique(i) && !isChestplateUnique(i); + return isArmor(i) && !isUnique(i) && !isDragonEggEquivalent(i); } - private static boolean isChestplateUnique(ItemStack i) { + private static boolean isDragonEggEquivalent(ItemStack i) { Material m = i.getType(); return m.equals(Material.NETHERITE_CHESTPLATE); } @@ -26,9 +27,20 @@ public class WorldUtils { public static boolean isUnique(ItemStack i) { Material m = i.getType(); String n = m.name(); - return n.contains("NETHERITE") + return (n.contains("NETHERITE") && isArmor(i)) || n.contains("MACE") || n.contains("NETHERITE_SWORD") || n.contains("NETHERITE_AXE"); } + + public static boolean isSimilar(ItemStack item1, ItemStack item2) { + if (item1 == null || item2 == null) return false; + if (item1.getType() != item2.getType()) return false; + if (item1.hasItemMeta() != item2.hasItemMeta()) return false; + + ItemMeta meta1 = item1.getItemMeta(); + ItemMeta meta2 = item2.getItemMeta(); + + return meta1 == null || meta2 == null || meta1.equals(meta2); + } }