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,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