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"