CDVote — наш бесплатный плагин наград за голосование. Ниже весь его код: один файл, 210 строк. Ставить его не обязательно: голоса с Cloud Delight понимает любой плагин наград — как подключить VotingPlugin или SuperbVote.
config.yml, хранит очередь наград для тех, кто был не в сети,
показывает ссылку по /vote и напоминает в чате. В сеть не выходит, ничего не скачивает и не обновляет себя:
в коде нет ни одного сетевого вызова — проверьте по списку import в начале файла.Скачать CDVote.java Готовый jar в каталоге
Нужны JDK от 11 до 21 и spigot-api любой версии от 1.13. Скомпилируйте код, положите в папку
build файлы plugin.yml и config.yml из готового jar (это обычный zip-архив) и упакуйте:
javac --release 8 -encoding UTF-8 -cp spigot-api.jar -d build CDVote.java jar cf CDVote.jar -C build .
package ru.clouddelight.cdvote;import org.bukkit.Bukkit;import org.bukkit.ChatColor;import org.bukkit.command.Command;import org.bukkit.command.CommandSender;import org.bukkit.configuration.file.YamlConfiguration;import org.bukkit.entity.Player;import org.bukkit.event.Event;import org.bukkit.event.EventHandler;import org.bukkit.event.EventPriority;import org.bukkit.event.Listener;import org.bukkit.event.player.PlayerJoinEvent;import org.bukkit.plugin.EventExecutor;import org.bukkit.plugin.java.JavaPlugin;import java.io.File;import java.io.IOException;import java.util.Locale;/*** CDVote — награды за голосование в мониторинге Cloud Delight (и любом другом, что шлёт голоса через Votifier).* Голос принимает NuVotifier / azuvotifier, CDVote выдаёт награду: команды из config.yml, офлайн-игрокам — при входе,* /vote — ссылка на страницу голосования, напоминание в чат. Зависимость от Votifier — во время работы (через* рефлексию), поэтому один jar подходит и к NuVotifier, и к форкам, и к старому Votifier 1.x.*/public final class CDVote extends JavaPlugin implements Listener {private File pendingFile;private YamlConfiguration pending;private int reminderTask = -1;@Overridepublic void onEnable() {saveDefaultConfig();pendingFile = new File(getDataFolder(), "pending.yml");pending = YamlConfiguration.loadConfiguration(pendingFile);getServer().getPluginManager().registerEvents(this, this);if (!hookVotifier()) {getLogger().warning("Не найден Votifier! Поставьте NuVotifier или azuvotifier — без него голоса не дойдут до сервера.");}startReminder();}@Overridepublic void onDisable() {savePending();}/** Подписка на com.vexsoftware.votifier.model.VotifierEvent без зависимости при сборке. */@SuppressWarnings("unchecked")private boolean hookVotifier() {try {Class<? extends Event> cls = (Class<? extends Event>) Class.forName("com.vexsoftware.votifier.model.VotifierEvent");EventExecutor ex = (listener, event) -> {if (!cls.isInstance(event)) return;try {Object vote = event.getClass().getMethod("getVote").invoke(event);String user = String.valueOf(vote.getClass().getMethod("getUsername").invoke(vote)).trim();String service = String.valueOf(vote.getClass().getMethod("getServiceName").invoke(vote));Bukkit.getScheduler().runTask(this, () -> onVote(user, service));} catch (ReflectiveOperationException e) {getLogger().warning("Не удалось прочитать голос: " + e);}};getServer().getPluginManager().registerEvent(cls, this, EventPriority.NORMAL, ex, this);getLogger().info("Votifier найден — ждём голоса.");return true;} catch (ClassNotFoundException e) {return false;}}private void onVote(String user, String service) {if (user.isEmpty() || !user.matches("[A-Za-z0-9_]{2,16}")) {getLogger().warning("Голос с непонятным ником «" + user + "» от " + service + " — пропущен");return;}if (user.equalsIgnoreCase(getConfig().getString("test-nick", "CloudDelightTest"))) {getLogger().info("Тестовый голос от " + service + " получен — связь работает, награда не выдаётся.");return;}getLogger().info("Голос: " + user + " (" + service + ")");pending.set("voted." + user.toLowerCase(Locale.ROOT), System.currentTimeMillis()); // напоминание его сутки не трогаетPlayer p = findOnline(user);if (p != null) {reward(p.getName(), service);} else if (getConfig().getBoolean("offline-rewards", true)) {String key = "queue." + user.toLowerCase(Locale.ROOT);pending.set(key, pending.getInt(key, 0) + 1);}savePending();String bc = getConfig().getString("messages.broadcast", "");if (!bc.isEmpty()) {Bukkit.broadcastMessage(color(bc.replace("%player%", user).replace("%service%", service)));}}private void reward(String name, String service) {for (String cmd : getConfig().getStringList("rewards")) {String c = cmd.replace("%player%", name).replace("%service%", service);if (c.startsWith("/")) c = c.substring(1);try {Bukkit.dispatchCommand(Bukkit.getConsoleSender(), c);} catch (Exception e) {getLogger().warning("Команда награды не выполнилась: " + c + " — " + e.getMessage());}}Player p = Bukkit.getPlayerExact(name);String msg = getConfig().getString("messages.thanks", "");if (p != null && !msg.isEmpty()) p.sendMessage(color(msg.replace("%player%", name).replace("%service%", service)));}@EventHandlerpublic void onJoin(PlayerJoinEvent e) {String key = "queue." + e.getPlayer().getName().toLowerCase(Locale.ROOT);int n = pending.getInt(key, 0);if (n <= 0) return;pending.set(key, null);savePending();String name = e.getPlayer().getName();Bukkit.getScheduler().runTaskLater(this, () -> {for (int i = 0; i < n; i++) reward(name, "офлайн");Player p = Bukkit.getPlayerExact(name);String msg = getConfig().getString("messages.offline", "");if (p != null && !msg.isEmpty()) p.sendMessage(color(msg.replace("%count%", String.valueOf(n))));}, 40L);}private Player findOnline(String user) {Player p = Bukkit.getPlayerExact(user);if (p != null) return p;for (Player o : Bukkit.getOnlinePlayers()) if (o.getName().equalsIgnoreCase(user)) return o;return null;}private void startReminder() {if (reminderTask != -1) Bukkit.getScheduler().cancelTask(reminderTask);reminderTask = -1;int min = getConfig().getInt("reminder.minutes", 60);String text = getConfig().getString("reminder.text", "");if (!getConfig().getBoolean("reminder.enabled", true) || min <= 0 || text.isEmpty()) return;long t = min * 60L * 20L;reminderTask = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {long day = System.currentTimeMillis() - 24L * 3600L * 1000L;for (Player p : Bukkit.getOnlinePlayers()) {if (pending.getLong("voted." + p.getName().toLowerCase(Locale.ROOT), 0L) < day) sendLink(p, text);}}, t, t);}/** Кликабельная ссылка через tellraw — работает на 1.8–1.21 без библиотек чата. */private void sendLink(Player p, String text) {String url = getConfig().getString("vote-url", "");String t = ChatColor.translateAlternateColorCodes('&', text);if (url.isEmpty() || url.contains("ВАШ-СЕРВЕР")) {p.sendMessage(t);return;}String json = "{\"text\":\"" + esc(t) + "\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"" + esc(url)+ "\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":\"" + esc(ChatColor.YELLOW + url) + "\"}}";Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + p.getName() + " " + json);}@Overridepublic boolean onCommand(CommandSender s, Command cmd, String label, String[] a) {if (cmd.getName().equalsIgnoreCase("vote")) {if (s instanceof Player) {sendLink((Player) s, getConfig().getString("messages.vote", "&eНажми, чтобы проголосовать"));} else {s.sendMessage("Ссылка на голосование: " + getConfig().getString("vote-url", ""));}return true;}if (!s.hasPermission("cdvote.admin")) {s.sendMessage(ChatColor.RED + "Недостаточно прав");return true;}if (a.length >= 1 && a[0].equalsIgnoreCase("reload")) {reloadConfig();startReminder();s.sendMessage(ChatColor.GREEN + "CDVote: конфиг перезагружен");return true;}if (a.length >= 2 && a[0].equalsIgnoreCase("test")) {onVote(a[1], "тест " + s.getName());s.sendMessage(ChatColor.GREEN + "CDVote: тестовый голос за " + a[1] + " — награда выдана (или ждёт входа игрока)");return true;}s.sendMessage(ChatColor.YELLOW + "/cdvote reload" + ChatColor.GRAY + " — перечитать конфиг");s.sendMessage(ChatColor.YELLOW + "/cdvote test <ник>" + ChatColor.GRAY + " — выдать награду как за голос (проверка наград)");return true;}private void savePending() {try {pending.save(pendingFile);} catch (IOException e) {getLogger().warning("Не сохранился pending.yml: " + e.getMessage());}}private static String color(String s) {return ChatColor.translateAlternateColorCodes('&', s);}private static String esc(String s) {return s.replace("\\", "\\\\").replace("\"", "\\\"");}}