VoteNetwork: plugin de vote reseau Velocity <-> Spigot
Reception directe en jeu (plugin messaging) ou stockage des votes en attente (MySQL ou YAML configurable), /claim, VoteParty local par serveur, mode maintenance, API publique pour scoreboard/GUI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
target/
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# VoteNetwork
|
||||||
|
|
||||||
|
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.
|
||||||
|
- `spigot/` — module serveur. Recoit le signal en direct via Plugin Messaging, gere `/claim`, le VoteParty local et le mode maintenance.
|
||||||
|
|
||||||
|
Compatible **Java 17**, compile contre l'API Spigot **1.18.2** (additive, donc compatible en avant vers 1.19 → 1.21.x et versions futures).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sommaire
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Build depuis les sources](#build-depuis-les-sources)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Stockage : MySQL ou YAML](#stockage--mysql-ou-yaml)
|
||||||
|
- [Fonctionnement du vote](#fonctionnement-du-vote)
|
||||||
|
- [Commandes & permissions](#commandes--permissions)
|
||||||
|
- [VoteParty](#voteparty)
|
||||||
|
- [API publique (scoreboard / GUI)](#api-publique-scoreboard--gui)
|
||||||
|
- [Structure de la base de données](#structure-de-la-base-de-données)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. `velocity/target/VoteNetwork-Velocity.jar` → `plugins/` du proxy Velocity.
|
||||||
|
2. `spigot/target/VoteNetwork-Spigot.jar` → `plugins/` de **chaque** serveur Spigot/Paper (A, B, C, ...).
|
||||||
|
3. Démarrer une fois pour générer les fichiers de config, puis les éditer :
|
||||||
|
- Proxy : `plugins/votenetwork/config.properties`
|
||||||
|
- Serveur : `plugins/votenetwork/config.yml`
|
||||||
|
4. Redémarrer.
|
||||||
|
|
||||||
|
## Build depuis les sources
|
||||||
|
|
||||||
|
```
|
||||||
|
mvn clean package
|
||||||
|
```
|
||||||
|
|
||||||
|
Jars produits dans `velocity/target/` et `spigot/target/`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Chaque composant a son propre fichier, tous deux dans un dossier **`votenetwork`** :
|
||||||
|
|
||||||
|
| Composant | Fichier | Dossier |
|
||||||
|
|---|---|---|
|
||||||
|
| Velocity | `config.properties` | `plugins/votenetwork/` (racine du proxy) |
|
||||||
|
| Spigot | `config.yml` | `plugins/votenetwork/` (sur chaque serveur) |
|
||||||
|
|
||||||
|
Le `config.yml` Spigot regroupe tout ce qui est personnalisable :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
storage:
|
||||||
|
type: mysql # mysql ou yaml
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
|
database: votenetwork
|
||||||
|
user: root
|
||||||
|
password: changeme
|
||||||
|
pool-size: 5
|
||||||
|
|
||||||
|
rewards:
|
||||||
|
vote-commands: # executees pour CHAQUE vote (direct ou via /claim, une fois par vote)
|
||||||
|
- "goldencrates give %player% vote_key 1"
|
||||||
|
|
||||||
|
voteparty:
|
||||||
|
votes-requis: 100 # déclenche le VoteParty à ce cumul (par serveur)
|
||||||
|
commands: # executees UNE fois quand le VoteParty se déclenche
|
||||||
|
- "goldencrates broadcast_give vote_key 1"
|
||||||
|
broadcast:
|
||||||
|
enabled: true
|
||||||
|
progress-message: "&b[Vote] &f%current%&7/&f%required% &fvotes avant le prochain VoteParty !"
|
||||||
|
triggered-message: "&a[Vote] &fVoteParty declenche ! Profitez des recompenses !"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
direct-vote: "&a[Vote] &fMerci pour ton vote !"
|
||||||
|
claim-success: "&a[Vote] &fVous avez recupere &e%amount% &fvote(s) en attente !"
|
||||||
|
claim-empty: "&c[Vote] &fVous n'avez aucun vote en attente."
|
||||||
|
maintenance: "&c[Vote] &fLe systeme de vote est actuellement en maintenance."
|
||||||
|
maintenance-enabled: "&c[Vote] &fMode maintenance active."
|
||||||
|
maintenance-disabled: "&a[Vote] &fMode maintenance desactive."
|
||||||
|
no-permission: "&cVous n'avez pas la permission d'utiliser cette commande."
|
||||||
|
```
|
||||||
|
|
||||||
|
- `%player%` dans `rewards.vote-commands` est remplacé par le pseudo du joueur.
|
||||||
|
- `%current%` / `%required%` dans les messages de progression VoteParty.
|
||||||
|
- `%amount%` dans le message de succès `/claim`.
|
||||||
|
- Codes couleur `&` supportés partout.
|
||||||
|
|
||||||
|
## Stockage : MySQL ou YAML
|
||||||
|
|
||||||
|
Chaque composant choisit indépendamment son stockage via `storage.type` :
|
||||||
|
|
||||||
|
- **`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.
|
||||||
|
- **`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é.
|
||||||
|
|
||||||
|
Testez la connexion/l'accès au stockage configuré côté Spigot avec :
|
||||||
|
|
||||||
|
```
|
||||||
|
/vote testdb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fonctionnement du vote
|
||||||
|
|
||||||
|
1. Un site de vote appelle la console du proxy : `votenetwork vote <pseudo>`.
|
||||||
|
2. **Joueur connecté** : le proxy détecte son serveur actuel et envoie un paquet sur le canal `votenetwork:vote`. Le serveur Spigot donne la récompense immédiatement et incrémente son VoteParty local de +1. **Rien n'est écrit en base.**
|
||||||
|
3. **Joueur déconnecté** : le proxy résout son UUID (API Mojang, avec repli si indisponible) et incrémente son compteur de votes en attente (MySQL ou YAML selon la config), de façon asynchrone.
|
||||||
|
4. Le joueur tape `/claim` sur un serveur Spigot : lecture asynchrone du compteur en attente, distribution des récompenses × N, VoteParty local +N, remise à 0.
|
||||||
|
5. `/vote stop` bloque `/claim` (mode maintenance, message personnalisable) ; `/vote start` le réactive.
|
||||||
|
|
||||||
|
## Commandes & permissions
|
||||||
|
|
||||||
|
| Commande | Où | Qui | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `votenetwork vote <pseudo>` | Velocity | Console uniquement | Enregistre un vote pour le joueur |
|
||||||
|
| `/claim` | Spigot | Joueurs | Récupère les votes en attente |
|
||||||
|
| `/vote stop\|start` | Spigot | `votenetwork.admin` (op par défaut) | Bascule le mode maintenance |
|
||||||
|
| `/vote testdb` | Spigot | `votenetwork.admin` (op par défaut) | Teste le stockage configuré (MySQL ou YAML) |
|
||||||
|
|
||||||
|
## VoteParty
|
||||||
|
|
||||||
|
- Compteur indépendant **par serveur**, persistant localement (`plugins/votenetwork/voteparty.yml`), survit aux redémarrages.
|
||||||
|
- `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).
|
||||||
|
- Messages de progression et de déclenchement personnalisables (`voteparty.broadcast`).
|
||||||
|
|
||||||
|
## API publique (scoreboard / GUI)
|
||||||
|
|
||||||
|
Un autre plugin sur le même serveur Spigot peut lire l'état du vote via `fr.northblue.vote.spigot.api.NorthBlueVoteAPI` :
|
||||||
|
|
||||||
|
```java
|
||||||
|
NorthBlueVoteAPI api = NorthBlueVoteAPI.get();
|
||||||
|
// ou : Bukkit.getServicesManager().getRegistration(NorthBlueVoteAPI.class).getProvider();
|
||||||
|
|
||||||
|
int current = api.getVotePartyCurrentVotes();
|
||||||
|
int required = api.getVotePartyRequiredVotes();
|
||||||
|
boolean maintenance = api.isMaintenanceEnabled();
|
||||||
|
|
||||||
|
api.getPendingVotes(player.getUniqueId(), pending -> {
|
||||||
|
// callback rappelé sur le thread principal — safe pour un scoreboard/GUI
|
||||||
|
scoreboardLine.setText("Votes en attente: " + pending);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getVotePartyCurrentVotes()` / `getVotePartyRequiredVotes()` : lecture directe en mémoire, thread principal uniquement.
|
||||||
|
- `getPendingVotes(...)` : lecture asynchrone du stockage configuré (MySQL ou YAML), ne consomme pas les votes contrairement à `/claim`.
|
||||||
|
|
||||||
|
## Structure de la base de données
|
||||||
|
|
||||||
|
Créée automatiquement si absente (mode `mysql`) :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS nb_votes_attente (
|
||||||
|
player_uuid VARCHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
player_name VARCHAR(16) NOT NULL,
|
||||||
|
nombre_votes INT DEFAULT 0
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes techniques
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
- 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.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>fr.northblue</groupId>
|
||||||
|
<artifactId>northblue-vote-parent</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>velocity</module>
|
||||||
|
<module>spigot</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<hikaricp.version>5.1.0</hikaricp.version>
|
||||||
|
<mysql.version>8.0.33</mysql.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>fr.northblue</groupId>
|
||||||
|
<artifactId>northblue-vote-parent</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>northblue-vote-spigot</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Compiled against Spigot 1.18.2 API on purpose: the Bukkit API is additive
|
||||||
|
across versions, so a plugin built against an older API version keeps
|
||||||
|
loading fine on newer server jars (1.19 -> 1.21.x and beyond) as long as
|
||||||
|
it only touches stable, non-removed API surface (which this plugin does).
|
||||||
|
-->
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.spigotmc</groupId>
|
||||||
|
<artifactId>spigot-api</artifactId>
|
||||||
|
<version>1.18.2-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.zaxxer</groupId>
|
||||||
|
<artifactId>HikariCP</artifactId>
|
||||||
|
<version>${hikaricp.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>${mysql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spigotmc-repo</id>
|
||||||
|
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>VoteNetwork-Spigot</finalName>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.zaxxer.hikari</pattern>
|
||||||
|
<shadedPattern>fr.northblue.vote.libs.hikari</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.mysql</pattern>
|
||||||
|
<shadedPattern>fr.northblue.vote.libs.mysql</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package fr.northblue.vote.spigot;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class MaintenanceState {
|
||||||
|
|
||||||
|
private final AtomicBoolean stopped = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public boolean isStopped() {
|
||||||
|
return stopped.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStopped(boolean value) {
|
||||||
|
stopped.set(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package fr.northblue.vote.spigot;
|
||||||
|
|
||||||
|
import fr.northblue.vote.spigot.api.NorthBlueVoteAPI;
|
||||||
|
import fr.northblue.vote.spigot.api.NorthBlueVoteAPIHolder;
|
||||||
|
import fr.northblue.vote.spigot.api.NorthBlueVoteAPIImpl;
|
||||||
|
import fr.northblue.vote.spigot.command.ClaimCommand;
|
||||||
|
import fr.northblue.vote.spigot.command.VoteAdminCommand;
|
||||||
|
import fr.northblue.vote.spigot.listener.VoteMessageListener;
|
||||||
|
import fr.northblue.vote.spigot.storage.MySqlPendingVoteStore;
|
||||||
|
import fr.northblue.vote.spigot.storage.PendingVoteStore;
|
||||||
|
import fr.northblue.vote.spigot.storage.YamlPendingVoteStore;
|
||||||
|
import fr.northblue.vote.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.plugin.ServicePriority;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public class NorthBlueVoteSpigot extends JavaPlugin {
|
||||||
|
|
||||||
|
public static final String CHANNEL = "votenetwork:vote";
|
||||||
|
|
||||||
|
private VoteConfig voteConfig;
|
||||||
|
private PendingVoteStore pendingVoteStore;
|
||||||
|
private VoteParty voteParty;
|
||||||
|
private MaintenanceState maintenanceState;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
saveDefaultConfig();
|
||||||
|
|
||||||
|
this.voteConfig = new VoteConfig(this);
|
||||||
|
this.maintenanceState = new MaintenanceState();
|
||||||
|
|
||||||
|
if (voteConfig.isMysqlStorage()) {
|
||||||
|
MySqlPendingVoteStore mysqlStore = new MySqlPendingVoteStore(this, voteConfig);
|
||||||
|
mysqlStore.connect();
|
||||||
|
this.pendingVoteStore = mysqlStore;
|
||||||
|
getLogger().info("Stockage des votes en attente : MySQL.");
|
||||||
|
} else {
|
||||||
|
this.pendingVoteStore = new YamlPendingVoteStore(this);
|
||||||
|
getLogger().info("Stockage des votes en attente : fichier YAML local (pending-votes.yml).");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.voteParty = new VoteParty(this, voteConfig);
|
||||||
|
this.voteParty.load();
|
||||||
|
|
||||||
|
getServer().getMessenger().registerIncomingPluginChannel(this, CHANNEL, new VoteMessageListener(voteConfig, voteParty));
|
||||||
|
getServer().getMessenger().registerOutgoingPluginChannel(this, CHANNEL);
|
||||||
|
|
||||||
|
getCommand("claim").setExecutor(new ClaimCommand(this, voteConfig, pendingVoteStore, voteParty, maintenanceState));
|
||||||
|
getCommand("vote").setExecutor(new VoteAdminCommand(this, voteConfig, pendingVoteStore, maintenanceState));
|
||||||
|
|
||||||
|
NorthBlueVoteAPI api = new NorthBlueVoteAPIImpl(this, voteConfig, pendingVoteStore, voteParty, maintenanceState);
|
||||||
|
NorthBlueVoteAPIHolder.set(api);
|
||||||
|
getServer().getServicesManager().register(NorthBlueVoteAPI.class, api, this, ServicePriority.Normal);
|
||||||
|
|
||||||
|
getLogger().info("VoteNetwork (Spigot) demarre.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
getServer().getServicesManager().unregisterAll(this);
|
||||||
|
NorthBlueVoteAPIHolder.clear();
|
||||||
|
|
||||||
|
if (voteParty != null) {
|
||||||
|
voteParty.save();
|
||||||
|
}
|
||||||
|
if (pendingVoteStore != null) {
|
||||||
|
pendingVoteStore.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VoteConfig getVoteConfig() {
|
||||||
|
return voteConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingVoteStore getPendingVoteStore() {
|
||||||
|
return pendingVoteStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VoteParty getVoteParty() {
|
||||||
|
return voteParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaintenanceState getMaintenanceState() {
|
||||||
|
return maintenanceState;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package fr.northblue.vote.spigot;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class VoteConfig {
|
||||||
|
|
||||||
|
private final NorthBlueVoteSpigot plugin;
|
||||||
|
|
||||||
|
public VoteConfig(NorthBlueVoteSpigot plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileConfiguration cfg() {
|
||||||
|
return plugin.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
plugin.reloadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStorageType() {
|
||||||
|
return cfg().getString("storage.type", "mysql");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMysqlStorage() {
|
||||||
|
return !"yaml".equalsIgnoreCase(getStorageType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMysqlHost() {
|
||||||
|
return cfg().getString("mysql.host", "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMysqlPort() {
|
||||||
|
return cfg().getInt("mysql.port", 3306);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMysqlDatabase() {
|
||||||
|
return cfg().getString("mysql.database", "northblue");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMysqlUser() {
|
||||||
|
return cfg().getString("mysql.user", "root");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMysqlPassword() {
|
||||||
|
return cfg().getString("mysql.password", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMysqlPoolSize() {
|
||||||
|
return cfg().getInt("mysql.pool-size", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getVoteRewardCommands() {
|
||||||
|
return cfg().getStringList("rewards.vote-commands");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVotePartyVotesRequired() {
|
||||||
|
return cfg().getInt("voteparty.votes-requis", 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getVotePartyCommands() {
|
||||||
|
return cfg().getStringList("voteparty.commands");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVotePartyBroadcastEnabled() {
|
||||||
|
return cfg().getBoolean("voteparty.broadcast.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVotePartyProgressMessage() {
|
||||||
|
return colorize(cfg().getString("voteparty.broadcast.progress-message",
|
||||||
|
"&b[Vote] &f%current%&7/&f%required% &fvotes avant le prochain VoteParty !"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVotePartyTriggeredMessage() {
|
||||||
|
return colorize(cfg().getString("voteparty.broadcast.triggered-message",
|
||||||
|
"&a[Vote] &fVoteParty declenche ! Profitez des recompenses !"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageClaimSuccess() {
|
||||||
|
return colorize(cfg().getString("messages.claim-success",
|
||||||
|
"&a[Vote] &fVous avez recupere &e%amount% &fvote(s) en attente !"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageClaimEmpty() {
|
||||||
|
return colorize(cfg().getString("messages.claim-empty",
|
||||||
|
"&c[Vote] &fVous n'avez aucun vote en attente."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageMaintenance() {
|
||||||
|
return colorize(cfg().getString("messages.maintenance",
|
||||||
|
"&c[Vote] &fLe systeme de vote est actuellement en maintenance."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageMaintenanceEnabled() {
|
||||||
|
return colorize(cfg().getString("messages.maintenance-enabled",
|
||||||
|
"&c[Vote] &fMode maintenance active."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageMaintenanceDisabled() {
|
||||||
|
return colorize(cfg().getString("messages.maintenance-disabled",
|
||||||
|
"&a[Vote] &fMode maintenance desactive."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageDirectVote() {
|
||||||
|
return colorize(cfg().getString("messages.direct-vote",
|
||||||
|
"&a[Vote] &fMerci pour ton vote !"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageNoPermission() {
|
||||||
|
return colorize(cfg().getString("messages.no-permission",
|
||||||
|
"&cVous n'avez pas la permission d'utiliser cette commande."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String colorize(String input) {
|
||||||
|
return input == null ? "" : ChatColor.translateAlternateColorCodes('&', input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> colorizeList(List<String> input) {
|
||||||
|
return input.stream().map(this::colorize).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package fr.northblue.vote.spigot.api;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API publique NorthBlueVote, pour affichage scoreboard / GUI par d'autres plugins.
|
||||||
|
*
|
||||||
|
* Recuperation :
|
||||||
|
* NorthBlueVoteAPI api = NorthBlueVoteAPI.get();
|
||||||
|
* ou via le ServicesManager Bukkit :
|
||||||
|
* RegisteredServiceProvider<NorthBlueVoteAPI> rsp = Bukkit.getServicesManager().getRegistration(NorthBlueVoteAPI.class);
|
||||||
|
*/
|
||||||
|
public interface NorthBlueVoteAPI {
|
||||||
|
|
||||||
|
/** Compteur VoteParty actuel sur CE serveur (thread principal uniquement). */
|
||||||
|
int getVotePartyCurrentVotes();
|
||||||
|
|
||||||
|
/** Objectif VoteParty configure sur CE serveur (thread principal uniquement). */
|
||||||
|
int getVotePartyRequiredVotes();
|
||||||
|
|
||||||
|
/** true si /claim est actuellement bloque (mode maintenance). */
|
||||||
|
boolean isMaintenanceEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lit (sans les consommer) les votes en attente en base pour ce joueur.
|
||||||
|
* Requete asynchrone : le callback est rappele sur le thread principal du serveur.
|
||||||
|
*/
|
||||||
|
void getPendingVotes(UUID playerUuid, Consumer<Integer> callback);
|
||||||
|
|
||||||
|
static NorthBlueVoteAPI get() {
|
||||||
|
return NorthBlueVoteAPIHolder.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package fr.northblue.vote.spigot.api;
|
||||||
|
|
||||||
|
public final class NorthBlueVoteAPIHolder {
|
||||||
|
|
||||||
|
static volatile NorthBlueVoteAPI INSTANCE;
|
||||||
|
|
||||||
|
private NorthBlueVoteAPIHolder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(NorthBlueVoteAPI api) {
|
||||||
|
INSTANCE = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
INSTANCE = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package fr.northblue.vote.spigot.api;
|
||||||
|
|
||||||
|
import fr.northblue.vote.spigot.MaintenanceState;
|
||||||
|
import fr.northblue.vote.spigot.NorthBlueVoteSpigot;
|
||||||
|
import fr.northblue.vote.spigot.VoteConfig;
|
||||||
|
import fr.northblue.vote.spigot.storage.PendingVoteStore;
|
||||||
|
import fr.northblue.vote.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class NorthBlueVoteAPIImpl implements NorthBlueVoteAPI {
|
||||||
|
|
||||||
|
private final NorthBlueVoteSpigot plugin;
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final PendingVoteStore pendingVoteStore;
|
||||||
|
private final VoteParty voteParty;
|
||||||
|
private final MaintenanceState maintenanceState;
|
||||||
|
|
||||||
|
public NorthBlueVoteAPIImpl(NorthBlueVoteSpigot 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 int getVotePartyCurrentVotes() {
|
||||||
|
return voteParty.getCurrentVotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVotePartyRequiredVotes() {
|
||||||
|
return config.getVotePartyVotesRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMaintenanceEnabled() {
|
||||||
|
return maintenanceState.isStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getPendingVotes(UUID playerUuid, Consumer<Integer> callback) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||||
|
pendingVoteStore.peek(
|
||||||
|
playerUuid,
|
||||||
|
pending -> Bukkit.getScheduler().runTask(plugin, () -> callback.accept(pending)),
|
||||||
|
exception -> {
|
||||||
|
plugin.getLogger().severe("Erreur API getPendingVotes: " + exception.getMessage());
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> callback.accept(0));
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package fr.northblue.vote.spigot.command;
|
||||||
|
|
||||||
|
import fr.northblue.vote.spigot.MaintenanceState;
|
||||||
|
import fr.northblue.vote.spigot.NorthBlueVoteSpigot;
|
||||||
|
import fr.northblue.vote.spigot.VoteConfig;
|
||||||
|
import fr.northblue.vote.spigot.storage.PendingVoteStore;
|
||||||
|
import fr.northblue.vote.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class ClaimCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final NorthBlueVoteSpigot plugin;
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final PendingVoteStore pendingVoteStore;
|
||||||
|
private final VoteParty voteParty;
|
||||||
|
private final MaintenanceState maintenanceState;
|
||||||
|
|
||||||
|
public ClaimCommand(NorthBlueVoteSpigot 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 boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage("Cette commande est reservee aux joueurs.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
if (maintenanceState.isStopped()) {
|
||||||
|
player.sendMessage(config.getMessageMaintenance());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package fr.northblue.vote.spigot.command;
|
||||||
|
|
||||||
|
import fr.northblue.vote.spigot.MaintenanceState;
|
||||||
|
import fr.northblue.vote.spigot.NorthBlueVoteSpigot;
|
||||||
|
import fr.northblue.vote.spigot.VoteConfig;
|
||||||
|
import fr.northblue.vote.spigot.storage.PendingVoteStore;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
public class VoteAdminCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final NorthBlueVoteSpigot plugin;
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final PendingVoteStore pendingVoteStore;
|
||||||
|
private final MaintenanceState maintenanceState;
|
||||||
|
|
||||||
|
public VoteAdminCommand(NorthBlueVoteSpigot plugin, VoteConfig config, PendingVoteStore pendingVoteStore, MaintenanceState maintenanceState) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.config = config;
|
||||||
|
this.pendingVoteStore = pendingVoteStore;
|
||||||
|
this.maintenanceState = maintenanceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!sender.hasPermission("votenetwork.admin")) {
|
||||||
|
sender.sendMessage(config.getMessageNoPermission());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length != 1) {
|
||||||
|
sender.sendMessage("Usage: /vote <stop|start|testdb>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "stop" -> {
|
||||||
|
maintenanceState.setStopped(true);
|
||||||
|
sender.sendMessage(config.getMessageMaintenanceEnabled());
|
||||||
|
}
|
||||||
|
case "start" -> {
|
||||||
|
maintenanceState.setStopped(false);
|
||||||
|
sender.sendMessage(config.getMessageMaintenanceDisabled());
|
||||||
|
}
|
||||||
|
case "testdb" -> testStorage(sender);
|
||||||
|
default -> sender.sendMessage("Usage: /vote <stop|start|testdb>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testStorage(CommandSender sender) {
|
||||||
|
sender.sendMessage(ChatColor.GRAY + "[Vote] Test du stockage en cours...");
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||||
|
pendingVoteStore.testConnection((success, message) ->
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
if (success) {
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "[Vote] " + message);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.RED + "[Vote] Echec : " + message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package fr.northblue.vote.spigot.listener;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteArrayDataInput;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import fr.northblue.vote.spigot.NorthBlueVoteSpigot;
|
||||||
|
import fr.northblue.vote.spigot.VoteConfig;
|
||||||
|
import fr.northblue.vote.spigot.voteparty.VoteParty;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class VoteMessageListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final VoteParty voteParty;
|
||||||
|
|
||||||
|
public VoteMessageListener(VoteConfig config, VoteParty voteParty) {
|
||||||
|
this.config = config;
|
||||||
|
this.voteParty = voteParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPluginMessageReceived(String channel, Player receivingPlayer, byte[] message) {
|
||||||
|
if (!channel.equals(NorthBlueVoteSpigot.CHANNEL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayDataInput in = ByteStreams.newDataInput(message);
|
||||||
|
UUID uuid = UUID.fromString(in.readUTF());
|
||||||
|
in.readUTF(); // pseudo, non utilise ici (on retrouve le joueur via son UUID)
|
||||||
|
Player target = Bukkit.getPlayer(uuid);
|
||||||
|
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
// Le joueur a quitte ce serveur juste avant l'arrivee du message : le vote reste
|
||||||
|
// gere par le fallback base de donnees cote Velocity lors du prochain vote.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String command : config.getVoteRewardCommands()) {
|
||||||
|
String parsed = command.replace("%player%", target.getName());
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.sendMessage(config.getMessageDirectVote());
|
||||||
|
|
||||||
|
voteParty.addVotes(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package fr.northblue.vote.spigot.storage;
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import fr.northblue.vote.spigot.NorthBlueVoteSpigot;
|
||||||
|
import fr.northblue.vote.spigot.VoteConfig;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
public class MySqlPendingVoteStore implements PendingVoteStore {
|
||||||
|
|
||||||
|
private static final String DRIVER_CLASS = "fr.northblue.vote.libs.mysql.cj.jdbc.Driver";
|
||||||
|
|
||||||
|
private final NorthBlueVoteSpigot plugin;
|
||||||
|
private final VoteConfig config;
|
||||||
|
private HikariDataSource dataSource;
|
||||||
|
|
||||||
|
public MySqlPendingVoteStore(NorthBlueVoteSpigot plugin, VoteConfig config) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect() {
|
||||||
|
try {
|
||||||
|
HikariConfig hikariConfig = new HikariConfig();
|
||||||
|
hikariConfig.setJdbcUrl("jdbc:mysql://" + config.getMysqlHost() + ":" + config.getMysqlPort()
|
||||||
|
+ "/" + config.getMysqlDatabase() + "?useSSL=false&autoReconnect=true&characterEncoding=utf8");
|
||||||
|
hikariConfig.setUsername(config.getMysqlUser());
|
||||||
|
hikariConfig.setPassword(config.getMysqlPassword());
|
||||||
|
hikariConfig.setMaximumPoolSize(config.getMysqlPoolSize());
|
||||||
|
hikariConfig.setPoolName("VoteNetwork-Spigot-Pool");
|
||||||
|
hikariConfig.setDriverClassName(DRIVER_CLASS);
|
||||||
|
hikariConfig.setInitializationFailTimeout(-1);
|
||||||
|
hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
|
||||||
|
hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
|
||||||
|
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||||||
|
|
||||||
|
this.dataSource = new HikariDataSource(hikariConfig);
|
||||||
|
createTableIfNotExists();
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Connexion MySQL impossible au demarrage: " + e.getMessage());
|
||||||
|
plugin.getLogger().severe("Corrigez config.yml puis utilisez /vote testdb, ou redemarrez le plugin.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return dataSource != null && !dataSource.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void testConnection(BiConsumer<Boolean, String> callback) {
|
||||||
|
String url = "jdbc:mysql://" + config.getMysqlHost() + ":" + config.getMysqlPort()
|
||||||
|
+ "/" + config.getMysqlDatabase() + "?useSSL=false&connectTimeout=3000&socketTimeout=3000";
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
Class.forName(DRIVER_CLASS);
|
||||||
|
try (Connection connection = DriverManager.getConnection(url, config.getMysqlUser(), config.getMysqlPassword())) {
|
||||||
|
long latency = System.currentTimeMillis() - start;
|
||||||
|
callback.accept(true, "Connexion reussie en " + latency + " ms (" + config.getMysqlHost() + ":" + config.getMysqlPort() + "/" + config.getMysqlDatabase() + ")");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.accept(false, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTableIfNotExists() {
|
||||||
|
String sql = "CREATE TABLE IF NOT EXISTS nb_votes_attente (" +
|
||||||
|
"player_uuid VARCHAR(36) NOT NULL PRIMARY KEY," +
|
||||||
|
"player_name VARCHAR(16) NOT NULL," +
|
||||||
|
"nombre_votes INT DEFAULT 0" +
|
||||||
|
")";
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
Statement statement = connection.createStatement()) {
|
||||||
|
statement.execute(sql);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Impossible de creer la table nb_votes_attente: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fetchAndClear(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
||||||
|
if (!isConnected()) {
|
||||||
|
onError.accept(new SQLException("Pool MySQL indisponible (echec de connexion au demarrage)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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 = ?";
|
||||||
|
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
int pendingVotes = 0;
|
||||||
|
|
||||||
|
try (PreparedStatement select = connection.prepareStatement(selectSql)) {
|
||||||
|
select.setString(1, uuid.toString());
|
||||||
|
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.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResult.accept(pendingVotes);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
onError.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void peek(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
||||||
|
if (!isConnected()) {
|
||||||
|
onError.accept(new SQLException("Pool MySQL indisponible (echec de connexion au demarrage)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String sql = "SELECT nombre_votes FROM nb_votes_attente WHERE player_uuid = ?";
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
statement.setString(1, uuid.toString());
|
||||||
|
try (ResultSet resultSet = statement.executeQuery()) {
|
||||||
|
onResult.accept(resultSet.next() ? resultSet.getInt("nombre_votes") : 0);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
onError.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package fr.northblue.vote.spigot.storage;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stockage des votes en attente (joueurs hors ligne au moment du vote).
|
||||||
|
* Deux implementations : MySQL (partage reseau) ou fichier YAML local.
|
||||||
|
*
|
||||||
|
* Toutes les methodes doivent etre appelees depuis un thread asynchrone
|
||||||
|
* (Bukkit.getScheduler().runTaskAsynchronously) : les deux implementations font de l'I/O bloquant.
|
||||||
|
*/
|
||||||
|
public interface PendingVoteStore {
|
||||||
|
|
||||||
|
/** Lit le compteur de votes en attente puis le remet a 0 (utilise par /claim). */
|
||||||
|
void fetchAndClear(UUID uuid, IntConsumer onResult, Consumer<Exception> onError);
|
||||||
|
|
||||||
|
/** Lit le compteur de votes en attente sans le modifier (utilise par l'API). */
|
||||||
|
void peek(UUID uuid, IntConsumer onResult, Consumer<Exception> onError);
|
||||||
|
|
||||||
|
/** Teste que le stockage est joignable/fonctionnel, pour /vote testdb. */
|
||||||
|
void testConnection(BiConsumer<Boolean, String> callback);
|
||||||
|
|
||||||
|
void close();
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package fr.northblue.vote.spigot.storage;
|
||||||
|
|
||||||
|
import fr.northblue.vote.spigot.NorthBlueVoteSpigot;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stockage local des votes en attente dans plugins/votenetwork/pending-votes.yml.
|
||||||
|
*
|
||||||
|
* ATTENTION : ce fichier est local a CE serveur. Si le proxy Velocity utilise aussi
|
||||||
|
* le stockage "yaml", son fichier pending-votes.yml (dossier du plugin Velocity) est
|
||||||
|
* distinct de celui-ci et n'est PAS synchronise automatiquement entre machines.
|
||||||
|
* Le mode "yaml" n'est donc fiable que pour un reseau mono-machine (proxy + serveurs
|
||||||
|
* sur le meme disque, via un lien/point de montage partage) ou pour des tests locaux.
|
||||||
|
* Pour un vrai reseau multi-serveurs, utilisez "mysql".
|
||||||
|
*/
|
||||||
|
public class YamlPendingVoteStore implements PendingVoteStore {
|
||||||
|
|
||||||
|
private final File file;
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
|
public YamlPendingVoteStore(NorthBlueVoteSpigot plugin) {
|
||||||
|
this.file = new File(plugin.getDataFolder(), "pending-votes.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fetchAndClear(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||||
|
String path = "players." + uuid;
|
||||||
|
int votes = yaml.getInt(path + ".votes", 0);
|
||||||
|
if (votes > 0) {
|
||||||
|
yaml.set(path + ".votes", 0);
|
||||||
|
yaml.save(file);
|
||||||
|
}
|
||||||
|
onResult.accept(votes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
onError.accept(e);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void peek(UUID uuid, IntConsumer onResult, Consumer<Exception> onError) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||||
|
onResult.accept(yaml.getInt("players." + uuid + ".votes", 0));
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void testConnection(BiConsumer<Boolean, String> callback) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
boolean writable = parent.exists() || parent.mkdirs();
|
||||||
|
callback.accept(writable, writable
|
||||||
|
? "Stockage local YAML actif : " + file.getPath()
|
||||||
|
: "Impossible d'ecrire dans " + file.getPath());
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package fr.northblue.vote.spigot.voteparty;
|
||||||
|
|
||||||
|
import fr.northblue.vote.spigot.NorthBlueVoteSpigot;
|
||||||
|
import fr.northblue.vote.spigot.VoteConfig;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class VoteParty {
|
||||||
|
|
||||||
|
private final NorthBlueVoteSpigot plugin;
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final File storageFile;
|
||||||
|
private final AtomicInteger currentVotes = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public VoteParty(NorthBlueVoteSpigot plugin, VoteConfig config) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.config = config;
|
||||||
|
this.storageFile = new File(plugin.getDataFolder(), "voteparty.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
if (!storageFile.exists()) {
|
||||||
|
currentVotes.set(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(storageFile);
|
||||||
|
currentVotes.set(yaml.getInt("current-votes", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
yaml.set("current-votes", currentVotes.get());
|
||||||
|
try {
|
||||||
|
yaml.save(storageFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("Impossible de sauvegarder le VoteParty local: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A appeler uniquement depuis le thread principal du serveur.
|
||||||
|
*/
|
||||||
|
public void addVotes(int amount) {
|
||||||
|
int required = config.getVotePartyVotesRequired();
|
||||||
|
int updated = currentVotes.addAndGet(amount);
|
||||||
|
|
||||||
|
if (config.isVotePartyBroadcastEnabled()) {
|
||||||
|
Bukkit.broadcastMessage(config.getVotePartyProgressMessage()
|
||||||
|
.replace("%current%", String.valueOf(Math.min(updated, required)))
|
||||||
|
.replace("%required%", String.valueOf(required)));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (updated >= required && required > 0) {
|
||||||
|
trigger();
|
||||||
|
updated = currentVotes.addAndGet(-required);
|
||||||
|
}
|
||||||
|
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trigger() {
|
||||||
|
if (config.isVotePartyBroadcastEnabled()) {
|
||||||
|
Bukkit.broadcastMessage(config.getVotePartyTriggeredMessage());
|
||||||
|
}
|
||||||
|
for (String command : config.getVotePartyCommands()) {
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentVotes() {
|
||||||
|
return currentVotes.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# =========================================
|
||||||
|
# VoteNetwork - Configuration (Spigot)
|
||||||
|
# =========================================
|
||||||
|
|
||||||
|
# Stockage des votes en attente (joueurs hors ligne au moment du vote) :
|
||||||
|
# - mysql : recommande pour un vrai reseau (proxy + plusieurs serveurs). Partage entre toutes les machines.
|
||||||
|
# - yaml : fichier local plugins/votenetwork/pending-votes.yml. Utile en solo/test, mais N'EST PAS
|
||||||
|
# partage automatiquement avec le fichier YAML du proxy Velocity si vous etes en reseau.
|
||||||
|
storage:
|
||||||
|
type: mysql
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
|
database: votenetwork
|
||||||
|
user: root
|
||||||
|
password: changeme
|
||||||
|
pool-size: 5
|
||||||
|
|
||||||
|
# Commandes executees par la console pour recompenser UN vote (vote direct en jeu ou /claim, une fois par vote).
|
||||||
|
# %player% est remplace par le pseudo du joueur.
|
||||||
|
rewards:
|
||||||
|
vote-commands:
|
||||||
|
- "goldencrates give %player% vote_key 1"
|
||||||
|
|
||||||
|
voteparty:
|
||||||
|
# Nombre de votes cumules (sur CE serveur) declenchant le VoteParty.
|
||||||
|
votes-requis: 100
|
||||||
|
# Commandes executees par la console quand le VoteParty se declenche.
|
||||||
|
commands:
|
||||||
|
- "goldencrates broadcast_give vote_key 1"
|
||||||
|
broadcast:
|
||||||
|
enabled: true
|
||||||
|
progress-message: "&b[Vote] &f%current%&7/&f%required% &fvotes avant le prochain VoteParty !"
|
||||||
|
triggered-message: "&a[Vote] &fVoteParty declenche ! Profitez des recompenses !"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
direct-vote: "&a[Vote] &fMerci pour ton vote !"
|
||||||
|
claim-success: "&a[Vote] &fVous avez recupere &e%amount% &fvote(s) en attente !"
|
||||||
|
claim-empty: "&c[Vote] &fVous n'avez aucun vote en attente."
|
||||||
|
maintenance: "&c[Vote] &fLe systeme de vote est actuellement en maintenance."
|
||||||
|
maintenance-enabled: "&c[Vote] &fMode maintenance active."
|
||||||
|
maintenance-disabled: "&a[Vote] &fMode maintenance desactive."
|
||||||
|
no-permission: "&cVous n'avez pas la permission d'utiliser cette commande."
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: votenetwork
|
||||||
|
main: fr.northblue.vote.spigot.NorthBlueVoteSpigot
|
||||||
|
version: ${project.version}
|
||||||
|
api-version: 1.18
|
||||||
|
author: VoteNetwork
|
||||||
|
description: Reception des votes reseau (direct ou en attente) avec VoteParty local.
|
||||||
|
|
||||||
|
commands:
|
||||||
|
claim:
|
||||||
|
description: Recupere vos votes en attente.
|
||||||
|
usage: /claim
|
||||||
|
vote:
|
||||||
|
description: Administration du systeme de vote (maintenance, test du stockage).
|
||||||
|
usage: /vote <stop|start|testdb>
|
||||||
|
permission: votenetwork.admin
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
votenetwork.admin:
|
||||||
|
description: Autorise l'utilisation de /vote stop|start|testdb.
|
||||||
|
default: op
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>fr.northblue</groupId>
|
||||||
|
<artifactId>northblue-vote-parent</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>northblue-vote-velocity</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.velocitypowered</groupId>
|
||||||
|
<artifactId>velocity-api</artifactId>
|
||||||
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.zaxxer</groupId>
|
||||||
|
<artifactId>HikariCP</artifactId>
|
||||||
|
<version>${hikaricp.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>${mysql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>VoteNetwork-Velocity</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>com.velocitypowered</groupId>
|
||||||
|
<artifactId>velocity-api</artifactId>
|
||||||
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.zaxxer.hikari</pattern>
|
||||||
|
<shadedPattern>fr.northblue.vote.libs.hikari</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.mysql</pattern>
|
||||||
|
<shadedPattern>fr.northblue.vote.libs.mysql</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package fr.northblue.vote.velocity;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class MojangUuidResolver {
|
||||||
|
|
||||||
|
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
|
||||||
|
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
|
public MojangUuidResolver(Logger logger) {
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<UUID> resolve(String playerName) {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("https://api.mojang.com/users/profiles/minecraft/" + playerName))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return HTTP_CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||||
|
.thenApply(response -> {
|
||||||
|
if (response.statusCode() != 200 || response.body() == null || response.body().isBlank()) {
|
||||||
|
return offlineFallback(playerName);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||||
|
String rawId = json.get("id").getAsString();
|
||||||
|
return dashedUuid(rawId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Reponse Mojang illisible pour {}, utilisation d'un UUID de secours", playerName);
|
||||||
|
return offlineFallback(playerName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
logger.warn("Impossible de contacter l'API Mojang pour {}", playerName, throwable);
|
||||||
|
return offlineFallback(playerName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID offlineFallback(String playerName) {
|
||||||
|
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName).getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID dashedUuid(String raw) {
|
||||||
|
String dashed = raw.replaceFirst(
|
||||||
|
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
|
||||||
|
"$1-$2-$3-$4-$5"
|
||||||
|
);
|
||||||
|
return UUID.fromString(dashed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package fr.northblue.vote.velocity;
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class MySqlPendingVoteStore implements PendingVoteStore {
|
||||||
|
|
||||||
|
private final VoteConfig config;
|
||||||
|
private final Logger logger;
|
||||||
|
private final Executor asyncExecutor = Executors.newFixedThreadPool(4, r -> {
|
||||||
|
Thread t = new Thread(r, "VoteNetwork-DB");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
private HikariDataSource dataSource;
|
||||||
|
|
||||||
|
public MySqlPendingVoteStore(VoteConfig config, Logger logger) {
|
||||||
|
this.config = config;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect() {
|
||||||
|
try {
|
||||||
|
HikariConfig hikariConfig = new HikariConfig();
|
||||||
|
hikariConfig.setJdbcUrl(config.getJdbcUrl());
|
||||||
|
hikariConfig.setUsername(config.getUser());
|
||||||
|
hikariConfig.setPassword(config.getPassword());
|
||||||
|
hikariConfig.setMaximumPoolSize(config.getPoolSize());
|
||||||
|
hikariConfig.setPoolName("VoteNetwork-Velocity-Pool");
|
||||||
|
hikariConfig.setDriverClassName("fr.northblue.vote.libs.mysql.cj.jdbc.Driver");
|
||||||
|
hikariConfig.setInitializationFailTimeout(-1);
|
||||||
|
hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
|
||||||
|
hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
|
||||||
|
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||||||
|
|
||||||
|
this.dataSource = new HikariDataSource(hikariConfig);
|
||||||
|
|
||||||
|
createTableIfNotExists();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Connexion MySQL impossible au demarrage (Velocity). Verifiez config.properties.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return dataSource != null && !dataSource.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTableIfNotExists() {
|
||||||
|
String sql = "CREATE TABLE IF NOT EXISTS nb_votes_attente (" +
|
||||||
|
"player_uuid VARCHAR(36) NOT NULL PRIMARY KEY," +
|
||||||
|
"player_name VARCHAR(16) NOT NULL," +
|
||||||
|
"nombre_votes INT DEFAULT 0" +
|
||||||
|
")";
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
Statement statement = connection.createStatement()) {
|
||||||
|
statement.execute(sql);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("Impossible de creer la table nb_votes_attente", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> addPendingVote(UUID uuid, String playerName) {
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
if (!isConnected()) {
|
||||||
|
logger.error("Impossible d'enregistrer le vote en attente pour {} : pool MySQL indisponible", playerName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}, asyncExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package fr.northblue.vote.velocity;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||||
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
id = "votenetwork",
|
||||||
|
name = "VoteNetwork",
|
||||||
|
version = "1.0.0",
|
||||||
|
description = "Systeme de vote reseau Velocity <-> Spigot",
|
||||||
|
authors = {"VoteNetwork"}
|
||||||
|
)
|
||||||
|
public class NorthBlueVoteVelocity {
|
||||||
|
|
||||||
|
public static final MinecraftChannelIdentifier VOTE_CHANNEL =
|
||||||
|
MinecraftChannelIdentifier.create("votenetwork", "vote");
|
||||||
|
|
||||||
|
private final ProxyServer proxy;
|
||||||
|
private final Logger logger;
|
||||||
|
private final Path dataDirectory;
|
||||||
|
|
||||||
|
private VoteConfig config;
|
||||||
|
private PendingVoteStore pendingVoteStore;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public NorthBlueVoteVelocity(ProxyServer proxy, Logger logger, @com.velocitypowered.api.plugin.annotation.DataDirectory Path dataDirectory) {
|
||||||
|
this.proxy = proxy;
|
||||||
|
this.logger = logger;
|
||||||
|
this.dataDirectory = dataDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyInitialize(ProxyInitializeEvent event) {
|
||||||
|
this.config = new VoteConfig(dataDirectory, logger);
|
||||||
|
this.config.load();
|
||||||
|
|
||||||
|
if (config.isMysqlStorage()) {
|
||||||
|
MySqlPendingVoteStore mysqlStore = new MySqlPendingVoteStore(config, logger);
|
||||||
|
mysqlStore.connect();
|
||||||
|
this.pendingVoteStore = mysqlStore;
|
||||||
|
logger.info("Stockage des votes en attente : MySQL.");
|
||||||
|
} else {
|
||||||
|
this.pendingVoteStore = new YamlPendingVoteStore(dataDirectory, logger);
|
||||||
|
logger.info("Stockage des votes en attente : fichier YAML local (pending-votes.yml).");
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.getChannelRegistrar().register(VOTE_CHANNEL);
|
||||||
|
|
||||||
|
proxy.getCommandManager().register(
|
||||||
|
proxy.getCommandManager().metaBuilder("votenetwork").build(),
|
||||||
|
new VoteCommand(proxy, pendingVoteStore, logger)
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info("VoteNetwork (Velocity) demarre.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyShutdown(ProxyShutdownEvent event) {
|
||||||
|
if (pendingVoteStore != null) {
|
||||||
|
pendingVoteStore.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package fr.northblue.vote.velocity;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stockage des votes en attente (joueurs hors ligne au moment du vote).
|
||||||
|
* Deux implementations : MySQL (partage reseau, recommande) ou fichier YAML local.
|
||||||
|
*/
|
||||||
|
public interface PendingVoteStore {
|
||||||
|
|
||||||
|
CompletableFuture<Void> addPendingVote(UUID uuid, String playerName);
|
||||||
|
|
||||||
|
void close();
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package fr.northblue.vote.velocity;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class VoteCommand implements SimpleCommand {
|
||||||
|
|
||||||
|
private final ProxyServer proxy;
|
||||||
|
private final PendingVoteStore pendingVoteStore;
|
||||||
|
private final Logger logger;
|
||||||
|
private final MojangUuidResolver uuidResolver;
|
||||||
|
|
||||||
|
public VoteCommand(ProxyServer proxy, PendingVoteStore pendingVoteStore, Logger logger) {
|
||||||
|
this.proxy = proxy;
|
||||||
|
this.pendingVoteStore = pendingVoteStore;
|
||||||
|
this.logger = logger;
|
||||||
|
this.uuidResolver = new MojangUuidResolver(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Invocation invocation) {
|
||||||
|
CommandSource source = invocation.source();
|
||||||
|
String[] args = invocation.arguments();
|
||||||
|
|
||||||
|
if (!(source instanceof com.velocitypowered.api.proxy.ConsoleCommandSource)) {
|
||||||
|
source.sendMessage(Component.text("Cette commande n'est executable que depuis la console."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < 2 || !args[0].equalsIgnoreCase("vote")) {
|
||||||
|
source.sendMessage(Component.text("Usage: votenetwork vote <pseudo>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String playerName = args[1];
|
||||||
|
Optional<Player> onlinePlayer = proxy.getPlayer(playerName);
|
||||||
|
|
||||||
|
if (onlinePlayer.isPresent()) {
|
||||||
|
Player player = onlinePlayer.get();
|
||||||
|
Optional<ServerConnection> serverConnection = player.getCurrentServer();
|
||||||
|
|
||||||
|
if (serverConnection.isPresent()) {
|
||||||
|
sendVoteSignal(serverConnection.get(), player);
|
||||||
|
logger.info("Vote direct envoye a {} pour le joueur {}", serverConnection.get().getServerInfo().getName(), player.getUsername());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uuidResolver.resolve(playerName)
|
||||||
|
.thenCompose(uuid -> pendingVoteStore.addPendingVote(uuid, playerName))
|
||||||
|
.thenRun(() -> logger.info("Vote stocke en base de donnees pour {} (hors ligne)", playerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendVoteSignal(ChannelMessageSink sink, Player player) {
|
||||||
|
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream out = new DataOutputStream(byteArray);
|
||||||
|
try {
|
||||||
|
out.writeUTF(player.getUniqueId().toString());
|
||||||
|
out.writeUTF(player.getUsername());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Erreur lors de la construction du message de vote", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sink.sendPluginMessage(NorthBlueVoteVelocity.VOTE_CHANNEL, byteArray.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(Invocation invocation) {
|
||||||
|
return invocation.source() instanceof com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package fr.northblue.vote.velocity;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class VoteConfig {
|
||||||
|
|
||||||
|
private final Path dataDirectory;
|
||||||
|
private final Logger logger;
|
||||||
|
private final Properties properties = new Properties();
|
||||||
|
|
||||||
|
private String storageType;
|
||||||
|
private String host;
|
||||||
|
private int port;
|
||||||
|
private String database;
|
||||||
|
private String user;
|
||||||
|
private String password;
|
||||||
|
private int poolSize;
|
||||||
|
|
||||||
|
public VoteConfig(Path dataDirectory, Logger logger) {
|
||||||
|
this.dataDirectory = dataDirectory;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(dataDirectory);
|
||||||
|
Path configFile = dataDirectory.resolve("config.properties");
|
||||||
|
|
||||||
|
if (!Files.exists(configFile)) {
|
||||||
|
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.properties")) {
|
||||||
|
if (in != null) {
|
||||||
|
Files.copy(in, configFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream in = Files.newInputStream(configFile)) {
|
||||||
|
properties.load(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storageType = properties.getProperty("storage.type", "mysql");
|
||||||
|
this.host = properties.getProperty("mysql.host", "127.0.0.1");
|
||||||
|
this.port = Integer.parseInt(properties.getProperty("mysql.port", "3306"));
|
||||||
|
this.database = properties.getProperty("mysql.database", "northblue");
|
||||||
|
this.user = properties.getProperty("mysql.user", "root");
|
||||||
|
this.password = properties.getProperty("mysql.password", "");
|
||||||
|
this.poolSize = Integer.parseInt(properties.getProperty("mysql.pool-size", "5"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Impossible de charger la configuration Velocity", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMysqlStorage() {
|
||||||
|
return !"yaml".equalsIgnoreCase(storageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJdbcUrl() {
|
||||||
|
return "jdbc:mysql://" + host + ":" + port + "/" + database
|
||||||
|
+ "?useSSL=false&autoReconnect=true&characterEncoding=utf8";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPoolSize() {
|
||||||
|
return poolSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package fr.northblue.vote.velocity;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stockage local des votes en attente dans pending-votes.yml (dossier du plugin Velocity).
|
||||||
|
*
|
||||||
|
* ATTENTION : ce fichier est local au proxy. Si un serveur Spigot utilise aussi le
|
||||||
|
* stockage "yaml", son propre pending-votes.yml (dossier du plugin Spigot) est distinct
|
||||||
|
* de celui-ci et n'est PAS synchronise automatiquement. Le mode "yaml" n'est fiable que
|
||||||
|
* pour un reseau mono-machine (partage de disque) ou pour des tests locaux.
|
||||||
|
* Pour un vrai reseau multi-serveurs, utilisez "mysql".
|
||||||
|
*/
|
||||||
|
public class YamlPendingVoteStore implements PendingVoteStore {
|
||||||
|
|
||||||
|
private static final Pattern UUID_LINE = Pattern.compile("^ {2}([0-9a-fA-F-]{36}):$");
|
||||||
|
private static final Pattern NAME_LINE = Pattern.compile("^ {4}name: (.+)$");
|
||||||
|
private static final Pattern VOTES_LINE = Pattern.compile("^ {4}votes: (\\d+)$");
|
||||||
|
|
||||||
|
private final Path file;
|
||||||
|
private final Logger logger;
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
private final Executor asyncExecutor = Executors.newSingleThreadExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "VoteNetwork-YamlStore");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
public YamlPendingVoteStore(Path dataDirectory, Logger logger) {
|
||||||
|
this.file = dataDirectory.resolve("pending-votes.yml");
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> addPendingVote(UUID uuid, String playerName) {
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
Map<UUID, Entry> data = load();
|
||||||
|
Entry entry = data.get(uuid);
|
||||||
|
if (entry == null) {
|
||||||
|
entry = new Entry(playerName, 0);
|
||||||
|
data.put(uuid, entry);
|
||||||
|
}
|
||||||
|
entry.name = playerName;
|
||||||
|
entry.votes++;
|
||||||
|
save(data);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Erreur stockage YAML du vote en attente pour {}", playerName, e);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}, asyncExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<UUID, Entry> load() throws IOException {
|
||||||
|
Map<UUID, Entry> result = new LinkedHashMap<>();
|
||||||
|
if (!Files.exists(file)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID currentUuid = null;
|
||||||
|
String currentName = null;
|
||||||
|
Integer currentVotes = null;
|
||||||
|
|
||||||
|
for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) {
|
||||||
|
Matcher uuidMatcher = UUID_LINE.matcher(line);
|
||||||
|
if (uuidMatcher.matches()) {
|
||||||
|
flush(result, currentUuid, currentName, currentVotes);
|
||||||
|
currentUuid = UUID.fromString(uuidMatcher.group(1));
|
||||||
|
currentName = null;
|
||||||
|
currentVotes = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Matcher nameMatcher = NAME_LINE.matcher(line);
|
||||||
|
if (nameMatcher.matches()) {
|
||||||
|
currentName = nameMatcher.group(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Matcher votesMatcher = VOTES_LINE.matcher(line);
|
||||||
|
if (votesMatcher.matches()) {
|
||||||
|
currentVotes = Integer.parseInt(votesMatcher.group(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush(result, currentUuid, currentName, currentVotes);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flush(Map<UUID, Entry> result, UUID uuid, String name, Integer votes) {
|
||||||
|
if (uuid != null && name != null && votes != null) {
|
||||||
|
result.put(uuid, new Entry(name, votes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save(Map<UUID, Entry> data) throws IOException {
|
||||||
|
StringBuilder sb = new StringBuilder("players:\n");
|
||||||
|
for (Map.Entry<UUID, Entry> e : data.entrySet()) {
|
||||||
|
sb.append(" ").append(e.getKey()).append(":\n");
|
||||||
|
sb.append(" name: ").append(e.getValue().name).append("\n");
|
||||||
|
sb.append(" votes: ").append(e.getValue().votes).append("\n");
|
||||||
|
}
|
||||||
|
Files.createDirectories(file.getParent());
|
||||||
|
Files.writeString(file, sb.toString(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Entry {
|
||||||
|
String name;
|
||||||
|
int votes;
|
||||||
|
|
||||||
|
Entry(String name, int votes) {
|
||||||
|
this.name = name;
|
||||||
|
this.votes = votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# VoteNetwork - Configuration (Velocity)
|
||||||
|
|
||||||
|
# Stockage des votes en attente (joueurs hors ligne au moment du vote) :
|
||||||
|
# mysql : recommande pour un vrai reseau (proxy + plusieurs serveurs). Partage entre toutes les machines.
|
||||||
|
# yaml : fichier local pending-votes.yml (dossier du plugin Velocity). Utile en solo/test, mais N'EST PAS
|
||||||
|
# partage automatiquement avec le pending-votes.yml des serveurs Spigot si vous etes en reseau.
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user