diff --git a/README.md b/README.md
index c34d614..3c56788 100644
--- a/README.md
+++ b/README.md
@@ -157,6 +157,20 @@ api.getPendingVotes(player.getUniqueId(), pending -> {
- `getVotePartyCurrentVotes()` / `getVotePartyRequiredVotes()` : lecture directe en mémoire, thread principal uniquement.
- `getPendingVotes(...)` : lecture asynchrone du stockage configuré (MySQL ou YAML), ne consomme pas les votes contrairement à `/claim`.
+### Placeholders PlaceholderAPI (scoreboard/GUI sans coder de plugin)
+
+Si [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) est installé, VoteNetwork enregistre automatiquement ses propres placeholders (aucune config nécessaire) :
+
+| Placeholder | Valeur |
+|---|---|
+| `%votenetwork_voteparty_current%` | Votes cumulés sur ce serveur pour le VoteParty |
+| `%votenetwork_voteparty_required%` | Objectif VoteParty (`voteparty.votes-requis`) |
+| `%votenetwork_voteparty_remaining%` | Votes restants avant déclenchement |
+| `%votenetwork_maintenance%` | `true`/`false` |
+| `%votenetwork_pending%` | Votes en attente du joueur (rafraîchi en arrière-plan, pas de lag) |
+
+Utilisez ces placeholders directement dans la config de votre plugin de scoreboard/tab/GUI habituel (FeatherBoard, TAB, DeluxeMenus, ...) — **pas** `%current%`/`%required%` bruts, qui n'existent pas côté PlaceholderAPI et doivent être définis dans la config du plugin de scoreboard lui-même.
+
## Structure de la base de données
Créée automatiquement si absente (mode `mysql`) :
diff --git a/spigot/pom.xml b/spigot/pom.xml
index 77f5ba0..062042d 100644
--- a/spigot/pom.xml
+++ b/spigot/pom.xml
@@ -36,6 +36,12 @@
mysql-connector-j
${mysql.version}
+
+ me.clip
+ placeholderapi
+ 2.11.6
+ provided
+
@@ -47,6 +53,10 @@
sonatype
https://oss.sonatype.org/content/groups/public/
+
+ placeholderapi
+ https://repo.extendedclip.com/content/repositories/placeholderapi/
+
diff --git a/spigot/src/main/java/fr/votenetwork/spigot/VoteNetworkSpigot.java b/spigot/src/main/java/fr/votenetwork/spigot/VoteNetworkSpigot.java
index f8315d7..f24e99b 100644
--- a/spigot/src/main/java/fr/votenetwork/spigot/VoteNetworkSpigot.java
+++ b/spigot/src/main/java/fr/votenetwork/spigot/VoteNetworkSpigot.java
@@ -6,10 +6,12 @@ import fr.votenetwork.spigot.api.VoteNetworkAPIImpl;
import fr.votenetwork.spigot.command.ClaimCommand;
import fr.votenetwork.spigot.command.VoteAdminCommand;
import fr.votenetwork.spigot.listener.VoteMessageListener;
+import fr.votenetwork.spigot.placeholder.VoteNetworkPlaceholders;
import fr.votenetwork.spigot.storage.MySqlPendingVoteStore;
import fr.votenetwork.spigot.storage.PendingVoteStore;
import fr.votenetwork.spigot.storage.YamlPendingVoteStore;
import fr.votenetwork.spigot.voteparty.VoteParty;
+import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
@@ -58,6 +60,11 @@ public class VoteNetworkSpigot extends JavaPlugin {
VoteNetworkAPIHolder.set(api);
getServer().getServicesManager().register(VoteNetworkAPI.class, api, this, ServicePriority.Normal);
+ if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
+ new VoteNetworkPlaceholders(this, voteConfig, pendingVoteStore, voteParty, maintenanceState).register();
+ getLogger().info("Placeholders PlaceholderAPI enregistres (%votenetwork_...%).");
+ }
+
getLogger().info("VoteNetwork (Spigot) demarre.");
}
diff --git a/spigot/src/main/java/fr/votenetwork/spigot/placeholder/VoteNetworkPlaceholders.java b/spigot/src/main/java/fr/votenetwork/spigot/placeholder/VoteNetworkPlaceholders.java
new file mode 100644
index 0000000..08f8cea
--- /dev/null
+++ b/spigot/src/main/java/fr/votenetwork/spigot/placeholder/VoteNetworkPlaceholders.java
@@ -0,0 +1,102 @@
+package fr.votenetwork.spigot.placeholder;
+
+import fr.votenetwork.spigot.MaintenanceState;
+import fr.votenetwork.spigot.VoteConfig;
+import fr.votenetwork.spigot.VoteNetworkSpigot;
+import fr.votenetwork.spigot.storage.PendingVoteStore;
+import fr.votenetwork.spigot.voteparty.VoteParty;
+import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Placeholders PlaceholderAPI :
+ * %votenetwork_voteparty_current%
+ * %votenetwork_voteparty_required%
+ * %votenetwork_voteparty_remaining%
+ * %votenetwork_maintenance% -> "true"/"false"
+ * %votenetwork_pending% -> votes en attente du joueur (rafraichi en arriere-plan)
+ *
+ * Utilisables dans n'importe quel plugin de scoreboard/GUI qui supporte PlaceholderAPI
+ * (%votenetwork_...%, pas %current%/%required% bruts : ces derniers doivent etre configures
+ * comme placeholders dans le plugin de scoreboard lui-meme).
+ */
+public class VoteNetworkPlaceholders extends PlaceholderExpansion {
+
+ private final VoteNetworkSpigot plugin;
+ private final VoteConfig config;
+ private final PendingVoteStore pendingVoteStore;
+ private final VoteParty voteParty;
+ private final MaintenanceState maintenanceState;
+
+ private final Map pendingVotesCache = new ConcurrentHashMap<>();
+
+ public VoteNetworkPlaceholders(VoteNetworkSpigot plugin, VoteConfig config, PendingVoteStore pendingVoteStore,
+ VoteParty voteParty, MaintenanceState maintenanceState) {
+ this.plugin = plugin;
+ this.config = config;
+ this.pendingVoteStore = pendingVoteStore;
+ this.voteParty = voteParty;
+ this.maintenanceState = maintenanceState;
+ }
+
+ @Override
+ public @NotNull String getIdentifier() {
+ return "votenetwork";
+ }
+
+ @Override
+ public @NotNull String getAuthor() {
+ return "VoteNetwork";
+ }
+
+ @Override
+ public @NotNull String getVersion() {
+ return plugin.getDescription().getVersion();
+ }
+
+ @Override
+ public boolean persist() {
+ return true;
+ }
+
+ @Override
+ public String onRequest(OfflinePlayer player, @NotNull String params) {
+ switch (params.toLowerCase()) {
+ case "voteparty_current":
+ return String.valueOf(voteParty.getCurrentVotes());
+ case "voteparty_required":
+ return String.valueOf(config.getVotePartyVotesRequired());
+ case "voteparty_remaining":
+ return String.valueOf(Math.max(0, config.getVotePartyVotesRequired() - voteParty.getCurrentVotes()));
+ case "maintenance":
+ return String.valueOf(maintenanceState.isStopped());
+ case "pending":
+ if (player == null) {
+ return "0";
+ }
+ return String.valueOf(getPendingVotesCached(player));
+ default:
+ return null;
+ }
+ }
+
+ private int getPendingVotesCached(OfflinePlayer player) {
+ String key = player.getUniqueId().toString();
+ Integer cached = pendingVotesCache.get(key);
+
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
+ pendingVoteStore.peek(
+ player.getUniqueId(),
+ votes -> pendingVotesCache.put(key, votes),
+ exception -> { /* on garde la derniere valeur en cache en cas d'erreur */ }
+ )
+ );
+
+ return cached != null ? cached : 0;
+ }
+}
diff --git a/spigot/src/main/resources/plugin.yml b/spigot/src/main/resources/plugin.yml
index a741b0a..644ad54 100644
--- a/spigot/src/main/resources/plugin.yml
+++ b/spigot/src/main/resources/plugin.yml
@@ -4,6 +4,7 @@ version: ${project.version}
api-version: 1.18
author: VoteNetwork
description: Reception des votes reseau (direct ou en attente) avec VoteParty local.
+softdepend: [PlaceholderAPI]
commands:
claim: