Ajoute la portee per-server pour les votes en attente + credits/licence
pending-votes.scope (config.properties Velocity) = global (defaut, comportement historique) ou per-server. En per-server, un vote hors ligne incremente le compteur de chaque serveur de direct-vote.servers independamment (nouvelle table nb_votes_attente_serveur) ; /claim sur un serveur ne remet a 0 que ce serveur-la. Necessite server-name (config.yml) et storage.pending-votes-scope assortis cote Spigot. Ajoute LICENSE (MIT) et credit Sar_Tron (plugin.yml, @Plugin Velocity, pom parent, README) en vue de la publication publique. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
74b2914529
commit
261e220d1a
@@ -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
|
||||
|
||||
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, ...).
|
||||
|
||||
- `velocity/` — module proxy. Recoit `votenetwork vote <pseudo>` (console uniquement) et route le vote en direct ou le met en attente.
|
||||
@@ -110,6 +112,21 @@ Testez la connexion/l'accès au stockage configuré côté Spigot avec :
|
||||
/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
|
||||
|
||||
1. Un site de vote appelle la console du proxy : `votenetwork vote <pseudo>`.
|
||||
@@ -173,14 +190,24 @@ Utilisez ces placeholders directement dans la config de votre plugin de scoreboa
|
||||
|
||||
## 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
|
||||
-- scope = global (par defaut)
|
||||
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
|
||||
);
|
||||
|
||||
-- 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)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -14,6 +14,23 @@
|
||||
<module>spigot</module>
|
||||
</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>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
|
||||
@@ -30,6 +30,15 @@ public class VoteConfig {
|
||||
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() {
|
||||
return cfg().getString("mysql.host", "127.0.0.1");
|
||||
}
|
||||
@@ -54,6 +63,14 @@ public class VoteConfig {
|
||||
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() {
|
||||
return cfg().getStringList("rewards.vote-commands");
|
||||
}
|
||||
|
||||
@@ -73,16 +73,24 @@ public class MySqlPendingVoteStore implements PendingVoteStore {
|
||||
}
|
||||
|
||||
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_name VARCHAR(16) NOT NULL," +
|
||||
"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();
|
||||
Statement statement = connection.createStatement()) {
|
||||
statement.execute(sql);
|
||||
statement.execute(globalSql);
|
||||
statement.execute(perServerSql);
|
||||
} 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)"));
|
||||
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 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
|
||||
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 = ?";
|
||||
|
||||
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();
|
||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, uuid.toString());
|
||||
if (perServer) {
|
||||
statement.setString(2, config.getServerName());
|
||||
}
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
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.
|
||||
storage:
|
||||
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:
|
||||
host: 127.0.0.1
|
||||
|
||||
@@ -2,7 +2,7 @@ name: votenetwork
|
||||
main: fr.votenetwork.spigot.VoteNetworkSpigot
|
||||
version: ${project.version}
|
||||
api-version: 1.18
|
||||
author: VoteNetwork
|
||||
author: Sar_Tron
|
||||
description: Reception des votes reseau (direct ou en attente) avec VoteParty local.
|
||||
softdepend: [PlaceholderAPI]
|
||||
|
||||
|
||||
@@ -57,16 +57,24 @@ public class MySqlPendingVoteStore implements PendingVoteStore {
|
||||
}
|
||||
|
||||
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_name VARCHAR(16) NOT NULL," +
|
||||
"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();
|
||||
Statement statement = connection.createStatement()) {
|
||||
statement.execute(sql);
|
||||
statement.execute(globalSql);
|
||||
statement.execute(perServerSql);
|
||||
} 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);
|
||||
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);
|
||||
if (config.isPerServerScope()) {
|
||||
addPendingVotePerServer(uuid, playerName);
|
||||
} else {
|
||||
addPendingVoteGlobal(uuid, playerName);
|
||||
}
|
||||
}, 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
|
||||
public void close() {
|
||||
if (dataSource != null) {
|
||||
|
||||
@@ -26,6 +26,7 @@ public class VoteConfig {
|
||||
private String password;
|
||||
private int poolSize;
|
||||
private Set<String> directVoteServers;
|
||||
private String pendingVotesScope;
|
||||
|
||||
public VoteConfig(Path dataDirectory, Logger logger) {
|
||||
this.dataDirectory = dataDirectory;
|
||||
@@ -65,6 +66,13 @@ public class VoteConfig {
|
||||
.filter(s -> !s.isEmpty())
|
||||
.map(s -> s.toLowerCase(Locale.ROOT))
|
||||
.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) {
|
||||
logger.error("Impossible de charger la configuration Velocity", e);
|
||||
}
|
||||
@@ -119,6 +127,19 @@ public class VoteConfig {
|
||||
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() {
|
||||
return "jdbc:mysql://" + host + ":" + port + "/" + database
|
||||
+ "?useSSL=false&autoReconnect=true&characterEncoding=utf8";
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.nio.file.Path;
|
||||
name = "VoteNetwork",
|
||||
version = "1.0.0",
|
||||
description = "Systeme de vote reseau Velocity <-> Spigot",
|
||||
authors = {"VoteNetwork"}
|
||||
authors = {"Sar_Tron"}
|
||||
)
|
||||
public class VoteNetworkVelocity {
|
||||
|
||||
|
||||
@@ -20,3 +20,17 @@ mysql.pool-size=5
|
||||
# Laisser vide = tous les serveurs sont consideres comme ayant le plugin (deconseille des
|
||||
# qu'un lobby/hub sans VoteNetwork-Spigot existe sur le reseau).
|
||||
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