Compare commits
12
Commits
62d4d30530
..
v1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a470339f4 | ||
|
|
4aa1a25c54 | ||
|
|
964f375841 | ||
|
|
35a8425254 | ||
|
|
5a7aec5344 | ||
|
|
686cb4de26 | ||
|
|
70d41af369 | ||
|
|
3a84f16f23 | ||
|
|
5abe49fdd0 | ||
|
|
d5f6e0cfb3 | ||
|
|
0ca99c7e2c | ||
|
|
ff1afd58fe |
@@ -0,0 +1,38 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '17'
|
||||||
|
cache: maven
|
||||||
|
|
||||||
|
- name: Install Maven
|
||||||
|
run: |
|
||||||
|
which mvn || (sudo apt-get update && sudo apt-get install -y maven)
|
||||||
|
|
||||||
|
- name: Build and test
|
||||||
|
run: mvn -B clean verify
|
||||||
|
|
||||||
|
- name: Upload jars
|
||||||
|
if: success()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: votenetwork-jars
|
||||||
|
path: |
|
||||||
|
velocity/target/VoteNetwork-Velocity.jar
|
||||||
|
spigot/target/VoteNetwork-Spigot.jar
|
||||||
|
retention-days: 14
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
target/
|
target/
|
||||||
dependency-reduced-pom.xml
|
dependency-reduced-pom.xml
|
||||||
|
dist/
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
.vscode/
|
.vscode/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
MSP3/
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ mvn clean package
|
|||||||
|
|
||||||
Jars produits dans `velocity/target/` et `spigot/target/`.
|
Jars produits dans `velocity/target/` et `spigot/target/`.
|
||||||
|
|
||||||
|
### Publier une release (Gitea)
|
||||||
|
|
||||||
|
```
|
||||||
|
GITEA_TOKEN=xxxx ./scripts/release.sh 1.0.1 "Description de la release"
|
||||||
|
```
|
||||||
|
|
||||||
|
Build les 2 jars, les zip dans `dist/VoteNetwork.zip`, crée le tag/release sur Gitea et y attache le zip. Nécessite un token Gitea (Paramètres → Applications → Générer un nouveau jeton, scope `repo`), passé uniquement en variable d'environnement — jamais commité.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Chaque composant a son propre fichier, tous deux dans un dossier **`votenetwork`** :
|
Chaque composant a son propre fichier, tous deux dans un dossier **`votenetwork`** :
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
[CENTER][SIZE=5][B]VoteNetwork[/B][/SIZE]
|
||||||
|
Systeme de vote reseau complet pour Velocity + Spigot/Paper
|
||||||
|
Par [B]Sar_Tron[/B] — Licence MIT — Code source : https://gitea.louconnect.fr/Sar_Tron/VoteNetwork[/CENTER]
|
||||||
|
|
||||||
|
[CENTER][B]⚠ Ce plugin necessite les DEUX modules du zip (proxy Velocity + serveurs Spigot) et une base MySQL. Il ne fonctionne PAS en le mettant seul sur un serveur solo sans proxy.[/B][/CENTER]
|
||||||
|
|
||||||
|
[HR][/HR]
|
||||||
|
[SIZE=4][B]Pourquoi VoteNetwork ?[/B][/SIZE]
|
||||||
|
|
||||||
|
La plupart des plugins de vote forcent un choix : soit le joueur doit etre connecte au bon moment, soit tout passe par une base de donnees meme quand ce n'est pas necessaire. VoteNetwork fait les deux intelligemment :
|
||||||
|
|
||||||
|
[LIST]
|
||||||
|
[*]Joueur connecte -> recompense [B]instantanee[/B] sur son serveur actuel, sans toucher a la base de donnees.
|
||||||
|
[*]Joueur deconnecte -> vote stocke automatiquement, recuperable plus tard via [B]/claim[/B].
|
||||||
|
[*]VoteParty [B]independant par serveur[/B] (chaque mode de jeu a son propre objectif et ses propres recompenses).
|
||||||
|
[*]Mode maintenance pour bloquer /claim pendant un reset sans desinstaller le plugin.
|
||||||
|
[/LIST]
|
||||||
|
|
||||||
|
[HR][/HR]
|
||||||
|
[SIZE=4][B]Fonctionnalites[/B][/SIZE]
|
||||||
|
|
||||||
|
[LIST]
|
||||||
|
[*][B]Vote direct en jeu[/B] via Plugin Messaging (canal custom, aucune latence de base de donnees si le joueur est en ligne).
|
||||||
|
[*][B]Votes en attente[/B] stockes en MySQL (partage reseau) ou en YAML local, au choix.
|
||||||
|
[*][B]Portee configurable[/B] des votes en attente : un compteur partage entre tous les serveurs, OU un compteur independant par serveur (le joueur peut claim ses votes du serveur A sans toucher a ceux du serveur B).
|
||||||
|
[*][B]VoteParty local[/B] par serveur : seuil, commandes et messages 100% personnalisables, persistant au redemarrage.
|
||||||
|
[*][B]Liste de commandes[/B] entierement configurable pour chaque vote et pour le declenchement du VoteParty.
|
||||||
|
[*][B]Message broadcast[/B] optionnel a chaque vote/claim, personnalisable.
|
||||||
|
[*][B]Mode maintenance[/B] (/vote stop|start) pour bloquer /claim proprement pendant un reset.
|
||||||
|
[*][B]Commande de diagnostic[/B] (/vote testdb) pour verifier la connexion MySQL ou l'acces au fichier YAML sans redemarrer.
|
||||||
|
[*][B]API Java publique[/B] (+ enregistrement ServicesManager Bukkit) pour lire l'etat du vote depuis un autre plugin.
|
||||||
|
[*][B]Support PlaceholderAPI[/B] natif : %votenetwork_voteparty_current%, %votenetwork_voteparty_required%, %votenetwork_voteparty_remaining%, %votenetwork_maintenance%, %votenetwork_pending% — pour scoreboard, tab, GUI, sans coder une ligne.
|
||||||
|
[*][B]Merge automatique de config[/B] : les nouvelles options ajoutees par une mise a jour n'ecrasent jamais tes reglages existants.
|
||||||
|
[*]Compatible [B]Java 17[/B], du [B]1.18[/B] aux versions les plus recentes (API additive, testee en production).
|
||||||
|
[/LIST]
|
||||||
|
|
||||||
|
[HR][/HR]
|
||||||
|
[SIZE=4][B]Installation[/B][/SIZE]
|
||||||
|
|
||||||
|
[LIST=1]
|
||||||
|
[*][B]VoteNetwork-Velocity.jar[/B] -> dossier plugins/ du proxy Velocity.
|
||||||
|
[*][B]VoteNetwork-Spigot.jar[/B] -> dossier plugins/ de CHAQUE serveur de jeu (aucune limite de nombre).
|
||||||
|
[*]Demarrer une fois pour generer les configs, puis editer :
|
||||||
|
[LIST]
|
||||||
|
[*]Proxy : plugins/votenetwork/config.properties
|
||||||
|
[*]Serveur : plugins/votenetwork/config.yml
|
||||||
|
[/LIST]
|
||||||
|
[*]Renseigner [B]les memes identifiants MySQL[/B] partout (proxy + tous les serveurs) — c'est cette base commune qui permet a un joueur de voter hors ligne puis de recuperer ses votes n'importe ou.
|
||||||
|
[*]Redemarrer. La table est creee automatiquement, aucun script SQL a lancer a la main.
|
||||||
|
[/LIST]
|
||||||
|
|
||||||
|
[HR][/HR]
|
||||||
|
[SIZE=4][B]Commandes & permissions[/B][/SIZE]
|
||||||
|
|
||||||
|
[TABLE]
|
||||||
|
[TR][TD][B]Commande[/B][/TD][TD][B]Ou[/B][/TD][TD][B]Qui[/B][/TD][TD][B]Description[/B][/TD][/TR]
|
||||||
|
[TR][TD]votenetwork vote <pseudo>[/TD][TD]Velocity[/TD][TD]Console uniquement[/TD][TD]Enregistre un vote pour le joueur[/TD][/TR]
|
||||||
|
[TR][TD]/claim[/TD][TD]Spigot[/TD][TD]Joueurs[/TD][TD]Recupere les votes en attente[/TD][/TR]
|
||||||
|
[TR][TD]/vote stop|start[/TD][TD]Spigot[/TD][TD]votenetwork.admin (op)[/TD][TD]Bascule le mode maintenance[/TD][/TR]
|
||||||
|
[TR][TD]/vote testdb[/TD][TD]Spigot[/TD][TD]votenetwork.admin (op)[/TD][TD]Teste le stockage configure[/TD][/TR]
|
||||||
|
[/TABLE]
|
||||||
|
|
||||||
|
[HR][/HR]
|
||||||
|
[SIZE=4][B]Pre-requis[/B][/SIZE]
|
||||||
|
|
||||||
|
[LIST]
|
||||||
|
[*]Un proxy [B]Velocity[/B] + au moins un serveur [B]Spigot/Paper 1.18+[/B].
|
||||||
|
[*]Une base [B]MySQL[/B] accessible depuis le proxy ET tous les serveurs (ou stockage YAML local pour un usage solo/test uniquement).
|
||||||
|
[*][B]Java 17[/B] minimum.
|
||||||
|
[*][I]Optionnel[/I] : PlaceholderAPI pour les placeholders de scoreboard.
|
||||||
|
[/LIST]
|
||||||
|
|
||||||
|
[HR][/HR]
|
||||||
|
[CENTER]Code source complet et documentation detaillee (config avancee, portee per-server, API) disponibles sur le depot Git : [B]https://gitea.louconnect.fr/Sar_Tron/VoteNetwork[/B]
|
||||||
|
|
||||||
|
Bugs / suggestions -> ouvrez une issue sur le depot.
|
||||||
|
|
||||||
|
Licence MIT — libre d'utilisation, modification et redistribution avec attribution.[/CENTER]
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>fr.votenetwork</groupId>
|
<groupId>fr.votenetwork</groupId>
|
||||||
<artifactId>votenetwork-parent</artifactId>
|
<artifactId>votenetwork-parent</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.2</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
@@ -37,6 +37,8 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<hikaricp.version>5.1.0</hikaricp.version>
|
<hikaricp.version>5.1.0</hikaricp.version>
|
||||||
<mysql.version>8.0.33</mysql.version>
|
<mysql.version>8.0.33</mysql.version>
|
||||||
|
<junit.version>5.10.2</junit.version>
|
||||||
|
<mockito.version>5.11.0</mockito.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
@@ -63,6 +65,14 @@
|
|||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>3.5.1</version>
|
<version>3.5.1</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.2.5</version>
|
||||||
|
<configuration>
|
||||||
|
<argLine>-Dnet.bytebuddy.experimental=true</argLine>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build les 2 jars, zip, puis cree une release Gitea avec le zip attache.
|
||||||
|
#
|
||||||
|
# Usage :
|
||||||
|
# GITEA_TOKEN=xxxx ./scripts/release.sh 1.0.1 "Description de la release"
|
||||||
|
#
|
||||||
|
# Le token doit avoir le scope "repo" (Parametres -> Applications -> Generer un nouveau jeton).
|
||||||
|
# Ne JAMAIS commit le token. Passe-le uniquement en variable d'environnement.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="${1:?Usage: release.sh <version> [notes]}"
|
||||||
|
NOTES="${2:-Release ${VERSION}}"
|
||||||
|
|
||||||
|
GITEA_HOST="https://gitea.louconnect.fr"
|
||||||
|
OWNER="Sar_Tron"
|
||||||
|
REPO="VoteNetwork"
|
||||||
|
TAG="v${VERSION}"
|
||||||
|
|
||||||
|
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||||
|
echo "Erreur : variable GITEA_TOKEN non definie." >&2
|
||||||
|
echo "Cree un token dans Gitea (Parametres > Applications, scope repo) puis :" >&2
|
||||||
|
echo " GITEA_TOKEN=xxxx ./scripts/release.sh ${VERSION}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo "==> Build Maven"
|
||||||
|
mvn -q clean package
|
||||||
|
|
||||||
|
echo "==> Preparation du zip de distribution"
|
||||||
|
rm -rf dist
|
||||||
|
mkdir -p dist
|
||||||
|
cp velocity/target/VoteNetwork-Velocity.jar dist/
|
||||||
|
cp spigot/target/VoteNetwork-Spigot.jar dist/
|
||||||
|
|
||||||
|
if command -v zip >/dev/null 2>&1; then
|
||||||
|
(cd dist && zip -q VoteNetwork.zip VoteNetwork-Velocity.jar VoteNetwork-Spigot.jar)
|
||||||
|
else
|
||||||
|
powershell -Command "Compress-Archive -Path dist/VoteNetwork-Velocity.jar,dist/VoteNetwork-Spigot.jar -DestinationPath dist/VoteNetwork.zip -Force"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Creation de la release Gitea ${TAG}"
|
||||||
|
RELEASE_JSON=$(curl -sf -X POST "${GITEA_HOST}/api/v1/repos/${OWNER}/${REPO}/releases" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"${TAG}\",\"name\":\"VoteNetwork ${VERSION}\",\"body\":$(printf '%s' "$NOTES" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))' 2>/dev/null || printf '"%s"' "$NOTES"),\"draft\":false,\"prerelease\":false}")
|
||||||
|
|
||||||
|
RELEASE_ID=$(printf '%s' "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
|
||||||
|
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
echo "Erreur : creation de la release echouee." >&2
|
||||||
|
echo "$RELEASE_JSON" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Upload du zip (release id ${RELEASE_ID})"
|
||||||
|
curl -sf -X POST "${GITEA_HOST}/api/v1/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}/assets?name=VoteNetwork.zip" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-F "attachment=@dist/VoteNetwork.zip" > /dev/null
|
||||||
|
|
||||||
|
echo "==> Fait : ${GITEA_HOST}/${OWNER}/${REPO}/releases/tag/${TAG}"
|
||||||
+13
-1
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>fr.votenetwork</groupId>
|
<groupId>fr.votenetwork</groupId>
|
||||||
<artifactId>votenetwork-parent</artifactId>
|
<artifactId>votenetwork-parent</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>votenetwork-spigot</artifactId>
|
<artifactId>votenetwork-spigot</artifactId>
|
||||||
@@ -42,6 +42,18 @@
|
|||||||
<version>2.11.6</version>
|
<version>2.11.6</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>${mockito.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package fr.votenetwork.spigot;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.storage.PendingVoteStore;
|
||||||
|
import fr.votenetwork.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reclame les votes en attente d'un joueur et distribue les recompenses. Partagee entre la
|
||||||
|
* commande /claim (texte) et le menu de reclamation visuel (/votemenu).
|
||||||
|
*/
|
||||||
|
public final class ClaimAction {
|
||||||
|
|
||||||
|
private ClaimAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void claim(VoteNetworkSpigot plugin, VoteConfig config, PendingVoteStore pendingVoteStore,
|
||||||
|
VoteParty voteParty, Player player) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||||
|
pendingVoteStore.fetchAndClear(
|
||||||
|
player.getUniqueId(),
|
||||||
|
pendingVotes -> Bukkit.getScheduler().runTask(plugin, () -> apply(plugin, config, voteParty, player, pendingVotes)),
|
||||||
|
exception -> Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
plugin.getLogger().severe("Erreur reclamation pour " + player.getName() + ": " + exception.getMessage());
|
||||||
|
player.sendMessage(config.getMessageMaintenance());
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void apply(VoteNetworkSpigot plugin, VoteConfig config, VoteParty voteParty, Player player, int pendingVotes) {
|
||||||
|
if (pendingVotes <= 0) {
|
||||||
|
player.sendMessage(config.getMessageClaimEmpty());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < pendingVotes; i++) {
|
||||||
|
for (String cmd : config.getVoteRewardCommands()) {
|
||||||
|
String parsed = cmd.replace("%player%", player.getName());
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voteParty.addVotes(pendingVotes);
|
||||||
|
|
||||||
|
player.sendMessage(config.getMessageClaimSuccess().replace("%amount%", String.valueOf(pendingVotes)));
|
||||||
|
|
||||||
|
if (config.isVoteBroadcastEnabled()) {
|
||||||
|
Bukkit.broadcastMessage(config.getVoteBroadcastMessage()
|
||||||
|
.replace("%player%", player.getName())
|
||||||
|
.replace("%amount%", String.valueOf(pendingVotes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
package fr.votenetwork.spigot;
|
package fr.votenetwork.spigot;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.gui.VoteSite;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class VoteConfig {
|
public class VoteConfig {
|
||||||
@@ -141,6 +146,159 @@ public class VoteConfig {
|
|||||||
"&cVous n'avez pas la permission d'utiliser cette commande."));
|
"&cVous n'avez pas la permission d'utiliser cette commande."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGuiTitle() {
|
||||||
|
return colorize(cfg().getString("gui.title", "&8VoteNetwork"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGuiProgressItemMaterial() {
|
||||||
|
return cfg().getString("gui.progress-item.material", "NETHER_STAR");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGuiProgressItemName() {
|
||||||
|
return colorize(cfg().getString("gui.progress-item.name", "&b&lVoteParty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getGuiProgressItemLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.progress-item.lore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGuiClickMessage() {
|
||||||
|
return colorize(cfg().getString("gui.click-message", "&a[Vote] Clique sur le lien pour voter :"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses gui.vote-sites (a YAML list of maps: name/material/url/lore) into VoteSite
|
||||||
|
* objects, one item per configured vote site.
|
||||||
|
*/
|
||||||
|
public List<VoteSite> getVoteSites() {
|
||||||
|
List<VoteSite> sites = new ArrayList<>();
|
||||||
|
for (Map<?, ?> raw : cfg().getMapList("gui.vote-sites")) {
|
||||||
|
String name = raw.containsKey("name") ? String.valueOf(raw.get("name")) : "&aVoter";
|
||||||
|
String materialName = raw.containsKey("material") ? String.valueOf(raw.get("material")) : "PAPER";
|
||||||
|
String url = raw.containsKey("url") ? String.valueOf(raw.get("url")) : "";
|
||||||
|
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
Object loreObj = raw.get("lore");
|
||||||
|
if (loreObj instanceof List<?> loreList) {
|
||||||
|
for (Object line : loreList) {
|
||||||
|
lore.add(String.valueOf(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Material material;
|
||||||
|
try {
|
||||||
|
material = Material.valueOf(materialName.toUpperCase(Locale.ROOT));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
material = Material.PAPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
sites.add(new VoteSite(colorize(name), material, url, colorizeList(lore)));
|
||||||
|
}
|
||||||
|
return sites;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Menu principal (/votemenu) ---
|
||||||
|
|
||||||
|
public String getMainMenuTitle() {
|
||||||
|
return colorize(cfg().getString("gui.main-menu.title", "&8VoteNetwork - Menu"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMainMenuHeadLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.main-menu.head-lore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainMenuVoteItemMaterial() {
|
||||||
|
return cfg().getString("gui.main-menu.vote-item.material", "PAPER");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainMenuVoteItemName() {
|
||||||
|
return colorize(cfg().getString("gui.main-menu.vote-item.name", "&aVoter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMainMenuVoteItemLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.main-menu.vote-item.lore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainMenuClaimItemMaterial() {
|
||||||
|
return cfg().getString("gui.main-menu.claim-item.material", "CHEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainMenuClaimItemName() {
|
||||||
|
return colorize(cfg().getString("gui.main-menu.claim-item.name", "&eMes votes en attente"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMainMenuClaimItemLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.main-menu.claim-item.lore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainMenuAdminItemMaterial() {
|
||||||
|
return cfg().getString("gui.main-menu.admin-item.material", "REDSTONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainMenuAdminItemName() {
|
||||||
|
return colorize(cfg().getString("gui.main-menu.admin-item.name", "&cAdministration"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMainMenuAdminItemLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.main-menu.admin-item.lore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Menu de reclamation (/claim visuel) ---
|
||||||
|
|
||||||
|
public String getClaimMenuTitle() {
|
||||||
|
return colorize(cfg().getString("gui.claim-menu.title", "&8VoteNetwork - Reclamer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClaimMenuLoadingLore() {
|
||||||
|
return colorize(cfg().getString("gui.claim-menu.loading-lore", "&7Chargement..."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClaimMenuAvailableMaterial() {
|
||||||
|
return cfg().getString("gui.claim-menu.available.material", "CHEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClaimMenuAvailableName() {
|
||||||
|
return colorize(cfg().getString("gui.claim-menu.available.name", "&e%amount% vote(s) en attente"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getClaimMenuAvailableLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.claim-menu.available.lore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClaimMenuEmptyMaterial() {
|
||||||
|
return cfg().getString("gui.claim-menu.empty.material", "BARRIER");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClaimMenuEmptyName() {
|
||||||
|
return colorize(cfg().getString("gui.claim-menu.empty.name", "&7Aucun vote en attente"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getClaimMenuEmptyLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.claim-menu.empty.lore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Menu d'administration (/votemenu, reserve aux ops) ---
|
||||||
|
|
||||||
|
public String getAdminMenuTitle() {
|
||||||
|
return colorize(cfg().getString("gui.admin-menu.title", "&8VoteNetwork - Administration"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAdminMenuItemMaterial() {
|
||||||
|
return cfg().getString("gui.admin-menu.item.material", "LEVER");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAdminMenuItemNameEnabled() {
|
||||||
|
return colorize(cfg().getString("gui.admin-menu.item.name-maintenance-on", "&c&lMaintenance : ACTIVEE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAdminMenuItemNameDisabled() {
|
||||||
|
return colorize(cfg().getString("gui.admin-menu.item.name-maintenance-off", "&a&lMaintenance : DESACTIVEE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAdminMenuItemLore() {
|
||||||
|
return colorizeList(cfg().getStringList("gui.admin-menu.item.lore"));
|
||||||
|
}
|
||||||
|
|
||||||
private String colorize(String input) {
|
private String colorize(String input) {
|
||||||
return input == null ? "" : ChatColor.translateAlternateColorCodes('&', input);
|
return input == null ? "" : ChatColor.translateAlternateColorCodes('&', input);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import fr.votenetwork.spigot.api.VoteNetworkAPIHolder;
|
|||||||
import fr.votenetwork.spigot.api.VoteNetworkAPIImpl;
|
import fr.votenetwork.spigot.api.VoteNetworkAPIImpl;
|
||||||
import fr.votenetwork.spigot.command.ClaimCommand;
|
import fr.votenetwork.spigot.command.ClaimCommand;
|
||||||
import fr.votenetwork.spigot.command.VoteAdminCommand;
|
import fr.votenetwork.spigot.command.VoteAdminCommand;
|
||||||
|
import fr.votenetwork.spigot.command.VoteGuiCommand;
|
||||||
|
import fr.votenetwork.spigot.command.VoteMenuCommand;
|
||||||
|
import fr.votenetwork.spigot.gui.VoteMenuListener;
|
||||||
|
import fr.votenetwork.spigot.gui.VotePartyGuiListener;
|
||||||
import fr.votenetwork.spigot.listener.VoteMessageListener;
|
import fr.votenetwork.spigot.listener.VoteMessageListener;
|
||||||
import fr.votenetwork.spigot.placeholder.VoteNetworkPlaceholders;
|
import fr.votenetwork.spigot.placeholder.VoteNetworkPlaceholders;
|
||||||
import fr.votenetwork.spigot.storage.MySqlPendingVoteStore;
|
import fr.votenetwork.spigot.storage.MySqlPendingVoteStore;
|
||||||
@@ -55,6 +59,11 @@ public class VoteNetworkSpigot extends JavaPlugin {
|
|||||||
|
|
||||||
getCommand("claim").setExecutor(new ClaimCommand(this, voteConfig, pendingVoteStore, voteParty, maintenanceState));
|
getCommand("claim").setExecutor(new ClaimCommand(this, voteConfig, pendingVoteStore, voteParty, maintenanceState));
|
||||||
getCommand("vote").setExecutor(new VoteAdminCommand(this, voteConfig, pendingVoteStore, maintenanceState));
|
getCommand("vote").setExecutor(new VoteAdminCommand(this, voteConfig, pendingVoteStore, maintenanceState));
|
||||||
|
getCommand("votegui").setExecutor(new VoteGuiCommand(voteConfig, voteParty));
|
||||||
|
getCommand("votemenu").setExecutor(new VoteMenuCommand(voteConfig, voteParty));
|
||||||
|
getServer().getPluginManager().registerEvents(new VotePartyGuiListener(voteConfig), this);
|
||||||
|
getServer().getPluginManager().registerEvents(
|
||||||
|
new VoteMenuListener(this, voteConfig, pendingVoteStore, voteParty, maintenanceState), this);
|
||||||
|
|
||||||
VoteNetworkAPI api = new VoteNetworkAPIImpl(this, voteConfig, pendingVoteStore, voteParty, maintenanceState);
|
VoteNetworkAPI api = new VoteNetworkAPIImpl(this, voteConfig, pendingVoteStore, voteParty, maintenanceState);
|
||||||
VoteNetworkAPIHolder.set(api);
|
VoteNetworkAPIHolder.set(api);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package fr.votenetwork.spigot.command;
|
package fr.votenetwork.spigot.command;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.ClaimAction;
|
||||||
import fr.votenetwork.spigot.MaintenanceState;
|
import fr.votenetwork.spigot.MaintenanceState;
|
||||||
import fr.votenetwork.spigot.VoteNetworkSpigot;
|
import fr.votenetwork.spigot.VoteNetworkSpigot;
|
||||||
import fr.votenetwork.spigot.VoteConfig;
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
import fr.votenetwork.spigot.storage.PendingVoteStore;
|
import fr.votenetwork.spigot.storage.PendingVoteStore;
|
||||||
import fr.votenetwork.spigot.voteparty.VoteParty;
|
import fr.votenetwork.spigot.voteparty.VoteParty;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
@@ -42,41 +42,7 @@ public class ClaimCommand implements CommandExecutor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
ClaimAction.claim(plugin, config, pendingVoteStore, voteParty, player);
|
||||||
pendingVoteStore.fetchAndClear(
|
|
||||||
player.getUniqueId(),
|
|
||||||
pendingVotes -> Bukkit.getScheduler().runTask(plugin, () -> applyClaim(player, pendingVotes)),
|
|
||||||
exception -> Bukkit.getScheduler().runTask(plugin, () -> {
|
|
||||||
plugin.getLogger().severe("Erreur /claim pour " + player.getName() + ": " + exception.getMessage());
|
|
||||||
player.sendMessage(config.getMessageMaintenance());
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyClaim(Player player, int pendingVotes) {
|
|
||||||
if (pendingVotes <= 0) {
|
|
||||||
player.sendMessage(config.getMessageClaimEmpty());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < pendingVotes; i++) {
|
|
||||||
for (String cmd : config.getVoteRewardCommands()) {
|
|
||||||
String parsed = cmd.replace("%player%", player.getName());
|
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
voteParty.addVotes(pendingVotes);
|
|
||||||
|
|
||||||
player.sendMessage(config.getMessageClaimSuccess().replace("%amount%", String.valueOf(pendingVotes)));
|
|
||||||
|
|
||||||
if (config.isVoteBroadcastEnabled()) {
|
|
||||||
Bukkit.broadcastMessage(config.getVoteBroadcastMessage()
|
|
||||||
.replace("%player%", player.getName())
|
|
||||||
.replace("%amount%", String.valueOf(pendingVotes)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package fr.votenetwork.spigot.command;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
|
import fr.votenetwork.spigot.gui.VotePartyGui;
|
||||||
|
import fr.votenetwork.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class VoteGuiCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final VoteParty voteParty;
|
||||||
|
|
||||||
|
public VoteGuiCommand(VoteConfig config, VoteParty voteParty) {
|
||||||
|
this.config = config;
|
||||||
|
this.voteParty = voteParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Commande reservee aux joueurs.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
VotePartyGui.open(player, config, voteParty);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package fr.votenetwork.spigot.command;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
|
import fr.votenetwork.spigot.gui.MainMenuGui;
|
||||||
|
import fr.votenetwork.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class VoteMenuCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final VoteParty voteParty;
|
||||||
|
|
||||||
|
public VoteMenuCommand(VoteConfig config, VoteParty voteParty) {
|
||||||
|
this.config = config;
|
||||||
|
this.voteParty = voteParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Commande reservee aux joueurs.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
MainMenuGui.open(player, config, voteParty);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.MaintenanceState;
|
||||||
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/** Ops-only screen (permission votenetwork.admin) to toggle maintenance mode with one click. */
|
||||||
|
public final class AdminMenuGui {
|
||||||
|
|
||||||
|
private AdminMenuGui() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void open(Player player, VoteConfig config, MaintenanceState maintenanceState) {
|
||||||
|
AdminMenuHolder holder = new AdminMenuHolder();
|
||||||
|
Inventory inventory = Bukkit.createInventory(holder, 27, config.getAdminMenuTitle());
|
||||||
|
holder.setInventory(inventory);
|
||||||
|
|
||||||
|
inventory.setItem(AdminMenuHolder.ITEM_SLOT, buildItem(config, maintenanceState.isStopped()));
|
||||||
|
|
||||||
|
player.openInventory(inventory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemStack buildItem(VoteConfig config, boolean stopped) {
|
||||||
|
Material material;
|
||||||
|
try {
|
||||||
|
material = Material.valueOf(config.getAdminMenuItemMaterial().toUpperCase(Locale.ROOT));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
material = Material.LEVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack item = new ItemStack(material);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(stopped ? config.getAdminMenuItemNameEnabled() : config.getAdminMenuItemNameDisabled());
|
||||||
|
meta.setLore(config.getAdminMenuItemLore());
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
|
||||||
|
/** Marker holder for the ops-only administration screen (toggle maintenance mode). */
|
||||||
|
public class AdminMenuHolder implements InventoryHolder {
|
||||||
|
|
||||||
|
public static final int ITEM_SLOT = 13;
|
||||||
|
|
||||||
|
private Inventory inventory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInventory(Inventory inventory) {
|
||||||
|
this.inventory = inventory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
|
import fr.votenetwork.spigot.VoteNetworkSpigot;
|
||||||
|
import fr.votenetwork.spigot.storage.PendingVoteStore;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visual counterpart of /claim: shows how many votes are waiting for the player, resolved
|
||||||
|
* asynchronously via {@link PendingVoteStore#peek}, and lets them claim by clicking the item.
|
||||||
|
*/
|
||||||
|
public final class ClaimMenuGui {
|
||||||
|
|
||||||
|
private ClaimMenuGui() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void open(Player player, VoteNetworkSpigot plugin, VoteConfig config, PendingVoteStore pendingVoteStore) {
|
||||||
|
ClaimMenuHolder holder = new ClaimMenuHolder();
|
||||||
|
Inventory inventory = Bukkit.createInventory(holder, 27, config.getClaimMenuTitle());
|
||||||
|
holder.setInventory(inventory);
|
||||||
|
|
||||||
|
ItemStack loading = new ItemStack(Material.CLOCK);
|
||||||
|
ItemMeta loadingMeta = loading.getItemMeta();
|
||||||
|
if (loadingMeta != null) {
|
||||||
|
loadingMeta.setDisplayName(config.getClaimMenuLoadingLore());
|
||||||
|
loading.setItemMeta(loadingMeta);
|
||||||
|
}
|
||||||
|
inventory.setItem(ClaimMenuHolder.ITEM_SLOT, loading);
|
||||||
|
|
||||||
|
player.openInventory(inventory);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||||
|
pendingVoteStore.peek(
|
||||||
|
player.getUniqueId(),
|
||||||
|
amount -> Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
holder.setPendingVotes(amount);
|
||||||
|
if (player.getOpenInventory().getTopInventory().getHolder() == holder) {
|
||||||
|
inventory.setItem(ClaimMenuHolder.ITEM_SLOT, buildItem(config, amount));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
exception -> Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
plugin.getLogger().severe("Erreur menu reclamation pour " + player.getName() + ": " + exception.getMessage());
|
||||||
|
player.closeInventory();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemStack buildItem(VoteConfig config, int amount) {
|
||||||
|
boolean available = amount > 0;
|
||||||
|
String materialName = available ? config.getClaimMenuAvailableMaterial() : config.getClaimMenuEmptyMaterial();
|
||||||
|
String name = available
|
||||||
|
? config.getClaimMenuAvailableName().replace("%amount%", String.valueOf(amount))
|
||||||
|
: config.getClaimMenuEmptyName();
|
||||||
|
java.util.List<String> lore = available ? config.getClaimMenuAvailableLore() : config.getClaimMenuEmptyLore();
|
||||||
|
|
||||||
|
Material material;
|
||||||
|
try {
|
||||||
|
material = Material.valueOf(materialName.toUpperCase(Locale.ROOT));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
material = Material.CHEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack item = new ItemStack(material);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(name);
|
||||||
|
meta.setLore(lore);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
|
||||||
|
/** Marker holder for the visual /claim screen opened from the main menu. */
|
||||||
|
public class ClaimMenuHolder implements InventoryHolder {
|
||||||
|
|
||||||
|
public static final int ITEM_SLOT = 13;
|
||||||
|
|
||||||
|
private Inventory inventory;
|
||||||
|
private volatile int pendingVotes = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInventory(Inventory inventory) {
|
||||||
|
this.inventory = inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** -1 while the async lookup is still pending, >= 0 once resolved. */
|
||||||
|
public int getPendingVotes() {
|
||||||
|
return pendingVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPendingVotes(int pendingVotes) {
|
||||||
|
this.pendingVotes = pendingVotes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
|
import fr.votenetwork.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /votemenu entry point: player head with VoteParty progress, plus navigation to the vote-site
|
||||||
|
* links (/votegui), the visual claim menu, and (ops only) the administration menu.
|
||||||
|
*/
|
||||||
|
public final class MainMenuGui {
|
||||||
|
|
||||||
|
private MainMenuGui() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void open(Player player, VoteConfig config, VoteParty voteParty) {
|
||||||
|
MainMenuHolder holder = new MainMenuHolder();
|
||||||
|
Inventory inventory = Bukkit.createInventory(holder, 27, config.getMainMenuTitle());
|
||||||
|
holder.setInventory(inventory);
|
||||||
|
|
||||||
|
int required = config.getVotePartyVotesRequired();
|
||||||
|
int current = Math.min(voteParty.getCurrentVotes(), required);
|
||||||
|
|
||||||
|
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
|
||||||
|
SkullMeta skullMeta = (SkullMeta) head.getItemMeta();
|
||||||
|
if (skullMeta != null) {
|
||||||
|
skullMeta.setOwningPlayer(player);
|
||||||
|
skullMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&b&l" + player.getName()));
|
||||||
|
skullMeta.setLore(config.getMainMenuHeadLore().stream()
|
||||||
|
.map(line -> line.replace("%player%", player.getName())
|
||||||
|
.replace("%current%", String.valueOf(current))
|
||||||
|
.replace("%required%", String.valueOf(required)))
|
||||||
|
.toList());
|
||||||
|
head.setItemMeta(skullMeta);
|
||||||
|
}
|
||||||
|
inventory.setItem(4, head);
|
||||||
|
|
||||||
|
inventory.setItem(MainMenuHolder.VOTE_SLOT, buildItem(
|
||||||
|
config.getMainMenuVoteItemMaterial(), config.getMainMenuVoteItemName(), config.getMainMenuVoteItemLore()));
|
||||||
|
inventory.setItem(MainMenuHolder.CLAIM_SLOT, buildItem(
|
||||||
|
config.getMainMenuClaimItemMaterial(), config.getMainMenuClaimItemName(), config.getMainMenuClaimItemLore()));
|
||||||
|
|
||||||
|
if (player.hasPermission("votenetwork.admin")) {
|
||||||
|
inventory.setItem(MainMenuHolder.ADMIN_SLOT, buildItem(
|
||||||
|
config.getMainMenuAdminItemMaterial(), config.getMainMenuAdminItemName(), config.getMainMenuAdminItemLore()));
|
||||||
|
}
|
||||||
|
|
||||||
|
player.openInventory(inventory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemStack buildItem(String materialName, String name, java.util.List<String> lore) {
|
||||||
|
Material material;
|
||||||
|
try {
|
||||||
|
material = Material.valueOf(materialName.toUpperCase(java.util.Locale.ROOT));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
material = Material.PAPER;
|
||||||
|
}
|
||||||
|
ItemStack item = new ItemStack(material);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(name);
|
||||||
|
meta.setLore(lore);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
|
||||||
|
/** Marker holder for the /votemenu main screen. */
|
||||||
|
public class MainMenuHolder implements InventoryHolder {
|
||||||
|
|
||||||
|
public static final int VOTE_SLOT = 11;
|
||||||
|
public static final int CLAIM_SLOT = 13;
|
||||||
|
public static final int ADMIN_SLOT = 15;
|
||||||
|
|
||||||
|
private Inventory inventory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInventory(Inventory inventory) {
|
||||||
|
this.inventory = inventory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.ClaimAction;
|
||||||
|
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 org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
|
||||||
|
/** Click handling for the main/claim/admin menus (/votemenu tree). */
|
||||||
|
public class VoteMenuListener implements Listener {
|
||||||
|
|
||||||
|
private final VoteNetworkSpigot plugin;
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final PendingVoteStore pendingVoteStore;
|
||||||
|
private final VoteParty voteParty;
|
||||||
|
private final MaintenanceState maintenanceState;
|
||||||
|
|
||||||
|
public VoteMenuListener(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
Object rawHolder = event.getInventory().getHolder();
|
||||||
|
if (!(event.getWhoClicked() instanceof Player player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawHolder instanceof MainMenuHolder) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
handleMainMenuClick(player, event.getRawSlot());
|
||||||
|
} else if (rawHolder instanceof ClaimMenuHolder holder) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
handleClaimMenuClick(player, holder, event.getRawSlot());
|
||||||
|
} else if (rawHolder instanceof AdminMenuHolder) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
handleAdminMenuClick(player, event.getRawSlot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMainMenuClick(Player player, int slot) {
|
||||||
|
if (slot == MainMenuHolder.VOTE_SLOT) {
|
||||||
|
VotePartyGui.open(player, config, voteParty);
|
||||||
|
} else if (slot == MainMenuHolder.CLAIM_SLOT) {
|
||||||
|
ClaimMenuGui.open(player, plugin, config, pendingVoteStore);
|
||||||
|
} else if (slot == MainMenuHolder.ADMIN_SLOT && player.hasPermission("votenetwork.admin")) {
|
||||||
|
AdminMenuGui.open(player, config, maintenanceState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleClaimMenuClick(Player player, ClaimMenuHolder holder, int slot) {
|
||||||
|
if (slot != ClaimMenuHolder.ITEM_SLOT || holder.getPendingVotes() < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (maintenanceState.isStopped()) {
|
||||||
|
player.closeInventory();
|
||||||
|
player.sendMessage(config.getMessageMaintenance());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.closeInventory();
|
||||||
|
ClaimAction.claim(plugin, config, pendingVoteStore, voteParty, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAdminMenuClick(Player player, int slot) {
|
||||||
|
if (slot != AdminMenuHolder.ITEM_SLOT || !player.hasPermission("votenetwork.admin")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean newState = !maintenanceState.isStopped();
|
||||||
|
maintenanceState.setStopped(newState);
|
||||||
|
player.sendMessage(newState ? config.getMessageMaintenanceEnabled() : config.getMessageMaintenanceDisabled());
|
||||||
|
player.getOpenInventory().getTopInventory().setItem(AdminMenuHolder.ITEM_SLOT, AdminMenuGui.buildItem(config, newState));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
|
import fr.votenetwork.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and opens the VoteParty progress GUI: a progress item (slot 4) plus one item per
|
||||||
|
* configured vote site (starting at slot 9), each opening its URL when clicked.
|
||||||
|
*/
|
||||||
|
public final class VotePartyGui {
|
||||||
|
|
||||||
|
public static final int PROGRESS_SLOT = 4;
|
||||||
|
public static final int SITES_START_SLOT = 9;
|
||||||
|
|
||||||
|
private VotePartyGui() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void open(Player player, VoteConfig config, VoteParty voteParty) {
|
||||||
|
List<VoteSite> sites = config.getVoteSites();
|
||||||
|
int size = ((SITES_START_SLOT + sites.size() + 8) / 9) * 9;
|
||||||
|
size = Math.max(size, 18);
|
||||||
|
|
||||||
|
VotePartyGuiHolder holder = new VotePartyGuiHolder();
|
||||||
|
Inventory inventory = Bukkit.createInventory(holder, size, config.getGuiTitle());
|
||||||
|
holder.setInventory(inventory);
|
||||||
|
|
||||||
|
int required = config.getVotePartyVotesRequired();
|
||||||
|
int current = Math.min(voteParty.getCurrentVotes(), required);
|
||||||
|
|
||||||
|
ItemStack progressItem = new ItemStack(materialOrFallback(config.getGuiProgressItemMaterial()));
|
||||||
|
ItemMeta progressMeta = progressItem.getItemMeta();
|
||||||
|
if (progressMeta != null) {
|
||||||
|
progressMeta.setDisplayName(config.getGuiProgressItemName());
|
||||||
|
progressMeta.setLore(config.getGuiProgressItemLore().stream()
|
||||||
|
.map(line -> line.replace("%current%", String.valueOf(current))
|
||||||
|
.replace("%required%", String.valueOf(required)))
|
||||||
|
.toList());
|
||||||
|
progressItem.setItemMeta(progressMeta);
|
||||||
|
}
|
||||||
|
inventory.setItem(PROGRESS_SLOT, progressItem);
|
||||||
|
|
||||||
|
int slot = SITES_START_SLOT;
|
||||||
|
for (VoteSite site : sites) {
|
||||||
|
ItemStack item = new ItemStack(site.getMaterial());
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(site.getName());
|
||||||
|
meta.setLore(site.getLore());
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
inventory.setItem(slot, item);
|
||||||
|
slot++;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.openInventory(inventory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static org.bukkit.Material materialOrFallback(String name) {
|
||||||
|
try {
|
||||||
|
return org.bukkit.Material.valueOf(name.toUpperCase(java.util.Locale.ROOT));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return org.bukkit.Material.NETHER_STAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker holder used to recognize our own GUI inventory in InventoryClickEvent, so the
|
||||||
|
* listener never touches clicks happening in unrelated inventories (chests, other plugins'
|
||||||
|
* GUIs, etc.).
|
||||||
|
*/
|
||||||
|
public class VotePartyGuiHolder implements InventoryHolder {
|
||||||
|
|
||||||
|
private Inventory inventory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInventory(Inventory inventory) {
|
||||||
|
this.inventory = inventory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.VoteConfig;
|
||||||
|
import net.md_5.bungee.api.chat.ClickEvent;
|
||||||
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bukkit cannot open a browser directly from an inventory click, so a click on a vote-site
|
||||||
|
* item closes the GUI and sends a clickable chat message (ClickEvent OPEN_URL) instead.
|
||||||
|
*/
|
||||||
|
public class VotePartyGuiListener implements Listener {
|
||||||
|
|
||||||
|
private final VoteConfig config;
|
||||||
|
|
||||||
|
public VotePartyGuiListener(VoteConfig config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (!(event.getInventory().getHolder() instanceof VotePartyGuiHolder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
int slot = event.getRawSlot();
|
||||||
|
if (slot < VotePartyGui.SITES_START_SLOT || !(event.getWhoClicked() instanceof Player player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<VoteSite> sites = config.getVoteSites();
|
||||||
|
int index = slot - VotePartyGui.SITES_START_SLOT;
|
||||||
|
if (index < 0 || index >= sites.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoteSite site = sites.get(index);
|
||||||
|
if (site.getUrl() == null || site.getUrl().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.closeInventory();
|
||||||
|
TextComponent message = new TextComponent(config.getGuiClickMessage() + " " + site.getUrl());
|
||||||
|
message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, site.getUrl()));
|
||||||
|
player.spigot().sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package fr.votenetwork.spigot.gui;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single vote-site entry configured in config.yml (gui.vote-sites): the display item and
|
||||||
|
* the URL opened (via a clickable chat message) when the player clicks it in the GUI.
|
||||||
|
*/
|
||||||
|
public class VoteSite {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Material material;
|
||||||
|
private final String url;
|
||||||
|
private final List<String> lore;
|
||||||
|
|
||||||
|
public VoteSite(String name, Material material, String url, List<String> lore) {
|
||||||
|
this.name = name;
|
||||||
|
this.material = material;
|
||||||
|
this.url = url;
|
||||||
|
this.lore = lore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Material getMaterial() {
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getLore() {
|
||||||
|
return lore;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,79 @@ voteparty:
|
|||||||
progress-message: "&b[Vote] &f%current%&7/&f%required% &fvotes avant le prochain VoteParty !"
|
progress-message: "&b[Vote] &f%current%&7/&f%required% &fvotes avant le prochain VoteParty !"
|
||||||
triggered-message: "&a[Vote] &fVoteParty declenche ! Profitez des recompenses !"
|
triggered-message: "&a[Vote] &fVoteParty declenche ! Profitez des recompenses !"
|
||||||
|
|
||||||
|
# GUI /votegui : affiche la progression du VoteParty et des items cliquables vers les sites
|
||||||
|
# de vote (ouvre le lien via un message chat cliquable, Bukkit ne peut pas ouvrir un navigateur
|
||||||
|
# directement depuis un clic d'inventaire).
|
||||||
|
gui:
|
||||||
|
title: "&8VoteNetwork"
|
||||||
|
progress-item:
|
||||||
|
material: NETHER_STAR
|
||||||
|
name: "&b&lVoteParty"
|
||||||
|
lore:
|
||||||
|
- "&7Progression actuelle :"
|
||||||
|
- "&f%current%&7/&f%required% &7votes"
|
||||||
|
click-message: "&a[Vote] Clique sur le lien pour voter :"
|
||||||
|
vote-sites:
|
||||||
|
- name: "&aVoter sur le site 1"
|
||||||
|
material: PAPER
|
||||||
|
url: "https://example.com/vote/site1"
|
||||||
|
lore:
|
||||||
|
- "&7Clique pour voter !"
|
||||||
|
- name: "&aVoter sur le site 2"
|
||||||
|
material: BOOK
|
||||||
|
url: "https://example.com/vote/site2"
|
||||||
|
lore:
|
||||||
|
- "&7Clique pour voter !"
|
||||||
|
|
||||||
|
# Menu principal /votemenu : profil du joueur, progression VoteParty, et boutons de
|
||||||
|
# navigation vers le menu de vote (/votegui), de reclamation et (pour les ops) d'administration.
|
||||||
|
main-menu:
|
||||||
|
title: "&8VoteNetwork - Menu"
|
||||||
|
head-lore:
|
||||||
|
- "&7Joueur : &f%player%"
|
||||||
|
- "&7Progression VoteParty :"
|
||||||
|
- "&f%current%&7/&f%required% &7votes"
|
||||||
|
vote-item:
|
||||||
|
material: PAPER
|
||||||
|
name: "&aVoter"
|
||||||
|
lore:
|
||||||
|
- "&7Clique pour voir les liens de vote."
|
||||||
|
claim-item:
|
||||||
|
material: CHEST
|
||||||
|
name: "&eMes votes en attente"
|
||||||
|
lore:
|
||||||
|
- "&7Clique pour reclamer tes votes stockes."
|
||||||
|
admin-item:
|
||||||
|
material: REDSTONE
|
||||||
|
name: "&cAdministration"
|
||||||
|
lore:
|
||||||
|
- "&7Clique pour gerer le mode maintenance."
|
||||||
|
|
||||||
|
# Menu de reclamation visuel (equivalent GUI de /claim).
|
||||||
|
claim-menu:
|
||||||
|
title: "&8VoteNetwork - Reclamer"
|
||||||
|
loading-lore: "&7Chargement..."
|
||||||
|
available:
|
||||||
|
material: CHEST
|
||||||
|
name: "&e%amount% vote(s) en attente"
|
||||||
|
lore:
|
||||||
|
- "&7Clique pour reclamer !"
|
||||||
|
empty:
|
||||||
|
material: BARRIER
|
||||||
|
name: "&7Aucun vote en attente"
|
||||||
|
lore:
|
||||||
|
- "&7Reviens plus tard."
|
||||||
|
|
||||||
|
# Menu d'administration (reserve aux ops, permission votenetwork.admin).
|
||||||
|
admin-menu:
|
||||||
|
title: "&8VoteNetwork - Administration"
|
||||||
|
item:
|
||||||
|
material: LEVER
|
||||||
|
name-maintenance-on: "&c&lMaintenance : ACTIVEE"
|
||||||
|
name-maintenance-off: "&a&lMaintenance : DESACTIVEE"
|
||||||
|
lore:
|
||||||
|
- "&7Clique pour basculer le mode maintenance."
|
||||||
|
|
||||||
messages:
|
messages:
|
||||||
direct-vote: "&a[Vote] &fMerci pour ton vote !"
|
direct-vote: "&a[Vote] &fMerci pour ton vote !"
|
||||||
claim-success: "&a[Vote] &fVous avez recupere &e%amount% &fvote(s) en attente !"
|
claim-success: "&a[Vote] &fVous avez recupere &e%amount% &fvote(s) en attente !"
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ commands:
|
|||||||
description: Administration du systeme de vote (maintenance, test du stockage).
|
description: Administration du systeme de vote (maintenance, test du stockage).
|
||||||
usage: /vote <stop|start|testdb>
|
usage: /vote <stop|start|testdb>
|
||||||
permission: votenetwork.admin
|
permission: votenetwork.admin
|
||||||
|
votegui:
|
||||||
|
description: Ouvre le GUI de progression du VoteParty avec les liens de vote.
|
||||||
|
usage: /votegui
|
||||||
|
votemenu:
|
||||||
|
description: Ouvre le menu principal (profil, votes en attente, administration).
|
||||||
|
usage: /votemenu
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
votenetwork.admin:
|
votenetwork.admin:
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package fr.votenetwork.spigot;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class VoteConfigTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void defaultsAreUsedWhenKeysAbsent() {
|
||||||
|
VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class);
|
||||||
|
when(plugin.getConfig()).thenReturn(new YamlConfiguration());
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(plugin);
|
||||||
|
|
||||||
|
assertTrue(config.isMysqlStorage());
|
||||||
|
assertEquals("127.0.0.1", config.getMysqlHost());
|
||||||
|
assertEquals(3306, config.getMysqlPort());
|
||||||
|
assertEquals("votenetwork", config.getMysqlDatabase());
|
||||||
|
assertEquals(100, config.getVotePartyVotesRequired());
|
||||||
|
assertFalse(config.isPerServerScope());
|
||||||
|
assertFalse(config.isVoteBroadcastEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mysqlStorageIsFalseOnlyForYaml() {
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
yaml.set("storage.type", "yaml");
|
||||||
|
VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class);
|
||||||
|
when(plugin.getConfig()).thenReturn(yaml);
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(plugin);
|
||||||
|
|
||||||
|
assertFalse(config.isMysqlStorage());
|
||||||
|
assertEquals("yaml", config.getStorageType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void perServerScopeReadFromConfig() {
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
yaml.set("storage.pending-votes-scope", "per-server");
|
||||||
|
yaml.set("server-name", "gen1");
|
||||||
|
VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class);
|
||||||
|
when(plugin.getConfig()).thenReturn(yaml);
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(plugin);
|
||||||
|
|
||||||
|
assertTrue(config.isPerServerScope());
|
||||||
|
assertEquals("gen1", config.getServerName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void messagesAreColorizedAndPlaceholdersPreserved() {
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
yaml.set("messages.claim-success", "&aTu as recupere &e%amount% &avotes !");
|
||||||
|
VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class);
|
||||||
|
when(plugin.getConfig()).thenReturn(yaml);
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(plugin);
|
||||||
|
String message = config.getMessageClaimSuccess();
|
||||||
|
|
||||||
|
assertFalse(message.contains("&a"), "les codes couleur '&' doivent etre traduits");
|
||||||
|
assertTrue(message.contains("%amount%"), "le placeholder %amount% doit rester intact pour etre remplace plus tard");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package fr.votenetwork.spigot.storage;
|
||||||
|
|
||||||
|
import fr.votenetwork.spigot.VoteNetworkSpigot;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class YamlPendingVoteStoreTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void peekReturnsZeroWhenNothingStored(@TempDir File dataFolder) {
|
||||||
|
YamlPendingVoteStore store = newStore(dataFolder);
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
AtomicInteger result = new AtomicInteger(-1);
|
||||||
|
store.peek(uuid, result::set, e -> { throw new AssertionError(e); });
|
||||||
|
|
||||||
|
assertEquals(0, result.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fetchAndClearResetsCounterToZeroAfterReading(@TempDir File dataFolder) throws Exception {
|
||||||
|
YamlPendingVoteStore store = newStore(dataFolder);
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
writeInitialVotes(dataFolder, uuid, 3);
|
||||||
|
|
||||||
|
AtomicInteger firstRead = new AtomicInteger(-1);
|
||||||
|
store.fetchAndClear(uuid, firstRead::set, e -> { throw new AssertionError(e); });
|
||||||
|
assertEquals(3, firstRead.get());
|
||||||
|
|
||||||
|
AtomicInteger secondRead = new AtomicInteger(-1);
|
||||||
|
store.peek(uuid, secondRead::set, e -> { throw new AssertionError(e); });
|
||||||
|
assertEquals(0, secondRead.get(), "fetchAndClear doit remettre le compteur a 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConnectionReportsWritableDirectory(@TempDir File dataFolder) {
|
||||||
|
YamlPendingVoteStore store = newStore(dataFolder);
|
||||||
|
|
||||||
|
AtomicReference<Boolean> success = new AtomicReference<>();
|
||||||
|
store.testConnection((ok, message) -> success.set(ok));
|
||||||
|
|
||||||
|
assertEquals(Boolean.TRUE, success.get(), "un dossier temporaire accessible en ecriture doit etre rapporte comme fonctionnel");
|
||||||
|
}
|
||||||
|
|
||||||
|
private YamlPendingVoteStore newStore(File dataFolder) {
|
||||||
|
VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class);
|
||||||
|
when(plugin.getDataFolder()).thenReturn(dataFolder);
|
||||||
|
return new YamlPendingVoteStore(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeInitialVotes(File dataFolder, UUID uuid, int votes) throws Exception {
|
||||||
|
org.bukkit.configuration.file.YamlConfiguration yaml = new org.bukkit.configuration.file.YamlConfiguration();
|
||||||
|
yaml.set("players." + uuid + ".votes", votes);
|
||||||
|
File file = new File(dataFolder, "pending-votes.yml");
|
||||||
|
yaml.save(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-1
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>fr.votenetwork</groupId>
|
<groupId>fr.votenetwork</groupId>
|
||||||
<artifactId>votenetwork-parent</artifactId>
|
<artifactId>votenetwork-parent</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>votenetwork-velocity</artifactId>
|
<artifactId>votenetwork-velocity</artifactId>
|
||||||
@@ -36,6 +36,12 @@
|
|||||||
<version>2.10.1</version>
|
<version>2.10.1</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.nio.file.Path;
|
|||||||
@Plugin(
|
@Plugin(
|
||||||
id = "votenetwork",
|
id = "votenetwork",
|
||||||
name = "VoteNetwork",
|
name = "VoteNetwork",
|
||||||
version = "1.0.0",
|
version = "1.0.2",
|
||||||
description = "Systeme de vote reseau Velocity <-> Spigot",
|
description = "Systeme de vote reseau Velocity <-> Spigot",
|
||||||
authors = {"Sar_Tron"}
|
authors = {"Sar_Tron"}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package fr.votenetwork.velocity;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.slf4j.helpers.NOPLogger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class VoteConfigTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loadCopiesDefaultsWhenFileMissing(@TempDir Path dataDir) {
|
||||||
|
VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER);
|
||||||
|
config.load();
|
||||||
|
|
||||||
|
assertTrue(config.isMysqlStorage());
|
||||||
|
assertEquals("jdbc:mysql://127.0.0.1:3306/votenetwork?useSSL=false&autoReconnect=true&characterEncoding=utf8",
|
||||||
|
config.getJdbcUrl());
|
||||||
|
assertTrue(Files.exists(dataDir.resolve("config.properties")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void perServerScopeFallsBackToGlobalWhenServerListEmpty(@TempDir Path dataDir) throws IOException {
|
||||||
|
writeConfig(dataDir, """
|
||||||
|
storage.type=mysql
|
||||||
|
mysql.host=127.0.0.1
|
||||||
|
mysql.port=3306
|
||||||
|
mysql.database=votenetwork
|
||||||
|
mysql.user=root
|
||||||
|
mysql.password=changeme
|
||||||
|
mysql.pool-size=5
|
||||||
|
direct-vote.servers=
|
||||||
|
pending-votes.scope=per-server
|
||||||
|
""");
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER);
|
||||||
|
config.load();
|
||||||
|
|
||||||
|
assertFalse(config.isPerServerScope(), "sans direct-vote.servers, per-server doit retomber sur global");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void perServerScopeAppliesWhenServersListed(@TempDir Path dataDir) throws IOException {
|
||||||
|
writeConfig(dataDir, """
|
||||||
|
storage.type=mysql
|
||||||
|
mysql.host=127.0.0.1
|
||||||
|
mysql.port=3306
|
||||||
|
mysql.database=votenetwork
|
||||||
|
mysql.user=root
|
||||||
|
mysql.password=changeme
|
||||||
|
mysql.pool-size=5
|
||||||
|
direct-vote.servers=gen1, GEN2
|
||||||
|
pending-votes.scope=per-server
|
||||||
|
""");
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER);
|
||||||
|
config.load();
|
||||||
|
|
||||||
|
assertTrue(config.isPerServerScope());
|
||||||
|
assertTrue(config.isDirectVoteServer("gen1"));
|
||||||
|
assertTrue(config.isDirectVoteServer("Gen2"), "la comparaison doit ignorer la casse");
|
||||||
|
assertFalse(config.isDirectVoteServer("lobby"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isDirectVoteServerAllowsAllWhenListEmpty(@TempDir Path dataDir) throws IOException {
|
||||||
|
writeConfig(dataDir, """
|
||||||
|
storage.type=mysql
|
||||||
|
direct-vote.servers=
|
||||||
|
""");
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER);
|
||||||
|
config.load();
|
||||||
|
|
||||||
|
assertTrue(config.isDirectVoteServer("n_importe_quoi"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergeAddsMissingKeysWithoutTouchingExistingOnes(@TempDir Path dataDir) throws IOException {
|
||||||
|
writeConfig(dataDir, """
|
||||||
|
mysql.password=ne-pas-toucher
|
||||||
|
""");
|
||||||
|
|
||||||
|
VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER);
|
||||||
|
config.load();
|
||||||
|
|
||||||
|
String fileContent = Files.readString(dataDir.resolve("config.properties"), StandardCharsets.UTF_8);
|
||||||
|
assertTrue(fileContent.contains("mysql.password=ne-pas-toucher"), "la valeur existante ne doit jamais etre ecrasee");
|
||||||
|
assertTrue(fileContent.contains("pending-votes.scope="), "les cles manquantes doivent etre ajoutees");
|
||||||
|
assertEquals("ne-pas-toucher", config.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeConfig(Path dataDir, String content) throws IOException {
|
||||||
|
Files.createDirectories(dataDir);
|
||||||
|
Files.writeString(dataDir.resolve("config.properties"), content, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package fr.votenetwork.velocity;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.slf4j.helpers.NOPLogger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class YamlPendingVoteStoreTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addPendingVoteCreatesEntryAndIncrements(@TempDir Path dataDir)
|
||||||
|
throws ExecutionException, InterruptedException, TimeoutException, IOException {
|
||||||
|
YamlPendingVoteStore store = new YamlPendingVoteStore(dataDir, NOPLogger.NOP_LOGGER);
|
||||||
|
UUID uuid = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5");
|
||||||
|
|
||||||
|
store.addPendingVote(uuid, "Notch").get(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
String content = Files.readString(dataDir.resolve("pending-votes.yml"), StandardCharsets.UTF_8);
|
||||||
|
assertTrue(content.contains(uuid.toString()));
|
||||||
|
assertTrue(content.contains("name: Notch"));
|
||||||
|
assertTrue(content.contains("votes: 1"));
|
||||||
|
|
||||||
|
store.addPendingVote(uuid, "Notch").get(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
content = Files.readString(dataDir.resolve("pending-votes.yml"), StandardCharsets.UTF_8);
|
||||||
|
assertTrue(content.contains("votes: 2"), "un deuxieme vote hors ligne doit incrementer le compteur existant");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addPendingVoteHandlesTwoDifferentPlayersIndependently(@TempDir Path dataDir)
|
||||||
|
throws ExecutionException, InterruptedException, TimeoutException, IOException {
|
||||||
|
YamlPendingVoteStore store = new YamlPendingVoteStore(dataDir, NOPLogger.NOP_LOGGER);
|
||||||
|
UUID uuidA = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5");
|
||||||
|
UUID uuidB = UUID.fromString("853c80ef-3c37-49fd-aa49-938b674adae6");
|
||||||
|
|
||||||
|
store.addPendingVote(uuidA, "Sar_Tron").get(5, TimeUnit.SECONDS);
|
||||||
|
store.addPendingVote(uuidB, "Notch").get(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
String content = Files.readString(dataDir.resolve("pending-votes.yml"), StandardCharsets.UTF_8);
|
||||||
|
assertTrue(content.contains(uuidA.toString()));
|
||||||
|
assertTrue(content.contains(uuidB.toString()));
|
||||||
|
assertTrue(content.contains("name: Sar_Tron"));
|
||||||
|
assertTrue(content.contains("name: Notch"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user