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,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
|
||||
Reference in New Issue
Block a user