diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/data/JsonSerializable.java b/src/main/java/me/trouper/alias/data/JsonSerializable.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/data/enums/ValidMaterial.java b/src/main/java/me/trouper/alias/data/enums/ValidMaterial.java new file mode 100644 index 0000000..d799707 --- /dev/null +++ b/src/main/java/me/trouper/alias/data/enums/ValidMaterial.java @@ -0,0 +1,41 @@ +package me.trouper.alias.data.enums; + +import org.bukkit.Material; +import org.bukkit.inventory.meta.trim.TrimMaterial; +import org.bukkit.inventory.meta.trim.TrimPattern; + +public enum ValidMaterial { + AMETHYST(TrimMaterial.AMETHYST), + COPPER(TrimMaterial.COPPER), + DIAMOND(TrimMaterial.DIAMOND), + EMERALD(TrimMaterial.EMERALD), + GOLD(TrimMaterial.GOLD), + IRON(TrimMaterial.IRON), + LAPIS(TrimMaterial.LAPIS), + NETHERITE(TrimMaterial.NETHERITE), + QUARTZ(TrimMaterial.QUARTZ), + REDSTONE(TrimMaterial.REDSTONE); + + private final TrimMaterial canonical; + + ValidMaterial(TrimMaterial canonical) { + this.canonical = canonical; + } + + public TrimMaterial getCanonical() { + return canonical; + } + + public static ValidMaterial validate(TrimMaterial material) { + for (ValidMaterial value : ValidMaterial.values()) { + if (!value.getCanonical().equals(material)) continue; + return value; + } + return null; + } + + public static TrimMaterial validate(String name) { + name = name.toUpperCase(); + return ValidMaterial.valueOf(name).getCanonical(); + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/alias/data/enums/ValidPattern.java b/src/main/java/me/trouper/alias/data/enums/ValidPattern.java new file mode 100644 index 0000000..4da8834 --- /dev/null +++ b/src/main/java/me/trouper/alias/data/enums/ValidPattern.java @@ -0,0 +1,54 @@ +package me.trouper.alias.data.enums; + +import org.bukkit.Material; +import org.bukkit.inventory.meta.trim.TrimPattern; + +public enum ValidPattern { + BOLT(TrimPattern.BOLT, Material.BOLT_ARMOR_TRIM_SMITHING_TEMPLATE), + COAST(TrimPattern.COAST, Material.COAST_ARMOR_TRIM_SMITHING_TEMPLATE), + DUNE(TrimPattern.DUNE, Material.DUNE_ARMOR_TRIM_SMITHING_TEMPLATE), + EYE(TrimPattern.EYE, Material.EYE_ARMOR_TRIM_SMITHING_TEMPLATE), + FLOW(TrimPattern.FLOW, Material.FLOW_ARMOR_TRIM_SMITHING_TEMPLATE), + HOST(TrimPattern.HOST, Material.HOST_ARMOR_TRIM_SMITHING_TEMPLATE), + RAISER(TrimPattern.RAISER, Material.RAISER_ARMOR_TRIM_SMITHING_TEMPLATE), + RIB(TrimPattern.RIB, Material.RIB_ARMOR_TRIM_SMITHING_TEMPLATE), + SENTRY(TrimPattern.SENTRY, Material.SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE), + SHAPER(TrimPattern.SHAPER, Material.SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE), + SILENCE(TrimPattern.SILENCE, Material.SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE), + SNOUT(TrimPattern.SNOUT, Material.SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE), + SPIRE(TrimPattern.SPIRE, Material.SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE), + TIDE(TrimPattern.TIDE, Material.TIDE_ARMOR_TRIM_SMITHING_TEMPLATE), + VEX(TrimPattern.VEX, Material.VEX_ARMOR_TRIM_SMITHING_TEMPLATE), + WARD(TrimPattern.WARD, Material.WARD_ARMOR_TRIM_SMITHING_TEMPLATE), + WAYFINDER(TrimPattern.WAYFINDER, Material.WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE), + WILD(TrimPattern.WILD, Material.WILD_ARMOR_TRIM_SMITHING_TEMPLATE); + + private final TrimPattern canonical; + private final Material material; + + ValidPattern(TrimPattern canonical, Material material) { + this.canonical = canonical; + this.material = material; + } + + public Material getMaterial() { + return material; + } + + public TrimPattern getCanonical() { + return canonical; + } + + public static ValidPattern validate(TrimPattern pattern) { + for (ValidPattern value : ValidPattern.values()) { + if (!value.getCanonical().equals(pattern)) continue; + return value; + } + return null; + } + + public static TrimPattern validate(String name) { + name = name.toUpperCase(); + return ValidPattern.valueOf(name).getCanonical(); + } +} diff --git a/src/main/java/me/trouper/alias/server/commands/Args.java b/src/main/java/me/trouper/alias/server/commands/Args.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/commands/CommandRegistry.java b/src/main/java/me/trouper/alias/server/commands/CommandRegistry.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/commands/Permission.java b/src/main/java/me/trouper/alias/server/commands/Permission.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/commands/completions/CompletionBuilder.java b/src/main/java/me/trouper/alias/server/commands/completions/CompletionBuilder.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/commands/completions/CompletionNode.java b/src/main/java/me/trouper/alias/server/commands/completions/CompletionNode.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/events/listeners/FreezeListener.java b/src/main/java/me/trouper/alias/server/events/listeners/FreezeListener.java index c944e7b..665800f 100644 --- a/src/main/java/me/trouper/alias/server/events/listeners/FreezeListener.java +++ b/src/main/java/me/trouper/alias/server/events/listeners/FreezeListener.java @@ -21,6 +21,13 @@ public class FreezeListener implements Listener { this.context = context; } + @EventHandler + public void onQuit(PlayerQuitEvent e) { + OfflinePlayer p = e.getPlayer(); + FreezeSession freeze = context.getFreezeManager().getSession(p.getUniqueId()); + if (freeze != null) freeze.handleQuit(p); + } + @EventHandler public void onMove(PlayerMoveEvent e) { Player p = e.getPlayer(); @@ -71,11 +78,4 @@ public class FreezeListener implements Listener { if (!freeze.canDmg()) e.setCancelled(true); freeze.handleDamage(p); } - - @EventHandler - public void onQuit(PlayerQuitEvent e) { - OfflinePlayer p = e.getPlayer(); - FreezeSession freeze = context.getFreezeManager().getSession(p.getUniqueId()); - if (freeze != null) freeze.handleQuit(p); - } } diff --git a/src/main/java/me/trouper/alias/server/systems/Text.java b/src/main/java/me/trouper/alias/server/systems/Text.java index b234635..3d1b9ab 100644 --- a/src/main/java/me/trouper/alias/server/systems/Text.java +++ b/src/main/java/me/trouper/alias/server/systems/Text.java @@ -409,6 +409,25 @@ public class Text { return Component.text(plainText); } + public Component createProgressBar(double progress, char barChar, int barLength, TextColor completeColor, TextColor incompleteColor) { + int completeLength = (int) Math.round(progress * barLength); + int incompleteLength = barLength - completeLength; + + TextComponent.Builder builder = Component.text(); + + if (completeLength > 0) { + builder.append(Component.text(String.valueOf(barChar).repeat(completeLength)) + .color(completeColor)); + } + + if (incompleteLength > 0) { + builder.append(Component.text(String.valueOf(barChar).repeat(incompleteLength)) + .color(incompleteColor)); + } + + return builder.build(); + } + /** * Gets the appropriate argument color based on the argument index. * @param pallet The color pallet diff --git a/src/main/java/me/trouper/alias/server/systems/display/tracing/BlockDisplayRaytracer.java b/src/main/java/me/trouper/alias/server/systems/display/tracing/BlockDisplayRaytracer.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/systems/display/tracing/CustomRaytracer.java b/src/main/java/me/trouper/alias/server/systems/display/tracing/CustomRaytracer.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/systems/display/tracing/Point.java b/src/main/java/me/trouper/alias/server/systems/display/tracing/Point.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/server/systems/display/visual/Patterns.java b/src/main/java/me/trouper/alias/server/systems/display/visual/Patterns.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/utils/ItemSimilarity.java b/src/main/java/me/trouper/alias/utils/ItemSimilarity.java new file mode 100644 index 0000000..8bdd12e --- /dev/null +++ b/src/main/java/me/trouper/alias/utils/ItemSimilarity.java @@ -0,0 +1,705 @@ +package me.trouper.alias.utils; + +import org.bukkit.Color; +import org.bukkit.FireworkEffect; +import org.bukkit.block.banner.Pattern; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.*; +import org.bukkit.inventory.meta.trim.ArmorTrim; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionType; + +import java.util.*; + +public class ItemSimilarity { + + public static class SimilarityConfiguration { + private double durabilityWeight = 0.05; + private double enchantmentsWeight = 0.10; + private double displayNameWeight = 0.10; + private double loreWeight = 0.15; + private double itemFlagsWeight = 0.25; + private double customModelDataWeight = 0.20; + private double specificMetaWeight = 0.07; + + private boolean checkDurability = true; + private boolean checkEnchantments = true; + private boolean checkDisplayName = true; + private boolean checkLore = true; + private boolean checkItemFlags = true; + private boolean checkCustomModelData = true; + private boolean checkSpecificMeta = true; + + public static SimilarityConfiguration defaultConfig() { + return new SimilarityConfiguration(); + } + + public static SimilarityConfiguration fastConfig() { + return new SimilarityConfiguration() + .setCheckDisplayName(false) + .setCheckLore(false) + .redistributeWeights(); + } + + public static SimilarityConfiguration preciseConfig() { + return new SimilarityConfiguration() + .setDurabilityWeight(0.08) + .setEnchantmentsWeight(0.20) + .setDisplayNameWeight(0.15) + .setLoreWeight(0.20) + .setItemFlagsWeight(0.15) + .setCustomModelDataWeight(0.10) + .setSpecificMetaWeight(0.12); + } + + private SimilarityConfiguration redistributeWeights() { + double totalActiveWeight = 0.0; + int activeFeatures = 0; + + if (checkDurability) { + totalActiveWeight += durabilityWeight; + activeFeatures++; + } + if (checkEnchantments) { + totalActiveWeight += enchantmentsWeight; + activeFeatures++; + } + if (checkDisplayName) { + totalActiveWeight += displayNameWeight; + activeFeatures++; + } + if (checkLore) { + totalActiveWeight += loreWeight; + activeFeatures++; + } + if (checkItemFlags) { + totalActiveWeight += itemFlagsWeight; + activeFeatures++; + } + if (checkCustomModelData) { + totalActiveWeight += customModelDataWeight; + activeFeatures++; + } + if (checkSpecificMeta) { + totalActiveWeight += specificMetaWeight; + activeFeatures++; + } + + if (activeFeatures > 0 && totalActiveWeight != 1.0) { + double redistributionFactor = 1.0 / totalActiveWeight; + + if (checkDurability) durabilityWeight *= redistributionFactor; + if (checkEnchantments) enchantmentsWeight *= redistributionFactor; + if (checkDisplayName) displayNameWeight *= redistributionFactor; + if (checkLore) loreWeight *= redistributionFactor; + if (checkItemFlags) itemFlagsWeight *= redistributionFactor; + if (checkCustomModelData) customModelDataWeight *= redistributionFactor; + if (checkSpecificMeta) specificMetaWeight *= redistributionFactor; + } + + return this; + } + + public double getDurabilityWeight() { + return durabilityWeight; + } + + public double getEnchantmentsWeight() { + return enchantmentsWeight; + } + + public double getDisplayNameWeight() { + return displayNameWeight; + } + + public double getLoreWeight() { + return loreWeight; + } + + public double getItemFlagsWeight() { + return itemFlagsWeight; + } + + public double getCustomModelDataWeight() { + return customModelDataWeight; + } + + public double getSpecificMetaWeight() { + return specificMetaWeight; + } + + public boolean isCheckDurability() { + return checkDurability; + } + + public boolean isCheckEnchantments() { + return checkEnchantments; + } + + public boolean isCheckDisplayName() { + return checkDisplayName; + } + + public boolean isCheckLore() { + return checkLore; + } + + public boolean isCheckItemFlags() { + return checkItemFlags; + } + + public boolean isCheckCustomModelData() { + return checkCustomModelData; + } + + public boolean isCheckSpecificMeta() { + return checkSpecificMeta; + } + + public SimilarityConfiguration setDurabilityWeight(double weight) { + this.durabilityWeight = weight; + return this; + } + + public SimilarityConfiguration setEnchantmentsWeight(double weight) { + this.enchantmentsWeight = weight; + return this; + } + + public SimilarityConfiguration setDisplayNameWeight(double weight) { + this.displayNameWeight = weight; + return this; + } + + public SimilarityConfiguration setLoreWeight(double weight) { + this.loreWeight = weight; + return this; + } + + public SimilarityConfiguration setItemFlagsWeight(double weight) { + this.itemFlagsWeight = weight; + return this; + } + + public SimilarityConfiguration setCustomModelDataWeight(double weight) { + this.customModelDataWeight = weight; + return this; + } + + public SimilarityConfiguration setSpecificMetaWeight(double weight) { + this.specificMetaWeight = weight; + return this; + } + + public SimilarityConfiguration setCheckDurability(boolean check) { + this.checkDurability = check; + return this; + } + + public SimilarityConfiguration setCheckEnchantments(boolean check) { + this.checkEnchantments = check; + return this; + } + + public SimilarityConfiguration setCheckDisplayName(boolean check) { + this.checkDisplayName = check; + return this; + } + + public SimilarityConfiguration setCheckLore(boolean check) { + this.checkLore = check; + return this; + } + + public SimilarityConfiguration setCheckItemFlags(boolean check) { + this.checkItemFlags = check; + return this; + } + + public SimilarityConfiguration setCheckCustomModelData(boolean check) { + this.checkCustomModelData = check; + return this; + } + + public SimilarityConfiguration setCheckSpecificMeta(boolean check) { + this.checkSpecificMeta = check; + return this; + } + + public SimilarityConfiguration normalize() { + return redistributeWeights(); + } + } + + private static final SimilarityConfiguration DEFAULT_CONFIG = SimilarityConfiguration.defaultConfig(); + + /** + * Calculate similarity between two ItemStacks using default configuration + * + * @param item1 First ItemStack + * @param item2 Second ItemStack + * @return Similarity score from 0.0 to 1.0 + */ + public static double calculateSimilarity(ItemStack item1, ItemStack item2) { + return calculateSimilarity(item1, item2, DEFAULT_CONFIG); + } + + /** + * Calculate similarity between two ItemStacks with custom configuration + * + * @param item1 First ItemStack + * @param item2 Second ItemStack + * @param config Configuration for weight and feature settings + * @return Similarity score from 0.0 to 1.0 + */ + public static double calculateSimilarity(ItemStack item1, ItemStack item2, SimilarityConfiguration config) { + if (item1 == null && item2 == null) return 1.0; + if (item1 == null || item2 == null) return 0.0; + if (!item1.getType().equals(item2.getType())) return 0.0; + + double totalScore = 0.0; + + if (config.isCheckDurability()) { + totalScore += compareDurability(item1, item2) * config.getDurabilityWeight(); + } + + ItemMeta meta1 = item1.getItemMeta(); + ItemMeta meta2 = item2.getItemMeta(); + + if (meta1 == null && meta2 == null) { + if (config.isCheckDisplayName()) totalScore += config.getDisplayNameWeight(); + if (config.isCheckLore()) totalScore += config.getLoreWeight(); + if (config.isCheckEnchantments()) totalScore += config.getEnchantmentsWeight(); + if (config.isCheckItemFlags()) totalScore += config.getItemFlagsWeight(); + if (config.isCheckCustomModelData()) totalScore += config.getCustomModelDataWeight(); + if (config.isCheckSpecificMeta()) totalScore += config.getSpecificMetaWeight(); + } else if (meta1 == null || meta2 == null) { + // Do nothing if only one is null, as they don't match. + } else { + if (config.isCheckDisplayName()) { + totalScore += compareDisplayNames(meta1, meta2) * config.getDisplayNameWeight(); + } + if (config.isCheckLore()) { + totalScore += compareLore(meta1, meta2) * config.getLoreWeight(); + } + if (config.isCheckEnchantments()) { + totalScore += compareEnchantments(meta1, meta2) * config.getEnchantmentsWeight(); + } + if (config.isCheckItemFlags()) { + totalScore += compareItemFlags(meta1, meta2) * config.getItemFlagsWeight(); + } + if (config.isCheckCustomModelData()) { + totalScore += compareCustomModelData(meta1, meta2) * config.getCustomModelDataWeight(); + } + if (config.isCheckSpecificMeta()) { + totalScore += compareSpecificMeta(meta1, meta2) * config.getSpecificMetaWeight(); + } + } + + return Math.min(1.0, Math.max(0.0, totalScore)); + } + + + private static double compareDurability(ItemStack item1, ItemStack item2) { + if (!(item1.getType().getMaxDurability() > 0)) return 1.0; + ItemMeta meta1 = item1.getItemMeta(); + ItemMeta meta2 = item2.getItemMeta(); + + if (meta1 instanceof Damageable && meta2 instanceof Damageable) { + int damage1 = ((Damageable) meta1).getDamage(); + int damage2 = ((Damageable) meta2).getDamage(); + + if (damage1 == damage2) return 1.0; + + int maxDurability = item1.getType().getMaxDurability(); + int difference = Math.abs(damage1 - damage2); + + return 1.0 - ((double) difference / maxDurability); + } + + return 1.0; + } + + private static double compareDisplayNames(ItemMeta meta1, ItemMeta meta2) { + String name1 = meta1.hasDisplayName() ? meta1.getDisplayName() : null; + String name2 = meta2.hasDisplayName() ? meta2.getDisplayName() : null; + + if (name1 == null && name2 == null) return 1.0; + if (name1 == null || name2 == null) return 0.0; + + return name1.equals(name2) ? 1.0 : calculateStringSimilarity(name1, name2); + } + + private static double compareLore(ItemMeta meta1, ItemMeta meta2) { + List lore1 = meta1.hasLore() ? meta1.getLore() : null; + List lore2 = meta2.hasLore() ? meta2.getLore() : null; + + if (lore1 == null && lore2 == null) return 1.0; + if (lore1 == null || lore2 == null) return 0.0; + + return compareStringLists(lore1, lore2); + } + + private static double compareEnchantments(ItemMeta meta1, ItemMeta meta2) { + Map enchants1 = meta1.getEnchants(); + Map enchants2 = meta2.getEnchants(); + + if (enchants1.isEmpty() && enchants2.isEmpty()) return 1.0; + if (enchants1.isEmpty() || enchants2.isEmpty()) { + return enchants1.isEmpty() && enchants2.isEmpty() ? 1.0 : 0.0; + } + + Set allEnchants = new HashSet<>(enchants1.keySet()); + allEnchants.addAll(enchants2.keySet()); + + double totalSimilarity = 0.0; + + for (Enchantment enchant : allEnchants) { + int level1 = enchants1.getOrDefault(enchant, 0); + int level2 = enchants2.getOrDefault(enchant, 0); + + if (level1 == 0 || level2 == 0) { + continue; + } + + int maxLevel = Math.max(level1, level2); + int difference = Math.abs(level1 - level2); + double enchantSimilarity = 1.0 - ((double) difference / maxLevel); + + totalSimilarity += enchantSimilarity; + } + + return allEnchants.isEmpty() ? 1.0 : totalSimilarity / allEnchants.size(); + } + + private static double compareItemFlags(ItemMeta meta1, ItemMeta meta2) { + Set flags1 = meta1.getItemFlags(); + Set flags2 = meta2.getItemFlags(); + + if (flags1.isEmpty() && flags2.isEmpty()) return 1.0; + + Set allFlags = new HashSet<>(flags1); + allFlags.addAll(flags2); + + Set commonFlags = new HashSet<>(flags1); + commonFlags.retainAll(flags2); + + return allFlags.isEmpty() ? 1.0 : (double) commonFlags.size() / allFlags.size(); + } + + private static double compareCustomModelData(ItemMeta meta1, ItemMeta meta2) { + boolean hasCustom1 = meta1.hasCustomModelData(); + boolean hasCustom2 = meta2.hasCustomModelData(); + + if (!hasCustom1 && !hasCustom2) return 1.0; + if (hasCustom1 != hasCustom2) return 0.0; + + return meta1.getCustomModelData() == meta2.getCustomModelData() ? 1.0 : 0.0; + } + + private static double compareSpecificMeta(ItemMeta meta1, ItemMeta meta2) { + if (!meta1.getClass().equals(meta2.getClass())) return 0.0; + + if (meta1 instanceof PotionMeta) { + return comparePotionMeta((PotionMeta) meta1, (PotionMeta) meta2); + } else if (meta1 instanceof BookMeta) { + return compareBookMeta((BookMeta) meta1, (BookMeta) meta2); + } else if (meta1 instanceof LeatherArmorMeta) { + return compareLeatherArmorMeta((LeatherArmorMeta) meta1, (LeatherArmorMeta) meta2); + } else if (meta1 instanceof FireworkMeta) { + return compareFireworkMeta((FireworkMeta) meta1, (FireworkMeta) meta2); + } else if (meta1 instanceof BannerMeta) { + return compareBannerMeta((BannerMeta) meta1, (BannerMeta) meta2); + } else if (meta1 instanceof SkullMeta) { + return compareSkullMeta((SkullMeta) meta1, (SkullMeta) meta2); + } else if (meta1 instanceof ArmorMeta) { + return compareArmorMeta((ArmorMeta) meta1, (ArmorMeta) meta2); + } else if (meta1 instanceof MusicInstrumentMeta) { + return compareMusicInstrumentMeta((MusicInstrumentMeta) meta1, (MusicInstrumentMeta) meta2); + } + + return 1.0; + } + + private static double comparePotionMeta(PotionMeta meta1, PotionMeta meta2) { + double score = 0.0; + int comparisons = 0; + + PotionType type1 = meta1.getBasePotionType(); + PotionType type2 = meta2.getBasePotionType(); + score += (type1 == type2) ? 1.0 : 0.0; + comparisons++; + + List effects1 = meta1.getCustomEffects(); + List effects2 = meta2.getCustomEffects(); + score += comparePotionEffects(effects1, effects2); + comparisons++; + + Color color1 = meta1.getColor(); + Color color2 = meta2.getColor(); + score += Objects.equals(color1, color2) ? 1.0 : 0.0; + comparisons++; + + return score / comparisons; + } + + private static double compareBookMeta(BookMeta meta1, BookMeta meta2) { + double score = 0.0; + int comparisons = 0; + + String title1 = meta1.getTitle(); + String title2 = meta2.getTitle(); + score += Objects.equals(title1, title2) ? 1.0 : + (title1 != null && title2 != null ? calculateStringSimilarity(title1, title2) : 0.0); + comparisons++; + + String author1 = meta1.getAuthor(); + String author2 = meta2.getAuthor(); + score += Objects.equals(author1, author2) ? 1.0 : + (author1 != null && author2 != null ? calculateStringSimilarity(author1, author2) : 0.0); + comparisons++; + + List pages1 = meta1.getPages(); + List pages2 = meta2.getPages(); + score += compareStringLists(pages1, pages2); + comparisons++; + + BookMeta.Generation gen1 = meta1.getGeneration(); + BookMeta.Generation gen2 = meta2.getGeneration(); + score += Objects.equals(gen1, gen2) ? 1.0 : 0.0; + comparisons++; + + return score / comparisons; + } + + private static double compareLeatherArmorMeta(LeatherArmorMeta meta1, LeatherArmorMeta meta2) { + Color color1 = meta1.getColor(); + Color color2 = meta2.getColor(); + + if (color1.equals(color2)) return 1.0; + + int r1 = color1.getRed(), g1 = color1.getGreen(), b1 = color1.getBlue(); + int r2 = color2.getRed(), g2 = color2.getGreen(), b2 = color2.getBlue(); + + double distance = Math.sqrt(Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2)); + double maxDistance = Math.sqrt(3 * Math.pow(255, 2)); + + return 1.0 - (distance / maxDistance); + } + + private static double compareFireworkMeta(FireworkMeta meta1, FireworkMeta meta2) { + double score = 0.0; + int comparisons = 0; + + score += (meta1.getPower() == meta2.getPower()) ? 1.0 : 0.0; + comparisons++; + + List effects1 = meta1.getEffects(); + List effects2 = meta2.getEffects(); + score += compareFireworkEffects(effects1, effects2); + comparisons++; + + return score / comparisons; + } + + private static double compareBannerMeta(BannerMeta meta1, BannerMeta meta2) { + List patterns1 = meta1.getPatterns(); + List patterns2 = meta2.getPatterns(); + + return compareBannerPatterns(patterns1, patterns2); + } + + private static double compareSkullMeta(SkullMeta meta1, SkullMeta meta2) { + String owner1 = meta1.getOwner(); + String owner2 = meta2.getOwner(); + + return Objects.equals(owner1, owner2) ? 1.0 : 0.0; + } + + private static double compareArmorMeta(ArmorMeta meta1, ArmorMeta meta2) { + ArmorTrim trim1 = meta1.getTrim(); + ArmorTrim trim2 = meta2.getTrim(); + + if (trim1 == null && trim2 == null) return 1.0; + if (trim1 == null || trim2 == null) return 0.0; + + return trim1.equals(trim2) ? 1.0 : 0.0; + } + + private static double compareMusicInstrumentMeta(MusicInstrumentMeta meta1, MusicInstrumentMeta meta2) { + return Objects.equals(meta1.getInstrument(), meta2.getInstrument()) ? 1.0 : 0.0; + } + + private static double calculateStringSimilarity(String str1, String str2) { + if (str1 == null || str2 == null) return 0.0; + if (str1.equals(str2)) return 1.0; + + Set bigrams1 = getBigrams(str1.toLowerCase()); + Set bigrams2 = getBigrams(str2.toLowerCase()); + + Set intersection = new HashSet<>(bigrams1); + intersection.retainAll(bigrams2); + + Set union = new HashSet<>(bigrams1); + union.addAll(bigrams2); + + return union.isEmpty() ? 1.0 : (double) intersection.size() / union.size(); + } + + private static Set getBigrams(String input) { + Set result = new HashSet<>(); + for (int i = 0; i < input.length() - 1; i++) { + result.add(input.substring(i, i + 2)); + } + return result; + } + + private static double compareStringLists(List list1, List list2) { + if (list1.isEmpty() && list2.isEmpty()) return 1.0; + if (list1.isEmpty() || list2.isEmpty()) return 0.0; + + int maxSize = Math.max(list1.size(), list2.size()); + double totalSimilarity = 0.0; + + for (int i = 0; i < maxSize; i++) { + String str1 = i < list1.size() ? list1.get(i) : null; + String str2 = i < list2.size() ? list2.get(i) : null; + + if (str1 == null && str2 == null) { + totalSimilarity += 1.0; + } else if (str1 == null || str2 == null) { + totalSimilarity += 0.0; + } else { + totalSimilarity += calculateStringSimilarity(str1, str2); + } + } + + return totalSimilarity / maxSize; + } + + private static double comparePotionEffects(List effects1, List effects2) { + if (effects1.isEmpty() && effects2.isEmpty()) return 1.0; + if (effects1.isEmpty() || effects2.isEmpty()) return 0.0; + + Map map1 = new HashMap<>(); + Map map2 = new HashMap<>(); + + for (PotionEffect effect : effects1) map1.put(effect.getType(), effect); + for (PotionEffect effect : effects2) map2.put(effect.getType(), effect); + + Set allTypes = new HashSet<>(map1.keySet()); + allTypes.addAll(map2.keySet()); + + double totalSimilarity = 0.0; + + for (org.bukkit.potion.PotionEffectType type : allTypes) { + PotionEffect effect1 = map1.get(type); + PotionEffect effect2 = map2.get(type); + + if (effect1 == null || effect2 == null) continue; + + double similarity = 0.0; + + int duration1 = effect1.getDuration(); + int duration2 = effect2.getDuration(); + int maxDuration = Math.max(duration1, duration2); + similarity += maxDuration == 0 ? 1.0 : 1.0 - ((double) Math.abs(duration1 - duration2) / maxDuration); + + int amp1 = effect1.getAmplifier(); + int amp2 = effect2.getAmplifier(); + similarity += (amp1 == amp2) ? 1.0 : 0.0; + + similarity += (effect1.isAmbient() == effect2.isAmbient()) ? 1.0 : 0.0; + similarity += (effect1.hasParticles() == effect2.hasParticles()) ? 1.0 : 0.0; + similarity += (effect1.hasIcon() == effect2.hasIcon()) ? 1.0 : 0.0; + + totalSimilarity += similarity / 5.0; + } + + return allTypes.isEmpty() ? 1.0 : totalSimilarity / allTypes.size(); + } + + private static double compareFireworkEffects(List effects1, List effects2) { + if (effects1.isEmpty() && effects2.isEmpty()) return 1.0; + if (effects1.size() != effects2.size()) return 0.0; + + double totalSimilarity = 0.0; + + for (int i = 0; i < effects1.size(); i++) { + FireworkEffect effect1 = effects1.get(i); + FireworkEffect effect2 = effects2.get(i); + + double similarity = 0.0; + int comparisons = 0; + + similarity += (effect1.getType() == effect2.getType()) ? 1.0 : 0.0; + comparisons++; + + similarity += compareColors(effect1.getColors(), effect2.getColors()); + comparisons++; + + similarity += compareColors(effect1.getFadeColors(), effect2.getFadeColors()); + comparisons++; + + similarity += (effect1.hasTrail() == effect2.hasTrail()) ? 1.0 : 0.0; + comparisons++; + + similarity += (effect1.hasFlicker() == effect2.hasFlicker()) ? 1.0 : 0.0; + comparisons++; + + totalSimilarity += similarity / comparisons; + } + + return totalSimilarity / effects1.size(); + } + + private static double compareColors(List colors1, List colors2) { + if (colors1.isEmpty() && colors2.isEmpty()) return 1.0; + if (colors1.size() != colors2.size()) return 0.0; + + double totalSimilarity = 0.0; + + for (int i = 0; i < colors1.size(); i++) { + Color color1 = colors1.get(i); + Color color2 = colors2.get(i); + + if (color1.equals(color2)) { + totalSimilarity += 1.0; + } else { + int r1 = color1.getRed(), g1 = color1.getGreen(), b1 = color1.getBlue(); + int r2 = color2.getRed(), g2 = color2.getGreen(), b2 = color2.getBlue(); + + double distance = Math.sqrt(Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2)); + double maxDistance = Math.sqrt(3 * Math.pow(255, 2)); + + totalSimilarity += 1.0 - (distance / maxDistance); + } + } + + return totalSimilarity / colors1.size(); + } + + private static double compareBannerPatterns(List patterns1, List patterns2) { + if (patterns1.isEmpty() && patterns2.isEmpty()) return 1.0; + if (patterns1.size() != patterns2.size()) return 0.0; + + double totalSimilarity = 0.0; + + for (int i = 0; i < patterns1.size(); i++) { + Pattern pattern1 = patterns1.get(i); + Pattern pattern2 = patterns2.get(i); + + double similarity = 0.0; + + similarity += (pattern1.getPattern() == pattern2.getPattern()) ? 1.0 : 0.0; + + similarity += (pattern1.getColor() == pattern2.getColor()) ? 1.0 : 0.0; + + totalSimilarity += similarity / 2.0; + } + + return totalSimilarity / patterns1.size(); + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/alias/utils/TargetingUtils.java b/src/main/java/me/trouper/alias/utils/TargetingUtils.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/utils/misc/ArrayUtils.java b/src/main/java/me/trouper/alias/utils/misc/ArrayUtils.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/utils/misc/FileValidationUtils.java b/src/main/java/me/trouper/alias/utils/misc/FileValidationUtils.java old mode 100755 new mode 100644 diff --git a/src/main/java/me/trouper/alias/utils/misc/Randomizer.java b/src/main/java/me/trouper/alias/utils/misc/Randomizer.java old mode 100755 new mode 100644