diff --git a/docs/optimisation_roadmap.md b/docs/optimisation_roadmap.md
new file mode 100644
index 0000000..29fc9e3
--- /dev/null
+++ b/docs/optimisation_roadmap.md
@@ -0,0 +1,335 @@
+# Roadmap de correction et d'optimisation du plugin SMPplugin
+
+## Objectif
+
+Ce document résume les points de friction actuels du plugin, les causes de performance, et la roadmap recommandée pour améliorer l'architecture sans réécrire tout le projet d'un coup.
+
+Le projet a une bonne base structurale, mais plusieurs zones critiques sont encore trop "hot path" et doivent être refactorisées pour rester scalable et maintenable.
+
+---
+
+## 1. Diagnostic rapide
+
+### Points forts
+
+- Structure logique par modules : `core`, `modules/perk`, `modules/spell`, `listeners`, `commands`.
+- Séparation des listeners, managers et logique de gameplay.
+- Le repo a une vraie intention architecturale, ce qui est bon pour la maintenabilité.
+- Les systèmes de perks et de spells sont bien pensés sur le plan conceptuel.
+
+### Points faibles
+
+- `PerkTasks` exécute trop de travail dans une boucle tick-based.
+- Le plugin recalcul souvent les niveaux/perks à partir des items PDC.
+- Les spells deviennent vite monolithiques et contiennent trop de logique métier dans une même classe.
+- Les classes de gestion globale (ex. `CorePlugin`, `PerkManager`, `AbstractPerk`) portent trop de responsabilités.
+- Le design est encore orienté "prototype / MVP" plutôt que "système extensible et optimisé".
+
+---
+
+## 2. Problèmes de performance observés
+
+### 2.1 Boucle de tick trop heavy
+
+La classe `PerkTasks` est la plus critique.
+
+Actuellement, le code parcourt les joueurs et répète plusieurs opérations à chaque tick :
+
+- lecture du niveau des perks sur l'armure
+- analyse du PDC des items
+- traitement de la vision nocturne
+- traitement de l'aimant
+- traitement du double saut
+
+Cela génère du travail inutile sur chaque tick et est coûteux sur un serveur Minecraft.
+
+### 2.2 Recalcul constant des données armure/perk
+
+Les méthodes comme `getArmorPerkLevels()` et `getEquippedPerk()` relisent souvent les données à partir des items.
+
+Le problème n'est pas seulement que cela est lent ; c'est surtout que cela se répète dans des chemins très critiques, alors que les données ne changent pas souvent.
+
+### 2.3 Les effets visuels et de gameplay sont trop imbriqués
+
+Dans `FlameSpell`, la logique mélange :
+
+- position de départ
+- direction
+- particules
+- collision
+- dégâts
+- knockback
+- cooldown
+
+C'est faisable pour 1 ou 2 sorts, mais ça ne scale pas quand il y a beaucoup de sorts, projectiles et effets.
+
+### 2.4 Architecture trop centralisée
+
+`CorePlugin` agit comme une classe de bootstrap + manager + orchestrateur + lifecycle + données globales.
+
+Ce n'est pas fatal, mais cela limite :
+
+- l'extensibilité
+- la testabilité
+- le découpage des responsabilités
+- la capacité à ajouter des services sans faire grossir la classe de manière incontrôlée
+
+---
+
+## 3. Architecture cible recommandée
+
+L'objectif est de passer à un modèle plus robuste :
+
+- registry / manager
+- cache d'état du joueur
+- services dédiés
+- contexte d'exécution
+- scheduler unique pour les effets
+
+### Structure cible
+
+```text
+modules/
+ perk/
+ PerkManager.java
+ PerkCache.java
+ PerkSnapshot.java
+ services/
+ NightVisionService.java
+ MagnetService.java
+ DoubleJumpService.java
+
+ spell/
+ SpellManager.java
+ SpellRegistry.java
+ SpellContext.java
+ SpellCooldownService.java
+ SpellExecutionService.java
+ type/
+ FlameSpell.java
+ FireballSpell.java
+ BeamSpell.java
+
+ listeners/
+ InventoryListener.java
+ ArmorListener.java
+ PlayerJoinListener.java
+```
+
+### Principe clé
+
+Le `hot path` ne doit plus reparcourir les items et relire le PDC à chaque tick.
+
+Il faut :
+
+- invalider le cache lors d'un changement d'armure / inventaire / reload
+- recalculer les données seulement quand nécessaire
+- lire uniquement les données déjà mises en cache pendant le runtime
+
+---
+
+## 4. Roadmap prioritaire
+
+## Phase 1 — Corriger les points de perf bloquants
+
+### Tâches
+
+- Introduire un `PlayerPerkCache` ou `PerkSnapshot`.
+- Invalider le cache sur :
+ - join
+ - leave
+ - armor equip / unequip
+ - changement d'inventaire
+ - reload
+- Réduire le travail de `PerkTasks.run()`.
+- Remplacer les recalculs complets par un accès au snapshot.
+- Limiter les traitements d'aimant et de vision nocturne à des conditions plus strictes.
+
+### Livrables
+
+- `PerkCache` ajouté
+- `markDirty()` / invalidation centralisée
+- `PerkTasks` ne lit plus les items PDC à chaque tick
+
+---
+
+## Phase 2 — Séparer les responsabilités
+
+### Tâches
+
+- Créer des services séparés pour :
+ - `NightVisionService`
+ - `MagnetService`
+ - `DoubleJumpService`
+- Déplacer la logique de compétence hors des classes de gestion globales.
+- Rendre `PerkManager` plus "registry" et moins "traitement de tout".
+- Faire de `AbstractPerk` une abstraction plus légère.
+
+### Livrables
+
+- plus de classes petites et spécialisées
+- logique de gameplay isolée dans ses services
+- code plus facile à tester
+
+---
+
+## Phase 3 — Refactor des spells
+
+### Tâches
+
+- Ajouter `SpellContext`.
+- Ajouter `SpellCooldownService`.
+- Ajouter `SpellExecutionService`.
+- Séparer :
+ - config du spell
+ - état runtime
+ - logique d'exécution
+ - effets visuels
+- Centraliser la gestion des projectiles / effets d'aoe / faisceaux.
+
+### Livrables
+
+- un système de spell extensible
+- chaque spell ne gère que son propre comportement
+- moins de duplicated logic
+
+---
+
+## Phase 4 — Refactor architecture globale
+
+### Tâches
+
+- Réduire la responsabilité de `CorePlugin`.
+- Déplacer les initialisations dans une structure de bootstrap propre.
+- Séparer bootstrap, managers, runtime state et lifecycle.
+- Mettre en place un vrai service layer si le projet grandit.
+
+### Livrables
+
+- plugin plus propre à maintenir
+- initialisation plus stable
+- reload plus robuste
+
+---
+
+## 5. Points à corriger dans le code actuel
+
+### `PerkTasks.java`
+
+Priorité absolue.
+
+À faire :
+
+- supprimer les recalculs complets dans `run()`
+- utiliser un cache de state joueur
+- réduire la fréquence de certains traitements
+- séparer les services
+
+### `PerkManager.java`
+
+À faire :
+
+- garder la registry, mais diminuer le travail de parsing dans les chemins fréquents
+- ajouter un snapshot par joueur
+- ne plus reconstruire les données à partir des items à chaque tick
+
+### `AbstractPerk.java`
+
+À faire :
+
+- séparer config et runtime effect logic
+- réduire la taille de cette classe
+- garder seulement les éléments centraux à la définition du perk
+
+### `AbstractSpell.java`
+
+À faire :
+
+- corriger la logique de permission et cooldown
+- centraliser le cooldown dans un service dédié
+- éviter les dépendances directes trop lourdes dans les classes dérivées
+
+### `FlameSpell.java`
+
+À faire :
+
+- séparer le comportement d'impact, la création du projectile et les effets visuels
+- utiliser un gestionnaire central de projectiles / effets
+- remplacer la logique monolithique par un système plus extensible
+
+---
+
+## 6. Priorités recommandées
+
+### Priorité 1 : performance
+
+1. `PerkTasks`
+2. `PerkManager`
+3. invalidation de cache
+
+### Priorité 2 : architecture
+
+4. `AbstractPerk`
+5. `AbstractSpell`
+6. `CorePlugin`
+
+### Priorité 3 : extensibilité
+
+7. `FlameSpell`
+8. Spell execution system
+9. services centralisés
+
+---
+
+## 7. Verdict final
+
+Le repo est déjà bien parti, mais il a encore un défaut majeur : il est trop dépendant des boucles périodiques et du recalcul de données que l'on ne devrait pas recalculer à chaque tick.
+
+La meilleure stratégie n'est pas de réécrire tout le plugin, mais de faire un refactor ciblé sur :
+
+- cache player state
+- invalidation par événements
+- services spécialisés
+- centralisation des effets
+- séparation config/runtime/execution
+
+C'est la bonne voie pour monter le système vers un niveau de maintenabilité et de performance nettement supérieur.
+
+---
+
+## 8. Exemple de plan de refactor en 3 itérations
+
+### Itération 1
+
+- `PerkCache`
+- invalidation sur events
+- `PerkTasks` plus léger
+
+### Itération 2
+
+- `SpellCooldownService`
+- `SpellContext`
+- architecture de sorts plus modulaire
+
+### Itération 3
+
+- `CorePlugin` moins chargé
+- services d'effets plus centralisés
+- architecture production-ready
+
+---
+
+## 9. Conclusion
+
+Le code actuel est correct pour un plugin fonctionnel, mais pas encore pour un système de gameplay qui doit rester performant et extensible.
+
+Le meilleur retour sur investissement est de corriger en priorité :
+
+- `PerkTasks`
+- `PerkManager`
+- le cache des données liées aux perks
+- la séparation des services de spell / cooldown / effects
+
+Si ces points sont traités correctement, le repo pourra passer d'un bon prototype à un plugin beaucoup plus solide et plus facile à évoluer.
+
+Si tu veux, je peux aussi te générer une version encore plus courte, ou une version “README-style” plus propre pour le dépôt.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9aa6151..48e097a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,9 +55,19 @@
papermc-repo
https://repo.papermc.io/repository/maven-public/
+
+ placeholderapi
+ https://repo.helpch.at/releases/
+
+
+ me.clip
+ placeholderapi
+ 2.12.3
+ provided
+
io.papermc.paper
paper-api
diff --git a/src/main/java/fr/antidino/smp/CorePlugin.java b/src/main/java/fr/antidino/smp/CorePlugin.java
index 7414a21..7193f0b 100644
--- a/src/main/java/fr/antidino/smp/CorePlugin.java
+++ b/src/main/java/fr/antidino/smp/CorePlugin.java
@@ -2,6 +2,10 @@
import fr.antidino.smp.modules.listeners.*;
import fr.antidino.smp.core.MessageManager;
+import fr.antidino.smp.modules.perk.*;
+import fr.antidino.smp.modules.perk.cache.DataCache;
+import fr.antidino.smp.modules.perk.cache.PlayerPerkCache;
+import fr.antidino.smp.modules.perk.papi.PapiExpansion;
import fr.antidino.smp.modules.perk.slotExpander.SlotExpander;
import fr.antidino.smp.modules.perk.slotExpander.SlotManager;
import fr.antidino.smp.modules.perk.codex.CodexListener;
@@ -10,23 +14,23 @@
import fr.antidino.smp.modules.commands.PerksCommand;
import fr.antidino.smp.core.database.DatabaseManager;
import fr.antidino.smp.core.database.DatabaseProvider;
-import fr.antidino.smp.modules.perk.SmithingRegisterAllItem;
import fr.antidino.smp.modules.perk.config.ConfigPerkManager;
-import fr.antidino.smp.modules.perk.AbstractPerk;
import fr.antidino.smp.modules.perk.listeners.PerkEffectListener;
-import fr.antidino.smp.modules.perk.PerkManager;
-import fr.antidino.smp.modules.perk.PerkTasks;
import fr.antidino.smp.modules.perk.rarity.RarityManager;
import fr.antidino.smp.core.Utils.PDCBuilder;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public final class CorePlugin extends JavaPlugin {
+ private static final Logger log = LoggerFactory.getLogger(CorePlugin.class);
public PDCBuilder pdcBuilder;
public PerkManager perkManager;
public SlotManager slotManager;
@@ -36,20 +40,34 @@ public final class CorePlugin extends JavaPlugin {
public PerkTasks perkTasks;
public SlotExpander slotExpander;
public RarityManager rarityManager;
+ public PlayerPerkCache playerPerkCache;
private DatabaseManager databaseManager;
public DatabaseProvider databaseProvider;
+ public DataCache dataCache;
@Override
public void onEnable() {
+ try {
+ new PapiExpansion(this).register();
+ }catch (Exception e){
+ log.info("PAPI Expansion Error");
+ }
+
+
+
+ dataCache = new DataCache(this);
+ dataCache.load();
configPerkManager = new ConfigPerkManager(this);
initConfig();
messageManager = new MessageManager(this);
+ playerPerkCache = new PlayerPerkCache(this);
getServer().getPluginManager().registerEvents(new CraftingListener(this), this);
getServer().getPluginManager().registerEvents(new SmithingListener(this), this);
getServer().getPluginManager().registerEvents(new ItemBreakListener(this), this);
- getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
+ getServer().getPluginManager().registerEvents(new PlayerJoinQuitEvent(this), this);
getServer().getPluginManager().registerEvents(new SavingPerkListeners(this), this);
+ getServer().getPluginManager().registerEvents(new PlayerChangeArmorListener(this), this);
rarityManager = new RarityManager(this);
pdcBuilder = new PDCBuilder(this);
@@ -102,7 +120,10 @@ public int fullReload() {
for (Player player : Bukkit.getOnlinePlayers()) {
player.discoverRecipes(keys);
}
+ dataCache.unload();
+ dataCache.load();
return perkManager.getPerks().size();
+
}
@Override
diff --git a/src/main/java/fr/antidino/smp/core/Utils/PDCBuilder.java b/src/main/java/fr/antidino/smp/core/Utils/PDCBuilder.java
index 3fbacb9..dda86e3 100644
--- a/src/main/java/fr/antidino/smp/core/Utils/PDCBuilder.java
+++ b/src/main/java/fr/antidino/smp/core/Utils/PDCBuilder.java
@@ -88,29 +88,30 @@ public ArrayList getAppliedPerks(ItemStack item) {
}
return new ArrayList<>(perksArray);
+
}
+
public ItemStack addPerkToItem(ItemStack item, String perkId) {
if (item == null || !item.hasItemMeta()) return item;
- ItemMeta meta = item.getItemMeta();
- PersistentDataContainer pdc = meta.getPersistentDataContainer();
+ item = plugin.perkManager.getPerk(perkId).onApply(item);
List perks = getAppliedPerks(item);
+ ItemMeta meta = item.getItemMeta();
+ PersistentDataContainer pdc = meta.getPersistentDataContainer();
perks.add(perkId);
-
pdc.set(SLOTS, PersistentDataType.LIST.strings(), perks);
pdc.set(USED_SLOTS, PersistentDataType.INTEGER, perks.size());
-
item.setItemMeta(meta);
-
-
- return updateItemLore(item);
+ item = updateItemLore(item);
+ return item;
}
public ItemStack updateItemLore(ItemStack item) {
+ log.info(item.toString());
ItemMeta meta = item.getItemMeta();
if (meta == null) {
plugin.getLogger().warning("[SMPplugin] Item is null");
diff --git a/src/main/java/fr/antidino/smp/core/database/DatabaseProvider.java b/src/main/java/fr/antidino/smp/core/database/DatabaseProvider.java
index bf742e6..cc08f19 100644
--- a/src/main/java/fr/antidino/smp/core/database/DatabaseProvider.java
+++ b/src/main/java/fr/antidino/smp/core/database/DatabaseProvider.java
@@ -63,7 +63,7 @@ public void InsertPerkCraftSQL(String perk_id,String uuid){
}
}
- public int NumberOfPerkCrafted(String perk_id){
+ public int getNumberOfCraft(String perk_id){
try (Connection connection = plugin.getConnexion()){
PreparedStatement ps = connection.prepareStatement(GET_PERK_CRAFT_SQL);
ps.setString(1, perk_id);
diff --git a/src/main/java/fr/antidino/smp/modules/commands/AdminCommands.java b/src/main/java/fr/antidino/smp/modules/commands/AdminCommands.java
index cdec232..45d1d78 100644
--- a/src/main/java/fr/antidino/smp/modules/commands/AdminCommands.java
+++ b/src/main/java/fr/antidino/smp/modules/commands/AdminCommands.java
@@ -51,6 +51,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
send(sender, messageManager.get("config_reloaded",vars));
}
case "give" -> give(sender, args);
+
default -> send(sender, messageManager.get("admin_command_usage"));
}
return true;
diff --git a/src/main/java/fr/antidino/smp/modules/listeners/CraftingListener.java b/src/main/java/fr/antidino/smp/modules/listeners/CraftingListener.java
index ec81e34..a770ffe 100644
--- a/src/main/java/fr/antidino/smp/modules/listeners/CraftingListener.java
+++ b/src/main/java/fr/antidino/smp/modules/listeners/CraftingListener.java
@@ -44,7 +44,7 @@ public void onCraftingEvent(CraftItemEvent event) {
String id = plugin.perkManager.getPerkId(resultType);
UUID playerId = player.getUniqueId();
if (id == null) return;
- int number_of_perk = plugin.databaseProvider.NumberOfPerkCrafted(id);
+ int number_of_perk = plugin.dataCache.getPerkCraftCount(id);
AbstractPerk perk = plugin.perkManager.getPerk(id);
if (perk.getMaxPossibleCraft() <= number_of_perk && perk.getMaxPossibleCraft() != -1) {
event.setCancelled(true);
@@ -59,8 +59,7 @@ public void onCraftingEvent(CraftItemEvent event) {
}
cooldownManager.setCooldown(playerId,0.3);
Bukkit.getScheduler().runTaskAsynchronously(plugin,()->{
- plugin.rarityManager.onCraft(player, perk, plugin.pdcBuilder.getPerkLevel(resultType));
- plugin.databaseProvider.InsertPerkCraftSQL(id,playerId.toString());
+ plugin.dataCache.addPerkCraftCount(id);
});
}
diff --git a/src/main/java/fr/antidino/smp/modules/listeners/PlayerChangeArmorListener.java b/src/main/java/fr/antidino/smp/modules/listeners/PlayerChangeArmorListener.java
new file mode 100644
index 0000000..26c4d30
--- /dev/null
+++ b/src/main/java/fr/antidino/smp/modules/listeners/PlayerChangeArmorListener.java
@@ -0,0 +1,26 @@
+package fr.antidino.smp.modules.listeners;
+
+import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent;
+import fr.antidino.smp.CorePlugin;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.inventory.EquipmentSlot;
+
+public class PlayerChangeArmorListener implements Listener {
+ private final CorePlugin plugin;
+
+ public PlayerChangeArmorListener(CorePlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler
+ public void onPlayerArmorChange(PlayerArmorChangeEvent event) {
+ Player player = event.getPlayer();
+ EquipmentSlot slot = event.getSlot();
+ plugin.playerPerkCache.updateSlot(player, slot);
+ Bukkit.getLogger().info(player.getName() + " has changed armor!");
+ }
+}
diff --git a/src/main/java/fr/antidino/smp/modules/listeners/PlayerJoinListener.java b/src/main/java/fr/antidino/smp/modules/listeners/PlayerJoinQuitEvent.java
similarity index 73%
rename from src/main/java/fr/antidino/smp/modules/listeners/PlayerJoinListener.java
rename to src/main/java/fr/antidino/smp/modules/listeners/PlayerJoinQuitEvent.java
index ea845fd..322b666 100644
--- a/src/main/java/fr/antidino/smp/modules/listeners/PlayerJoinListener.java
+++ b/src/main/java/fr/antidino/smp/modules/listeners/PlayerJoinQuitEvent.java
@@ -7,15 +7,18 @@
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
import java.util.List;
-public class PlayerJoinListener implements Listener {
+public class PlayerJoinQuitEvent implements Listener {
CorePlugin plugin;
- public PlayerJoinListener(CorePlugin plugin) {
+ public PlayerJoinQuitEvent(CorePlugin plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
+
List keys = plugin.perkManager.getPerks().stream()
.map(AbstractPerk::getKey)
.toList();
@@ -23,9 +26,15 @@ public void onPlayerJoin(PlayerJoinEvent event) {
event.getPlayer().discoverRecipes(keys);
}, 20L);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+
if ( !plugin.databaseProvider.hasEntry(event.getPlayer().getUniqueId().toString())){
plugin.databaseProvider.insertPlayerSQL(event.getPlayer());
}
+ plugin.playerPerkCache.update(event.getPlayer());
});
}
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ plugin.playerPerkCache.cleanup(event.getPlayer());
+ }
}
diff --git a/src/main/java/fr/antidino/smp/modules/listeners/SavingPerkListeners.java b/src/main/java/fr/antidino/smp/modules/listeners/SavingPerkListeners.java
index 620a756..e660ea4 100644
--- a/src/main/java/fr/antidino/smp/modules/listeners/SavingPerkListeners.java
+++ b/src/main/java/fr/antidino/smp/modules/listeners/SavingPerkListeners.java
@@ -40,7 +40,9 @@ public void onEntityDamage(EntityDamageEvent event) {
@EventHandler
public void onEntityRemove(EntityRemoveFromWorldEvent event) {
if (!(event.getEntity() instanceof Item item)) return;
+ if (item.getLocation().getY() > 0) return;
if (!isAPerkItem(item.getItemStack()))return;
+ if (item.getLocation().getBlockY()>0) return;
Location loc = item.getLocation();
World world = loc.getWorld();
Location chestLoc = new Location(world, loc.getBlockX(), SAFE_Y, loc.getBlockZ());
diff --git a/src/main/java/fr/antidino/smp/modules/perk/PerkManager.java b/src/main/java/fr/antidino/smp/modules/perk/PerkManager.java
index 4b6c388..d9586d1 100644
--- a/src/main/java/fr/antidino/smp/modules/perk/PerkManager.java
+++ b/src/main/java/fr/antidino/smp/modules/perk/PerkManager.java
@@ -145,4 +145,7 @@ public Map getArmorPerkLevels(Player player) {
}
return levels;
}
+ public List listPerks(){
+ return new ArrayList<>(registeredPerks.keySet());
+ }
}
diff --git a/src/main/java/fr/antidino/smp/modules/perk/PerkTasks.java b/src/main/java/fr/antidino/smp/modules/perk/PerkTasks.java
index d4b3daf..2e3db8b 100644
--- a/src/main/java/fr/antidino/smp/modules/perk/PerkTasks.java
+++ b/src/main/java/fr/antidino/smp/modules/perk/PerkTasks.java
@@ -10,16 +10,13 @@
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
+import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
/**
* Tâche répétée (toutes les 4 ticks) pour les perks "actifs en continu" portés sur l'armure :
@@ -57,13 +54,41 @@ public boolean isManaged(UUID id) {
private void run() {
tick++;
- boolean refreshNightVision = tick % 10 == 0;
+ boolean refreshNightVision = (tick % 10 == 0);
+
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.isDead()) continue;
- Map levels = plugin.perkManager.getArmorPerkLevels(player);
- nightVision(player, levels.getOrDefault(PerkTypeId.NIGHT_VISION, 0), refreshNightVision);
- magnet(player, levels.getOrDefault(PerkTypeId.MAGNET, 0));
- doubleJump(player, levels.getOrDefault(PerkTypeId.DOUBLE_JUMP, 0));
+
+ var cacheWrapper = plugin.playerPerkCache.getPerksCache(player.getUniqueId());
+ if (cacheWrapper == null) continue;
+
+ Map> perkcache = cacheWrapper.get();
+ if (perkcache == null || perkcache.isEmpty()) continue;
+
+
+ boolean hasNightVision = false;
+ boolean hasMagnet = false;
+ boolean hasDoubleJump = false;
+
+
+ for (List entries : perkcache.values()) {
+ if (entries == null) continue;
+
+ if (!hasNightVision && entries.contains(PerkTypeId.NIGHT_VISION)) {
+ hasNightVision = true;
+ }
+ if (!hasMagnet && entries.contains(PerkTypeId.MAGNET)) {
+ hasMagnet = true;
+ }
+ if (!hasDoubleJump && entries.contains(PerkTypeId.DOUBLE_JUMP)) {
+ hasDoubleJump = true;
+ }
+ }
+
+
+ nightVision(player, hasNightVision ? 1 : 0, refreshNightVision);
+ magnet(player, hasMagnet ? 3 : 0);
+ doubleJump(player, hasDoubleJump ? 1 : 0);
}
}
@@ -72,7 +97,6 @@ private void run() {
private void nightVision(Player player, int level, boolean refresh) {
UUID id = player.getUniqueId();
if (level > 0) {
- // 25s de durée, renouvelée toutes les 2s : jamais de clignotement
if (refresh || !nightVision.contains(id)) {
player.addPotionEffect(new PotionEffect(PotionEffectType.NIGHT_VISION, 500, 0, true, false, false));
nightVision.add(id);
@@ -95,12 +119,12 @@ private void magnet(Player player, int level) {
for (Entity entity : player.getNearbyEntities(radius, radius, radius)) {
if (!(entity instanceof Item item)) continue;
if (item.getPickupDelay() > 0) continue;
- // ne ramène pas les items que le joueur vient de jeter lui-même
+
UUID thrower = item.getThrower();
if (thrower != null && thrower.equals(player.getUniqueId())) continue;
Vector direction = target.clone().subtract(item.getLocation().toVector());
- if (direction.lengthSquared() < 0.64) continue; // le ramassage vanilla prend le relais
+ if (direction.lengthSquared() < 0.64) continue;
item.setVelocity(direction.normalize().multiply(speed));
}
}
diff --git a/src/main/java/fr/antidino/smp/modules/perk/cache/DataCache.java b/src/main/java/fr/antidino/smp/modules/perk/cache/DataCache.java
new file mode 100644
index 0000000..28effb1
--- /dev/null
+++ b/src/main/java/fr/antidino/smp/modules/perk/cache/DataCache.java
@@ -0,0 +1,47 @@
+package fr.antidino.smp.modules.perk.cache;
+
+import fr.antidino.smp.CorePlugin;
+import org.bukkit.Bukkit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DataCache {
+ private static final Logger log = LoggerFactory.getLogger(DataCache.class);
+ private final HashMap perk_craft_count = new HashMap<>();
+ private final CorePlugin plugin;
+ public DataCache(CorePlugin plugin) {
+ this.plugin = plugin;
+ }
+ public void load() {
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ for (String id : plugin.perkManager.listPerks()){
+ int numberOfCraft = plugin.databaseProvider.getNumberOfCraft(id);
+ perk_craft_count.put(id, numberOfCraft);
+ }
+ log.info("Loaded {} perks", perk_craft_count.size());
+ });
+ }
+ public void unload() {
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ for (String id : plugin.perkManager.listPerks()){
+ perk_craft_count.remove(id);
+ }
+ });
+ }
+ public int getPerkCraftCount(String id) {
+ return perk_craft_count.getOrDefault(id,0);
+ }
+ public void addPerkCraftCount(String id) {
+ int numberOfCraft = getPerkCraftCount(id);
+ perk_craft_count.put(id,numberOfCraft+1);
+ }
+
+
+ /**Debug **/
+ public Map getPerkCraftCount() {
+ return perk_craft_count;
+ }
+}
diff --git a/src/main/java/fr/antidino/smp/modules/perk/cache/PerksCache.java b/src/main/java/fr/antidino/smp/modules/perk/cache/PerksCache.java
new file mode 100644
index 0000000..27834fc
--- /dev/null
+++ b/src/main/java/fr/antidino/smp/modules/perk/cache/PerksCache.java
@@ -0,0 +1,23 @@
+package fr.antidino.smp.modules.perk.cache;
+
+
+import org.bukkit.inventory.EquipmentSlot;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PerksCache {
+ private final Map> perks = new HashMap<>();
+
+ public Map> get() {
+ return perks;
+ }
+
+
+ public void set(EquipmentSlot slot, List perks_list) {
+ perks.put(slot, perks_list);
+ }
+
+
+
+}
diff --git a/src/main/java/fr/antidino/smp/modules/perk/cache/PlayerPerkCache.java b/src/main/java/fr/antidino/smp/modules/perk/cache/PlayerPerkCache.java
new file mode 100644
index 0000000..c211fea
--- /dev/null
+++ b/src/main/java/fr/antidino/smp/modules/perk/cache/PlayerPerkCache.java
@@ -0,0 +1,82 @@
+package fr.antidino.smp.modules.perk.cache;
+
+
+import fr.antidino.smp.CorePlugin;
+import fr.antidino.smp.core.Utils.PDCBuilder;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.persistence.PersistentDataType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import java.util.*;
+
+public class PlayerPerkCache {
+ private static final Logger log = LoggerFactory.getLogger(PlayerPerkCache.class);
+ private final CorePlugin plugin;
+
+ private final Map cache = new HashMap<>();
+
+ public PlayerPerkCache(CorePlugin plugin) {
+ this.plugin = plugin;
+ }
+ /** Clean Cache Perk **/
+ public void cleanup(Player player) {
+ cache.remove(player.getUniqueId());
+ }
+ /** Clean Cache Perk **/
+ public void cleanup(UUID uuid) {
+ cache.remove(uuid);
+ }
+ public Map getCache() {
+ return cache;
+ }
+ public PerksCache getPerksCache(UUID uuid) {
+ return cache.get(uuid);
+ }
+
+ public void update(Player player) {
+ PerksCache perksCache = cache.get(player.getUniqueId());
+ if (perksCache == null) {
+ perksCache = new PerksCache();
+ }
+ for (EquipmentSlot slot : EquipmentSlot.values()) {
+ ItemStack item = getItemInSlot(player, slot);
+ List perks = readEntries(item);
+ perksCache.set(slot, perks);
+ }
+ cache.put(player.getUniqueId(), perksCache);
+ log.info("PlayerPerkCache updated");
+
+ }
+ public void updateSlot(Player player, EquipmentSlot slot) {
+ PerksCache perksCache = cache.get(player.getUniqueId());
+ if (perksCache == null) {
+ perksCache = new PerksCache();
+ }
+ ItemStack item = getItemInSlot(player, slot);
+ List perks = readEntries(item);
+ perksCache.set(slot, perks);
+ cache.put(player.getUniqueId(), perksCache);
+ }
+
+ private ItemStack getItemInSlot(Player player, EquipmentSlot slot) {
+ return switch (slot) {
+ case LEGS -> player.getInventory().getLeggings();
+ case FEET -> player.getInventory().getBoots();
+ case HEAD -> player.getInventory().getHelmet();
+ case CHEST -> player.getInventory().getChestplate();
+ case OFF_HAND -> player.getInventory().getItemInOffHand();
+ case HAND -> player.getInventory().getItemInMainHand();
+ default -> null;
+ };
+ }
+
+ private List readEntries(ItemStack item) {
+ if (item == null || !item.hasItemMeta()) return null;
+ return item.getItemMeta().getPersistentDataContainer().get(PDCBuilder.SLOTS, PersistentDataType.LIST.strings());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/fr/antidino/smp/modules/perk/papi/PapiExpansion.java b/src/main/java/fr/antidino/smp/modules/perk/papi/PapiExpansion.java
new file mode 100644
index 0000000..b4544a3
--- /dev/null
+++ b/src/main/java/fr/antidino/smp/modules/perk/papi/PapiExpansion.java
@@ -0,0 +1,49 @@
+package fr.antidino.smp.modules.perk.papi;
+
+import fr.antidino.smp.CorePlugin;
+import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PapiExpansion extends PlaceholderExpansion {
+ private static final Logger log = LoggerFactory.getLogger(PapiExpansion.class);
+ private final CorePlugin plugin;
+ public PapiExpansion(CorePlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ @NotNull
+ public String getAuthor() {
+ return "Antidino72"; //
+ }
+
+ @Override
+ @NotNull
+ public String getIdentifier() {
+ return "smp"; //
+ }
+
+ @Override
+ @NotNull
+ public String getVersion() {
+ return "1.0.0"; //
+ }
+
+
+ @Override
+ public String onPlaceholderRequest(Player player, @NotNull String params) {
+ if (player == null) {
+ return "";
+ }
+ if (params.startsWith("perk_number")) {
+ String subParam = params.replace("perk_number_", "");
+ return String.valueOf(plugin.dataCache.getPerkCraftCount(subParam));
+ }
+ return null;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/resources/perks/health.yml b/src/main/resources/perks/health.yml
index 66fcbda..951eab9 100644
--- a/src/main/resources/perks/health.yml
+++ b/src/main/resources/perks/health.yml
@@ -1,7 +1,7 @@
id : "sprinter"
item:
- material : "SUGAR"
- display_name : "Sprinter"
+ material : "REDSTONE"
+ display_name : "HEALTH BOOST"
craft :
number : -1
shape :
@@ -18,12 +18,24 @@ level:
modifiers:
- health:
+ health-head:
attribute: "MAX_HEALTH"
amount: 2.0
amount_per_level: 1.0
operation: "ADD_NUMBER"
slot_group: "HEAD"
+ health-body:
+ attribute: "MAX_HEALTH"
+ amount: 2.0
+ amount_per_level: 1.0
+ operation: "ADD_NUMBER"
+ slot_group: "CHEST"
+ health-leg:
+ attribute: "MAX_HEALTH"
+ amount: 2.0
+ amount_per_level: 1.0
+ operation: "ADD_NUMBER"
+ slot_group: "LEGS"
description:
- "Augmente votre vie maximale"
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index f4d26b7..2588117 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,9 +1,12 @@
name: SMPplugin
version: '${version}'
-
+author: "Antidino72"
main: fr.antidino.smp.CorePlugin
api-version: '26.3'
load: STARTUP
+softdepend: [
+ "PlaceholderAPI",
+]
commands :
perk:
permission : smp.admin.perk