Compare commits
7
Commits
a47376db3a
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca99c7e2c | ||
|
|
ff1afd58fe | ||
|
|
62d4d30530 | ||
|
|
261e220d1a | ||
|
|
74b2914529 | ||
|
|
a76c641b7d | ||
|
|
ffbeb2de66 |
@@ -1,5 +1,6 @@
|
|||||||
target/
|
target/
|
||||||
dependency-reduced-pom.xml
|
dependency-reduced-pom.xml
|
||||||
|
dist/
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Sar_Tron
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# VoteNetwork
|
# VoteNetwork
|
||||||
|
|
||||||
|
Par **Sar_Tron**, sous licence [MIT](LICENSE).
|
||||||
|
|
||||||
Systeme de vote reseau en deux modules, pour une architecture **proxy Velocity + serveurs Spigot/Paper** (modes A, B, C, ...).
|
Systeme de vote reseau en deux modules, pour une architecture **proxy Velocity + serveurs Spigot/Paper** (modes A, B, C, ...).
|
||||||
|
|
||||||
- `velocity/` — module proxy. Recoit `votenetwork vote <pseudo>` (console uniquement) et route le vote en direct ou le met en attente.
|
- `velocity/` — module proxy. Recoit `votenetwork vote <pseudo>` (console uniquement) et route le vote en direct ou le met en attente.
|
||||||
@@ -32,6 +34,8 @@ Compatible **Java 17**, compile contre l'API Spigot **1.18.2** (additive, donc c
|
|||||||
- Serveur : `plugins/votenetwork/config.yml`
|
- Serveur : `plugins/votenetwork/config.yml`
|
||||||
4. Redémarrer.
|
4. Redémarrer.
|
||||||
|
|
||||||
|
> **⚠️ Même base de données partout.** `mysql.host` / `mysql.port` / `mysql.database` / `mysql.user` / `mysql.password` doivent être **identiques** dans `config.properties` (Velocity) et dans **chaque** `config.yml` (tous les serveurs Spigot : A, B, C...). C'est cette base commune qui permet à un joueur de voter hors ligne, puis de récupérer ses votes avec `/claim` sur n'importe lequel de vos serveurs. Des bases différentes = `/claim` ne retrouvera jamais les votes stockés par Velocity. Voir aussi [Stockage : MySQL ou YAML](#stockage--mysql-ou-yaml).
|
||||||
|
|
||||||
## Build depuis les sources
|
## Build depuis les sources
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -97,10 +101,10 @@ messages:
|
|||||||
|
|
||||||
## Stockage : MySQL ou YAML
|
## Stockage : MySQL ou YAML
|
||||||
|
|
||||||
Chaque composant choisit indépendamment son stockage via `storage.type` :
|
Chaque composant choisit son stockage via `storage.type`, **mais ce choix doit être le même partout** (Velocity + tous les serveurs Spigot), avec les mêmes identifiants de connexion :
|
||||||
|
|
||||||
- **`mysql`** (recommandé pour un vrai réseau) : partagé entre le proxy et tous les serveurs. La table `nb_votes_attente` est créée automatiquement au démarrage si absente.
|
- **`mysql`** (recommandé, seul mode fiable pour un vrai réseau) : Velocity et tous les serveurs Spigot pointent sur **la même base** (même `host`/`database`/`user`/`password`). La table `nb_votes_attente` est créée automatiquement au démarrage si absente. Un vote stocké par Velocity (joueur hors ligne) est donc visible par `/claim` sur n'importe quel serveur.
|
||||||
- **`yaml`** : fichier local `pending-votes.yml` dans le dossier du plugin. Pratique en solo/test, **mais pas synchronisé automatiquement** entre le fichier du proxy et celui d'un serveur — chacun a sa propre copie locale. N'utilisez ce mode en réseau multi-machines que si les dossiers de plugins sont partagés sur le même disque (montage réseau, symlink). Sinon, un vote stocké côté proxy en YAML ne sera jamais vu par `/claim` sur un serveur Spigot séparé.
|
- **`yaml`** : fichier local `pending-votes.yml` dans le dossier de **chaque** plugin (un par proxy, un par serveur). **Pas synchronisé automatiquement** entre eux. Si Velocity est en `yaml` et qu'un serveur Spigot vote pour lui-même en `mysql` (ou l'inverse), `/claim` ne trouvera jamais les votes stockés côté proxy. N'utilisez ce mode qu'en solo/test, ou si tous les dossiers de plugins partagent le même disque (montage réseau, symlink) — sinon utilisez `mysql` partout.
|
||||||
|
|
||||||
Testez la connexion/l'accès au stockage configuré côté Spigot avec :
|
Testez la connexion/l'accès au stockage configuré côté Spigot avec :
|
||||||
|
|
||||||
@@ -108,6 +112,21 @@ Testez la connexion/l'accès au stockage configuré côté Spigot avec :
|
|||||||
/vote testdb
|
/vote testdb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Portée des votes en attente : partagée (`global`) ou par serveur (`per-server`)
|
||||||
|
|
||||||
|
En mode `mysql` uniquement, `pending-votes.scope` (dans `config.properties` côté Velocity) contrôle si le compteur de votes en attente est **partagé** entre tous les serveurs ou **indépendant par serveur** :
|
||||||
|
|
||||||
|
- **`global`** (par défaut) : un seul compteur par joueur. Un `/claim` sur n'importe quel serveur le remet à 0 **partout** à la fois.
|
||||||
|
- **`per-server`** : chaque serveur listé dans `direct-vote.servers` a son propre compteur. Un vote hors ligne incrémente le compteur de **chaque** serveur de la liste. Un `/claim` sur le serveur 1 ne remet à 0 **que** le serveur 1 — le joueur garde ses votes en attente sur le serveur 2 et peut les réclamer séparément là-bas.
|
||||||
|
|
||||||
|
> Exemple : le joueur vote 2 fois hors ligne → 2 votes en attente sur *chaque* serveur. `/claim` sur le serveur 1 → 2 récompenses, serveur 1 repasse à 0, serveur 2 reste à 2. S'il revote une fois → serveur 1 = 1, serveur 2 = 3.
|
||||||
|
|
||||||
|
Pour activer `per-server`, il faut **les deux** :
|
||||||
|
1. `pending-votes.scope=per-server` + `direct-vote.servers` rempli dans `config.properties` (Velocity).
|
||||||
|
2. `storage.pending-votes-scope: per-server` + `server-name: "gen1"` (nom exact tiré de `direct-vote.servers`) dans le `config.yml` de **chaque** serveur Spigot concerné.
|
||||||
|
|
||||||
|
Si ces réglages ne correspondent pas entre le proxy et un serveur (scope différent, ou `server-name` absent/mal orthographié), ce serveur ne retrouvera jamais les votes stockés par Velocity.
|
||||||
|
|
||||||
## Fonctionnement du vote
|
## Fonctionnement du vote
|
||||||
|
|
||||||
1. Un site de vote appelle la console du proxy : `votenetwork vote <pseudo>`.
|
1. Un site de vote appelle la console du proxy : `votenetwork vote <pseudo>`.
|
||||||
@@ -129,7 +148,7 @@ Testez la connexion/l'accès au stockage configuré côté Spigot avec :
|
|||||||
|
|
||||||
## VoteParty
|
## VoteParty
|
||||||
|
|
||||||
- Compteur indépendant **par serveur**, persistant localement (`plugins/votenetwork/voteparty.yml`), survit aux redémarrages.
|
- Compteur indépendant **par serveur**, toujours persistant localement dans `plugins/votenetwork/voteparty.yml` (indépendant de `storage.type`, qui ne concerne que les votes en attente), survit aux redémarrages.
|
||||||
- `voteparty.votes-requis` définit le seuil de déclenchement.
|
- `voteparty.votes-requis` définit le seuil de déclenchement.
|
||||||
- `voteparty.commands` s'exécutent une fois le seuil atteint, puis le compteur repart à 0 (avec report de l'excédent si plusieurs votes arrivent d'un coup, ex. via `/claim` de N votes).
|
- `voteparty.commands` s'exécutent une fois le seuil atteint, puis le compteur repart à 0 (avec report de l'excédent si plusieurs votes arrivent d'un coup, ex. via `/claim` de N votes).
|
||||||
- Messages de progression et de déclenchement personnalisables (`voteparty.broadcast`).
|
- Messages de progression et de déclenchement personnalisables (`voteparty.broadcast`).
|
||||||
@@ -155,16 +174,40 @@ api.getPendingVotes(player.getUniqueId(), pending -> {
|
|||||||
- `getVotePartyCurrentVotes()` / `getVotePartyRequiredVotes()` : lecture directe en mémoire, thread principal uniquement.
|
- `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`.
|
- `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
|
## Structure de la base de données
|
||||||
|
|
||||||
Créée automatiquement si absente (mode `mysql`) :
|
Créées automatiquement si absentes (mode `mysql`), les deux tables coexistent toujours — seule celle correspondant à `pending-votes.scope` est utilisée :
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
|
-- scope = global (par defaut)
|
||||||
CREATE TABLE IF NOT EXISTS nb_votes_attente (
|
CREATE TABLE IF NOT EXISTS nb_votes_attente (
|
||||||
player_uuid VARCHAR(36) NOT NULL PRIMARY KEY,
|
player_uuid VARCHAR(36) NOT NULL PRIMARY KEY,
|
||||||
player_name VARCHAR(16) NOT NULL,
|
player_name VARCHAR(16) NOT NULL,
|
||||||
nombre_votes INT DEFAULT 0
|
nombre_votes INT DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- scope = per-server
|
||||||
|
CREATE TABLE IF NOT EXISTS nb_votes_attente_serveur (
|
||||||
|
player_uuid VARCHAR(36) NOT NULL,
|
||||||
|
server_name VARCHAR(64) NOT NULL,
|
||||||
|
player_name VARCHAR(16) NOT NULL,
|
||||||
|
nombre_votes INT DEFAULT 0,
|
||||||
|
PRIMARY KEY (player_uuid, server_name)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -174,3 +217,4 @@ CREATE TABLE IF NOT EXISTS nb_votes_attente (
|
|||||||
- HikariCP + MySQL Connector/J sont shadés et relocalisés dans les deux jars (rien à installer manuellement sur le serveur). Le driver JDBC est chargé explicitement par son nom de classe relocalisé pour rester fiable après le shading.
|
- HikariCP + MySQL Connector/J sont shadés et relocalisés dans les deux jars (rien à installer manuellement sur le serveur). Le driver JDBC est chargé explicitement par son nom de classe relocalisé pour rester fiable après le shading.
|
||||||
- Toutes les requêtes SQL et I/O fichier sont asynchrones — aucun accès réseau ou disque ne bloque le thread principal du serveur ni celui du proxy.
|
- Toutes les requêtes SQL et I/O fichier sont asynchrones — aucun accès réseau ou disque ne bloque le thread principal du serveur ni celui du proxy.
|
||||||
- Si la connexion MySQL échoue au démarrage, le plugin reste actif (pas de crash) : corrigez `config.yml`/`config.properties` puis utilisez `/vote testdb`, ou redémarrez.
|
- Si la connexion MySQL échoue au démarrage, le plugin reste actif (pas de crash) : corrigez `config.yml`/`config.properties` puis utilisez `/vote testdb`, ou redémarrez.
|
||||||
|
- **Mise à jour de config** : à chaque démarrage, les nouvelles options apportées par une mise à jour du plugin (absentes de votre `config.yml`/`config.properties` existant) sont ajoutées automatiquement, **sans jamais toucher aux valeurs que vous avez déjà personnalisées**. Côté Spigot, la réécriture du `config.yml` fait perdre les commentaires (limitation de l'API Bukkit) ; côté Velocity, les nouvelles clés sont simplement ajoutées en bas du `config.properties`, les commentaires existants sont préservés.
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -14,6 +14,23 @@
|
|||||||
<module>spigot</module>
|
<module>spigot</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
<name>VoteNetwork</name>
|
||||||
|
<description>Systeme de vote reseau Velocity + Spigot/Paper</description>
|
||||||
|
<url>https://gitea.louconnect.fr/Sar_Tron/VoteNetwork</url>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>https://opensource.org/licenses/MIT</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>Sar_Tron</name>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
|||||||
@@ -36,6 +36,12 @@
|
|||||||
<artifactId>mysql-connector-j</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
<version>${mysql.version}</version>
|
<version>${mysql.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.clip</groupId>
|
||||||
|
<artifactId>placeholderapi</artifactId>
|
||||||
|
<version>2.11.6</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
@@ -47,6 +53,10 @@
|
|||||||
<id>sonatype</id>
|
<id>sonatype</id>
|
||||||
<url>https://oss.sonatype.org/content/groups/public/</url>
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>placeholderapi</id>
|
||||||
|
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ public class VoteConfig {
|
|||||||
return !"yaml".equalsIgnoreCase(getStorageType());
|
return !"yaml".equalsIgnoreCase(getStorageType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doit correspondre exactement a pending-votes.scope cote Velocity :
|
||||||
|
* global : /claim consomme le compteur partage entre tous les serveurs.
|
||||||
|
* per-server : /claim ne consomme que le compteur de CE serveur (server-name).
|
||||||
|
*/
|
||||||
|
public boolean isPerServerScope() {
|
||||||
|
return "per-server".equalsIgnoreCase(cfg().getString("storage.pending-votes-scope", "global"));
|
||||||
|
}
|
||||||
|
|
||||||
public String getMysqlHost() {
|
public String getMysqlHost() {
|
||||||
return cfg().getString("mysql.host", "127.0.0.1");
|
return cfg().getString("mysql.host", "127.0.0.1");
|
||||||
}
|
}
|
||||||
@@ -54,6 +63,14 @@ public class VoteConfig {
|
|||||||
return cfg().getInt("mysql.pool-size", 5);
|
return cfg().getInt("mysql.pool-size", 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nom de CE serveur, tel que declare cote Velocity dans direct-vote.servers.
|
||||||
|
* Uniquement necessaire si le proxy utilise pending-votes.scope=per-server.
|
||||||
|
*/
|
||||||
|
public String getServerName() {
|
||||||
|
return cfg().getString("server-name", "");
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getVoteRewardCommands() {
|
public List<String> getVoteRewardCommands() {
|
||||||
return cfg().getStringList("rewards.vote-commands");
|
return cfg().getStringList("rewards.vote-commands");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,20 @@ 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.listener.VoteMessageListener;
|
import fr.votenetwork.spigot.listener.VoteMessageListener;
|
||||||
|
import fr.votenetwork.spigot.placeholder.VoteNetworkPlaceholders;
|
||||||
import fr.votenetwork.spigot.storage.MySqlPendingVoteStore;
|
import fr.votenetwork.spigot.storage.MySqlPendingVoteStore;
|
||||||
import fr.votenetwork.spigot.storage.PendingVoteStore;
|
import fr.votenetwork.spigot.storage.PendingVoteStore;
|
||||||
import fr.votenetwork.spigot.storage.YamlPendingVoteStore;
|
import fr.votenetwork.spigot.storage.YamlPendingVoteStore;
|
||||||
import fr.votenetwork.spigot.voteparty.VoteParty;
|
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.ServicePriority;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class VoteNetworkSpigot extends JavaPlugin {
|
public class VoteNetworkSpigot extends JavaPlugin {
|
||||||
|
|
||||||
public static final String CHANNEL = "votenetwork:vote";
|
public static final String CHANNEL = "votenetwork:vote";
|
||||||
@@ -25,6 +32,7 @@ public class VoteNetworkSpigot extends JavaPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
mergeMissingConfigKeys();
|
||||||
|
|
||||||
this.voteConfig = new VoteConfig(this);
|
this.voteConfig = new VoteConfig(this);
|
||||||
this.maintenanceState = new MaintenanceState();
|
this.maintenanceState = new MaintenanceState();
|
||||||
@@ -52,9 +60,51 @@ public class VoteNetworkSpigot extends JavaPlugin {
|
|||||||
VoteNetworkAPIHolder.set(api);
|
VoteNetworkAPIHolder.set(api);
|
||||||
getServer().getServicesManager().register(VoteNetworkAPI.class, api, this, ServicePriority.Normal);
|
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.");
|
getLogger().info("VoteNetwork (Spigot) demarre.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajoute dans config.yml les cles presentes dans le config.yml embarque dans le jar mais
|
||||||
|
* absentes du fichier existant (nouvelles options apportees par une mise a jour du plugin),
|
||||||
|
* sans jamais modifier une valeur deja definie par l'admin.
|
||||||
|
*
|
||||||
|
* Limite connue : Bukkit ne preserve pas les commentaires lors de la reecriture du fichier.
|
||||||
|
*/
|
||||||
|
private void mergeMissingConfigKeys() {
|
||||||
|
try (InputStreamReader reader = new InputStreamReader(getResource("config.yml"), StandardCharsets.UTF_8)) {
|
||||||
|
YamlConfiguration defaults = YamlConfiguration.loadConfiguration(reader);
|
||||||
|
YamlConfiguration current = YamlConfiguration.loadConfiguration(getConfigFile());
|
||||||
|
|
||||||
|
boolean changed = false;
|
||||||
|
for (String key : defaults.getKeys(true)) {
|
||||||
|
if (defaults.isConfigurationSection(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!current.isSet(key)) {
|
||||||
|
current.set(key, defaults.get(key));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
current.save(getConfigFile());
|
||||||
|
reloadConfig();
|
||||||
|
getLogger().info("config.yml mis a jour avec les nouvelles options manquantes (valeurs existantes conservees).");
|
||||||
|
}
|
||||||
|
} catch (IOException | java.io.UncheckedIOException e) {
|
||||||
|
getLogger().warning("Impossible de fusionner les nouvelles options de config.yml : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.io.File getConfigFile() {
|
||||||
|
return new java.io.File(getDataFolder(), "config.yml");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
getServer().getServicesManager().unregisterAll(this);
|
getServer().getServicesManager().unregisterAll(this);
|
||||||
|
|||||||
@@ -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<String, Integer> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,16 +73,24 @@ public class MySqlPendingVoteStore implements PendingVoteStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createTableIfNotExists() {
|
private void createTableIfNotExists() {
|
||||||
String sql = "CREATE TABLE IF NOT EXISTS nb_votes_attente (" +
|
String globalSql = "CREATE TABLE IF NOT EXISTS nb_votes_attente (" +
|
||||||
"player_uuid VARCHAR(36) NOT NULL PRIMARY KEY," +
|
"player_uuid VARCHAR(36) NOT NULL PRIMARY KEY," +
|
||||||
"player_name VARCHAR(16) NOT NULL," +
|
"player_name VARCHAR(16) NOT NULL," +
|
||||||
"nombre_votes INT DEFAULT 0" +
|
"nombre_votes INT DEFAULT 0" +
|
||||||
")";
|
")";
|
||||||
|
String perServerSql = "CREATE TABLE IF NOT EXISTS nb_votes_attente_serveur (" +
|
||||||
|
"player_uuid VARCHAR(36) NOT NULL," +
|
||||||
|
"server_name VARCHAR(64) NOT NULL," +
|
||||||
|
"player_name VARCHAR(16) NOT NULL," +
|
||||||
|
"nombre_votes INT DEFAULT 0," +
|
||||||
|
"PRIMARY KEY (player_uuid, server_name)" +
|
||||||
|
")";
|
||||||
try (Connection connection = dataSource.getConnection();
|
try (Connection connection = dataSource.getConnection();
|
||||||
Statement statement = connection.createStatement()) {
|
Statement statement = connection.createStatement()) {
|
||||||
statement.execute(sql);
|
statement.execute(globalSql);
|
||||||
|
statement.execute(perServerSql);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getLogger().severe("Impossible de creer la table nb_votes_attente: " + e.getMessage());
|
plugin.getLogger().severe("Impossible de creer les tables de votes en attente: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +100,14 @@ public class MySqlPendingVoteStore implements PendingVoteStore {
|
|||||||
onError.accept(new SQLException("Pool MySQL indisponible (echec de connexion au demarrage)"));
|
onError.accept(new SQLException("Pool MySQL indisponible (echec de connexion au demarrage)"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (config.isPerServerScope()) {
|
||||||
|
fetchAndClearPerServer(uuid, onResult, onError);
|
||||||
|
} else {
|
||||||
|
fetchAndClearGlobal(uuid, onResult, onError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchAndClearGlobal(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
||||||
String selectSql = "SELECT nombre_votes FROM nb_votes_attente WHERE player_uuid = ?";
|
String selectSql = "SELECT nombre_votes FROM nb_votes_attente WHERE player_uuid = ?";
|
||||||
String resetSql = "UPDATE nb_votes_attente SET nombre_votes = 0 WHERE player_uuid = ?";
|
String resetSql = "UPDATE nb_votes_attente SET nombre_votes = 0 WHERE player_uuid = ?";
|
||||||
|
|
||||||
@@ -120,16 +136,59 @@ public class MySqlPendingVoteStore implements PendingVoteStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fetchAndClearPerServer(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
||||||
|
if (config.getServerName().isEmpty()) {
|
||||||
|
onError.accept(new SQLException("storage.pending-votes-scope=per-server mais server-name est vide dans config.yml"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String selectSql = "SELECT nombre_votes FROM nb_votes_attente_serveur WHERE player_uuid = ? AND server_name = ?";
|
||||||
|
String resetSql = "UPDATE nb_votes_attente_serveur SET nombre_votes = 0 WHERE player_uuid = ? AND server_name = ?";
|
||||||
|
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
int pendingVotes = 0;
|
||||||
|
|
||||||
|
try (PreparedStatement select = connection.prepareStatement(selectSql)) {
|
||||||
|
select.setString(1, uuid.toString());
|
||||||
|
select.setString(2, config.getServerName());
|
||||||
|
try (ResultSet resultSet = select.executeQuery()) {
|
||||||
|
if (resultSet.next()) {
|
||||||
|
pendingVotes = resultSet.getInt("nombre_votes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingVotes > 0) {
|
||||||
|
try (PreparedStatement reset = connection.prepareStatement(resetSql)) {
|
||||||
|
reset.setString(1, uuid.toString());
|
||||||
|
reset.setString(2, config.getServerName());
|
||||||
|
reset.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResult.accept(pendingVotes);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
onError.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void peek(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
public void peek(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
onError.accept(new SQLException("Pool MySQL indisponible (echec de connexion au demarrage)"));
|
onError.accept(new SQLException("Pool MySQL indisponible (echec de connexion au demarrage)"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String sql = "SELECT nombre_votes FROM nb_votes_attente WHERE player_uuid = ?";
|
|
||||||
|
boolean perServer = config.isPerServerScope();
|
||||||
|
String sql = perServer
|
||||||
|
? "SELECT nombre_votes FROM nb_votes_attente_serveur WHERE player_uuid = ? AND server_name = ?"
|
||||||
|
: "SELECT nombre_votes FROM nb_votes_attente WHERE player_uuid = ?";
|
||||||
|
|
||||||
try (Connection connection = dataSource.getConnection();
|
try (Connection connection = dataSource.getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
statement.setString(1, uuid.toString());
|
statement.setString(1, uuid.toString());
|
||||||
|
if (perServer) {
|
||||||
|
statement.setString(2, config.getServerName());
|
||||||
|
}
|
||||||
try (ResultSet resultSet = statement.executeQuery()) {
|
try (ResultSet resultSet = statement.executeQuery()) {
|
||||||
onResult.accept(resultSet.next() ? resultSet.getInt("nombre_votes") : 0);
|
onResult.accept(resultSet.next() ? resultSet.getInt("nombre_votes") : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
# partage automatiquement avec le fichier YAML du proxy Velocity si vous etes en reseau.
|
# partage automatiquement avec le fichier YAML du proxy Velocity si vous etes en reseau.
|
||||||
storage:
|
storage:
|
||||||
type: mysql
|
type: mysql
|
||||||
|
# Doit correspondre EXACTEMENT a pending-votes.scope cote Velocity (config.properties) :
|
||||||
|
# global (par defaut) : /claim consomme le compteur partage entre tous les serveurs.
|
||||||
|
# per-server : /claim ne consomme que le compteur de CE serveur (voir server-name ci-dessous).
|
||||||
|
pending-votes-scope: global
|
||||||
|
|
||||||
|
# Nom de CE serveur, IDENTIQUE a celui utilise dans direct-vote.servers cote Velocity.
|
||||||
|
# Uniquement necessaire si storage.pending-votes-scope=per-server.
|
||||||
|
server-name: ""
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ name: votenetwork
|
|||||||
main: fr.votenetwork.spigot.VoteNetworkSpigot
|
main: fr.votenetwork.spigot.VoteNetworkSpigot
|
||||||
version: ${project.version}
|
version: ${project.version}
|
||||||
api-version: 1.18
|
api-version: 1.18
|
||||||
author: VoteNetwork
|
author: Sar_Tron
|
||||||
description: Reception des votes reseau (direct ou en attente) avec VoteParty local.
|
description: Reception des votes reseau (direct ou en attente) avec VoteParty local.
|
||||||
|
softdepend: [PlaceholderAPI]
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
claim:
|
claim:
|
||||||
|
|||||||
@@ -57,16 +57,24 @@ public class MySqlPendingVoteStore implements PendingVoteStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createTableIfNotExists() {
|
private void createTableIfNotExists() {
|
||||||
String sql = "CREATE TABLE IF NOT EXISTS nb_votes_attente (" +
|
String globalSql = "CREATE TABLE IF NOT EXISTS nb_votes_attente (" +
|
||||||
"player_uuid VARCHAR(36) NOT NULL PRIMARY KEY," +
|
"player_uuid VARCHAR(36) NOT NULL PRIMARY KEY," +
|
||||||
"player_name VARCHAR(16) NOT NULL," +
|
"player_name VARCHAR(16) NOT NULL," +
|
||||||
"nombre_votes INT DEFAULT 0" +
|
"nombre_votes INT DEFAULT 0" +
|
||||||
")";
|
")";
|
||||||
|
String perServerSql = "CREATE TABLE IF NOT EXISTS nb_votes_attente_serveur (" +
|
||||||
|
"player_uuid VARCHAR(36) NOT NULL," +
|
||||||
|
"server_name VARCHAR(64) NOT NULL," +
|
||||||
|
"player_name VARCHAR(16) NOT NULL," +
|
||||||
|
"nombre_votes INT DEFAULT 0," +
|
||||||
|
"PRIMARY KEY (player_uuid, server_name)" +
|
||||||
|
")";
|
||||||
try (Connection connection = dataSource.getConnection();
|
try (Connection connection = dataSource.getConnection();
|
||||||
Statement statement = connection.createStatement()) {
|
Statement statement = connection.createStatement()) {
|
||||||
statement.execute(sql);
|
statement.execute(globalSql);
|
||||||
|
statement.execute(perServerSql);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
logger.error("Impossible de creer la table nb_votes_attente", e);
|
logger.error("Impossible de creer les tables de votes en attente", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,20 +85,46 @@ public class MySqlPendingVoteStore implements PendingVoteStore {
|
|||||||
logger.error("Impossible d'enregistrer le vote en attente pour {} : pool MySQL indisponible", playerName);
|
logger.error("Impossible d'enregistrer le vote en attente pour {} : pool MySQL indisponible", playerName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String sql = "INSERT INTO nb_votes_attente (player_uuid, player_name, nombre_votes) " +
|
if (config.isPerServerScope()) {
|
||||||
"VALUES (?, ?, 1) " +
|
addPendingVotePerServer(uuid, playerName);
|
||||||
"ON DUPLICATE KEY UPDATE nombre_votes = nombre_votes + 1, player_name = VALUES(player_name)";
|
} else {
|
||||||
try (Connection connection = dataSource.getConnection();
|
addPendingVoteGlobal(uuid, playerName);
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
|
||||||
statement.setString(1, uuid.toString());
|
|
||||||
statement.setString(2, playerName);
|
|
||||||
statement.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error("Erreur lors de l'insertion du vote en attente pour {}", playerName, e);
|
|
||||||
}
|
}
|
||||||
}, asyncExecutor);
|
}, asyncExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addPendingVoteGlobal(UUID uuid, String playerName) {
|
||||||
|
String sql = "INSERT INTO nb_votes_attente (player_uuid, player_name, nombre_votes) " +
|
||||||
|
"VALUES (?, ?, 1) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE nombre_votes = nombre_votes + 1, player_name = VALUES(player_name)";
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
statement.setString(1, uuid.toString());
|
||||||
|
statement.setString(2, playerName);
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("Erreur lors de l'insertion du vote en attente pour {}", playerName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPendingVotePerServer(UUID uuid, String playerName) {
|
||||||
|
String sql = "INSERT INTO nb_votes_attente_serveur (player_uuid, server_name, player_name, nombre_votes) " +
|
||||||
|
"VALUES (?, ?, ?, 1) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE nombre_votes = nombre_votes + 1, player_name = VALUES(player_name)";
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
for (String server : config.getDirectVoteServers()) {
|
||||||
|
statement.setString(1, uuid.toString());
|
||||||
|
statement.setString(2, server);
|
||||||
|
statement.setString(3, playerName);
|
||||||
|
statement.addBatch();
|
||||||
|
}
|
||||||
|
statement.executeBatch();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("Erreur lors de l'insertion du vote en attente (per-server) pour {}", playerName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (dataSource != null) {
|
if (dataSource != null) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class VoteConfig {
|
|||||||
private String password;
|
private String password;
|
||||||
private int poolSize;
|
private int poolSize;
|
||||||
private Set<String> directVoteServers;
|
private Set<String> directVoteServers;
|
||||||
|
private String pendingVotesScope;
|
||||||
|
|
||||||
public VoteConfig(Path dataDirectory, Logger logger) {
|
public VoteConfig(Path dataDirectory, Logger logger) {
|
||||||
this.dataDirectory = dataDirectory;
|
this.dataDirectory = dataDirectory;
|
||||||
@@ -45,6 +46,8 @@ public class VoteConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mergeMissingConfigKeys(configFile);
|
||||||
|
|
||||||
try (InputStream in = Files.newInputStream(configFile)) {
|
try (InputStream in = Files.newInputStream(configFile)) {
|
||||||
properties.load(in);
|
properties.load(in);
|
||||||
}
|
}
|
||||||
@@ -63,11 +66,69 @@ public class VoteConfig {
|
|||||||
.filter(s -> !s.isEmpty())
|
.filter(s -> !s.isEmpty())
|
||||||
.map(s -> s.toLowerCase(Locale.ROOT))
|
.map(s -> s.toLowerCase(Locale.ROOT))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
this.pendingVotesScope = properties.getProperty("pending-votes.scope", "global");
|
||||||
|
if (isPerServerScope() && directVoteServers.isEmpty()) {
|
||||||
|
logger.warn("pending-votes.scope=per-server mais direct-vote.servers est vide : bascule sur 'global'. "
|
||||||
|
+ "Renseignez direct-vote.servers pour utiliser le mode per-server.");
|
||||||
|
this.pendingVotesScope = "global";
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Impossible de charger la configuration Velocity", e);
|
logger.error("Impossible de charger la configuration Velocity", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajoute a la fin de config.properties les blocs (commentaires + cle=valeur) du fichier par
|
||||||
|
* defaut du jar dont la cle est absente du fichier existant (nouvelles options apportees par
|
||||||
|
* une mise a jour du plugin), sans jamais toucher aux lignes/valeurs deja presentes. Copie le
|
||||||
|
* bloc entier tel quel (donc avec son explication en commentaire), pas juste "cle=valeur".
|
||||||
|
*/
|
||||||
|
private void mergeMissingConfigKeys(Path configFile) throws IOException {
|
||||||
|
String defaultsText;
|
||||||
|
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.properties")) {
|
||||||
|
if (in == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defaultsText = new String(in.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties existing = new Properties();
|
||||||
|
try (InputStream in = Files.newInputStream(configFile)) {
|
||||||
|
existing.load(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] blocks = defaultsText.split("\\r?\\n\\s*\\r?\\n");
|
||||||
|
StringBuilder toAppend = new StringBuilder();
|
||||||
|
|
||||||
|
for (String block : blocks) {
|
||||||
|
java.util.List<String> keysInBlock = new java.util.ArrayList<>();
|
||||||
|
for (String line : block.split("\\r?\\n")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (trimmed.isEmpty() || trimmed.startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int eq = trimmed.indexOf('=');
|
||||||
|
if (eq > 0) {
|
||||||
|
keysInBlock.add(trimmed.substring(0, eq).trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keysInBlock.isEmpty() && keysInBlock.stream().noneMatch(existing::containsKey)) {
|
||||||
|
toAppend.append(block.stripTrailing()).append(System.lineSeparator()).append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toAppend.length() > 0) {
|
||||||
|
String addition = System.lineSeparator()
|
||||||
|
+ "# --- nouvelles options ajoutees par une mise a jour du plugin (voir description ci-dessous) ---"
|
||||||
|
+ System.lineSeparator() + System.lineSeparator()
|
||||||
|
+ toAppend;
|
||||||
|
Files.writeString(configFile, addition, java.nio.file.StandardOpenOption.APPEND);
|
||||||
|
logger.info("config.properties mis a jour avec les nouvelles options manquantes (valeurs existantes conservees).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isMysqlStorage() {
|
public boolean isMysqlStorage() {
|
||||||
return !"yaml".equalsIgnoreCase(storageType);
|
return !"yaml".equalsIgnoreCase(storageType);
|
||||||
}
|
}
|
||||||
@@ -81,6 +142,19 @@ public class VoteConfig {
|
|||||||
return directVoteServers.isEmpty() || directVoteServers.contains(serverName.toLowerCase(Locale.ROOT));
|
return directVoteServers.isEmpty() || directVoteServers.contains(serverName.toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* true si chaque serveur doit avoir son propre compteur de votes en attente independant
|
||||||
|
* (un /claim sur un serveur ne remet a 0 que ce serveur-la). Sinon (par defaut), un seul
|
||||||
|
* compteur est partage entre tous les serveurs : /claim le remet a 0 partout a la fois.
|
||||||
|
*/
|
||||||
|
public boolean isPerServerScope() {
|
||||||
|
return "per-server".equalsIgnoreCase(pendingVotesScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getDirectVoteServers() {
|
||||||
|
return directVoteServers;
|
||||||
|
}
|
||||||
|
|
||||||
public String getJdbcUrl() {
|
public String getJdbcUrl() {
|
||||||
return "jdbc:mysql://" + host + ":" + port + "/" + database
|
return "jdbc:mysql://" + host + ":" + port + "/" + database
|
||||||
+ "?useSSL=false&autoReconnect=true&characterEncoding=utf8";
|
+ "?useSSL=false&autoReconnect=true&characterEncoding=utf8";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.nio.file.Path;
|
|||||||
name = "VoteNetwork",
|
name = "VoteNetwork",
|
||||||
version = "1.0.0",
|
version = "1.0.0",
|
||||||
description = "Systeme de vote reseau Velocity <-> Spigot",
|
description = "Systeme de vote reseau Velocity <-> Spigot",
|
||||||
authors = {"VoteNetwork"}
|
authors = {"Sar_Tron"}
|
||||||
)
|
)
|
||||||
public class VoteNetworkVelocity {
|
public class VoteNetworkVelocity {
|
||||||
|
|
||||||
|
|||||||
@@ -20,3 +20,17 @@ mysql.pool-size=5
|
|||||||
# Laisser vide = tous les serveurs sont consideres comme ayant le plugin (deconseille des
|
# Laisser vide = tous les serveurs sont consideres comme ayant le plugin (deconseille des
|
||||||
# qu'un lobby/hub sans VoteNetwork-Spigot existe sur le reseau).
|
# qu'un lobby/hub sans VoteNetwork-Spigot existe sur le reseau).
|
||||||
direct-vote.servers=gen1,gen2
|
direct-vote.servers=gen1,gen2
|
||||||
|
|
||||||
|
# Portee des votes en attente (joueur hors ligne au moment du vote), uniquement en storage.type=mysql :
|
||||||
|
# global (par defaut) : un seul compteur partage entre tous les serveurs. Un /claim sur
|
||||||
|
# N'IMPORTE QUEL serveur remet le compteur a 0 pour TOUS les serveurs a la fois.
|
||||||
|
# per-server : chaque serveur de direct-vote.servers a son propre compteur independant. Un
|
||||||
|
# vote hors ligne incremente le compteur de CHAQUE serveur de la liste. Un /claim
|
||||||
|
# sur le serveur 1 ne remet a 0 QUE le compteur du serveur 1 : le joueur garde ses
|
||||||
|
# votes en attente sur le serveur 2 et peut les reclamer separement la-bas.
|
||||||
|
# Exemple concret en per-server : le joueur vote 2 fois hors ligne -> 2 votes en attente sur
|
||||||
|
# CHAQUE serveur. Il fait /claim sur le serveur 1 -> recoit 2 recompenses, serveur 1 repasse a 0,
|
||||||
|
# serveur 2 reste a 2. S'il revote une fois -> serveur 1 = 1, serveur 2 = 3.
|
||||||
|
# Necessite direct-vote.servers rempli ET que chaque serveur Spigot declare son server-name
|
||||||
|
# (config.yml) correspondant exactement a un nom de cette liste.
|
||||||
|
pending-votes.scope=global
|
||||||
|
|||||||
Reference in New Issue
Block a user