From effa35f57d0622cda65c3b3674916b54d96d68b0 Mon Sep 17 00:00:00 2001 From: contactvertexdev Date: Thu, 22 Oct 2026 21:02:27 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20ajout=20du=20syst=C3=A8me=20de=20raret?= =?UTF-8?q?=C3=A9=20des=20perks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les perks peuvent maintenant avoir une rareté, configurable dans le nouveau fichier rarity.yml. Chaque rareté a un nom, une couleur ou un dégradé (MiniMessage uniquement) et une liste de perks associés par id. La rareté est affichée dans le codex (liste, détail, titre du craft) et colore le nom des perks dans le lore des équipements. Le codex peut être trié par rareté. Tout se recharge avec /perk reload. Ajout des descriptions manquantes pour warden et lifesteal, et correction de celle de health. --- src/main/java/fr/antidino/smp/CorePlugin.java | 6 +- .../smp/core/manager/codex/CodexMenu.java | 108 ++++++++------- .../smp/core/manager/rarity/Rarity.java | 17 +++ .../core/manager/rarity/RarityManager.java | 124 ++++++++++++++++++ .../fr/antidino/smp/librairie/PDCBuilder.java | 18 ++- src/main/resources/perks/health.yml | 5 +- src/main/resources/perks/lifesteal.yml | 8 +- src/main/resources/perks/warden.yml | 5 + src/main/resources/rarity.yml | 50 +++++++ 9 files changed, 276 insertions(+), 65 deletions(-) create mode 100644 src/main/java/fr/antidino/smp/core/manager/rarity/Rarity.java create mode 100644 src/main/java/fr/antidino/smp/core/manager/rarity/RarityManager.java create mode 100644 src/main/resources/rarity.yml diff --git a/src/main/java/fr/antidino/smp/CorePlugin.java b/src/main/java/fr/antidino/smp/CorePlugin.java index 7243137..ed90585 100644 --- a/src/main/java/fr/antidino/smp/CorePlugin.java +++ b/src/main/java/fr/antidino/smp/CorePlugin.java @@ -16,6 +16,7 @@ import fr.antidino.smp.core.manager.perk.PerkEffectListener; import fr.antidino.smp.core.manager.perk.PerkManager; import fr.antidino.smp.core.manager.perk.PerkTasks; +import fr.antidino.smp.core.manager.rarity.RarityManager; import fr.antidino.smp.librairie.PDCBuilder; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; @@ -34,6 +35,7 @@ public final class CorePlugin extends JavaPlugin { public CodexMenu codexMenu; public PerkTasks perkTasks; public SlotExpander slotExpander; + public RarityManager rarityManager; private DatabaseManager databaseManager; public DatabaseProvider databaseProvider; @@ -49,6 +51,7 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this); getServer().getPluginManager().registerEvents(new SavingPerkListeners(this), this); + rarityManager = new RarityManager(this); pdcBuilder = new PDCBuilder(this); PDCBuilder.init(this); @@ -92,6 +95,7 @@ public int fullReload() { reloadConfig(); configPerkManager.LoadConfigAtStart(); perkManager.reload(); + if (rarityManager != null) rarityManager.reload(); if (slotExpander != null) slotExpander.reload(); List keys = perkManager.getPerks().stream().map(AbstractPerk::getKey).toList(); @@ -110,4 +114,4 @@ public void onDisable() { databaseManager.close(); } } -} +} \ No newline at end of file diff --git a/src/main/java/fr/antidino/smp/core/manager/codex/CodexMenu.java b/src/main/java/fr/antidino/smp/core/manager/codex/CodexMenu.java index 512f15f..1abe119 100644 --- a/src/main/java/fr/antidino/smp/core/manager/codex/CodexMenu.java +++ b/src/main/java/fr/antidino/smp/core/manager/codex/CodexMenu.java @@ -4,7 +4,6 @@ import fr.antidino.smp.core.manager.perk.AbstractPerk; import fr.antidino.smp.librairie.ItemBuilder; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -17,16 +16,13 @@ import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; /** * Menus du /perks codex : - * - LIST : tous les perks actifs du serveur (paginé) + item d'extension de slots + * - LIST : tous les perks actifs du serveur (paginé) + item d'extension de slots * - DETAIL : craft + description (+ niveaux) d'un perk ou de l'item de slots - - * Tous les textes affichés passent par le MessageManager (fichiers de langue). */ public class CodexMenu { @@ -47,6 +43,8 @@ public class CodexMenu { public static final String EXPANDER_ID = "__slot_expander__"; + + public enum Type { LIST, DETAIL } public static class CodexHolder implements InventoryHolder { @@ -82,7 +80,11 @@ public CodexMenu(CorePlugin plugin) { /** Liste triée de façon stable (même ordre à l'ouverture et au clic). */ public List getSortedPerks() { List perks = plugin.perkManager.getPerks(); - perks.sort(Comparator.comparing(AbstractPerk::getId)); + if (plugin.rarityManager != null && plugin.rarityManager.sortCodexByRarity()) { + perks.sort(Comparator.comparingInt(plugin.rarityManager::order).thenComparing(AbstractPerk::getId)); + } else { + perks.sort(Comparator.comparing(AbstractPerk::getId)); + } return perks; } @@ -93,12 +95,11 @@ public void openList(Player player, int page) { int maxPage = Math.max(0, (perks.size() - 1) / PERKS_PER_PAGE); page = Math.clamp(page, 0, maxPage); - Component title = maxPage > 0 - ? msg("codex_title_paged", "page", String.valueOf(page + 1), "max", String.valueOf(maxPage + 1)) - : msg("codex_title"); + String title = "Codex des perks"; + if (maxPage > 0) title += " (" + (page + 1) + "/" + (maxPage + 1) + ")"; CodexHolder holder = new CodexHolder(Type.LIST, page, null); - Inventory inv = Bukkit.createInventory(holder, 54, title); + Inventory inv = Bukkit.createInventory(holder, 54, mm(title)); holder.setInventory(inv); int start = page * PERKS_PER_PAGE; @@ -108,10 +109,10 @@ public void openList(Player player, int page) { ItemStack filler = filler(); for (int s = 45; s < 54; s++) inv.setItem(s, filler); - if (page > 0) inv.setItem(PREV_SLOT, button(Material.ARROW, msg("codex_prev_page"))); - if (page < maxPage) inv.setItem(NEXT_SLOT, button(Material.ARROW, msg("codex_next_page"))); + if (page > 0) inv.setItem(PREV_SLOT, button(Material.ARROW, "← Page précédente")); + if (page < maxPage) inv.setItem(NEXT_SLOT, button(Material.ARROW, "Page suivante →")); if (plugin.slotExpander != null) inv.setItem(EXPANDER_SLOT, expanderListItem()); - inv.setItem(CLOSE_SLOT, button(Material.BARRIER, msg("codex_close"))); + inv.setItem(CLOSE_SLOT, button(Material.BARRIER, "Fermer")); player.openInventory(inv); } @@ -122,14 +123,16 @@ private ItemStack listItem(AbstractPerk perk) { if (meta == null) return item; List lore = new ArrayList<>(); + Component rarityLine = rarityLine(perk); + if (rarityLine != null) lore.add(rarityLine); lore.add(Component.empty()); lore.addAll(descriptionLines(perk.getDescription())); if (perk.getMaxLevel() > 1) { lore.add(Component.empty()); - lore.add(msg("codex_max_level", "level", AbstractPerk.roman(perk.getMaxLevel()))); + lore.add(mm("Niveau max : " + AbstractPerk.roman(perk.getMaxLevel()))); } lore.add(Component.empty()); - lore.add(msg("codex_click_craft")); + lore.add(mm("▶ Clic pour voir le craft")); meta.lore(lore); item.setItemMeta(meta); return item; @@ -144,7 +147,7 @@ private ItemStack expanderListItem() { lore.add(Component.empty()); lore.addAll(descriptionLines(plugin.slotExpander.getDescription())); lore.add(Component.empty()); - lore.add(msg("codex_click_craft")); + lore.add(mm("▶ Clic pour voir le craft")); meta.lore(lore); item.setItemMeta(meta); return item; @@ -153,15 +156,22 @@ private ItemStack expanderListItem() { // ----------------------------------------------------------------- DETAIL public void openDetail(Player player, AbstractPerk perk, int fromPage) { - List description = new ArrayList<>(descriptionLines(perk.getDescription())); + List description = new ArrayList<>(); + Component rarityLine = rarityLine(perk); + if (rarityLine != null) { + description.add(rarityLine); + description.add(Component.empty()); + } + description.addAll(descriptionLines(perk.getDescription())); if (perk.getMaxLevel() > 1) { description.add(Component.empty()); - description.add(msg("codex_levels", "max", AbstractPerk.roman(perk.getMaxLevel()))); - description.add(msg("codex_upgrade_cost", "cost", String.valueOf(perk.getUpgradeCost()))); - description.add(msg("codex_upgrade_hint")); + description.add(mm("Niveaux : I → " + AbstractPerk.roman(perk.getMaxLevel()))); + description.add(mm("Amélioration : " + perk.getUpgradeCost() + " perks de même niveau")); + description.add(mm("en table de craft = niveau suivant")); } openRecipeView(player, perk.getId(), fromPage, - msg("codex_craft_title").append(mm(perk.getDisplayName())), + mm("Craft : ").append(plugin.rarityManager != null + ? plugin.rarityManager.styledName(perk) : mm(perk.getDisplayName())), perk.getItem().clone(), perk.getCraftShape(), perk.getCraftIngredients(), description); } @@ -169,11 +179,11 @@ public void openExpanderDetail(Player player, int fromPage) { if (plugin.slotExpander == null) return; List description = new ArrayList<>(descriptionLines(plugin.slotExpander.getDescription())); description.add(Component.empty()); - description.add(msg("codex_expander_usage")); - description.add(msg("codex_expander_added", "added", String.valueOf(plugin.slotExpander.getAdded()))); - description.add(msg("codex_expander_max", "max", String.valueOf(plugin.slotExpander.getMax()))); + description.add(mm("Utilisation : table de forge")); + description.add(mm("Ajoute +" + plugin.slotExpander.getAdded() + " emplacement(s)")); + description.add(mm("Maximum : " + plugin.slotExpander.getMax() + " par équipement")); openRecipeView(player, EXPANDER_ID, fromPage, - msg("codex_craft_title").append(mm(plugin.slotExpander.getDisplayName())), + mm("Craft : ").append(mm(plugin.slotExpander.getDisplayName())), plugin.slotExpander.getItem(), plugin.slotExpander.getCraftShape(), plugin.slotExpander.getCraftIngredients(), description); } @@ -201,42 +211,44 @@ private void openRecipeView(Player player, String id, int fromPage, Component ti } } - inv.setItem(ARROW_SLOT, button(Material.ARROW, mm("→"))); + inv.setItem(ARROW_SLOT, button(Material.ARROW, "→")); inv.setItem(RESULT_SLOT, result); - if (player.hasPermission("smp.perks.codex.give")) { - ItemStack give_button = new ItemBuilder(Material.BARRIER, plugin) - .setName(msg("codex_give_button")) - .build(); + if (player.hasPermission("smp.perks.codex.give")){ + ItemStack give_button = new ItemBuilder(Material.BARRIER,plugin).setName(mm("GIVE ITEM")).build(); inv.setItem(GIVE_SLOT, give_button); } - ItemStack book = new ItemBuilder(Material.BOOK, plugin) - .setLore(description) - .setName(msg("codex_description_title")) - .build(); + ItemStack book = + new ItemBuilder(Material.BOOK,plugin) + .setLore(description) + .setName(mm("Description")) + .build(); inv.setItem(DESCRIPTION_SLOT, book); - inv.setItem(BACK_SLOT, button(Material.ARROW, msg("codex_back"))); + inv.setItem(BACK_SLOT, button(Material.ARROW, "← Retour")); player.openInventory(inv); } // ---------------------------------------------------------------- HELPERS + private Component rarityLine(AbstractPerk perk) { + return plugin.rarityManager == null ? null : plugin.rarityManager.rarityLine(perk); + } + private List descriptionLines(List lines) { List out = new ArrayList<>(); if (lines.isEmpty()) { - out.add(msg("codex_no_description")); + out.add(mm("Aucune description configurée.")); } else { - // Descriptions définies dans la config des perks (pas dans les fichiers de langue) for (String line : lines) out.add(mm(line)); } return out; } - private ItemStack button(Material material, Component name) { + private ItemStack button(Material material, String name) { ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); - meta.displayName(name); + meta.displayName(mm(name)); item.setItemMeta(meta); return item; } @@ -249,24 +261,8 @@ private ItemStack filler() { return item; } - /** - * Récupère un message traduit, sans italique par défaut. - * Les variables sont passées par paires clé/valeur : msg("key", "count", "5", "max", "10") - */ - private Component msg(String key, String... vars) { - Component component; - if (vars.length == 0) { - component = plugin.messageManager.get(key); - } else { - Map map = new HashMap<>(); - for (int i = 0; i + 1 < vars.length; i += 2) map.put(vars[i], vars[i + 1]); - component = plugin.messageManager.get(key, map); - } - return component.decorationIfAbsent(TextDecoration.ITALIC, TextDecoration.State.FALSE); - } - /** MiniMessage sans italique par défaut (pour les textes venant de la config des perks). */ private static Component mm(String text) { return MiniMessage.miniMessage().deserialize("" + text); } -} \ No newline at end of file +} diff --git a/src/main/java/fr/antidino/smp/core/manager/rarity/Rarity.java b/src/main/java/fr/antidino/smp/core/manager/rarity/Rarity.java new file mode 100644 index 0000000..187651e --- /dev/null +++ b/src/main/java/fr/antidino/smp/core/manager/rarity/Rarity.java @@ -0,0 +1,17 @@ +package fr.antidino.smp.core.manager.rarity; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; + +public record Rarity(String id, String displayName, String color, boolean colorName, int order) { + + private static final MiniMessage MM = MiniMessage.miniMessage(); + + public Component label() { + return MM.deserialize("" + color + displayName); + } + + public String labelMini() { + return color + displayName; + } +} diff --git a/src/main/java/fr/antidino/smp/core/manager/rarity/RarityManager.java b/src/main/java/fr/antidino/smp/core/manager/rarity/RarityManager.java new file mode 100644 index 0000000..e98cc6a --- /dev/null +++ b/src/main/java/fr/antidino/smp/core/manager/rarity/RarityManager.java @@ -0,0 +1,124 @@ +package fr.antidino.smp.core.manager.rarity; + +import fr.antidino.smp.CorePlugin; +import fr.antidino.smp.core.manager.perk.AbstractPerk; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Charge rarity.yml : des niveaux de rareté ( + * auxquels on associe des perks par id. + */ +public class RarityManager { + + private static final MiniMessage MM = MiniMessage.miniMessage(); + + private final CorePlugin plugin; + + private final Map rarities = new LinkedHashMap<>(); + private final Map perkRarity = new LinkedHashMap<>(); + private Rarity defaultRarity; + private boolean colorPerkName = true; + private boolean sortCodex = true; + private String rarityLine = "Rareté : %rarity%"; + + public RarityManager(CorePlugin plugin) { + this.plugin = plugin; + reload(); + } + + public void reload() { + File file = new File(plugin.getDataFolder(), "rarity.yml"); + if (!file.exists()) plugin.saveResource("rarity.yml", false); + FileConfiguration config = YamlConfiguration.loadConfiguration(file); + + rarities.clear(); + perkRarity.clear(); + defaultRarity = null; + + colorPerkName = config.getBoolean("color_perk_name", true); + sortCodex = config.getBoolean("sort_codex_by_rarity", true); + rarityLine = config.getString("rarity_line", "Rareté : %rarity%"); + + ConfigurationSection sec = config.getConfigurationSection("rarities"); + if (sec != null) { + int order = 0; + for (String id : sec.getKeys(false)) { + ConfigurationSection r = sec.getConfigurationSection(id); + if (r == null) continue; + Rarity rarity = new Rarity( + id, + r.getString("display_name", id), + r.getString("color", ""), + r.getBoolean("color_name", colorPerkName), + order++); + rarities.put(id.toLowerCase(Locale.ROOT), rarity); + + for (String perkId : r.getStringList("perks")) { + String key = normalize(perkId); + Rarity previous = perkRarity.putIfAbsent(key, rarity); + if (previous != null && previous != rarity) { + plugin.getLogger().warning("[rarity] le perk '" + perkId + "' est dans '" + + previous.id() + "' et '" + id + "' : '" + previous.id() + "' est gardée."); + } + } + } + } else { + plugin.getLogger().warning("[rarity] section 'rarities' absente de rarity.yml"); + } + + String def = config.getString("default_rarity", ""); + if (def != null && !def.isBlank()) { + defaultRarity = rarities.get(def.toLowerCase(Locale.ROOT)); + if (defaultRarity == null) { + plugin.getLogger().warning("[rarity] default_rarity '" + def + "' n'existe pas."); + } + } + } + + private static String normalize(String id) { + String s = id == null ? "" : id.trim().toLowerCase(Locale.ROOT); + return s.endsWith("_perk") ? s.substring(0, s.length() - 5) : s; + } + + public Rarity getRarity(String perkId) { + Rarity r = perkRarity.get(normalize(perkId)); + return r != null ? r : defaultRarity; + } + + public Rarity getRarity(AbstractPerk perk) { + return perk == null ? null : getRarity(perk.getId()); + } + + public Component styledName(AbstractPerk perk) { + Rarity rarity = getRarity(perk); + if (rarity == null || !rarity.colorName()) { + return MM.deserialize("" + perk.getDisplayName()); + } + String plain = MM.stripTags(perk.getDisplayName()); + return MM.deserialize("" + rarity.color() + plain); + } + + public Component rarityLine(AbstractPerk perk) { + Rarity rarity = getRarity(perk); + if (rarity == null) return null; + return MM.deserialize("" + rarityLine.replace("%rarity%", rarity.labelMini())); + } + + public boolean sortCodexByRarity() { + return sortCodex; + } + + public int order(AbstractPerk perk) { + Rarity r = getRarity(perk); + return r == null ? -1 : r.order(); + } +} diff --git a/src/main/java/fr/antidino/smp/librairie/PDCBuilder.java b/src/main/java/fr/antidino/smp/librairie/PDCBuilder.java index d188630..aa19c84 100644 --- a/src/main/java/fr/antidino/smp/librairie/PDCBuilder.java +++ b/src/main/java/fr/antidino/smp/librairie/PDCBuilder.java @@ -130,9 +130,19 @@ public ItemStack updateItemLore(ItemStack item) { for (String raw : perks) { PerkEntry entry = PerkEntry.parse(raw); AbstractPerk perk = plugin.perkManager == null ? null : plugin.perkManager.getPerk(entry.id()); - String name = perk != null ? perk.getDisplayName() : entry.id(); - String level = entry.level() > 1 ? " " + AbstractPerk.roman(entry.level()) : ""; - lore.add(MiniMessage.miniMessage().deserialize(" ▪ " + name + level)); + Component line = MiniMessage.miniMessage().deserialize(" ▪ "); + if (perk != null && plugin.rarityManager != null) { + // nom coloré selon la rareté (rarity.yml) + line = line.append(plugin.rarityManager.styledName(perk)); + } else { + line = line.append(MiniMessage.miniMessage().deserialize( + "" + (perk != null ? perk.getDisplayName() : entry.id()))); + } + if (entry.level() > 1) { + line = line.append(MiniMessage.miniMessage().deserialize( + " " + AbstractPerk.roman(entry.level()))); + } + lore.add(line); } } @@ -143,7 +153,7 @@ public ItemStack updateItemLore(ItemStack item) { return item; } - /** Niveau d'un item perk (1 si absent : anciens items et niveau 1). */ + public int getPerkLevel(ItemStack perkItem) { if (perkItem == null || !perkItem.hasItemMeta()) return 1; return Math.max(1, perkItem.getItemMeta().getPersistentDataContainer() diff --git a/src/main/resources/perks/health.yml b/src/main/resources/perks/health.yml index 1f26139..66fcbda 100644 --- a/src/main/resources/perks/health.yml +++ b/src/main/resources/perks/health.yml @@ -26,6 +26,5 @@ modifiers: slot_group: "HEAD" description: - - "+ vitesse de déplacement" - - "- vie maximale" - - "(à appliquer sur des bottes)." + - "Augmente votre vie maximale" + - "(à appliquer sur un casque)." \ No newline at end of file diff --git a/src/main/resources/perks/lifesteal.yml b/src/main/resources/perks/lifesteal.yml index 6de0134..3b877d4 100644 --- a/src/main/resources/perks/lifesteal.yml +++ b/src/main/resources/perks/lifesteal.yml @@ -13,4 +13,10 @@ craft : C : APPLE values : - heal_percent : [10, 26, 40] \ No newline at end of file + heal_percent : [10, 26, 40] + + +description: + - "Vous soigne d'un pourcentage des dégâts" + - "infligés à chaque coup." + - "(à appliquer sur une arme)." \ No newline at end of file diff --git a/src/main/resources/perks/warden.yml b/src/main/resources/perks/warden.yml index 4c95d35..b5f7ed8 100644 --- a/src/main/resources/perks/warden.yml +++ b/src/main/resources/perks/warden.yml @@ -16,3 +16,8 @@ craft : D: "DIAMOND_BLOCK" cooldown : 30 cooldown-message : "Tu dois attendre encore %time%s avant de réutiliser La Capacité du Warden !" + +description: + - "Libère un rayon sonique dévastateur" + - "(Shift + clic à mains nues)." + - "À appliquer sur un plastron." \ No newline at end of file diff --git a/src/main/resources/rarity.yml b/src/main/resources/rarity.yml new file mode 100644 index 0000000..02f0c69 --- /dev/null +++ b/src/main/resources/rarity.yml @@ -0,0 +1,50 @@ +default_rarity: "common" + +color_perk_name: true + +# Ligne affichée dans le codex +rarity_line: "Rareté : %rarity%" + +sort_codex_by_rarity: true + +# Les raretés sont classées dans l'ordre du fichier : la première est la plus commune. +rarities: + common: + display_name: "Commun" + color: "" + perks: # ids des perks + - "sprinter_perk" + - "night_vision_perk" + + uncommon: + display_name: "Peu commun" + color: "" + perks: + - "health_perk" + - "speed_perk" + - "armor_perk" + - "miner_perk" + - "magnet_perk" + + rare: + display_name: "Rare" + color: "" + perks: + - "attack_damage_perk" + - "attack_speed_perk" + - "smelter_perk" + - "double_jump_perk" + - "no_fall_perk" + + epic: + display_name: "Épique" + color: "" + perks: + - "lifesteal_perk" + - "glass_cannon_perk" + + legendary: + display_name: "Légendaire" + color: "" + perks: + - "warden_perk"