diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java index 875c6a4fe..6f824dc86 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java @@ -1,12 +1,6 @@ package com.bencodez.votingplugin; import java.io.File; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; -import java.security.CodeSource; -import java.time.Instant; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.YearMonth; import java.time.ZoneId; @@ -20,15 +14,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.permissions.Permission; @@ -53,15 +44,15 @@ import com.bencodez.advancedcore.api.rewards.injected.RewardInjectValidator; import com.bencodez.advancedcore.api.rewards.injectedrequirement.RequirementInjectConfigurationSection; import com.bencodez.advancedcore.api.user.AdvancedCoreUser; -import com.bencodez.advancedcore.api.user.UserDataFetchMode; import com.bencodez.advancedcore.api.user.UserStartup; import com.bencodez.simpleapi.file.YMLConfig; -import com.bencodez.simpleapi.skull.SkullCache; import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; import com.bencodez.simpleapi.time.ParsedDuration; import com.bencodez.simpleapi.updater.Updater; import com.bencodez.votingplugin.broadcast.BroadcastHandler; import com.bencodez.votingplugin.backendproxy.BackendProxyHandler; +import com.bencodez.votingplugin.backgroundtask.VotingPluginBackgroundTask; +import com.bencodez.votingplugin.backendproxy.BackendProxyRewardRegistrar; import com.bencodez.votingplugin.broadcast.BroadcastSettings; import com.bencodez.votingplugin.commands.CommandLoader; import com.bencodez.votingplugin.commands.executers.CommandAdminVote; @@ -76,10 +67,12 @@ import com.bencodez.votingplugin.config.GUI; import com.bencodez.votingplugin.config.ShopFile; import com.bencodez.votingplugin.config.SpecialRewardsConfig; +import com.bencodez.votingplugin.config.VotingPluginConfigHealth; import com.bencodez.votingplugin.cooldown.CoolDownCheck; import com.bencodez.votingplugin.data.ServerData; import com.bencodez.votingplugin.discord.DiscordHandler; import com.bencodez.votingplugin.listeners.BlockBreak; +import com.bencodez.votingplugin.integration.votifier.VotifierIntegration; import com.bencodez.votingplugin.listeners.PlayerInteract; import com.bencodez.votingplugin.listeners.PlayerJoinEvent; import com.bencodez.votingplugin.listeners.PlayerVoteListener; @@ -90,35 +83,47 @@ import com.bencodez.votingplugin.placeholders.PlaceHolders; import com.bencodez.votingplugin.placeholders.VotingPluginExpansion; import com.bencodez.votingplugin.presets.VoteSitePresetSetupHandler; +import com.bencodez.votingplugin.rewards.VotingPluginRewardRegistrar; import com.bencodez.votingplugin.servicesites.ServiceSiteHandler; import com.bencodez.votingplugin.signs.Signs; import com.bencodez.votingplugin.specialrewards.NameMCLikeCheckerTask; import com.bencodez.votingplugin.specialrewards.SpecialRewards; +import com.bencodez.votingplugin.specialrewards.SpecialRewardsRewardRegistrar; +import com.bencodez.votingplugin.specialrewards.votemilestones.VoteMilestoneRewardRegistrar; import com.bencodez.votingplugin.specialrewards.votemilestones.VoteMilestonesManager; import com.bencodez.votingplugin.specialrewards.voteparty.VoteParty; +import com.bencodez.votingplugin.specialrewards.voteparty.VotePartyRewardRegistrar; import com.bencodez.votingplugin.specialrewards.votestreak.VoteStreakHandler; +import com.bencodez.votingplugin.specialrewards.votestreak.VoteStreakRewardRegistrar; import com.bencodez.votingplugin.test.VoteTester; import com.bencodez.votingplugin.timequeue.TimeQueueHandler; import com.bencodez.votingplugin.topvoter.TopVoter; import com.bencodez.votingplugin.topvoter.TopVoterHandler; import com.bencodez.votingplugin.topvoter.TopVoterPlayer; import com.bencodez.votingplugin.topvoter.TopVoterState; +import com.bencodez.votingplugin.topvoter.TopVoterRewardRegistrar; import com.bencodez.votingplugin.updater.CheckUpdate; import com.bencodez.votingplugin.user.UserManager; import com.bencodez.votingplugin.user.VotingPluginUser; +import com.bencodez.votingplugin.version.VotingPluginVersionInfo; import com.bencodez.votingplugin.votelog.VoteLogMysqlTable; +import com.bencodez.votingplugin.votelog.VoteLogManager; import com.bencodez.votingplugin.votelog.listeners.PlayerPostVoteLoggerListener; import com.bencodez.votingplugin.votelog.listeners.PlayerSpecialRewardLoggerListener; import com.bencodez.votingplugin.votelog.listeners.VoteMilestoneVoteLogListener; import com.bencodez.votingplugin.votelog.listeners.VoteShopPurchaseLoggerListener; import com.bencodez.votingplugin.votereminding.VoteRemindersLegacyMigrator; import com.bencodez.votingplugin.votereminding.VoteRemindersListener; +import com.bencodez.votingplugin.votereminding.VoteReminderRewardRegistrar; import com.bencodez.votingplugin.votereminding.VoteRemindersManager; import com.bencodez.votingplugin.votereminding.store.UserDataVoteReminderCooldownStore; import com.bencodez.votingplugin.voteshop.VoteShopManager; +import com.bencodez.votingplugin.voteshop.VoteShopRewardRegistrar; import com.bencodez.votingplugin.votesites.VoteSite; import com.bencodez.votingplugin.votesites.VoteSiteManager; +import com.bencodez.votingplugin.votesites.VoteSiteRewardRegistrar; import com.bencodez.votingplugin.webhook.VotingPluginWebhooks; +import com.bencodez.votingplugin.webhook.VotingPluginWebhookManager; import com.bencodez.votingplugin.webhook.WebhookRewardEntry; import com.bencodez.votingplugin.webhook.WebhookRewardParser; @@ -182,11 +187,7 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private VoteTester voteTester; - @Getter - private String profile = ""; - @Getter - private String buildNumber = "NOTSET"; @Getter private ServerData serverData; @@ -201,8 +202,6 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private SpecialRewardsConfig specialRewardsConfig; - @Getter - private String time = ""; @Getter private TopVoterHandler topVoterHandler; @@ -210,16 +209,11 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private TopVoterState topVoterState; - @Getter - @Setter - private boolean update = false; @Getter @Setter private Updater updater; - @Getter - private boolean updateStarted = false; @Getter @Setter @@ -238,10 +232,7 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private VoteSiteManager voteSiteManager; - private boolean votifierLoaded = true; - @Getter - private boolean ymlError = false; @Getter private ScheduledExecutorService voteTimer; @@ -255,10 +246,7 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private ServiceSiteHandler serviceSiteHandler; - @Getter - private long lastBackgroundTaskTimeTaken = -1; - private boolean firstTimeLoaded = false; @Getter @Setter @@ -267,6 +255,32 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private DiscordHandler discordHandler; + private VotingPluginBackgroundTask backgroundTask; + private VotingPluginVersionInfo versionInfo; + private VotingPluginConfigHealth configHealth; + private VotifierIntegration votifierIntegration; + private VoteLogManager voteLogManager; + private VotingPluginWebhookManager webhookManager; + + public boolean isUpdate() { + return backgroundTask != null && backgroundTask.isRequested(); + } + + public void setUpdate(boolean update) { + if (backgroundTask == null) { + backgroundTask = new VotingPluginBackgroundTask(this); + } + backgroundTask.setRequested(update); + } + + public boolean isUpdateStarted() { + return backgroundTask != null && backgroundTask.isRunning(); + } + + public long getLastBackgroundTaskTimeTaken() { + return backgroundTask == null ? -1 : backgroundTask.getLastRunTimeSeconds(); + } + public void addDirectlyDefinedRewards(DirectlyDefinedReward directlyDefinedReward) { getRewardHandler().addDirectlyDefined(directlyDefinedReward); } @@ -281,917 +295,173 @@ public void basicBungeeUpdate() { } } - public void checkFirstTimeLoaded() { - if (!firstTimeLoaded) { - - if (getGui().isChestVoteTopUseSkull()) { - int maxToLoad = 200; - for (TopVoter top : getTopVoter().keySet()) { - int num = 1; - Set players = getTopVoter().get(top).keySet(); - for (TopVoterPlayer p : players) { - if (num <= maxToLoad) { - getSkullCacheHandler().addToCache(p.getUuid(), p.getPlayerName()); - } - num++; - } - } - } - } - firstTimeLoaded = true; - } + /** * Check votifier. */ private void checkVotifier() { - try { - Class.forName("com.vexsoftware.votifier.model.VotifierEvent"); - } catch (ClassNotFoundException e) { - if (!bungeeSettings.isUseBungeecoord()) { - plugin.getLogger() - .warning("No VotifierEvent found, install Votifier, NuVotifier, or another Votifier plugin"); - } else { - plugin.debug("No VotifierEvent found, but usebungeecoord enabled"); - } - votifierLoaded = false; + if (votifierIntegration == null) { + votifierIntegration = new VotifierIntegration(this); } + votifierIntegration.detect(); } - private void checkYMLError() { - if (configFile.isFailedToRead() || configVoteSites.isFailedToRead() || specialRewardsConfig.isFailedToRead() - || bungeeSettings.isFailedToRead() || gui.isFailedToRead()) { - ymlError = true; - } else { - ymlError = false; - } - - if (ymlError) { - plugin.getBukkitScheduler().runTaskLaterAsynchronously(plugin, new Runnable() { - @Override - public void run() { - plugin.getLogger().severe("Failed to load a file, check startup log"); - } - }, 1); + private void checkYMLError() { + if (configHealth == null) { + configHealth = new VotingPluginConfigHealth(this); } + configHealth.check(); } - public void loadVoteShopManager() { - voteShopManager = new VoteShopManager(this); - } - - public ArrayList convertSet(Set set) { - return new ArrayList<>(set); - } - - @Override - public FileConfiguration getConfig() { - return configFile.getData(); - } - - public LinkedHashMap> getTopVoter() { - return topVoterState.getTopVoters(); - } - - public void setTopVoter(LinkedHashMap> topVoter) { - topVoterState.setTopVoters(topVoter); - } - - public LinkedHashMap getTopVoter(TopVoter top) { - return topVoterState.getTopVoters(top); - } - - public LinkedHashMap getLastMonthTopVoter() { - return topVoterState.getLastMonthTopVoters(); - } - - public LinkedHashMap> getPreviousMonthsTopVoters() { - return topVoterState.getPreviousMonthsTopVoters(); - } - - public void setPreviousMonthsTopVoters( - LinkedHashMap> previousMonthsTopVoters) { - topVoterState.setPreviousMonthsTopVoters(previousMonthsTopVoters); - } - - public LinkedHashMap> getVoteToday() { - return topVoterState.getVoteToday(); - } - public void setVoteToday(LinkedHashMap> voteToday) { - topVoterState.setVoteToday(voteToday); + public String getProfile() { + return versionInfo == null ? "" : versionInfo.getProfile(); } - /** - * Gets the user. - * - * @param uuid the uuid - * @return the user - */ - @Deprecated - public VotingPluginUser getUser(UUID uuid) { - return getVotingPluginUserManager().getVotingPluginUser(uuid); + public String getBuildNumber() { + return versionInfo == null ? "NOTSET" : versionInfo.getBuildNumber(); } - private YamlConfiguration getVersionFile() { - try { - CodeSource src = this.getClass().getProtectionDomain().getCodeSource(); - if (src != null) { - URL jar = src.getLocation(); - ZipInputStream zip = null; - zip = new ZipInputStream(jar.openStream()); - while (true) { - ZipEntry e = zip.getNextEntry(); - if (e != null) { - String name = e.getName(); - if (name.equals("votingpluginversion.yml")) { - Reader defConfigStream = new InputStreamReader(zip); - if (defConfigStream != null) { - YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream); - defConfigStream.close(); - return defConfig; - } - } - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; + public String getTime() { + return versionInfo == null ? "" : versionInfo.getTime(); } - @Deprecated - public VoteSite getVoteSite(String site, boolean checkEnabled) { - return voteSiteManager.getVoteSite(site, checkEnabled); + public boolean isYmlError() { + return configHealth != null && configHealth.hasYmlError(); } - @Deprecated - public String getVoteSiteName(boolean checkEnabled, String... urls) { - return voteSiteManager.getVoteSiteName(checkEnabled, urls); + public boolean isVotifierLoaded() { + return votifierIntegration == null || votifierIntegration.isLoaded(); } - @Deprecated - public ArrayList getVoteSitesEnabled() { - return voteSiteManager.getVoteSitesEnabled(); + public VoteLogMysqlTable getVoteLogMysqlTable() { + return voteLogManager == null ? null : voteLogManager.getVoteLogMysqlTable(); } - @Deprecated - public String getVoteSiteServiceSite(String name) { - return voteSiteManager.getVoteSiteServiceSite(name); + public VotingPluginWebhooks getWebhooks() { + return webhookManager == null ? null : webhookManager.getWebhooks(); } - @Deprecated - public boolean hasVoteSite(String site) { - return voteSiteManager.hasVoteSite(site); + public void loadVoteShopManager() { + voteShopManager = new VoteShopManager(this); } - @Deprecated - public boolean isVoteSite(String voteSite) { - return voteSiteManager.isVoteSite(voteSite); - } - private void loadBungeeHandler() { - backendProxyHandler = new BackendProxyHandler(this); - backendProxyHandler.load(); - if (getOptions().getServer().equalsIgnoreCase("PleaseSet")) { - getLogger().warning("Bungeecoord is true and server name is not set, bungeecoord features may not work"); - } + @Override + public FileConfiguration getConfig() { + return configFile.getData(); } - @Getter - private VoteStreakHandler voteStreakHandler; - - /** - * Registers a directly editable rewards path from SpecialRewards.yml. - * - * @param rewardPath full configuration path to the rewards section - */ - private void addSpecialRewardsEditorPath(String rewardPath) { - addDirectlyDefinedRewards(new DirectlyDefinedReward(rewardPath) { - - @Override - public void setData(String path, Object value) { - plugin.getSpecialRewardsConfig().getData().set(path, value); - } - - @Override - public void createSection(String key) { - plugin.getSpecialRewardsConfig().getData().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return plugin.getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - plugin.getSpecialRewardsConfig().saveData(); - } - }); + public LinkedHashMap> getTopVoter() { + return topVoterState.getTopVoters(); } - public void loadDirectlyDefined() { - getRewardHandler().getDirectlyDefinedRewards().clear(); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteReminderOptions.Defaults.Rewards") { - - @Override - public void createSection(String key) { - getConfigFile().saveData(); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigFile().getData(); - } - - @Override - public void save() { - getConfigFile().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigFile().setValue(path, value); - } - }); - - ConfigurationSection sec = getConfigFile().getData().getConfigurationSection("VoteReminders"); - - if (sec != null) { - for (String key : sec.getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteReminders." + key + ".Rewards") { - - @Override - public void createSection(String key) { - getConfigFile().saveData(); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigFile().getData(); - } - - @Override - public void save() { - getConfigFile().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigFile().setValue(path, value); - } - }); - } - } - - // vote cooldown ended - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteCoolDownEndedReward") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // any site rewards - addDirectlyDefinedRewards(new DirectlyDefinedReward("AnySiteRewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("EverySiteReward") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - - // login rewards - addDirectlyDefinedRewards(new DirectlyDefinedReward("LoginRewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // logout rewards - addDirectlyDefinedRewards(new DirectlyDefinedReward("LogoutRewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // VoteParty - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteParty.Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy reward handlng for migrator - if (getSpecialRewardsConfig().getData().getConfigurationSection("Cumulative") != null) { - for (String num : getSpecialRewardsConfig().getData().getConfigurationSection("Cumulative") - .getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("Cumulative." + num + ".Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - } - - if (getSpecialRewardsConfig().getData().getConfigurationSection("Milestones") != null) { - for (String num : getSpecialRewardsConfig().getData().getConfigurationSection("Milestones") - .getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("Milestones." + num + ".Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - } - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("AllSites") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("AlmostAllSites") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("FirstVote") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("FirstVoteToday") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // VoteSites - for (VoteSite site : plugin.getVoteSiteManager().getVoteSites()) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + site.getKey() + ".Rewards") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + site.getKey() + ".WaitUntilVoteDelayRewards") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + site.getKey() + ".CoolDownEndRewards") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - } - - ConfigurationSection voteMilestonesSection = plugin.getSpecialRewardsConfig().getData() - .getConfigurationSection("VoteMilestones"); - - if (voteMilestonesSection != null) { - for (String milestoneId : voteMilestonesSection.getKeys(false)) { - addSpecialRewardsEditorPath("VoteMilestones." + milestoneId + ".Rewards"); - } - } - - // VoteStreak rewards - ConfigurationSection voteStreaksSection = plugin.getSpecialRewardsConfig().getData() - .getConfigurationSection("VoteStreaks"); - - if (voteStreaksSection != null) { - for (String streakId : voteStreaksSection.getKeys(false)) { - if ("ProgressGroups".equalsIgnoreCase(streakId)) { - continue; - } - - ConfigurationSection streakSection = voteStreaksSection.getConfigurationSection(streakId); - if (streakSection == null) { - continue; - } - - addSpecialRewardsEditorPath("VoteStreaks." + streakId + ".Rewards"); - } - - ConfigurationSection progressGroupsSection = voteStreaksSection.getConfigurationSection("ProgressGroups"); - - if (progressGroupsSection != null) { - for (String groupId : progressGroupsSection.getKeys(false)) { - ConfigurationSection groupSection = progressGroupsSection.getConfigurationSection(groupId); - if (groupSection == null) { - continue; - } - - addSpecialRewardsEditorPath( - "VoteStreaks.ProgressGroups." + groupId + ".LostRewards"); - - ConfigurationSection milestonesSection = groupSection.getConfigurationSection("Milestones"); - if (milestonesSection == null) { - continue; - } - - for (String milestoneId : milestonesSection.getKeys(false)) { - ConfigurationSection milestoneSection = milestonesSection - .getConfigurationSection(milestoneId); - - if (milestoneSection == null) { - continue; - } - - addSpecialRewardsEditorPath( - "VoteStreaks.ProgressGroups." + groupId - + ".Milestones." + milestoneId + ".Rewards"); - } - } - } - } - - // vote streaks, old way - String[] types = new String[] { "Day", "Week", "Month" }; - for (String type : types) { - for (String str : plugin.getSpecialRewardsConfig().getVoteStreakVotes(type)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteStreak." + type + "." + str + ".Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - } - - for (String path : plugin.getSpecialRewardsConfig().getMonthlyPossibleRewardPlaces()) { - addDirectlyDefinedRewards( - new DirectlyDefinedReward(plugin.getSpecialRewardsConfig().getMonthlyAwardRewardsPath(path)) { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - - for (String path : plugin.getSpecialRewardsConfig().getWeeklyPossibleRewardPlaces()) { - addDirectlyDefinedRewards( - new DirectlyDefinedReward(plugin.getSpecialRewardsConfig().getWeeklyAwardRewardsPath(path)) { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - - for (String path : plugin.getSpecialRewardsConfig().getDailyPossibleRewardPlaces()) { - addDirectlyDefinedRewards( - new DirectlyDefinedReward(plugin.getSpecialRewardsConfig().getDailyAwardRewardsPath(path)) { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } + public void setTopVoter(LinkedHashMap> topVoter) { + topVoterState.setTopVoters(topVoter); + } - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } + public LinkedHashMap getTopVoter(TopVoter top) { + return topVoterState.getTopVoters(top); + } - for (String identifier : plugin.getShopFile().getShopIdentifiers()) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("Shop." + identifier + ".Rewards") { + public LinkedHashMap getLastMonthTopVoter() { + return topVoterState.getLastMonthTopVoters(); + } - @Override - public void createSection(String key) { - getShopFile().createSection(key); - } + public LinkedHashMap> getPreviousMonthsTopVoters() { + return topVoterState.getPreviousMonthsTopVoters(); + } - @Override - public ConfigurationSection getFileData() { - return getShopFile().getData(); - } + public void setPreviousMonthsTopVoters( + LinkedHashMap> previousMonthsTopVoters) { + topVoterState.setPreviousMonthsTopVoters(previousMonthsTopVoters); + } - @Override - public void save() { - getShopFile().saveData(); - } + public LinkedHashMap> getVoteToday() { + return topVoterState.getVoteToday(); + } - @Override - public void setData(String path, Object value) { - getShopFile().setValue(path, value); - } - }); - } + public void setVoteToday(LinkedHashMap> voteToday) { + topVoterState.setVoteToday(voteToday); + } - ConfigurationSection categoriesSection = plugin.getShopFile().getCategoriesSection(); - if (categoriesSection != null) { - for (String categoryShop : categoriesSection.getKeys(false)) { - if (plugin.getShopFile().getCategoryShopSection(categoryShop) != null) { - for (String categoryShopItems : plugin.getShopFile().getCategoryShopSection(categoryShop) - .getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward( - "Categories." + categoryShop + "." + categoryShopItems + ".Rewards") { - - @Override - public void createSection(String key) { - getShopFile().createSection(key); - } + /** + * Gets the user. + * + * @param uuid the uuid + * @return the user + */ + @Deprecated + public VotingPluginUser getUser(UUID uuid) { + return getVotingPluginUserManager().getVotingPluginUser(uuid); + } - @Override - public ConfigurationSection getFileData() { - return getShopFile().getData(); - } - @Override - public void save() { - getShopFile().saveData(); - } - @Override - public void setData(String path, Object value) { - getShopFile().setValue(path, value); - } - }); - } - } - } - } + @Deprecated + public VoteSite getVoteSite(String site, boolean checkEnabled) { + return voteSiteManager.getVoteSite(site, checkEnabled); + } - // NameMC like reward - addDirectlyDefinedRewards(new DirectlyDefinedReward("NameMCLikeReward.Rewards") { + @Deprecated + public String getVoteSiteName(boolean checkEnabled, String... urls) { + return voteSiteManager.getVoteSiteName(checkEnabled, urls); + } - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } + @Deprecated + public ArrayList getVoteSitesEnabled() { + return voteSiteManager.getVoteSitesEnabled(); + } - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } + @Deprecated + public String getVoteSiteServiceSite(String name) { + return voteSiteManager.getVoteSiteServiceSite(name); + } - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } + @Deprecated + public boolean hasVoteSite(String site) { + return voteSiteManager.hasVoteSite(site); + } - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); + @Deprecated + public boolean isVoteSite(String voteSite) { + return voteSiteManager.isVoteSite(voteSite); + } - addDirectlyDefinedRewards(new DirectlyDefinedReward("BungeeVotePartyRewards") { + private void loadBungeeHandler() { + backendProxyHandler = new BackendProxyHandler(this); + backendProxyHandler.load(); - @Override - public void createSection(String key) { - getBungeeSettings().createSection(key); - } + if (getOptions().getServer().equalsIgnoreCase("PleaseSet")) { + getLogger().warning("Bungeecoord is true and server name is not set, bungeecoord features may not work"); + } + } - @Override - public ConfigurationSection getFileData() { - return getBungeeSettings().getData(); - } + @Getter + private VoteStreakHandler voteStreakHandler; - @Override - public void save() { - getBungeeSettings().saveData(); - } + /** + * Registers a directly editable rewards path from SpecialRewards.yml. + * + * @param rewardPath full configuration path to the rewards section + */ + public void loadDirectlyDefined() { + getRewardHandler().getDirectlyDefinedRewards().clear(); - @Override - public void setData(String path, Object value) { - getBungeeSettings().setValue(path, value); - } - }); + VoteReminderRewardRegistrar.register(this); + SpecialRewardsRewardRegistrar.register(this); + VoteSiteRewardRegistrar.register(this); + VotePartyRewardRegistrar.register(this); + VoteMilestoneRewardRegistrar.register(this); + VoteStreakRewardRegistrar.register(this); + TopVoterRewardRegistrar.register(this); + VoteShopRewardRegistrar.register(this); + BackendProxyRewardRegistrar.register(this); getRewardHandler().checkDirectlyDefined(); - } private void loadTimer() { @@ -1228,14 +498,11 @@ public void run() { } private void loadVersionFile() { - YamlConfiguration conf = getVersionFile(); - if (conf != null) { - time = conf.getString("time", ""); - profile = conf.getString("profile", ""); - buildNumber = conf.getString("buildnumber", "NOTSET"); - } + versionInfo = new VotingPluginVersionInfo(this); + versionInfo.load(); } + public void loadVoteSites() { configVoteSites.setup(); voteSiteManager = new VoteSiteManager(this); @@ -1388,8 +655,8 @@ public Object getObject(OfflinePlayer player) { // load vote logging if enabled loadVoteLoggingMySQL(); - webhooks = new VotingPluginWebhooks(this); - webhooks.reload(getConfig().getConfigurationSection("Webhooks")); + webhookManager = new VotingPluginWebhookManager(this); + webhookManager.load(); if (getSpecialRewardsConfig().isNameMCLikeRewardEnabled()) { nameMCLikeCheckerTask = new NameMCLikeCheckerTask(this); @@ -1399,138 +666,12 @@ public Object getObject(OfflinePlayer player) { nameMCLikeCheckerTask.runTaskTimerAsynchronously(this, 20L, interval); } - // Add rewards - getRewardHandler().addInjectedReward(new RewardInjectInt("Points", 0) { - - @Override - public String onRewardRequest(Reward reward, com.bencodez.advancedcore.api.user.AdvancedCoreUser user, - int num, HashMap placeholders) { - VotingPluginUser vpUser = getVotingPluginUserManager().getVotingPluginUser(user); - String str = "" + vpUser.addPoints(num); - debug("Setting points to " + str); - return str; - } - }.synchronize().asPlaceholder("newpoints").addEditButton( - new EditGUIButton(new ItemBuilder(Material.PAPER), new EditGUIValueNumber("Points", null) { - - @Override - public void setValue(Player player, Number value) { - RewardEditData reward = (RewardEditData) getInv().getData("Reward"); - reward.setValue("Points", value.intValue()); - } - }.addLore("Give player voting points"))).validator(new RewardInjectValidator() { - - @Override - public void onValidate(Reward reward, RewardInject inject, ConfigurationSection data) { - if (data.getInt(inject.getPath(), -1) == 0) { - warning(reward, inject, "Points can not be 0"); - } - } - })); - - getRewardHandler().addInjectedReward(new RewardInjectConfigurationSection("VoteBossBar") { - - @Override - public String onRewardRequested(Reward arg0, com.bencodez.advancedcore.api.user.AdvancedCoreUser user, - ConfigurationSection section, HashMap placeholders) { - if (section.getBoolean("Enabled")) { - user.sendBossBar( - PlaceholderUtils.replacePlaceHolder(section.getString("Message", ""), placeholders), - section.getString("Color", "BLUE"), section.getString("Style", "SOLID"), - (double) getVotingPluginUserManager().getVotingPluginUser(user).getSitesVotedOn() - / plugin.getVoteSites().size(), - section.getInt("Delay", 30)); - } - return null; - } - }); - - getRewardHandler().addInjectedReward(new RewardInjectConfigurationSection("WebhookReward") { - - @Override - public String onRewardRequested(Reward reward, AdvancedCoreUser user, ConfigurationSection section, - HashMap placeholders) { - - if (section == null || webhooks == null) { - return null; - } - - // Parse list from inside this section - List entries = WebhookRewardParser.parse(section); - - if (entries == null || entries.isEmpty()) { - return null; - } - - webhooks.createExecutor().executeAll(entries, (input) -> { - return PlaceholderUtils.replacePlaceHolder(input, placeholders); - }); - - return null; - } - }); - - getRewardHandler().addInjectedRequirements(new RequirementInjectConfigurationSection("VoteTotal") { - - @Override - public boolean onRequirementsRequested(Reward reward, AdvancedCoreUser acUser, ConfigurationSection section, - RewardOptions rewardOptions) { - boolean atleast = section.getBoolean("AtleastMode", false); - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(acUser); - - for (TopVoter top : TopVoter.values()) { - int required = section.getInt(top.toString(), -1); - if (required >= 0) { - int total = user.getTotal(top); - if (atleast) { - if (total < required) { - debug("Failed requirement " + top.toString() + " " + total + "/" + required); - return false; - } - } else { - if (total != required) { - debug("Failed requirement " + top.toString() + " " + total + "!=" + required); - return false; - } - } - } - } - - int pointsRequired = section.getInt("Points", -1); - if (pointsRequired >= 0) { - int points = user.getPoints(); - if (atleast) { - if (points < pointsRequired) { - debug("Failed requirement points " + points + "/" + pointsRequired); - return false; - } - } else { - if (points != pointsRequired) { - debug("Failed requirement points " + points + "!=" + pointsRequired); - return false; - } - } - } - - return true; - } - }); - - for (final TopVoter top : TopVoter.values()) { - getRewardHandler().addPlaceholder(new RewardPlaceholderHandle("Total_" + top.toString()) { - - @Override - public String getValue(Reward reward, com.bencodez.advancedcore.api.user.AdvancedCoreUser user) { - VotingPluginUser vUser = getVotingPluginUserManager().getVotingPluginUser(user); - return "" + vUser.getTotal(top); - } - }); - } + VotingPluginRewardRegistrar.register(this); plugin.getLogger().info("Enabled VotingPlugin " + plugin.getDescription().getVersion()); if (plugin.getDescription().getVersion().contains("SNAPSHOT")) { plugin.getLogger().info( - "Using dev build, this is not a stable build, use at your own risk. Build number: " + buildNumber); + "Using dev build, this is not a stable build, use at your own risk. Build number: " + getBuildNumber()); } boolean hasRewards = getRewardHandler().hasRewards(getConfigVoteSites().getData(), @@ -1630,11 +771,6 @@ private void migrateVoteBroadcast(Config configFile) { configFile.saveData(); } - /* - * (non-Javadoc) - * - * @see org.bukkit.plugin.java.JavaPlugin#onEnable() - */ @Override public void onPreLoad() { plugin = this; @@ -1673,11 +809,6 @@ public void onFinish() { } - /* - * (non-Javadoc) - * - * @see org.bukkit.plugin.java.JavaPlugin#onDisable() - */ @Override public void onUnLoad() { if (getBackendProxyHandler() != null) { @@ -1687,9 +818,8 @@ public void onUnLoad() { debug(e); } } - if (webhooks != null) { - webhooks.shutdown(); - webhooks = null; + if (webhookManager != null) { + webhookManager.shutdown(); } voteTimer.shutdown(); try { @@ -1760,7 +890,7 @@ private void registerEvents() { PluginManager pm = getServer().getPluginManager(); pm.registerEvents(new PlayerJoinEvent(this), this); - if (votifierLoaded) { + if (isVotifierLoaded()) { pm.registerEvents(new VotiferEvent(this), this); } pm.registerEvents(new PlayerVoteListener(this), this); @@ -1851,7 +981,7 @@ private void reloadPlugin(boolean userStorage) { voteRemindersManager.reload(); } - webhooks.reload(getConfig().getConfigurationSection("Webhooks")); + if (webhookManager != null) { webhookManager.reload(); } coolDownCheck.checkEnabled(); @@ -1910,253 +1040,26 @@ private void setupFiles() { } public synchronized void update() { - if (!(update || configFile.isAlwaysUpdate())) { - return; - } - if (plugin == null) { - return; - } - if (updateStarted) { - return; - } - if (configFile.isUpdateWithPlayersOnlineOnly() && Bukkit.getOnlinePlayers().isEmpty()) { - return; - } - - updateStarted = true; - update = false; - - synchronized (plugin) { - try { - if (plugin == null || !plugin.isEnabled()) { - return; - } - - getUserManager().getDataManager().clearCacheBasic(); - SkullCache.flushWeek(); - - plugin.debug("Starting background task, current cached users: " - + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); - - boolean extraBackgroundUpdate = configFile.isExtraBackgroundUpdate(); - long startTime = System.currentTimeMillis(); - - LinkedHashMap> voteToday = new LinkedHashMap<>(); - LinkedHashMap> tempTopVoter = new LinkedHashMap<>(); - - ArrayList topVotersToCheck = new ArrayList<>(); - for (TopVoter top : TopVoter.values()) { - if (plugin == null) { - return; - } - if (plugin.getConfigFile().getLoadTopVoter(top)) { - topVotersToCheck.add(top); - tempTopVoter.put(top, new LinkedHashMap<>()); - } - } - - boolean topVoterIgnorePermissionUse = plugin.getConfigFile().isTopVoterIgnorePermission(); - ArrayList blackList = plugin.getConfigFile().getBlackList(); - - // Compute "today" bounds once (avoid LocalDateTime.now() + MiscUtils - // conversions in tight loops) - final ZoneId zone = ZoneId.systemDefault(); - final LocalDate today = LocalDate.now(zone); - final long startOfDayMs = today.atStartOfDay(zone).toInstant().toEpochMilli(); - final long startOfNextDayMs = today.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli(); - - final long afterSetup = System.currentTimeMillis(); - - // STREAM all users (MYSQL/SQLITE/FLAT) via the new shortcut method - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - if (plugin == null || !plugin.isEnabled()) { - return; - } - - VotingPluginUser user = getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - try { - if (!user.isBanned() && !blackList.contains(user.getPlayerName())) { - - if (!topVoterIgnorePermissionUse || !user.isTopVoterIgnore()) { - TopVoterPlayer tvp = user.getTopVoterPlayer(); - for (TopVoter top : topVotersToCheck) { - int total = user.getTotal(top); - if (total > 0) { - tempTopVoter.get(top).put(tvp, total); - } - } - } - - // Only allocate if we actually find a "today" vote - HashMap times = null; - - for (Entry entry : user.getLastVotes().entrySet()) { - VoteSite site = entry.getKey(); - if (!site.isEnabled() || site.isHidden()) { - continue; - } - - long time = entry.getValue(); - if (time >= startOfDayMs && time < startOfNextDayMs) { - if (times == null) { - times = new HashMap<>(); - } - times.put(site, LocalDateTime.ofInstant(Instant.ofEpochMilli(time), zone)); - } - } - - if (times != null && !times.isEmpty()) { - voteToday.put(user.getTopVoterPlayer(), times); - } - } - - if (extraBackgroundUpdate && user.isOnline()) { - user.offVote(); - } - - if (!plugin.getPlaceholders().getCacheLevel().onlineOnly() || user.isOnline()) { - plugin.getPlaceholders().onUpdate(user, false); - } - } finally { - user.clearTempCache(); - } - - }, (count) -> { - long time1 = (System.currentTimeMillis() - afterSetup) / 1000; - plugin.debug("Finished getting player data in " + time1 + " seconds, " + count + " users, " - + plugin.getStorageType()); - }); - - // Final processing (runs once after ALL users processed) - topVoterHandler.updateTopVoters(tempTopVoter); - placeholders.onUpdate(); - setVoteToday(voteToday); - serverData.updateValues(); - getSigns().updateSigns(); - - checkFirstTimeLoaded(); - - if (plugin.getConfigFile().isDiscordSRVEnabled() && getDiscordHandler() != null) { - for (TopVoter top : TopVoter.values()) { - if (!plugin.getConfigFile().isDiscordSRVTopVoterNewMessageOnUpdate(top)) { - getDiscordHandler().updateTopVoterMessageId(top); - } - } - } - - plugin.getUserManager().getDataManager().clearNonNeededCachedUsers(); - plugin.extraDebug("Current cached users: " - + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); - - update = false; - - long totalTime = (System.currentTimeMillis() - startTime) / 1000; - lastBackgroundTaskTimeTaken = totalTime; - plugin.debug("Background task finished. Total time: " + totalTime + " seconds"); - - } catch (Exception ex) { - if (plugin != null) { - plugin.getLogger().info("Looks like something went wrong"); - } - ex.printStackTrace(); - } finally { - updateStarted = false; - } + if (backgroundTask == null) { + backgroundTask = new VotingPluginBackgroundTask(this); } + backgroundTask.run(); } - @Getter - private VoteLogMysqlTable voteLogMysqlTable; + @Getter private VoteMilestonesManager voteMilestonesManager; - @Getter - private VotingPluginWebhooks webhooks; public void loadVoteLoggingMySQL() { - if (getConfigFile().isVoteLoggingEnabled()) { - if (getConfigFile().isVoteLoggingUseMainMySQL()) { - voteLogMysqlTable = new VoteLogMysqlTable("votingplugin_votelog", getMysql().getMysql(), - new MysqlConfigSpigot(getConfigFile().getVoteLoggingSection()), - getOptions().getDebug().isDebug()) { - - @Override - public void logSevere(String string) { - plugin.getLogger().severe(string); - } - - @Override - public void logInfo(String string) { - plugin.getLogger().info(string); - } - - @Override - public String getServerName() { - if (plugin.getBungeeSettings().isUseBungeecoord()) { - return plugin.getBungeeSettings().getServer(); - } else { - return ""; - } - } - - @Override - public void debug(Throwable t) { - if (getOptions().getDebug().isDebug()) { - plugin.debug(t); - } - } - }; - } else { - voteLogMysqlTable = new VoteLogMysqlTable("votingplugin_votelog", - new MysqlConfigSpigot(getConfigFile().getVoteLoggingSection()), - getOptions().getDebug().isDebug()) { - - @Override - public void logSevere(String string) { - plugin.getLogger().severe(string); - } - - @Override - public void logInfo(String string) { - plugin.getLogger().info(string); - } - - @Override - public void debug(Throwable t) { - if (getOptions().getDebug().isDebug()) { - plugin.debug(t); - } - } - - @Override - public String getServerName() { - if (plugin.getBungeeSettings().isUseBungeecoord()) { - return plugin.getBungeeSettings().getServer(); - } else { - return ""; - } - } - }; - } - - getTimer().scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - voteLogMysqlTable.purgeOlderThanDays(getConfigFile().getVoteLoggingPurgeDays(), 100); - } - }, 60, 60 * 60, TimeUnit.SECONDS); // Purge old logs every hour - - debug("Vote logging MySQL enabled"); - } else { - debug("Vote logging MySQL disabled"); + if (voteLogManager == null) { + voteLogManager = new VoteLogManager(this); } + voteLogManager.load(); } + public void updateAdvancedCoreHook() { getJavascriptEngine().put("VotingPlugin", this); allowDownloadingFromSpigot(15358); diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java index 00c4d1435..f167a8f38 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java @@ -1,868 +1,179 @@ -// File: com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java package com.bencodez.votingplugin.backendproxy; -import java.io.File; -import java.sql.SQLException; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.bukkit.event.Listener; -import org.eclipse.paho.client.mqttv3.MqttException; -import com.bencodez.advancedcore.api.misc.MiscUtils; -import com.bencodez.advancedcore.api.rewards.RewardBuilder; import com.bencodez.advancedcore.api.time.TimeType; -import com.bencodez.advancedcore.api.user.UserStorage; -import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalDataHandler; -import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalMySQL; -import com.bencodez.simpleapi.encryption.EncryptionHandler; import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; -import com.bencodez.simpleapi.servercomm.global.GlobalMessageListener; import com.bencodez.simpleapi.servercomm.mqtt.MqttHandler; -import com.bencodez.simpleapi.servercomm.mqtt.MqttServerComm; import com.bencodez.simpleapi.servercomm.mysql.MySqlMessenger; -import com.bencodez.simpleapi.servercomm.pluginmessage.PluginMessageHandler; import com.bencodez.simpleapi.servercomm.redis.RedisHandler; -import com.bencodez.simpleapi.servercomm.redis.RedisListener; import com.bencodez.simpleapi.servercomm.sockets.ClientHandler; import com.bencodez.simpleapi.servercomm.sockets.SocketHandler; -import com.bencodez.simpleapi.servercomm.sockets.SocketReceiver; import com.bencodez.simpleapi.sql.data.DataValue; -import com.bencodez.simpleapi.sql.data.DataValueBoolean; -import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.backendproxy.cache.ProcessedVoteCache; +import com.bencodez.votingplugin.backendproxy.global.BackendGlobalDataSync; +import com.bencodez.votingplugin.backendproxy.messaging.BackendProxyMessageRouter; import com.bencodez.votingplugin.backendproxy.presence.BackendPresenceManager; +import com.bencodez.votingplugin.backendproxy.transport.BackendProxyTransportManager; +import com.bencodez.votingplugin.backendproxy.voteparty.BackendVotePartySync; import com.bencodez.votingplugin.proxy.BungeeMethod; -import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot; -import com.bencodez.votingplugin.proxy.VotingPluginWire; -import com.bencodez.votingplugin.user.VotingPluginUser; -import com.bencodez.votingplugin.util.ServiceSiteValidator; -import com.bencodez.votingplugin.votesites.VoteSite; import lombok.Getter; /** - * Handler for Bungee/proxy server integration. + * Coordinates backend/proxy communication components. */ public class BackendProxyHandler implements Listener { - private static final long PROCESSED_VOTE_TTL_MILLIS = TimeUnit.MINUTES.toMillis(30); + private final VotingPluginMain plugin; + private final ProcessedVoteCache processedVoteCache = new ProcessedVoteCache(); + private final BackendProxyTransportManager transportManager; + private final BackendGlobalDataSync globalDataSync; - @Getter - private final ConcurrentHashMap processedWireVotes = new ConcurrentHashMap<>(); private BackendPresenceManager presenceManager; - - @Getter - private ClientHandler clientHandler; - - private EncryptionHandler encryptionHandler; + private BackendVotePartySync votePartySync; + private BackendProxyMessageRouter messageRouter; @Getter private BungeeMethod method; - - private VotingPluginMain plugin; - - @Getter - private int bungeeVotePartyCurrent = -2; - - @Getter - private int bungeeVotePartyRequired = -2; - - @Getter - private SocketHandler socketHandler; - - private GlobalDataHandler globalDataHandler; - - @Getter - private ScheduledExecutorService timer; - - @Getter - private RedisHandler redisHandler; - @Getter private GlobalMessageHandler globalMessageHandler; - private Thread redisThread; - - @Getter - private MySqlMessenger backendMysqlMessenger; - - @Getter - private MqttHandler mqttHandler; - - /** - * Constructs a new BackendProxyHandler. - * - * @param plugin the main plugin instance - */ public BackendProxyHandler(VotingPluginMain plugin) { this.plugin = plugin; + transportManager = new BackendProxyTransportManager(plugin); + globalDataSync = new BackendGlobalDataSync(plugin, this::sendEnvelope); } /** - * Checks and processes global data from the global data handler. - */ - public void checkGlobalData() { - HashMap data = globalDataHandler.getExact(plugin.getBungeeSettings().getServer()); - - if (data.containsKey("ForceUpdate")) { - boolean b = checkGlobalDataTimeValue(data.get("ForceUpdate")); - if (b) { - if (plugin.getStorageType().equals(UserStorage.MYSQL)) { - plugin.getMysql().clearCacheBasic(); - } - plugin.getUserManager().getDataManager().clearCache(); - plugin.setUpdate(true); - plugin.update(); - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "ForceUpdate", false); - } - } - - boolean forceUpdate = false; - - if (checkGlobalDataTime(TimeType.MONTH, data)) { - forceUpdate = true; - } - if (checkGlobalDataTime(TimeType.WEEK, data)) { - forceUpdate = true; - } - if (checkGlobalDataTime(TimeType.DAY, data)) { - forceUpdate = true; - } - - if (forceUpdate) { - HashMap dataToSet = new HashMap<>(); - dataToSet.put("FinishedProcessing", new DataValueBoolean(true)); - dataToSet.put("Processing", new DataValueBoolean(false)); - globalDataHandler.setData(plugin.getBungeeSettings().getServer(), dataToSet); - } - } - - /** - * Checks global data for a time type change. - * - * @param type the time type to check - * @param data the global data map - * @return true if currently processing a time change - */ - public boolean checkGlobalDataTime(TimeType type, HashMap data) { - boolean isProcessing = false; - if (data.containsKey(type.toString())) { - - DataValue value = data.get(type.toString()); - boolean b = checkGlobalDataTimeValue(value); - if (b) { - long lastUpdated = Long.valueOf(data.get("LastUpdated").getString()).longValue(); - plugin.debug("LastUpdated: " + lastUpdated); - if (LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli() - lastUpdated > 1000 * 60 * 60 - * 2) { - plugin.getLogger().warning("Ignoring bungee time change since it was more than 2 hours ago"); - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); - return false; - } - - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "Processing", true); - isProcessing = true; - - plugin.debug("Detected time change from bungee: " + type.toString()); - plugin.getTimeChecker().forceChanged(type, false, true, true); - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); - - HashMap fields = new HashMap<>(); - fields.put("server", plugin.getBungeeSettings().getServer()); - sendSubChannel("TimeChangeFinished", fields); - } - } - return isProcessing; - } - - /** - * Checks and extracts the boolean value from a DataValue. - * - * @param data the data value - * @return the boolean value - */ - public boolean checkGlobalDataTimeValue(DataValue data) { - if (data.isBoolean()) { - return data.getBoolean(); - } - return Boolean.valueOf(data.getString()); - } - - /** - * Closes and cleans up all handlers and connections. - */ - public void close() { - if (presenceManager != null) { - presenceManager.stop(); - } - - if (backendMysqlMessenger != null) { - backendMysqlMessenger.shutdown(); - } - - if (socketHandler != null) { - socketHandler.closeConnection(); - } - if (clientHandler != null) { - clientHandler.stopConnection(); - } - plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent); - plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired); - if (globalDataHandler != null) { - globalDataHandler.getGlobalMysql().close(); - } - } - - /** - * Loads and initializes the bungee handler with the configured method. + * Loads the configured backend/proxy communication components. */ public void load() { plugin.debug("Loading backend proxy handler"); - method = BungeeMethod.getByName(plugin.getBungeeSettings().getBungeeMethod()); - plugin.getLogger().info("Using BungeeMethod: " + method.toString()); - loadGlobalMysql(); - + globalDataSync.load(); globalMessageHandler = new GlobalMessageHandler() { @Override public void sendMessage(JsonEnvelope envelope) { - if (method.equals(BungeeMethod.MYSQL)) { - try { - backendMysqlMessenger.sendToProxy(envelope); - } catch (SQLException e) { - e.printStackTrace(); - } - } else if (method.equals(BungeeMethod.PLUGINMESSAGING)) { - plugin.getPluginMessaging().sendEnvelope(envelope); - } else if (method.equals(BungeeMethod.SOCKETS)) { - sendEnvelopeSocket(envelope); - } else if (method.equals(BungeeMethod.REDIS)) { - redisHandler.publishEnvelope(plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin", - envelope); - } else if (method.equals(BungeeMethod.MQTT)) { - try { - mqttHandler.publishEnvelope( - plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/proxy", envelope); - } catch (Exception e) { - e.printStackTrace(); - } - } + transportManager.send(envelope); } }; presenceManager = new BackendPresenceManager(plugin, method, globalMessageHandler); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE) { - @Override - public void onReceive(JsonEnvelope msg) { - handleWireVote(msg); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_ONLINE) { - @Override - public void onReceive(JsonEnvelope msg) { - handleWireVote(msg); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_DELAY_REJECTED) { - @Override - public void onReceive(JsonEnvelope msg) { - handleWireVoteDelayRejected(msg); - } - }); - - if (method.supportsBackendPresence()) { - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_RESYNC_REQUEST) { - @Override - public void onReceive(JsonEnvelope msg) { - presenceManager.handleResyncRequest(msg); - } - }); - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_SNAPSHOT_REQUEST) { - @Override - public void onReceive(JsonEnvelope msg) { - presenceManager.handleSnapshotRequest(msg); - } - }); - } - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_UPDATE) { - @Override - public void onReceive(JsonEnvelope msg) { - VotingPluginWire.VoteUpdate v = VotingPluginWire.readVoteUpdate(msg); - - String playerUuid = v.uuid; - if (playerUuid == null || playerUuid.isEmpty()) { - return; - } - - plugin.debug("pluginmessaging voteupdate received for " + playerUuid + ": " + v.votePartyCurrent + "/" - + v.votePartyRequired + " on " + v.service); - - if (v.votePartyCurrent >= 0 || bungeeVotePartyCurrent == -2) { - bungeeVotePartyCurrent = v.votePartyCurrent; - } - if (v.votePartyRequired >= 0 || bungeeVotePartyRequired == -2) { - bungeeVotePartyRequired = v.votePartyRequired; - } - plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent); - plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired); - - VotingPluginUser user = plugin.getVotingPluginUserManager() - .getVotingPluginUser(UUID.fromString(playerUuid)); - user.cache(); - user.offVote(); - - String service = v.service; - long time = v.time; - - if (service != null && !service.isEmpty() && time > 0) { - user.setTime(plugin.getVoteSiteManager().getVoteSite(service, true), time); - } else if (service != null && !service.isEmpty() && time <= 0 - && plugin.getBungeeSettings().isBungeeDebug()) { - plugin.debug("Invalid last vote time received from bungee: " + time); - } - - plugin.setUpdate(true); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_BUNGEE_TIME_CHANGE) { - @Override - public void onReceive(JsonEnvelope msg) { - checkGlobalData(); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_BROADCAST) { - @Override - public void onReceive(JsonEnvelope msg) { - Map f = msg.getFields(); - - final String uuidStr = nvl(f.get(VotingPluginWire.K_UUID)); - final String playerNameRaw = nvl(f.get(VotingPluginWire.K_PLAYER)); - final String service = nvl(f.get(VotingPluginWire.K_SERVICE)); - - if (uuidStr.isEmpty() || service.isEmpty()) { - return; - } - - UUID javaUuid; - try { - javaUuid = UUID.fromString(uuidStr); - } catch (Exception e) { - plugin.getLogger().warning("Invalid UUID in VoteBroadcast: " + uuidStr); - return; - } - - @SuppressWarnings("unused") - final long time = readLongSafe(f.get(VotingPluginWire.K_TIME), 0L); - final String totalsRaw = nvl(f.get(VotingPluginWire.K_TOTALS)); - final VoteTotalsSnapshot totals = totalsRaw.isEmpty() ? null : VoteTotalsSnapshot.parseStorage(totalsRaw); - - VoteSite voteSite = plugin.getVoteSiteManager() - .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, service), true); - - if (voteSite == null) { - plugin.getLogger().warning("No voting site with the service site: '" + service + "'"); - return; - } - if (!voteSite.isEnabled()) { - plugin.debug("Votesite: " + voteSite.getKey() + " is not enabled (VoteBroadcast)"); - return; - } - - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, playerNameRaw); - user.cache(); - user.updateName(true); - - if (plugin.getBroadcastHandler() == null) { - return; - } - - if (user.isVanished()) { - plugin.debug("Not broadcasting vote for vanished user: " + user.getPlayerName()); - return; - } - - final boolean online = f.containsKey(VotingPluginWire.K_WAS_ONLINE) - ? Boolean.parseBoolean(f.get(VotingPluginWire.K_WAS_ONLINE)) - : user.isOnline(); - plugin.getBroadcastHandler().broadcastVote(user.getJavaUUID(), user.getPlayerName(), - voteSite.getDisplayName(), online, totals); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("Status") { - @Override - public void onReceive(JsonEnvelope msg) { - String server = nvl(msg.getFields().get("server")); - HashMap out = new HashMap<>(); - out.put("server", server); - sendSubChannel("statusokay", out); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("ServerName") { - @Override - public void onReceive(JsonEnvelope msg) { - String server = nvl(msg.getFields().get("server")); - if (!plugin.getOptions().getServer().equals(server)) { - plugin.getLogger().warning("Server name doesn't match in BungeeSettings.yml, should be " + server); - } - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBungee") { - @Override - public void onReceive(JsonEnvelope msg) { - for (final String cmd : plugin.getBungeeSettings().getBungeeVotePartyGlobalCommands()) { - plugin.getBukkitScheduler().runTask(plugin, new Runnable() { - @Override - public void run() { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), cmd); - } - }); - } - for (Player p : Bukkit.getOnlinePlayers()) { - new RewardBuilder(plugin.getBungeeSettings().getData(), "BungeeVotePartyRewards").send(p); - } - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBroadcast") { - @Override - public void onReceive(JsonEnvelope msg) { - String broadcast = nvl(msg.getFields().get("broadcast")); - MiscUtils.getInstance().broadcast(broadcast); - } - }); - - if (method.equals(BungeeMethod.MYSQL)) { - plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); - - try { - backendMysqlMessenger = new MySqlMessenger("VotingPlugin", - plugin.getMysql().getMysql().getConnectionManager().getDataSource(), - MySqlMessenger.Mode.BACKEND, plugin.getOptions().getServer(), msg -> { - if (plugin.getBungeeSettings().isBungeeDebug()) { - plugin.debug("Proxy sent envelope: " + msg.envelope.getSubChannel() + " " - + msg.envelope.getFields()); - } - globalMessageHandler.onMessage(msg.envelope); - }); - } catch (SQLException e) { - e.printStackTrace(); - } - } else if (method.equals(BungeeMethod.REDIS)) { - redisHandler = new RedisHandler(plugin.getBungeeSettings().getRedisHost(), - plugin.getBungeeSettings().getRedisPort(), plugin.getBungeeSettings().getRedisUsername(), - plugin.getBungeeSettings().getRedisPassword(), plugin.getBungeeSettings().getRedisdbindex()) { - - @Override - public void debug(String message) { - if (plugin.getBungeeSettings().isBungeeDebug()) { - plugin.debug(message); - } - } - }; - - redisThread = new Thread(new Runnable() { - @Override - public void run() { - if (plugin.isEnabled()) { - RedisListener listener = redisHandler.createEnvelopeListener( - plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin_" - + plugin.getBungeeSettings().getServer(), - (ch, env) -> globalMessageHandler.onMessage(env)); - redisHandler.loadListener(listener); - } - } - }); - redisThread.start(); - - } else if (method.equals(BungeeMethod.PLUGINMESSAGING)) { - plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); - - if (plugin.getBungeeSettings().isPluginMessageEncryption()) { - encryptionHandler = new EncryptionHandler(plugin.getName(), new File(plugin.getDataFolder(), "secretkey.key")); - plugin.getPluginMessaging().setEncryptionHandler(encryptionHandler); - } - - plugin.getPluginMessaging().setDebug(plugin.getBungeeSettings().isBungeeDebug()); - - plugin.getPluginMessaging().add(new PluginMessageHandler() { - @Override - public void onReceive(JsonEnvelope envelope) { - globalMessageHandler.onMessage(envelope); - } - }); - - } else if (method.equals(BungeeMethod.SOCKETS)) { - encryptionHandler = new EncryptionHandler(plugin.getName(), new File(plugin.getDataFolder(), "secretkey.key")); - - clientHandler = new ClientHandler(plugin.getBungeeSettings().getBungeeServerHost(), - plugin.getBungeeSettings().getBungeeServerPort(), encryptionHandler, - plugin.getBungeeSettings().isBungeeDebug()); - - socketHandler = new SocketHandler("vp-socket", plugin.getBungeeSettings().getSpigotServerHost(), - plugin.getBungeeSettings().getSpigotServerPort(), encryptionHandler, - plugin.getBungeeSettings().isBungeeDebug()) { - - @Override - public void log(String str) { - plugin.getLogger().info(str); - } - }; - - socketHandler.add(new SocketReceiver() { - @Override - public void onReceiveEnvelope(JsonEnvelope envelope) { - globalMessageHandler.onMessage(envelope); - } - }); - - } else if (method.equals(BungeeMethod.MQTT)) { - try { - String id = plugin.getBungeeSettings().getMqttClientID(); - if (id.isEmpty()) { - id = plugin.getOptions().getServer(); - } - mqttHandler = new MqttHandler(new MqttServerComm(id, plugin.getBungeeSettings().getMqttBrokerURL(), - plugin.getBungeeSettings().getMqttUsername(), plugin.getBungeeSettings().getMqttPassword()), 2); - - mqttHandler.subscribeEnvelopes(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/" - + plugin.getOptions().getServer(), (topic, envelope) -> globalMessageHandler.onMessage(envelope)); - - } catch (MqttException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - bungeeVotePartyCurrent = plugin.getServerData().getBungeeVotePartyCurrent(); - bungeeVotePartyRequired = plugin.getServerData().getBungeeVotePartyRequired(); + votePartySync = new BackendVotePartySync(plugin); + messageRouter = new BackendProxyMessageRouter(plugin, presenceManager, globalDataSync, votePartySync, + processedVoteCache); + messageRouter.register(globalMessageHandler, method); + transportManager.start(method, globalMessageHandler); if (plugin.getOptions().getServer().equalsIgnoreCase("pleaseset")) { plugin.getLogger().warning("Server name for bungee voting is not set, please set it"); } - presenceManager.start(); } /** - * Announces a player login to the configured proxy transport. + * Closes backend/proxy components and persists cached proxy state. */ + public void close() { + if (presenceManager != null) { + presenceManager.stop(); + } + transportManager.close(); + if (votePartySync != null) { + votePartySync.persist(); + } + globalDataSync.close(); + } + public void playerOnline(String playerName, String uuid) { - presenceManager.playerOnline(playerName, uuid); + if (presenceManager != null) { + presenceManager.playerOnline(playerName, uuid); + } } - /** - * Announces a player disconnect when backend presence is supported. - */ public void playerOffline(String playerName) { - presenceManager.playerOffline(playerName); + if (presenceManager != null) { + presenceManager.playerOffline(playerName); + } } - /** - * Restarts presence reporting when the configured backend identity changes. - */ public void reloadPresenceReporting() { if (presenceManager != null) { presenceManager.reload(); } } - /** - * Stops presence reporting without closing the global-message handler. - */ public void disablePresenceReporting() { if (presenceManager != null) { presenceManager.stop(); } } - private static long readLongSafe(String v, long def) { - if (v == null) - return def; - try { - return Long.parseLong(v); - } catch (Exception ignored) { - return def; - } + public void loadGlobalMysql() { + globalDataSync.load(); } - private void handleWireVoteDelayRejected(JsonEnvelope msg) { - if (msg.getSchema() != VotingPluginWire.SCHEMA_VERSION) { - plugin.getLogger().warning("Incompatible version with bungee/proxy, please update all servers: " - + msg.getSchema() + " != " + VotingPluginWire.SCHEMA_VERSION); - return; - } - - if (!plugin.getOptions().isProcessRewards()) { - return; - } - - VotingPluginWire.VoteDelayRejected rejected = VotingPluginWire.readVoteDelayRejected(msg); - if (rejected.uuid.isEmpty() || rejected.service.isEmpty()) { - return; - } - - UUID javaUuid; - try { - javaUuid = UUID.fromString(rejected.uuid); - } catch (IllegalArgumentException e) { - plugin.getLogger().warning("Invalid UUID in VoteDelayRejected: " + rejected.uuid); - return; - } - - VoteSite voteSite = plugin.getVoteSiteManager() - .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, rejected.service), true); - if (voteSite == null) { - plugin.getLogger().warning("No voting site with the service site: '" + rejected.service + "'"); - return; - } - - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, rejected.player); - user.cache(); - user.updateName(true); - voteSite.giveWaitUntilVoteDelayRewards(user, rejected.wasOnline && user.isOnline(), true); + public void checkGlobalData() { + globalDataSync.checkGlobalData(); } - /** - * Wire vote handler (Vote + VoteOnline). - */ - private void handleWireVote(JsonEnvelope msg) { - int schema = msg.getSchema(); - if (schema != VotingPluginWire.SCHEMA_VERSION) { - plugin.getLogger().warning("Incompatible version with bungee/proxy, please update all servers: " + schema - + " != " + VotingPluginWire.SCHEMA_VERSION); - return; - } - - VotingPluginWire.Vote v = VotingPluginWire.readVote(msg); - - String uuidStr = v.uuid; - String player = v.player; - String service = v.service; - - if (uuidStr == null || uuidStr.isEmpty()) { - return; - } - if (!ServiceSiteValidator.isValid(service)) { - plugin.getLogger().warning("Rejected proxy vote with invalid service site '" - + ServiceSiteValidator.sanitizeForLog(service) + "'"); - return; - } - - plugin.debug("wire vote received from " + player + "/" + uuidStr + " on " + service); - - VoteTotalsSnapshot text = VoteTotalsSnapshot.parseStorage(v.totals == null ? "" : v.totals); - @SuppressWarnings("deprecation") - UUID voteId = v.voteId != null ? v.voteId : text.getVoteUUID(); - - if (!reserveWireVote(voteId)) { - plugin.debug("Ignoring duplicate wire vote " + voteId + " for " + player + " on " + service); - return; - } - - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(uuidStr), player); - - bungeeVotePartyCurrent = text.getVotePartyCurrent(); - bungeeVotePartyRequired = text.getVotePartyRequired(); - plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent); - plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired); - - user.cache(); - - boolean setTotalsOnBackend = !v.manageTotals; - user.bungeeVotePluginMessaging(service, v.time, text, setTotalsOnBackend, v.wasOnline, v.broadcast, v.num); - - if (plugin.getBungeeSettings().isPerServerPoints()) { - user.addPoints(plugin.getConfigFile().getPointsOnVote()); - } - - if (service != null && !service.isEmpty()) { - plugin.getServerData().addServiceSite(service); - } - - @SuppressWarnings("unused") - int _ignored = v.numberOfVotes; + public boolean checkGlobalDataTime(TimeType type, HashMap data) { + return globalDataSync.checkGlobalDataTime(type, data); } - private boolean reserveWireVote(UUID voteId) { - if (voteId == null) { - return true; - } + public boolean checkGlobalDataTimeValue(DataValue data) { + return globalDataSync.checkGlobalDataTimeValue(data); + } - long now = System.currentTimeMillis(); - long expiresAt = now + PROCESSED_VOTE_TTL_MILLIS; - - while (true) { - Long currentExpiry = processedWireVotes.get(voteId); - if (currentExpiry == null) { - if (processedWireVotes.putIfAbsent(voteId, expiresAt) == null) { - cleanupProcessedWireVotes(now); - return true; - } - continue; - } + public ConcurrentHashMap getProcessedWireVotes() { + return processedVoteCache.getProcessedVotes(); + } - if (currentExpiry > now) { - return false; - } + public int getBungeeVotePartyCurrent() { + return votePartySync == null ? plugin.getServerData().getBungeeVotePartyCurrent() : votePartySync.getCurrent(); + } - if (processedWireVotes.replace(voteId, currentExpiry, expiresAt)) { - cleanupProcessedWireVotes(now); - return true; - } - } + public int getBungeeVotePartyRequired() { + return votePartySync == null ? plugin.getServerData().getBungeeVotePartyRequired() : votePartySync.getRequired(); } - private void cleanupProcessedWireVotes(long now) { - processedWireVotes.entrySet().removeIf(entry -> entry.getValue() <= now); + public ScheduledExecutorService getTimer() { + return globalDataSync.getTimer(); } - /** - * Loads the global MySQL handler for cross-server data synchronization. - */ - public void loadGlobalMysql() { - if (plugin.getBungeeSettings().isGloblalDataEnabled()) { - if (timer != null) { - timer.shutdown(); - try { - timer.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - timer.shutdownNow(); - } - timer = Executors.newScheduledThreadPool(1); - timer.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - checkGlobalData(); - } - }, 60, 10, TimeUnit.SECONDS); - timer.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - globalDataHandler.setString(plugin.getBungeeSettings().getServer(), "LastOnline", - "" + LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()); - } - }, 1, 60, TimeUnit.MINUTES); - - if (globalDataHandler != null) { - globalDataHandler.getGlobalMysql().close(); - } + public ClientHandler getClientHandler() { + return transportManager.getClientHandler(); + } - if (plugin.getBungeeSettings().isGloblalDataUseMainMySQL() - && plugin.getStorageType().equals(UserStorage.MYSQL)) { - globalDataHandler = new GlobalDataHandler( - new GlobalMySQL("VotingPlugin_GlobalData", plugin.getMysql().getMysql()) { - @Override - public void debugEx(Exception e) { - plugin.debug(e); - } - - @Override - public void debugLog(String text) { - plugin.debug(text); - } - - @Override - public void info(String text) { - plugin.getLogger().info(text); - } - - @Override - public void logSevere(String text) { - plugin.getLogger().severe(text); - } - - @Override - public void warning(String text) { - plugin.getLogger().warning(text); - } - }); - } else { - globalDataHandler = new GlobalDataHandler( - new GlobalMySQL("VotingPlugin_GlobalData", new MysqlConfigSpigot( - plugin.getBungeeSettings().getData().getConfigurationSection("GlobalData"))) { - @Override - public void debugEx(Exception e) { - plugin.debug(e); - } - - @Override - public void debugLog(String text) { - plugin.debug(text); - } - - @Override - public void info(String text) { - plugin.getLogger().info(text); - } - - @Override - public void logSevere(String text) { - plugin.getLogger().severe(text); - } - - @Override - public void warning(String text) { - plugin.getLogger().warning(text); - } - }); - } + public SocketHandler getSocketHandler() { + return transportManager.getSocketHandler(); + } - globalDataHandler.getGlobalMysql().alterColumnType("IgnoreTime", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("MONTH", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("WEEK", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("DAY", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("FinishedProcessing", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("Processing", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("LastUpdated", "MEDIUMTEXT"); - globalDataHandler.getGlobalMysql().alterColumnType("ForceUpdate", "VARCHAR(5)"); - plugin.getTimeChecker().setProcessingEnabled(false); - } + public RedisHandler getRedisHandler() { + return transportManager.getRedisHandler(); } - private void sendEnvelopeSocket(JsonEnvelope envelope) { - if (clientHandler != null) { - clientHandler.sendEnvelope(envelope); - } + public MySqlMessenger getBackendMysqlMessenger() { + return transportManager.getBackendMysqlMessenger(); } - private void sendSubChannel(String subChannel, HashMap fields) { - JsonEnvelope.Builder b = JsonEnvelope.builder(subChannel).schema(VotingPluginWire.SCHEMA_VERSION); - if (fields != null) { - for (Map.Entry e : fields.entrySet()) { - b.put(e.getKey(), e.getValue()); - } - } - globalMessageHandler.sendMessage(b.build()); + public MqttHandler getMqttHandler() { + return transportManager.getMqttHandler(); } - private static String nvl(String s) { - return s == null ? "" : s; + private void sendEnvelope(JsonEnvelope envelope) { + if (globalMessageHandler != null) { + globalMessageHandler.sendMessage(envelope); + } } } diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyRewardRegistrar.java new file mode 100644 index 000000000..87475240d --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyRewardRegistrar.java @@ -0,0 +1,16 @@ +package com.bencodez.votingplugin.backendproxy; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined backend/proxy reward paths. */ +public final class BackendProxyRewardRegistrar { + + private BackendProxyRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("BungeeVotePartyRewards", + plugin.getBungeeSettings())); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/cache/ProcessedVoteCache.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/cache/ProcessedVoteCache.java new file mode 100644 index 000000000..0fae38444 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/cache/ProcessedVoteCache.java @@ -0,0 +1,60 @@ +package com.bencodez.votingplugin.backendproxy.cache; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import lombok.Getter; + +/** + * Tracks recently processed proxy vote ids so duplicate delivery is ignored. + */ +public class ProcessedVoteCache { + + private static final long DEFAULT_TTL_MILLIS = TimeUnit.MINUTES.toMillis(30); + + @Getter + private final ConcurrentHashMap processedVotes = new ConcurrentHashMap<>(); + private final long ttlMillis; + + public ProcessedVoteCache() { + this(DEFAULT_TTL_MILLIS); + } + + public ProcessedVoteCache(long ttlMillis) { + this.ttlMillis = ttlMillis; + } + + public boolean reserve(UUID voteId) { + if (voteId == null) { + return true; + } + + long now = System.currentTimeMillis(); + long expiresAt = now + ttlMillis; + + while (true) { + Long currentExpiry = processedVotes.get(voteId); + if (currentExpiry == null) { + if (processedVotes.putIfAbsent(voteId, expiresAt) == null) { + cleanup(now); + return true; + } + continue; + } + + if (currentExpiry > now) { + return false; + } + + if (processedVotes.replace(voteId, currentExpiry, expiresAt)) { + cleanup(now); + return true; + } + } + } + + private void cleanup(long now) { + processedVotes.entrySet().removeIf(entry -> entry.getValue() <= now); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/global/BackendGlobalDataSync.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/global/BackendGlobalDataSync.java new file mode 100644 index 000000000..adaaee969 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/global/BackendGlobalDataSync.java @@ -0,0 +1,175 @@ +package com.bencodez.votingplugin.backendproxy.global; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import com.bencodez.advancedcore.api.time.TimeType; +import com.bencodez.advancedcore.api.user.UserStorage; +import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalDataHandler; +import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalMySQL; +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.sql.data.DataValue; +import com.bencodez.simpleapi.sql.data.DataValueBoolean; +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.proxy.VotingPluginWire; + +import lombok.Getter; + +/** + * Owns backend global-data polling and proxy-driven time-change processing. + */ +public class BackendGlobalDataSync { + + private final VotingPluginMain plugin; + private final Consumer sender; + + @Getter + private GlobalDataHandler globalDataHandler; + @Getter + private ScheduledExecutorService timer; + + public BackendGlobalDataSync(VotingPluginMain plugin, Consumer sender) { + this.plugin = plugin; + this.sender = sender; + } + + public void checkGlobalData() { + if (globalDataHandler == null) { + return; + } + HashMap data = globalDataHandler.getExact(plugin.getBungeeSettings().getServer()); + + if (data.containsKey("ForceUpdate") && checkGlobalDataTimeValue(data.get("ForceUpdate"))) { + if (plugin.getStorageType().equals(UserStorage.MYSQL)) { + plugin.getMysql().clearCacheBasic(); + } + plugin.getUserManager().getDataManager().clearCache(); + plugin.setUpdate(true); + plugin.update(); + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "ForceUpdate", false); + } + + boolean forceUpdate = checkGlobalDataTime(TimeType.MONTH, data); + forceUpdate |= checkGlobalDataTime(TimeType.WEEK, data); + forceUpdate |= checkGlobalDataTime(TimeType.DAY, data); + + if (forceUpdate) { + HashMap dataToSet = new HashMap<>(); + dataToSet.put("FinishedProcessing", new DataValueBoolean(true)); + dataToSet.put("Processing", new DataValueBoolean(false)); + globalDataHandler.setData(plugin.getBungeeSettings().getServer(), dataToSet); + } + } + + public boolean checkGlobalDataTime(TimeType type, HashMap data) { + if (!data.containsKey(type.toString()) || !checkGlobalDataTimeValue(data.get(type.toString()))) { + return false; + } + + long lastUpdated = Long.valueOf(data.get("LastUpdated").getString()).longValue(); + plugin.debug("LastUpdated: " + lastUpdated); + if (LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli() - lastUpdated > 1000 * 60 * 60 * 2) { + plugin.getLogger().warning("Ignoring bungee time change since it was more than 2 hours ago"); + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); + return false; + } + + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "Processing", true); + plugin.debug("Detected time change from bungee: " + type.toString()); + plugin.getTimeChecker().forceChanged(type, false, true, true); + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); + + JsonEnvelope.Builder builder = JsonEnvelope.builder("TimeChangeFinished").schema(VotingPluginWire.SCHEMA_VERSION); + builder.put("server", plugin.getBungeeSettings().getServer()); + sender.accept(builder.build()); + return true; + } + + public boolean checkGlobalDataTimeValue(DataValue data) { + if (data.isBoolean()) { + return data.getBoolean(); + } + return Boolean.valueOf(data.getString()); + } + + public void load() { + if (!plugin.getBungeeSettings().isGloblalDataEnabled()) { + return; + } + + shutdownTimer(); + timer = Executors.newScheduledThreadPool(1); + timer.scheduleWithFixedDelay(this::checkGlobalData, 60, 10, TimeUnit.SECONDS); + timer.scheduleWithFixedDelay(() -> { + if (globalDataHandler != null) { + globalDataHandler.setString(plugin.getBungeeSettings().getServer(), "LastOnline", + "" + LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()); + } + }, 1, 60, TimeUnit.MINUTES); + + closeGlobalMysql(); + + if (plugin.getBungeeSettings().isGloblalDataUseMainMySQL() + && plugin.getStorageType().equals(UserStorage.MYSQL)) { + globalDataHandler = new GlobalDataHandler(new GlobalMySQL("VotingPlugin_GlobalData", plugin.getMysql().getMysql()) { + @Override public void debugEx(Exception e) { plugin.debug(e); } + @Override public void debugLog(String text) { plugin.debug(text); } + @Override public void info(String text) { plugin.getLogger().info(text); } + @Override public void logSevere(String text) { plugin.getLogger().severe(text); } + @Override public void warning(String text) { plugin.getLogger().warning(text); } + }); + } else { + globalDataHandler = new GlobalDataHandler(new GlobalMySQL("VotingPlugin_GlobalData", + new MysqlConfigSpigot(plugin.getBungeeSettings().getData().getConfigurationSection("GlobalData"))) { + @Override public void debugEx(Exception e) { plugin.debug(e); } + @Override public void debugLog(String text) { plugin.debug(text); } + @Override public void info(String text) { plugin.getLogger().info(text); } + @Override public void logSevere(String text) { plugin.getLogger().severe(text); } + @Override public void warning(String text) { plugin.getLogger().warning(text); } + }); + } + + for (Map.Entry column : Map.of( + "IgnoreTime", "VARCHAR(5)", + "MONTH", "VARCHAR(5)", + "WEEK", "VARCHAR(5)", + "DAY", "VARCHAR(5)", + "FinishedProcessing", "VARCHAR(5)", + "Processing", "VARCHAR(5)", + "LastUpdated", "MEDIUMTEXT", + "ForceUpdate", "VARCHAR(5)").entrySet()) { + globalDataHandler.getGlobalMysql().alterColumnType(column.getKey(), column.getValue()); + } + plugin.getTimeChecker().setProcessingEnabled(false); + } + + public void close() { + closeGlobalMysql(); + } + + private void closeGlobalMysql() { + if (globalDataHandler != null) { + globalDataHandler.getGlobalMysql().close(); + } + } + + private void shutdownTimer() { + if (timer == null) { + return; + } + timer.shutdown(); + try { + timer.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + timer.shutdownNow(); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/messaging/BackendProxyMessageRouter.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/messaging/BackendProxyMessageRouter.java new file mode 100644 index 000000000..59563bffb --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/messaging/BackendProxyMessageRouter.java @@ -0,0 +1,263 @@ +package com.bencodez.votingplugin.backendproxy.messaging; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageListener; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.backendproxy.cache.ProcessedVoteCache; +import com.bencodez.votingplugin.backendproxy.global.BackendGlobalDataSync; +import com.bencodez.votingplugin.backendproxy.presence.BackendPresenceManager; +import com.bencodez.votingplugin.backendproxy.voteparty.BackendVotePartySync; +import com.bencodez.votingplugin.proxy.BungeeMethod; +import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot; +import com.bencodez.votingplugin.proxy.VotingPluginWire; +import com.bencodez.votingplugin.user.VotingPluginUser; +import com.bencodez.votingplugin.util.ServiceSiteValidator; +import com.bencodez.votingplugin.votesites.VoteSite; + +/** + * Registers and handles backend-side proxy message routes. + */ +public class BackendProxyMessageRouter { + + private final VotingPluginMain plugin; + private final BackendPresenceManager presenceManager; + private final BackendGlobalDataSync globalDataSync; + private final BackendVotePartySync votePartySync; + private final ProcessedVoteCache processedVoteCache; + + public BackendProxyMessageRouter(VotingPluginMain plugin, BackendPresenceManager presenceManager, + BackendGlobalDataSync globalDataSync, BackendVotePartySync votePartySync, + ProcessedVoteCache processedVoteCache) { + this.plugin = plugin; + this.presenceManager = presenceManager; + this.globalDataSync = globalDataSync; + this.votePartySync = votePartySync; + this.processedVoteCache = processedVoteCache; + } + + public void register(GlobalMessageHandler messages, BungeeMethod method) { + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE) { + @Override public void onReceive(JsonEnvelope msg) { handleWireVote(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_ONLINE) { + @Override public void onReceive(JsonEnvelope msg) { handleWireVote(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_DELAY_REJECTED) { + @Override public void onReceive(JsonEnvelope msg) { handleWireVoteDelayRejected(msg); } + }); + + if (method.supportsBackendPresence()) { + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_RESYNC_REQUEST) { + @Override public void onReceive(JsonEnvelope msg) { presenceManager.handleResyncRequest(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_SNAPSHOT_REQUEST) { + @Override public void onReceive(JsonEnvelope msg) { presenceManager.handleSnapshotRequest(msg); } + }); + } + + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_UPDATE) { + @Override public void onReceive(JsonEnvelope msg) { handleVoteUpdate(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_BUNGEE_TIME_CHANGE) { + @Override public void onReceive(JsonEnvelope msg) { globalDataSync.checkGlobalData(); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_BROADCAST) { + @Override public void onReceive(JsonEnvelope msg) { handleVoteBroadcast(msg); } + }); + messages.addListener(new GlobalMessageListener("Status") { + @Override public void onReceive(JsonEnvelope msg) { + HashMap out = new HashMap<>(); + out.put("server", nvl(msg.getFields().get("server"))); + sendSubChannel(messages, "statusokay", out); + } + }); + messages.addListener(new GlobalMessageListener("ServerName") { + @Override public void onReceive(JsonEnvelope msg) { + String server = nvl(msg.getFields().get("server")); + if (!plugin.getOptions().getServer().equals(server)) { + plugin.getLogger().warning("Server name doesn't match in BungeeSettings.yml, should be " + server); + } + } + }); + messages.addListener(new GlobalMessageListener("VotePartyBungee") { + @Override public void onReceive(JsonEnvelope msg) { votePartySync.runGlobalRewards(); } + }); + messages.addListener(new GlobalMessageListener("VotePartyBroadcast") { + @Override public void onReceive(JsonEnvelope msg) { + votePartySync.broadcast(nvl(msg.getFields().get("broadcast"))); + } + }); + } + + private void handleVoteUpdate(JsonEnvelope msg) { + VotingPluginWire.VoteUpdate update = VotingPluginWire.readVoteUpdate(msg); + String playerUuid = update.uuid; + if (playerUuid == null || playerUuid.isEmpty()) { + return; + } + + plugin.debug("pluginmessaging voteupdate received for " + playerUuid + ": " + update.votePartyCurrent + "/" + + update.votePartyRequired + " on " + update.service); + votePartySync.update(update.votePartyCurrent, update.votePartyRequired); + + VotingPluginUser user; + try { + user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(playerUuid)); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid UUID in VoteUpdate: " + playerUuid); + return; + } + user.cache(); + user.offVote(); + + if (update.service != null && !update.service.isEmpty() && update.time > 0) { + user.setTime(plugin.getVoteSiteManager().getVoteSite(update.service, true), update.time); + } else if (update.service != null && !update.service.isEmpty() && update.time <= 0 + && plugin.getBungeeSettings().isBungeeDebug()) { + plugin.debug("Invalid last vote time received from bungee: " + update.time); + } + plugin.setUpdate(true); + } + + private void handleVoteBroadcast(JsonEnvelope msg) { + Map fields = msg.getFields(); + String uuidStr = nvl(fields.get(VotingPluginWire.K_UUID)); + String playerName = nvl(fields.get(VotingPluginWire.K_PLAYER)); + String service = nvl(fields.get(VotingPluginWire.K_SERVICE)); + if (uuidStr.isEmpty() || service.isEmpty()) { + return; + } + + UUID javaUuid; + try { + javaUuid = UUID.fromString(uuidStr); + } catch (Exception e) { + plugin.getLogger().warning("Invalid UUID in VoteBroadcast: " + uuidStr); + return; + } + + String totalsRaw = nvl(fields.get(VotingPluginWire.K_TOTALS)); + VoteTotalsSnapshot totals = totalsRaw.isEmpty() ? null : VoteTotalsSnapshot.parseStorage(totalsRaw); + VoteSite voteSite = plugin.getVoteSiteManager() + .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, service), true); + if (voteSite == null) { + plugin.getLogger().warning("No voting site with the service site: '" + service + "'"); + return; + } + if (!voteSite.isEnabled()) { + plugin.debug("Votesite: " + voteSite.getKey() + " is not enabled (VoteBroadcast)"); + return; + } + + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, playerName); + user.cache(); + user.updateName(true); + if (plugin.getBroadcastHandler() == null || user.isVanished()) { + if (user.isVanished()) { + plugin.debug("Not broadcasting vote for vanished user: " + user.getPlayerName()); + } + return; + } + + boolean online = fields.containsKey(VotingPluginWire.K_WAS_ONLINE) + ? Boolean.parseBoolean(fields.get(VotingPluginWire.K_WAS_ONLINE)) : user.isOnline(); + plugin.getBroadcastHandler().broadcastVote(user.getJavaUUID(), user.getPlayerName(), + voteSite.getDisplayName(), online, totals); + } + + private void handleWireVoteDelayRejected(JsonEnvelope msg) { + if (!validSchema(msg) || !plugin.getOptions().isProcessRewards()) { + return; + } + VotingPluginWire.VoteDelayRejected rejected = VotingPluginWire.readVoteDelayRejected(msg); + if (rejected.uuid.isEmpty() || rejected.service.isEmpty()) { + return; + } + UUID javaUuid; + try { + javaUuid = UUID.fromString(rejected.uuid); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid UUID in VoteDelayRejected: " + rejected.uuid); + return; + } + VoteSite voteSite = plugin.getVoteSiteManager() + .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, rejected.service), true); + if (voteSite == null) { + plugin.getLogger().warning("No voting site with the service site: '" + rejected.service + "'"); + return; + } + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, rejected.player); + user.cache(); + user.updateName(true); + voteSite.giveWaitUntilVoteDelayRewards(user, rejected.wasOnline && user.isOnline(), true); + } + + private void handleWireVote(JsonEnvelope msg) { + if (!validSchema(msg)) { + return; + } + VotingPluginWire.Vote vote = VotingPluginWire.readVote(msg); + if (vote.uuid == null || vote.uuid.isEmpty()) { + return; + } + if (!ServiceSiteValidator.isValid(vote.service)) { + plugin.getLogger().warning("Rejected proxy vote with invalid service site '" + + ServiceSiteValidator.sanitizeForLog(vote.service) + "'"); + return; + } + + plugin.debug("wire vote received from " + vote.player + "/" + vote.uuid + " on " + vote.service); + VoteTotalsSnapshot totals = VoteTotalsSnapshot.parseStorage(vote.totals == null ? "" : vote.totals); + @SuppressWarnings("deprecation") + UUID voteId = vote.voteId != null ? vote.voteId : totals.getVoteUUID(); + if (!processedVoteCache.reserve(voteId)) { + plugin.debug("Ignoring duplicate wire vote " + voteId + " for " + vote.player + " on " + vote.service); + return; + } + + UUID javaUuid; + try { + javaUuid = UUID.fromString(vote.uuid); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid UUID in proxy vote: " + vote.uuid); + return; + } + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, vote.player); + votePartySync.replace(totals.getVotePartyCurrent(), totals.getVotePartyRequired()); + user.cache(); + user.bungeeVotePluginMessaging(vote.service, vote.time, totals, !vote.manageTotals, + vote.wasOnline, vote.broadcast, vote.num); + if (plugin.getBungeeSettings().isPerServerPoints()) { + user.addPoints(plugin.getConfigFile().getPointsOnVote()); + } + if (vote.service != null && !vote.service.isEmpty()) { + plugin.getServerData().addServiceSite(vote.service); + } + } + + private boolean validSchema(JsonEnvelope msg) { + if (msg.getSchema() == VotingPluginWire.SCHEMA_VERSION) { + return true; + } + plugin.getLogger().warning("Incompatible version with bungee/proxy, please update all servers: " + + msg.getSchema() + " != " + VotingPluginWire.SCHEMA_VERSION); + return false; + } + + private void sendSubChannel(GlobalMessageHandler messages, String subChannel, HashMap fields) { + JsonEnvelope.Builder builder = JsonEnvelope.builder(subChannel).schema(VotingPluginWire.SCHEMA_VERSION); + for (Map.Entry entry : fields.entrySet()) { + builder.put(entry.getKey(), entry.getValue()); + } + messages.sendMessage(builder.build()); + } + + private static String nvl(String value) { + return value == null ? "" : value; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransport.java new file mode 100644 index 000000000..5e7b40b49 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransport.java @@ -0,0 +1,16 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; + +/** + * Transport used by a backend server to communicate with the VotingPlugin proxy. + */ +public interface BackendProxyTransport { + + void start(GlobalMessageHandler messageHandler); + + void send(JsonEnvelope envelope); + + void close(); +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransportManager.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransportManager.java new file mode 100644 index 000000000..e8377f753 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransportManager.java @@ -0,0 +1,86 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.mqtt.MqttHandler; +import com.bencodez.simpleapi.servercomm.mysql.MySqlMessenger; +import com.bencodez.simpleapi.servercomm.redis.RedisHandler; +import com.bencodez.simpleapi.servercomm.sockets.ClientHandler; +import com.bencodez.simpleapi.servercomm.sockets.SocketHandler; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.proxy.BungeeMethod; + +/** + * Selects and owns the active backend-to-proxy transport. + */ +public class BackendProxyTransportManager { + + private final VotingPluginMain plugin; + private BackendProxyTransport transport; + + public BackendProxyTransportManager(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public void start(BungeeMethod method, GlobalMessageHandler messageHandler) { + close(); + switch (method) { + case MYSQL: + transport = new MysqlBackendProxyTransport(plugin); + break; + case PLUGINMESSAGING: + transport = new PluginMessagingBackendProxyTransport(plugin); + break; + case SOCKETS: + transport = new SocketBackendProxyTransport(plugin); + break; + case REDIS: + transport = new RedisBackendProxyTransport(plugin); + break; + case MQTT: + transport = new MqttBackendProxyTransport(plugin); + break; + default: + throw new IllegalArgumentException("Unsupported backend proxy method: " + method); + } + transport.start(messageHandler); + } + + public void send(JsonEnvelope envelope) { + if (transport != null) { + transport.send(envelope); + } + } + + public void close() { + if (transport != null) { + transport.close(); + transport = null; + } + } + + public ClientHandler getClientHandler() { + return transport instanceof SocketBackendProxyTransport + ? ((SocketBackendProxyTransport) transport).getClientHandler() : null; + } + + public SocketHandler getSocketHandler() { + return transport instanceof SocketBackendProxyTransport + ? ((SocketBackendProxyTransport) transport).getSocketHandler() : null; + } + + public RedisHandler getRedisHandler() { + return transport instanceof RedisBackendProxyTransport + ? ((RedisBackendProxyTransport) transport).getRedisHandler() : null; + } + + public MySqlMessenger getBackendMysqlMessenger() { + return transport instanceof MysqlBackendProxyTransport + ? ((MysqlBackendProxyTransport) transport).getMessenger() : null; + } + + public MqttHandler getMqttHandler() { + return transport instanceof MqttBackendProxyTransport + ? ((MqttBackendProxyTransport) transport).getMqttHandler() : null; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MqttBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MqttBackendProxyTransport.java new file mode 100644 index 000000000..f092e77da --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MqttBackendProxyTransport.java @@ -0,0 +1,58 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import org.eclipse.paho.client.mqttv3.MqttException; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.mqtt.MqttHandler; +import com.bencodez.simpleapi.servercomm.mqtt.MqttServerComm; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class MqttBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private MqttHandler mqttHandler; + + public MqttBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + try { + String id = plugin.getBungeeSettings().getMqttClientID(); + if (id.isEmpty()) { + id = plugin.getOptions().getServer(); + } + mqttHandler = new MqttHandler(new MqttServerComm(id, plugin.getBungeeSettings().getMqttBrokerURL(), + plugin.getBungeeSettings().getMqttUsername(), plugin.getBungeeSettings().getMqttPassword()), 2); + mqttHandler.subscribeEnvelopes(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/" + + plugin.getOptions().getServer(), (topic, envelope) -> messageHandler.onMessage(envelope)); + } catch (MqttException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void send(JsonEnvelope envelope) { + if (mqttHandler == null) { + return; + } + try { + mqttHandler.publishEnvelope(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/proxy", + envelope); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void close() { + // Preserve current MQTT lifecycle behavior. + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MysqlBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MysqlBackendProxyTransport.java new file mode 100644 index 000000000..d55b2e88f --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MysqlBackendProxyTransport.java @@ -0,0 +1,58 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import java.sql.SQLException; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.mysql.MySqlMessenger; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class MysqlBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private MySqlMessenger messenger; + + public MysqlBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); + try { + messenger = new MySqlMessenger("VotingPlugin", + plugin.getMysql().getMysql().getConnectionManager().getDataSource(), + MySqlMessenger.Mode.BACKEND, plugin.getOptions().getServer(), msg -> { + if (plugin.getBungeeSettings().isBungeeDebug()) { + plugin.debug("Proxy sent envelope: " + msg.envelope.getSubChannel() + " " + + msg.envelope.getFields()); + } + messageHandler.onMessage(msg.envelope); + }); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public void send(JsonEnvelope envelope) { + if (messenger == null) { + return; + } + try { + messenger.sendToProxy(envelope); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public void close() { + if (messenger != null) { + messenger.shutdown(); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/PluginMessagingBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/PluginMessagingBackendProxyTransport.java new file mode 100644 index 000000000..6616ff748 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/PluginMessagingBackendProxyTransport.java @@ -0,0 +1,45 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import java.io.File; + +import com.bencodez.simpleapi.encryption.EncryptionHandler; +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.pluginmessage.PluginMessageHandler; +import com.bencodez.votingplugin.VotingPluginMain; + +public class PluginMessagingBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + + public PluginMessagingBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); + if (plugin.getBungeeSettings().isPluginMessageEncryption()) { + EncryptionHandler encryptionHandler = new EncryptionHandler(plugin.getName(), + new File(plugin.getDataFolder(), "secretkey.key")); + plugin.getPluginMessaging().setEncryptionHandler(encryptionHandler); + } + plugin.getPluginMessaging().setDebug(plugin.getBungeeSettings().isBungeeDebug()); + plugin.getPluginMessaging().add(new PluginMessageHandler() { + @Override + public void onReceive(JsonEnvelope envelope) { + messageHandler.onMessage(envelope); + } + }); + } + + @Override + public void send(JsonEnvelope envelope) { + plugin.getPluginMessaging().sendEnvelope(envelope); + } + + @Override + public void close() { + // Plugin messaging is owned by the plugin lifecycle. + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/RedisBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/RedisBackendProxyTransport.java new file mode 100644 index 000000000..34873e528 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/RedisBackendProxyTransport.java @@ -0,0 +1,57 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.redis.RedisHandler; +import com.bencodez.simpleapi.servercomm.redis.RedisListener; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class RedisBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private RedisHandler redisHandler; + private Thread listenerThread; + + public RedisBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + redisHandler = new RedisHandler(plugin.getBungeeSettings().getRedisHost(), + plugin.getBungeeSettings().getRedisPort(), plugin.getBungeeSettings().getRedisUsername(), + plugin.getBungeeSettings().getRedisPassword(), plugin.getBungeeSettings().getRedisdbindex()) { + @Override + public void debug(String message) { + if (plugin.getBungeeSettings().isBungeeDebug()) { + plugin.debug(message); + } + } + }; + + listenerThread = new Thread(() -> { + if (plugin.isEnabled()) { + RedisListener listener = redisHandler.createEnvelopeListener( + plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin_" + plugin.getBungeeSettings().getServer(), + (ch, envelope) -> messageHandler.onMessage(envelope)); + redisHandler.loadListener(listener); + } + }, "VotingPlugin-Redis-Backend"); + listenerThread.start(); + } + + @Override + public void send(JsonEnvelope envelope) { + if (redisHandler != null) { + redisHandler.publishEnvelope(plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin", envelope); + } + } + + @Override + public void close() { + // Preserve current Redis lifecycle behavior; the listener is owned by RedisHandler. + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/SocketBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/SocketBackendProxyTransport.java new file mode 100644 index 000000000..c4342ca58 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/SocketBackendProxyTransport.java @@ -0,0 +1,66 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import java.io.File; + +import com.bencodez.simpleapi.encryption.EncryptionHandler; +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.sockets.ClientHandler; +import com.bencodez.simpleapi.servercomm.sockets.SocketHandler; +import com.bencodez.simpleapi.servercomm.sockets.SocketReceiver; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class SocketBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private ClientHandler clientHandler; + @Getter + private SocketHandler socketHandler; + + public SocketBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + EncryptionHandler encryptionHandler = new EncryptionHandler(plugin.getName(), + new File(plugin.getDataFolder(), "secretkey.key")); + clientHandler = new ClientHandler(plugin.getBungeeSettings().getBungeeServerHost(), + plugin.getBungeeSettings().getBungeeServerPort(), encryptionHandler, + plugin.getBungeeSettings().isBungeeDebug()); + socketHandler = new SocketHandler("vp-socket", plugin.getBungeeSettings().getSpigotServerHost(), + plugin.getBungeeSettings().getSpigotServerPort(), encryptionHandler, + plugin.getBungeeSettings().isBungeeDebug()) { + @Override + public void log(String str) { + plugin.getLogger().info(str); + } + }; + socketHandler.add(new SocketReceiver() { + @Override + public void onReceiveEnvelope(JsonEnvelope envelope) { + messageHandler.onMessage(envelope); + } + }); + } + + @Override + public void send(JsonEnvelope envelope) { + if (clientHandler != null) { + clientHandler.sendEnvelope(envelope); + } + } + + @Override + public void close() { + if (socketHandler != null) { + socketHandler.closeConnection(); + } + if (clientHandler != null) { + clientHandler.stopConnection(); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/voteparty/BackendVotePartySync.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/voteparty/BackendVotePartySync.java new file mode 100644 index 000000000..87c10392f --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/voteparty/BackendVotePartySync.java @@ -0,0 +1,64 @@ +package com.bencodez.votingplugin.backendproxy.voteparty; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.advancedcore.api.rewards.RewardBuilder; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +/** + * Owns backend-side cached proxy vote-party state and proxy vote-party events. + */ +public class BackendVotePartySync { + + private final VotingPluginMain plugin; + + @Getter + private int current; + @Getter + private int required; + + public BackendVotePartySync(VotingPluginMain plugin) { + this.plugin = plugin; + current = plugin.getServerData().getBungeeVotePartyCurrent(); + required = plugin.getServerData().getBungeeVotePartyRequired(); + } + + public void update(int votePartyCurrent, int votePartyRequired) { + if (votePartyCurrent >= 0 || current == -2) { + current = votePartyCurrent; + } + if (votePartyRequired >= 0 || required == -2) { + required = votePartyRequired; + } + persist(); + } + + public void replace(int votePartyCurrent, int votePartyRequired) { + current = votePartyCurrent; + required = votePartyRequired; + persist(); + } + + public void persist() { + plugin.getServerData().setBungeeVotePartyCurrent(current); + plugin.getServerData().setBungeeVotePartyRequired(required); + } + + public void runGlobalRewards() { + for (final String command : plugin.getBungeeSettings().getBungeeVotePartyGlobalCommands()) { + plugin.getBukkitScheduler().runTask(plugin, + () -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command)); + } + for (Player player : Bukkit.getOnlinePlayers()) { + new RewardBuilder(plugin.getBungeeSettings().getData(), "BungeeVotePartyRewards").send(player); + } + } + + public void broadcast(String message) { + MiscUtils.getInstance().broadcast(message); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backgroundtask/VotingPluginBackgroundTask.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backgroundtask/VotingPluginBackgroundTask.java new file mode 100644 index 000000000..5e25f63cf --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backgroundtask/VotingPluginBackgroundTask.java @@ -0,0 +1,185 @@ +package com.bencodez.votingplugin.backgroundtask; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import org.bukkit.Bukkit; + +import com.bencodez.advancedcore.api.user.UserDataFetchMode; +import com.bencodez.simpleapi.skull.SkullCache; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.topvoter.TopVoter; +import com.bencodez.votingplugin.topvoter.TopVoterPlayer; +import com.bencodez.votingplugin.user.VotingPluginUser; +import com.bencodez.votingplugin.votesites.VoteSite; + +/** Owns VotingPlugin's periodic background data refresh task and its state. */ +public final class VotingPluginBackgroundTask { + + private final VotingPluginMain plugin; + private volatile boolean requested; + private volatile boolean running; + private volatile long lastRunTimeSeconds = -1; + + public VotingPluginBackgroundTask(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public synchronized void run() { + if (!(requested || plugin.getConfigFile().isAlwaysUpdate())) { + return; + } + if (!plugin.isEnabled() || running) { + return; + } + if (plugin.getConfigFile().isUpdateWithPlayersOnlineOnly() && Bukkit.getOnlinePlayers().isEmpty()) { + return; + } + + running = true; + requested = false; + try { + runRefresh(); + } finally { + running = false; + } + } + + private void runRefresh() { + synchronized (plugin) { + try { + if (!plugin.isEnabled()) { + return; + } + + plugin.getUserManager().getDataManager().clearCacheBasic(); + SkullCache.flushWeek(); + plugin.debug("Starting background task, current cached users: " + + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); + + boolean extraBackgroundUpdate = plugin.getConfigFile().isExtraBackgroundUpdate(); + long startTime = System.currentTimeMillis(); + LinkedHashMap> voteToday = new LinkedHashMap<>(); + LinkedHashMap> tempTopVoter = new LinkedHashMap<>(); + + ArrayList topVotersToCheck = new ArrayList<>(); + for (TopVoter top : TopVoter.values()) { + if (plugin.getConfigFile().getLoadTopVoter(top)) { + topVotersToCheck.add(top); + tempTopVoter.put(top, new LinkedHashMap<>()); + } + } + + boolean ignorePermission = plugin.getConfigFile().isTopVoterIgnorePermission(); + ArrayList blackList = plugin.getConfigFile().getBlackList(); + ZoneId zone = ZoneId.systemDefault(); + LocalDate today = LocalDate.now(zone); + long startOfDayMs = today.atStartOfDay(zone).toInstant().toEpochMilli(); + long startOfNextDayMs = today.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli(); + long afterSetup = System.currentTimeMillis(); + + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + if (!plugin.isEnabled()) { + return; + } + + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + try { + if (!user.isBanned() && !blackList.contains(user.getPlayerName())) { + if (!ignorePermission || !user.isTopVoterIgnore()) { + TopVoterPlayer player = user.getTopVoterPlayer(); + for (TopVoter top : topVotersToCheck) { + int total = user.getTotal(top); + if (total > 0) { + tempTopVoter.get(top).put(player, total); + } + } + } + + HashMap times = null; + for (Entry entry : user.getLastVotes().entrySet()) { + VoteSite site = entry.getKey(); + if (!site.isEnabled() || site.isHidden()) { + continue; + } + long time = entry.getValue(); + if (time >= startOfDayMs && time < startOfNextDayMs) { + if (times == null) { + times = new HashMap<>(); + } + times.put(site, LocalDateTime.ofInstant(Instant.ofEpochMilli(time), zone)); + } + } + if (times != null && !times.isEmpty()) { + voteToday.put(user.getTopVoterPlayer(), times); + } + } + + if (extraBackgroundUpdate && user.isOnline()) { + user.offVote(); + } + if (!plugin.getPlaceholders().getCacheLevel().onlineOnly() || user.isOnline()) { + plugin.getPlaceholders().onUpdate(user, false); + } + } finally { + user.clearTempCache(); + } + }, count -> { + long elapsed = (System.currentTimeMillis() - afterSetup) / 1000; + plugin.debug("Finished getting player data in " + elapsed + " seconds, " + count + " users, " + + plugin.getStorageType()); + }); + + plugin.getTopVoterHandler().updateTopVoters(tempTopVoter); + plugin.getPlaceholders().onUpdate(); + plugin.setVoteToday(voteToday); + plugin.getServerData().updateValues(); + plugin.getSigns().updateSigns(); + + if (plugin.getConfigFile().isDiscordSRVEnabled() && plugin.getDiscordHandler() != null) { + for (TopVoter top : TopVoter.values()) { + if (!plugin.getConfigFile().isDiscordSRVTopVoterNewMessageOnUpdate(top)) { + plugin.getDiscordHandler().updateTopVoterMessageId(top); + } + } + } + + plugin.getUserManager().getDataManager().clearNonNeededCachedUsers(); + plugin.extraDebug("Current cached users: " + + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); + + lastRunTimeSeconds = (System.currentTimeMillis() - startTime) / 1000; + plugin.debug("Background task finished. Total time: " + lastRunTimeSeconds + " seconds"); + } catch (Exception exception) { + if (plugin.isEnabled()) { + plugin.getLogger().info("Looks like something went wrong"); + } + plugin.debug(exception); + } + } + } + + public boolean isRequested() { + return requested; + } + + public void setRequested(boolean requested) { + this.requested = requested; + } + + public boolean isRunning() { + return running; + } + + public long getLastRunTimeSeconds() { + return lastRunTimeSeconds; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/VotingPluginConfigHealth.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/VotingPluginConfigHealth.java new file mode 100644 index 000000000..77d3977f6 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/VotingPluginConfigHealth.java @@ -0,0 +1,29 @@ +package com.bencodez.votingplugin.config; + +import com.bencodez.votingplugin.VotingPluginMain; + +/** Owns aggregate YAML load-health reporting for VotingPlugin configuration files. */ +public final class VotingPluginConfigHealth { + + private final VotingPluginMain plugin; + private boolean ymlError; + + public VotingPluginConfigHealth(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public void check() { + ymlError = plugin.getConfigFile().isFailedToRead() || plugin.getConfigVoteSites().isFailedToRead() + || plugin.getSpecialRewardsConfig().isFailedToRead() || plugin.getBungeeSettings().isFailedToRead() + || plugin.getGui().isFailedToRead(); + + if (ymlError) { + plugin.getBukkitScheduler().runTaskLaterAsynchronously(plugin, + () -> plugin.getLogger().severe("Failed to load a file, check startup log"), 1); + } + } + + public boolean hasYmlError() { + return ymlError; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/integration/votifier/VotifierIntegration.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/integration/votifier/VotifierIntegration.java new file mode 100644 index 000000000..1440b2148 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/integration/votifier/VotifierIntegration.java @@ -0,0 +1,32 @@ +package com.bencodez.votingplugin.integration.votifier; + +import com.bencodez.votingplugin.VotingPluginMain; + +/** Owns Votifier/NuVotifier availability detection. */ +public final class VotifierIntegration { + + private final VotingPluginMain plugin; + private boolean loaded = true; + + public VotifierIntegration(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public void detect() { + loaded = true; + try { + Class.forName("com.vexsoftware.votifier.model.VotifierEvent"); + } catch (ClassNotFoundException e) { + if (!plugin.getBungeeSettings().isUseBungeecoord()) { + plugin.getLogger().warning("No VotifierEvent found, install Votifier, NuVotifier, or another Votifier plugin"); + } else { + plugin.debug("No VotifierEvent found, but usebungeecoord enabled"); + } + loaded = false; + } + } + + public boolean isLoaded() { + return loaded; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/DirectlyDefinedRewardPath.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/DirectlyDefinedRewardPath.java new file mode 100644 index 000000000..b48ec0eda --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/DirectlyDefinedRewardPath.java @@ -0,0 +1,39 @@ +package com.bencodez.votingplugin.rewards; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.advancedcore.api.rewards.DirectlyDefinedReward; +import com.bencodez.simpleapi.file.YMLFile; + +/** + * Creates directly-defined reward editors backed by a VotingPlugin YAML file. + */ +public final class DirectlyDefinedRewardPath { + + private DirectlyDefinedRewardPath() { + } + + public static DirectlyDefinedReward of(String path, YMLFile file) { + return new DirectlyDefinedReward(path) { + @Override + public void createSection(String key) { + file.createSection(key); + } + + @Override + public ConfigurationSection getFileData() { + return file.getData(); + } + + @Override + public void save() { + file.saveData(); + } + + @Override + public void setData(String dataPath, Object value) { + file.setValue(dataPath, value); + } + }; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/VotingPluginRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/VotingPluginRewardRegistrar.java new file mode 100644 index 000000000..330955f31 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/VotingPluginRewardRegistrar.java @@ -0,0 +1,27 @@ +package com.bencodez.votingplugin.rewards; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.builtin.RewardPoints; +import com.bencodez.votingplugin.rewards.builtin.RewardVoteBossBar; +import com.bencodez.votingplugin.rewards.builtin.RewardWebhook; +import com.bencodez.votingplugin.rewards.builtin.placeholders.RewardTotalPlaceholder; +import com.bencodez.votingplugin.rewards.builtin.requirements.RequirementVoteTotal; +import com.bencodez.votingplugin.topvoter.TopVoter; + +/** Registers VotingPlugin-specific AdvancedCore reward extensions. */ +public final class VotingPluginRewardRegistrar { + + private VotingPluginRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.getRewardHandler().addInjectedReward(new RewardPoints(plugin)); + plugin.getRewardHandler().addInjectedReward(new RewardVoteBossBar(plugin)); + plugin.getRewardHandler().addInjectedReward(new RewardWebhook(plugin)); + plugin.getRewardHandler().addInjectedRequirements(new RequirementVoteTotal(plugin)); + + for (TopVoter topVoter : TopVoter.values()) { + plugin.getRewardHandler().addPlaceholder(new RewardTotalPlaceholder(plugin, topVoter)); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardPoints.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardPoints.java new file mode 100644 index 000000000..820e8e204 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardPoints.java @@ -0,0 +1,53 @@ +package com.bencodez.votingplugin.rewards.builtin; + +import java.util.HashMap; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; + +import com.bencodez.advancedcore.api.inventory.editgui.EditGUIButton; +import com.bencodez.advancedcore.api.inventory.editgui.valuetypes.EditGUIValueNumber; +import com.bencodez.advancedcore.api.item.ItemBuilder; +import com.bencodez.advancedcore.api.rewards.Reward; +import com.bencodez.advancedcore.api.rewards.RewardEditData; +import com.bencodez.advancedcore.api.rewards.injected.RewardInject; +import com.bencodez.advancedcore.api.rewards.injected.RewardInjectInt; +import com.bencodez.advancedcore.api.rewards.injected.RewardInjectValidator; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.user.VotingPluginUser; + +/** VotingPlugin's injected Points reward. */ +public class RewardPoints extends RewardInjectInt { + + private final VotingPluginMain plugin; + + public RewardPoints(VotingPluginMain plugin) { + super("Points", 0); + this.plugin = plugin; + synchronize().asPlaceholder("newpoints").addEditButton( + new EditGUIButton(new ItemBuilder(Material.PAPER), new EditGUIValueNumber("Points", null) { + @Override + public void setValue(Player player, Number value) { + RewardEditData reward = (RewardEditData) getInv().getData("Reward"); + reward.setValue("Points", value.intValue()); + } + }.addLore("Give player voting points"))).validator(new RewardInjectValidator() { + @Override + public void onValidate(Reward reward, RewardInject inject, ConfigurationSection data) { + if (data.getInt(inject.getPath(), -1) == 0) { + warning(reward, inject, "Points can not be 0"); + } + } + }); + } + + @Override + public String onRewardRequest(Reward reward, com.bencodez.advancedcore.api.user.AdvancedCoreUser user, int num, + HashMap placeholders) { + VotingPluginUser vpUser = plugin.getVotingPluginUserManager().getVotingPluginUser(user); + String result = "" + vpUser.addPoints(num); + plugin.debug("Setting points to " + result); + return result; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardVoteBossBar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardVoteBossBar.java new file mode 100644 index 000000000..0562bddfa --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardVoteBossBar.java @@ -0,0 +1,34 @@ +package com.bencodez.votingplugin.rewards.builtin; + +import java.util.HashMap; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.advancedcore.api.messages.PlaceholderUtils; +import com.bencodez.advancedcore.api.rewards.Reward; +import com.bencodez.advancedcore.api.rewards.injected.RewardInjectConfigurationSection; +import com.bencodez.votingplugin.VotingPluginMain; + +/** VotingPlugin's injected vote progress boss-bar reward. */ +public class RewardVoteBossBar extends RewardInjectConfigurationSection { + + private final VotingPluginMain plugin; + + public RewardVoteBossBar(VotingPluginMain plugin) { + super("VoteBossBar"); + this.plugin = plugin; + } + + @Override + public String onRewardRequested(Reward reward, com.bencodez.advancedcore.api.user.AdvancedCoreUser user, + ConfigurationSection section, HashMap placeholders) { + if (section.getBoolean("Enabled")) { + user.sendBossBar(PlaceholderUtils.replacePlaceHolder(section.getString("Message", ""), placeholders), + section.getString("Color", "BLUE"), section.getString("Style", "SOLID"), + (double) plugin.getVotingPluginUserManager().getVotingPluginUser(user).getSitesVotedOn() + / plugin.getVoteSites().size(), + section.getInt("Delay", 30)); + } + return null; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardWebhook.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardWebhook.java new file mode 100644 index 000000000..d1dc662b6 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/RewardWebhook.java @@ -0,0 +1,39 @@ +package com.bencodez.votingplugin.rewards.builtin; + +import java.util.HashMap; +import java.util.List; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.advancedcore.api.messages.PlaceholderUtils; +import com.bencodez.advancedcore.api.rewards.Reward; +import com.bencodez.advancedcore.api.rewards.injected.RewardInjectConfigurationSection; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.webhook.WebhookRewardEntry; +import com.bencodez.votingplugin.webhook.WebhookRewardParser; + +/** VotingPlugin's injected webhook reward. */ +public class RewardWebhook extends RewardInjectConfigurationSection { + + private final VotingPluginMain plugin; + + public RewardWebhook(VotingPluginMain plugin) { + super("WebhookReward"); + this.plugin = plugin; + } + + @Override + public String onRewardRequested(Reward reward, com.bencodez.advancedcore.api.user.AdvancedCoreUser user, + ConfigurationSection section, HashMap placeholders) { + if (section == null || plugin.getWebhooks() == null) { + return null; + } + List entries = WebhookRewardParser.parse(section); + if (entries == null || entries.isEmpty()) { + return null; + } + plugin.getWebhooks().createExecutor().executeAll(entries, + input -> PlaceholderUtils.replacePlaceHolder(input, placeholders)); + return null; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/placeholders/RewardTotalPlaceholder.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/placeholders/RewardTotalPlaceholder.java new file mode 100644 index 000000000..753e07f6c --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/placeholders/RewardTotalPlaceholder.java @@ -0,0 +1,25 @@ +package com.bencodez.votingplugin.rewards.builtin.placeholders; + +import com.bencodez.advancedcore.api.rewards.Reward; +import com.bencodez.advancedcore.api.rewards.RewardPlaceholderHandle; +import com.bencodez.advancedcore.api.user.AdvancedCoreUser; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.topvoter.TopVoter; + +/** Reward placeholder exposing one VotingPlugin total. */ +public class RewardTotalPlaceholder extends RewardPlaceholderHandle { + + private final VotingPluginMain plugin; + private final TopVoter topVoter; + + public RewardTotalPlaceholder(VotingPluginMain plugin, TopVoter topVoter) { + super("Total_" + topVoter.toString()); + this.plugin = plugin; + this.topVoter = topVoter; + } + + @Override + public String getValue(Reward reward, AdvancedCoreUser user) { + return "" + plugin.getVotingPluginUserManager().getVotingPluginUser(user).getTotal(topVoter); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/requirements/RequirementVoteTotal.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/requirements/RequirementVoteTotal.java new file mode 100644 index 000000000..d276ce6da --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/builtin/requirements/RequirementVoteTotal.java @@ -0,0 +1,51 @@ +package com.bencodez.votingplugin.rewards.builtin.requirements; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.advancedcore.api.rewards.Reward; +import com.bencodez.advancedcore.api.rewards.RewardOptions; +import com.bencodez.advancedcore.api.rewards.injectedrequirement.RequirementInjectConfigurationSection; +import com.bencodez.advancedcore.api.user.AdvancedCoreUser; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.topvoter.TopVoter; +import com.bencodez.votingplugin.user.VotingPluginUser; + +/** VotingPlugin vote-total/points reward requirement. */ +public class RequirementVoteTotal extends RequirementInjectConfigurationSection { + + private final VotingPluginMain plugin; + + public RequirementVoteTotal(VotingPluginMain plugin) { + super("VoteTotal"); + this.plugin = plugin; + } + + @Override + public boolean onRequirementsRequested(Reward reward, AdvancedCoreUser acUser, ConfigurationSection section, + RewardOptions rewardOptions) { + boolean atleast = section.getBoolean("AtleastMode", false); + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(acUser); + + for (TopVoter top : TopVoter.values()) { + int required = section.getInt(top.toString(), -1); + if (required < 0) { + continue; + } + int total = user.getTotal(top); + if (atleast ? total < required : total != required) { + plugin.debug("Failed requirement " + top + " " + total + (atleast ? "/" : "!=") + required); + return false; + } + } + + int pointsRequired = section.getInt("Points", -1); + if (pointsRequired >= 0) { + int points = user.getPoints(); + if (atleast ? points < pointsRequired : points != pointsRequired) { + plugin.debug("Failed requirement points " + points + (atleast ? "/" : "!=") + pointsRequired); + return false; + } + } + return true; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/signs/SignHandler.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/signs/SignHandler.java index 0b62bc7ce..688eda03a 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/signs/SignHandler.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/signs/SignHandler.java @@ -1,318 +1,318 @@ -package com.bencodez.votingplugin.signs; - -import java.util.ArrayList; -import java.util.UUID; - -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.block.Skull; -import org.bukkit.block.data.Directional; -import org.bukkit.inventory.meta.SkullMeta; - -import com.bencodez.advancedcore.api.misc.MiscUtils; -import com.bencodez.simpleapi.array.ArrayUtils; -import com.bencodez.votingplugin.VotingPluginMain; -import com.bencodez.votingplugin.topvoter.TopVoter; -import com.bencodez.votingplugin.topvoter.TopVoterPlayer; - -import lombok.Getter; -import lombok.Setter; - -// TODO: Auto-generated Javadoc -/** - * The Class SignHandler. - */ -public class SignHandler { - - @Getter - @Setter - private String data; - - /** The lines. */ - private ArrayList lines; - - @Getter - @Setter - private Location location; - - private String playerName; - - private UUID uuid; - - private VotingPluginMain plugin; - - private int position; - - @Getter - @Setter - private String sign; - - @Getter - @Setter - private Location skullLocation; - - @Getter - @Setter - private boolean valid; - - private int votes; - - /** - * Constructs a new SignHandler. - * - * @param plugin the main plugin instance - * @param sign the sign identifier - * @param location the sign location - * @param skullLocation the skull location - * @param data the sign data - * @param position the sign position - */ - public SignHandler(VotingPluginMain plugin, String sign, Location location, Location skullLocation, String data, - int position) { - this.plugin = plugin; - setSign(sign); - setLocation(location); - setSkullLocation(skullLocation); - setData(data); - this.position = position; - setValid(true); - lines = new ArrayList<>(); - checkValidSign(); - playerName = ""; - } - - @SuppressWarnings("deprecation") - private boolean checkSkull(Block block) { - if (block.getState() instanceof Skull) { - if (!playerName.equals("No Player")) { - Skull skull = (Skull) block.getState(); - try { - SkullMeta meta = (SkullMeta) plugin.getSkullCacheHandler().getSkull(uuid, playerName).getItemMeta(); - skull.setOwnerProfile(meta.getOwnerProfile()); - skull.update(true, false); - } catch (Exception e) { - plugin.debug("Failed to set skull for " + playerName); - plugin.debug(e); - skull.setOwner(playerName); - skull.update(true, false); - } - - } - return true; - } - return false; - } - - /** - * Check skulls. - */ - public void checkSkulls() { - if (noNameAvailable()) { - return; - } - if (skullLocation != null) { - checkSkull(skullLocation.getBlock()); - return; - } - - Location loc = location; - Location loc1 = new Location(loc.getWorld(), loc.getBlockX() - 1, loc.getBlockY() - 1, loc.getBlockZ() - 1); - Location loc2 = new Location(loc.getWorld(), loc.getBlockX() + 1, loc.getBlockY() + 1, loc.getBlockZ() + 1); - updateSkulls(loc1, loc2); - } - - /** - * Check valid sign. - */ - public void checkValidSign() { - plugin.getBukkitScheduler().runTask(plugin, new Runnable() { - - @Override - public void run() { - try { - setValid(getLocation().getBlock().getState() instanceof Sign); - } catch (Exception ex) { - setValid(false); - } - } - }, getLocation()); - - } - - /** - * Gets the right click message. - * - * @return the right click message - */ - public String getRightClickMessage() { - String msg = plugin.getConfigFile().getFormatSignTopVoterSignRightClickMessage(); - msg = msg.replace("%player%", playerName); - msg = msg.replace("%position%", "" + position); - msg = msg.replace("%votes%", "" + votes); - msg = msg.replace("%SiteName%", data); - return msg; - } - - /** - * Checks if is location same. - * - * @param loc the loc - * @return true, if is location same - */ - public boolean isLocationSame(Location loc) { - return loc.equals(getLocation()); - } - - /** - * Checks if skull location is set. - * - * @return true if skull is set - */ - public boolean isSkullSet() { - return skullLocation != null; - } - - /** - * Checks if no player name is available. - * - * @return true if no name available - */ - public boolean noNameAvailable() { - return playerName.equalsIgnoreCase("No Player") || playerName.equals(""); - } - - /** - * Removes the sign. - */ - public void removeSign() { - plugin.getServerData().removeSign(sign); - } - - /** - * Store sign. - */ - public void storeSign() { - plugin.getServerData().setSign(sign, location, skullLocation, data, position); - } - - /** - * Update lines. - */ - public void updateLines() { - lines = new ArrayList<>(); - checkValidSign(); - if (position != 0) { - String line1 = plugin.getConfigFile().getFormatSignTopVoterSignLine1(); - String line2 = plugin.getConfigFile().getFormatSignTopVoterSignLine2(); - String line3 = plugin.getConfigFile().getFormatSignTopVoterSignLine3(); - String line4 = plugin.getConfigFile().getFormatSignTopVoterSignLine4(); - lines.add(line1); - lines.add(line2); - lines.add(line3); - lines.add(line4); - - ArrayList users = null; - if (data.equalsIgnoreCase("all")) { - users = plugin.convertSet(plugin.getTopVoter(TopVoter.AllTime).keySet()); - } else if (data.equalsIgnoreCase("monthly")) { - users = plugin.convertSet(plugin.getTopVoter(TopVoter.Monthly).keySet()); - } else if (data.equalsIgnoreCase("weekly")) { - users = plugin.convertSet(plugin.getTopVoter(TopVoter.Weekly).keySet()); - } else if (data.equalsIgnoreCase("daily")) { - users = plugin.convertSet(plugin.getTopVoter(TopVoter.Daily).keySet()); - } - - if (users != null && users.size() >= position) { - TopVoterPlayer user = users.get(position - 1); - playerName = user.getPlayerName(); - uuid = user.getUuid(); - - votes = 0; - if (data.equalsIgnoreCase("all")) { - votes = plugin.getTopVoter(TopVoter.AllTime).get(user); - } else if (data.equalsIgnoreCase("monthly")) { - votes = plugin.getTopVoter(TopVoter.Monthly).get(user); - } else if (data.equalsIgnoreCase("weekly")) { - votes = plugin.getTopVoter(TopVoter.Weekly).get(user); - } else if (data.equalsIgnoreCase("daily")) { - votes = plugin.getTopVoter(TopVoter.Daily).get(user); - } - - for (int j = 0; j < lines.size(); j++) { - lines.set(j, lines.get(j).replace("%votes%", "" + votes).replace("%player%", playerName)); - } - } else { - playerName = "No Player"; - votes = 0; - for (int j = 0; j < lines.size(); j++) { - lines.set(j, lines.get(j).replace("%votes%", "" + votes).replace("%player%", playerName)); - } - } - - for (int j = 0; j < lines.size(); j++) { - lines.set(j, lines.get(j).replace("%SiteName%", data).replace("%position%", "" + position)); - } - - lines = ArrayUtils.colorize(lines); - - } - - } - - /** - * Update sign. - * - * @param delay the delay - */ - public void updateSign(int delay) { - plugin.getBukkitScheduler().runTaskLater(plugin, new Runnable() { - - @SuppressWarnings("deprecation") - @Override - public void run() { - try { - BlockState state = getLocation().getBlock().getState(); - if (state instanceof Sign) { - Sign s = (Sign) state; - - for (int j = 0; j < lines.size() && j < 4; j++) { - s.setLine(j, lines.get(j)); - } - s.update(); - checkSkulls(); - } - - } catch (Exception ex) { - plugin.debug("Failed to update sign at " + getLocation().toString()); - VotingPluginMain.plugin.debug(ex); - } - } - }, delay, getLocation()); - } - - /** - * Updates skulls in the specified region. - * - * @param loc1 the first location - * @param loc2 the second location - */ - public void updateSkulls(Location loc1, Location loc2) { - BlockState state = getLocation().getBlock().getState(); - if (state instanceof Sign && state.getBlockData() instanceof Directional) { - Directional s = (Directional) state.getBlockData(); - Block b = location.getBlock().getRelative(s.getFacing()); - Block above = b.getRelative(BlockFace.UP); - if (checkSkull(above)) { - return; - } - - } - - for (Block block : MiscUtils.getInstance().getRegionBlocks(location.getWorld(), loc1, loc2)) { - checkSkull(block); - } - } -} +package com.bencodez.votingplugin.signs; + +import java.util.ArrayList; +import java.util.UUID; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.block.Skull; +import org.bukkit.block.data.Directional; +import org.bukkit.inventory.meta.SkullMeta; + +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.simpleapi.array.ArrayUtils; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.topvoter.TopVoter; +import com.bencodez.votingplugin.topvoter.TopVoterPlayer; + +import lombok.Getter; +import lombok.Setter; + +// TODO: Auto-generated Javadoc +/** + * The Class SignHandler. + */ +public class SignHandler { + + @Getter + @Setter + private String data; + + /** The lines. */ + private ArrayList lines; + + @Getter + @Setter + private Location location; + + private String playerName; + + private UUID uuid; + + private VotingPluginMain plugin; + + private int position; + + @Getter + @Setter + private String sign; + + @Getter + @Setter + private Location skullLocation; + + @Getter + @Setter + private boolean valid; + + private int votes; + + /** + * Constructs a new SignHandler. + * + * @param plugin the main plugin instance + * @param sign the sign identifier + * @param location the sign location + * @param skullLocation the skull location + * @param data the sign data + * @param position the sign position + */ + public SignHandler(VotingPluginMain plugin, String sign, Location location, Location skullLocation, String data, + int position) { + this.plugin = plugin; + setSign(sign); + setLocation(location); + setSkullLocation(skullLocation); + setData(data); + this.position = position; + setValid(true); + lines = new ArrayList<>(); + checkValidSign(); + playerName = ""; + } + + @SuppressWarnings("deprecation") + private boolean checkSkull(Block block) { + if (block.getState() instanceof Skull) { + if (!playerName.equals("No Player")) { + Skull skull = (Skull) block.getState(); + try { + SkullMeta meta = (SkullMeta) plugin.getSkullCacheHandler().getSkull(uuid, playerName).getItemMeta(); + skull.setOwnerProfile(meta.getOwnerProfile()); + skull.update(true, false); + } catch (Exception e) { + plugin.debug("Failed to set skull for " + playerName); + plugin.debug(e); + skull.setOwner(playerName); + skull.update(true, false); + } + + } + return true; + } + return false; + } + + /** + * Check skulls. + */ + public void checkSkulls() { + if (noNameAvailable()) { + return; + } + if (skullLocation != null) { + checkSkull(skullLocation.getBlock()); + return; + } + + Location loc = location; + Location loc1 = new Location(loc.getWorld(), loc.getBlockX() - 1, loc.getBlockY() - 1, loc.getBlockZ() - 1); + Location loc2 = new Location(loc.getWorld(), loc.getBlockX() + 1, loc.getBlockY() + 1, loc.getBlockZ() + 1); + updateSkulls(loc1, loc2); + } + + /** + * Check valid sign. + */ + public void checkValidSign() { + plugin.getBukkitScheduler().runTask(plugin, new Runnable() { + + @Override + public void run() { + try { + setValid(getLocation().getBlock().getState() instanceof Sign); + } catch (Exception ex) { + setValid(false); + } + } + }, getLocation()); + + } + + /** + * Gets the right click message. + * + * @return the right click message + */ + public String getRightClickMessage() { + String msg = plugin.getConfigFile().getFormatSignTopVoterSignRightClickMessage(); + msg = msg.replace("%player%", playerName); + msg = msg.replace("%position%", "" + position); + msg = msg.replace("%votes%", "" + votes); + msg = msg.replace("%SiteName%", data); + return msg; + } + + /** + * Checks if is location same. + * + * @param loc the loc + * @return true, if is location same + */ + public boolean isLocationSame(Location loc) { + return loc.equals(getLocation()); + } + + /** + * Checks if skull location is set. + * + * @return true if skull is set + */ + public boolean isSkullSet() { + return skullLocation != null; + } + + /** + * Checks if no player name is available. + * + * @return true if no name available + */ + public boolean noNameAvailable() { + return playerName.equalsIgnoreCase("No Player") || playerName.equals(""); + } + + /** + * Removes the sign. + */ + public void removeSign() { + plugin.getServerData().removeSign(sign); + } + + /** + * Store sign. + */ + public void storeSign() { + plugin.getServerData().setSign(sign, location, skullLocation, data, position); + } + + /** + * Update lines. + */ + public void updateLines() { + lines = new ArrayList<>(); + checkValidSign(); + if (position != 0) { + String line1 = plugin.getConfigFile().getFormatSignTopVoterSignLine1(); + String line2 = plugin.getConfigFile().getFormatSignTopVoterSignLine2(); + String line3 = plugin.getConfigFile().getFormatSignTopVoterSignLine3(); + String line4 = plugin.getConfigFile().getFormatSignTopVoterSignLine4(); + lines.add(line1); + lines.add(line2); + lines.add(line3); + lines.add(line4); + + ArrayList users = null; + if (data.equalsIgnoreCase("all")) { + users = new ArrayList<>(plugin.getTopVoter(TopVoter.AllTime).keySet()); + } else if (data.equalsIgnoreCase("monthly")) { + users = new ArrayList<>(plugin.getTopVoter(TopVoter.Monthly).keySet()); + } else if (data.equalsIgnoreCase("weekly")) { + users = new ArrayList<>(plugin.getTopVoter(TopVoter.Weekly).keySet()); + } else if (data.equalsIgnoreCase("daily")) { + users = new ArrayList<>(plugin.getTopVoter(TopVoter.Daily).keySet()); + } + + if (users != null && users.size() >= position) { + TopVoterPlayer user = users.get(position - 1); + playerName = user.getPlayerName(); + uuid = user.getUuid(); + + votes = 0; + if (data.equalsIgnoreCase("all")) { + votes = plugin.getTopVoter(TopVoter.AllTime).get(user); + } else if (data.equalsIgnoreCase("monthly")) { + votes = plugin.getTopVoter(TopVoter.Monthly).get(user); + } else if (data.equalsIgnoreCase("weekly")) { + votes = plugin.getTopVoter(TopVoter.Weekly).get(user); + } else if (data.equalsIgnoreCase("daily")) { + votes = plugin.getTopVoter(TopVoter.Daily).get(user); + } + + for (int j = 0; j < lines.size(); j++) { + lines.set(j, lines.get(j).replace("%votes%", "" + votes).replace("%player%", playerName)); + } + } else { + playerName = "No Player"; + votes = 0; + for (int j = 0; j < lines.size(); j++) { + lines.set(j, lines.get(j).replace("%votes%", "" + votes).replace("%player%", playerName)); + } + } + + for (int j = 0; j < lines.size(); j++) { + lines.set(j, lines.get(j).replace("%SiteName%", data).replace("%position%", "" + position)); + } + + lines = ArrayUtils.colorize(lines); + + } + + } + + /** + * Update sign. + * + * @param delay the delay + */ + public void updateSign(int delay) { + plugin.getBukkitScheduler().runTaskLater(plugin, new Runnable() { + + @SuppressWarnings("deprecation") + @Override + public void run() { + try { + BlockState state = getLocation().getBlock().getState(); + if (state instanceof Sign) { + Sign s = (Sign) state; + + for (int j = 0; j < lines.size() && j < 4; j++) { + s.setLine(j, lines.get(j)); + } + s.update(); + checkSkulls(); + } + + } catch (Exception ex) { + plugin.debug("Failed to update sign at " + getLocation().toString()); + VotingPluginMain.plugin.debug(ex); + } + } + }, delay, getLocation()); + } + + /** + * Updates skulls in the specified region. + * + * @param loc1 the first location + * @param loc2 the second location + */ + public void updateSkulls(Location loc1, Location loc2) { + BlockState state = getLocation().getBlock().getState(); + if (state instanceof Sign && state.getBlockData() instanceof Directional) { + Directional s = (Directional) state.getBlockData(); + Block b = location.getBlock().getRelative(s.getFacing()); + Block above = b.getRelative(BlockFace.UP); + if (checkSkull(above)) { + return; + } + + } + + for (Block block : MiscUtils.getInstance().getRegionBlocks(location.getWorld(), loc1, loc2)) { + checkSkull(block); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/SpecialRewardsRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/SpecialRewardsRewardRegistrar.java new file mode 100644 index 000000000..c7b6a6bdd --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/SpecialRewardsRewardRegistrar.java @@ -0,0 +1,37 @@ +package com.bencodez.votingplugin.specialrewards; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined general and legacy special reward paths. */ +public final class SpecialRewardsRewardRegistrar { + + private SpecialRewardsRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + String[] simplePaths = { + "VoteCoolDownEndedReward", "LoginRewards", "LogoutRewards", + "AllSites", "AlmostAllSites", "FirstVote", "FirstVoteToday", "NameMCLikeReward.Rewards" + }; + for (String path : simplePaths) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(path, plugin.getSpecialRewardsConfig())); + } + + registerLegacyGroup(plugin, "Cumulative"); + registerLegacyGroup(plugin, "Milestones"); + } + + private static void registerLegacyGroup(VotingPluginMain plugin, String key) { + ConfigurationSection section = plugin.getSpecialRewardsConfig().getData().getConfigurationSection(key); + if (section == null) { + return; + } + for (String entry : section.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(key + "." + entry + ".Rewards", + plugin.getSpecialRewardsConfig())); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votemilestones/VoteMilestoneRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votemilestones/VoteMilestoneRewardRegistrar.java new file mode 100644 index 000000000..8073e5b5e --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votemilestones/VoteMilestoneRewardRegistrar.java @@ -0,0 +1,25 @@ +package com.bencodez.votingplugin.specialrewards.votemilestones; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined VoteMilestones reward paths. */ +public final class VoteMilestoneRewardRegistrar { + + private VoteMilestoneRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + ConfigurationSection section = plugin.getSpecialRewardsConfig().getData() + .getConfigurationSection("VoteMilestones"); + if (section == null) { + return; + } + for (String milestoneId : section.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteMilestones." + milestoneId + ".Rewards", plugin.getSpecialRewardsConfig())); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VoteParty.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VoteParty.java index 2177fb8a9..141ee99b9 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VoteParty.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VoteParty.java @@ -1,475 +1,352 @@ - -package com.bencodez.votingplugin.specialrewards.voteparty; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -import com.bencodez.advancedcore.api.messages.PlaceholderUtils; -import com.bencodez.advancedcore.api.misc.MiscUtils; -import com.bencodez.advancedcore.api.rewards.RewardBuilder; -import com.bencodez.advancedcore.api.time.events.DayChangeEvent; -import com.bencodez.advancedcore.api.time.events.MonthChangeEvent; -import com.bencodez.advancedcore.api.time.events.WeekChangeEvent; -import com.bencodez.advancedcore.api.user.UserDataFetchMode; -import com.bencodez.simpleapi.array.ArrayUtils; -import com.bencodez.simpleapi.messages.MessageAPI; -import com.bencodez.simpleapi.sql.DataType; -import com.bencodez.votingplugin.VotingPluginMain; -import com.bencodez.votingplugin.events.VotePartyEvent; -import com.bencodez.votingplugin.user.VotingPluginUser; - -// TODO: Auto-generated Javadoc -/** - * The Class VoteParty. - */ -public class VoteParty implements Listener { - - private VotingPluginMain plugin; - - /** - * Constructs a new VoteParty. - * - * @param plugin the main plugin instance - */ - public VoteParty(VotingPluginMain plugin) { - this.plugin = plugin; - } - - /** - * Adds a vote total for the user. - * - * @param user the voting plugin user - */ - public void addTotal(VotingPluginUser user) { - setTotalVotes(getTotalVotes() + 1); - user.setVotePartyVotes(user.getVotePartyVotes() + 1); - plugin.getPlaceholders().onVotePartyUpdate(); - } - - /** - * Adds the vote player. - * - * @param user the user - */ - public void addVotePlayer(VotingPluginUser user) { - String uuid = user.getUUID(); - List voted = getVotedUsers(); - if (voted == null) { - voted = new ArrayList<>(); - } - if (!voted.contains(uuid)) { - voted.add(uuid); - setVotedUsers(voted); - } - } - - /** - * Checks if vote party requirements are met and triggers if appropriate. - * - * @param user the voting plugin user - * @param forceBungee whether to force Bungee processing - */ - public void check(VotingPluginUser user, boolean forceBungee) { - if (getTotalVotes() < getVotesRequired()) { - plugin.extraDebug("Not enough votes for vote party: " + getTotalVotes() + " / " + getVotesRequired()); - return; - } - - if (plugin.getSpecialRewardsConfig().isVotePartyOnlyOncePerDay() - && plugin.getServerData().isLastVotePartySameDay()) { - plugin.extraDebug("Already had vote party today"); - return; - } - if (plugin.getSpecialRewardsConfig().isVotePartyOnlyOncePerWeek() - && plugin.getServerData().isLastVotePartySameWeek()) { - plugin.extraDebug("Already had vote party this week"); - return; - } - if (plugin.getSpecialRewardsConfig().isVotePartyResetCount()) { - setTotalVotes(getTotalVotes() - getVotesRequired()); - } - - VotePartyEvent event = new VotePartyEvent(); - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - - giveRewards(user, forceBungee); - - if (plugin.getSpecialRewardsConfig().getVotePartyIncreaseVotesRequired() > 0) { - plugin.getServerData().setVotePartyExtraRequired(plugin.getServerData().getVotePartyExtraRequired() - + plugin.getSpecialRewardsConfig().getVotePartyIncreaseVotesRequired()); - } - - plugin.getServerData().updateLastVoteParty(); - - if (plugin.getSpecialRewardsConfig().isVotePartyOnlyOncePerWeek()) { - plugin.getServerData().updateLastVotePartyWeek(); - } - - } - - /** - * Checks and sends vote reminders if requirements are met. - * - * @param user the voting plugin user - */ - public void checkVoteReminder(VotingPluginUser user) { - if (!user.isVanished()) { - int neededVotes = getNeededVotes(); - - for (Integer num1 : plugin.getSpecialRewardsConfig().getVotePartyVoteReminderAtVotes()) { - int num = num1.intValue(); - if (neededVotes == num) { - String broadcastMessage = plugin.getSpecialRewardsConfig().getVotePartyVoteReminderBroadcast(); - HashMap placeholders = new HashMap<>(); - placeholders.put("player", user.getPlayerName()); - placeholders.put("votesrequired", "" + neededVotes); - MiscUtils.getInstance() - .broadcast(PlaceholderUtils.replacePlaceHolder(broadcastMessage, placeholders)); - - MiscUtils.getInstance().executeConsoleCommands( - plugin.getSpecialRewardsConfig().getVotePartyVoteReminderCommands(), placeholders, false); - } - } - } - - } - - /** - * Command vote party. - * - * @param sender the sender - */ - public void commandVoteParty(CommandSender sender) { - if (plugin.getSpecialRewardsConfig().isVotePartyEnabled()) { - ArrayList msg = plugin.getConfigFile().getFormatCommandsVoteParty(); - int votesRequired = getVotesRequired(); - int votes = getTotalVotes(); - int neededVotes = votesRequired - votes; - HashMap placeholders = new HashMap<>(); - placeholders.put("votesrequired", "" + votesRequired); - placeholders.put("neededvotes", "" + neededVotes); - placeholders.put("votes", "" + votes); - msg = ArrayUtils.colorize(PlaceholderUtils.replacePlaceHolder(msg, placeholders)); - if (sender instanceof Player) { - Player p = (Player) sender; - sender.sendMessage(ArrayUtils.convert(PlaceholderUtils.replacePlaceHolders(msg, p))); - } else { - sender.sendMessage(ArrayUtils.convert(msg)); - } - } else { - sender.sendMessage(MessageAPI.colorize("&cVoteParty not enabled")); - } - } - - /** - * Gets the needed votes. - * - * @return the needed votes - */ - public int getNeededVotes() { - int votesRequired = getVotesRequired(); - int votes = getTotalVotes(); - int neededVotes = votesRequired - votes; - return neededVotes; - } - - /** - * Gets a random player name from voted users. - * - * @return random player name or "No Player" if none available - */ - public String getRandomPlayerName() { - ArrayList allPlayers = new ArrayList<>(); - for (Player players : Bukkit.getOnlinePlayers()) { - allPlayers.add(players.getName()); - } - if (allPlayers.size() == 0) { - return "No Player"; - } - int random = new Random().nextInt(allPlayers.size()); - return allPlayers.get(random); - } - - /** - * Gets the total votes. - * - * @return the total votes - */ - public int getTotalVotes() { - return plugin.getServerData().getData().getInt("VoteParty.Total"); - } - - /** - * Gets the voted users. - * - * @return the voted users - */ - public List getVotedUsers() { - List list = plugin.getServerData().getData().getStringList("VoteParty.Voted"); - if (list != null) { - return list; - } - return new ArrayList<>(); - } - - /** - * Gets the number of votes required for the vote party. - * - * @return votes required including extra requirements - */ - public int getVotesRequired() { - int required = plugin.getSpecialRewardsConfig().getVotePartyVotesRequired(); - int extra = plugin.getServerData().getVotePartyExtraRequired(); - if (extra > 0) { - return required + extra; - } - return required; - } - - /** - * Gives vote party reward to a user. - * - * @param user the voting plugin user - * @param useBungee whether to use Bungee processing - */ - public void giveReward(VotingPluginUser user, boolean useBungee) { - if (plugin.getSpecialRewardsConfig().getVotePartyUserVotesRequired() > 0) { - if (user.getVotePartyVotes() < plugin.getSpecialRewardsConfig().getVotePartyUserVotesRequired()) { - return; - } - } - giveReward(user, user.isOnline(), useBungee); - } - - /** - * Gives vote party reward to a user with online status. - * - * @param user the voting plugin user - * @param online whether the user is online - * @param useBungee whether to use Bungee processing - */ - public void giveReward(VotingPluginUser user, boolean online, boolean useBungee) { - new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), - plugin.getSpecialRewardsConfig().getVotePartyRewardsPath()).setOnline(online) - .withPlaceHolder("VotesRequired", "" + plugin.getSpecialRewardsConfig().getVotePartyVotesRequired()) - .withPlaceHolder("FirstVotePartyToday", "" + !plugin.getServerData().isLastVotePartySameDay()) - .setServer(useBungee).send(user); - } - - /** - * Gives rewards to all eligible players for the vote party. - * - * @param orgUser the original voting user who triggered the party - * @param forceBungee whether to force Bungee processing - */ - public void giveRewards(VotingPluginUser orgUser, boolean forceBungee) { - MiscUtils.getInstance().broadcast(plugin.getSpecialRewardsConfig().getVotePartyBroadcast()); - - String player = getRandomPlayerName(); - for (final String cmd : plugin.getSpecialRewardsConfig().getVotePartyGlobalCommands()) { - plugin.getBukkitScheduler().runTask(plugin, new Runnable() { - - @Override - public void run() { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), - PlaceholderUtils.replacePlaceHolder(cmd, "randomonlineplayer", player)); - } - - }); - } - - final ArrayList list = plugin.getSpecialRewardsConfig().getVotePartyGlobalRandomCommand(); - if (list.size() > 0) { - plugin.getBukkitScheduler().runTask(plugin, new Runnable() { - - @Override - public void run() { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), PlaceholderUtils.replacePlaceHolder( - list.get(ThreadLocalRandom.current().nextInt(list.size())), "randomonlineplayer", player)); - } - - }); - } - - if (plugin.getSpecialRewardsConfig().isVotePartyGiveAllPlayers()) { - plugin.debug("Trying to give all players vote party"); - - ArrayList alreadyGotten = new ArrayList<>(); - for (Player p : Bukkit.getOnlinePlayers()) { - VotingPluginUser user; - if (orgUser != null && orgUser.getJavaUUID().equals(p.getUniqueId())) { - user = orgUser; - } else { - user = plugin.getVotingPluginUserManager().getVotingPluginUser(p); - user.userDataFetechMode(UserDataFetchMode.NO_CACHE); - } - - if (!plugin.getSpecialRewardsConfig().isVotePartyGiveOnlinePlayersOnly() || user.isOnline()) { - giveReward(user, forceBungee); - } - alreadyGotten.add(p.getUniqueId().toString()); - } - for (String uuid : plugin.getVotingPluginUserManager().getAllUUIDs()) { - if (!alreadyGotten.contains(uuid)) { - VotingPluginUser user; - if (orgUser != null && orgUser.getJavaUUID().toString().equals(uuid)) { - user = orgUser; - } else { - user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(uuid)); - user.userDataFetechMode(UserDataFetchMode.NO_CACHE); - } - - if (!plugin.getSpecialRewardsConfig().isVotePartyGiveOnlinePlayersOnly() || user.isOnline()) { - giveReward(user, forceBungee); - } - } - } - } else { - plugin.debug("Trying to give all voted players vote party"); - plugin.debug(ArrayUtils.makeStringList(getVotedUsers())); - for (String uuid : - - getVotedUsers()) { - VotingPluginUser user; - if (orgUser != null && orgUser.getJavaUUID().toString().equals(uuid)) { - user = orgUser; - } else { - user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(uuid)); - user.userDataFetechMode(UserDataFetchMode.NO_CACHE); - } - if (!plugin.getSpecialRewardsConfig().isVotePartyGiveOnlinePlayersOnly() || user.isOnline()) { - giveReward(user, forceBungee); - } - - } - } - - reset(false); - } - - /** - * Handles day change events to reset vote party if configured. - * - * @param event the day change event - */ - @EventHandler - public void onDayChange(DayChangeEvent event) { - if (plugin.getSpecialRewardsConfig().isVotePartyResetEachDay()) { - reset(true); - } - } - - /** - * Handles month change events to reset vote party if configured. - * - * @param event the month change event - */ - @EventHandler - public void onMonthChange(MonthChangeEvent event) { - if (plugin.getSpecialRewardsConfig().isVotePartyResetMonthly()) { - reset(true); - } - - if (plugin.getSpecialRewardsConfig().isVotePartyResetExtraVotesMonthly()) { - plugin.getServerData().setVotePartyExtraRequired(0); - } - } - - /** - * Handles week change events to reset vote party if configured. - * - * @param event the week change event - */ - @EventHandler - public void onWeekChange(WeekChangeEvent event) { - if (plugin.getSpecialRewardsConfig().isVotePartyResetWeekly()) { - reset(true); - } - - if (plugin.getSpecialRewardsConfig().isVotePartyResetExtraVotesWeekly()) { - plugin.getServerData().setVotePartyExtraRequired(0); - } - } - - /** - * Registers this class as an event listener. - */ - public void register() { - plugin.getServer().getPluginManager().registerEvents(this, plugin); - } - - /** - * Resets the vote party state. - * - * @param override whether to override total votes to zero - */ - public void reset(boolean override) { - if (override) { - setTotalVotes(0); - } - setVotedUsers(new ArrayList<>()); - resetVotePartyCount(); - - } - - /** - * Resets the vote party count for all users. - */ - public void resetVotePartyCount() { - plugin.getUserManager().removeAllKeyValues("VotePartyVotes", DataType.INTEGER); - } - - /** - * Sets the total votes. - * - * @param value the new total votes - */ - public void setTotalVotes(int value) { - plugin.getServerData().getData().set("VoteParty.Total", value); - plugin.getServerData().saveData(); - } - - /** - * Sets the voted users. - * - * @param value the new voted users - */ - public void setVotedUsers(List value) { - plugin.getServerData().getData().set("VoteParty.Voted", value); - plugin.getServerData().saveData(); - } - - /** - * Processes a vote for the vote party system. - * - * @param user the voting plugin user - * @param realVote whether this is a real vote - * @param forceBungee whether to force Bungee processing - */ - public synchronized void vote(VotingPluginUser user, boolean realVote, boolean forceBungee) { - if (plugin.getSpecialRewardsConfig().isVotePartyEnabled()) { - if (plugin.getSpecialRewardsConfig().isVotePartyCountFakeVotes() || realVote) { - if (plugin.getSpecialRewardsConfig().isVotePartyCountOfflineVotes() || user.isOnline()) { - addTotal(user); - addVotePlayer(user); - check(user, forceBungee); - checkVoteReminder(user); - } - } - } - } - -} + +package com.bencodez.votingplugin.specialrewards.voteparty; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import com.bencodez.advancedcore.api.messages.PlaceholderUtils; +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.advancedcore.api.time.events.DayChangeEvent; +import com.bencodez.advancedcore.api.time.events.MonthChangeEvent; +import com.bencodez.advancedcore.api.time.events.WeekChangeEvent; +import com.bencodez.simpleapi.array.ArrayUtils; +import com.bencodez.simpleapi.messages.MessageAPI; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.events.VotePartyEvent; +import com.bencodez.votingplugin.user.VotingPluginUser; + +/** Coordinates VoteParty voting, reminders, commands, and reset events. */ +public class VoteParty implements Listener { + + private VotingPluginMain plugin; + private final VotePartyState state; + private final VotePartyRewardHandler rewardHandler; + + /** + * Constructs a new VoteParty. + * + * @param plugin the main plugin instance + */ + public VoteParty(VotingPluginMain plugin) { + this.plugin = plugin; + this.state = new VotePartyState(plugin); + this.rewardHandler = new VotePartyRewardHandler(plugin, state, this); + } + + /** + * Adds a vote total for the user. + * + * @param user the voting plugin user + */ + public void addTotal(VotingPluginUser user) { + state.setTotalVotes(state.getTotalVotes() + 1); + user.setVotePartyVotes(user.getVotePartyVotes() + 1); + plugin.getPlaceholders().onVotePartyUpdate(); + } + + /** + * Adds the vote player. + * + * @param user the user + */ + public void addVotePlayer(VotingPluginUser user) { + String uuid = user.getUUID(); + List voted = state.getVotedUsers(); + if (!voted.contains(uuid)) { + voted.add(uuid); + state.setVotedUsers(voted); + } + } + + /** + * Checks if vote party requirements are met and triggers if appropriate. + * + * @param user the voting plugin user + * @param forceBungee whether to force Bungee processing + */ + public void check(VotingPluginUser user, boolean forceBungee) { + if (getTotalVotes() < getVotesRequired()) { + plugin.extraDebug("Not enough votes for vote party: " + getTotalVotes() + " / " + getVotesRequired()); + return; + } + + if (plugin.getSpecialRewardsConfig().isVotePartyOnlyOncePerDay() + && plugin.getServerData().isLastVotePartySameDay()) { + plugin.extraDebug("Already had vote party today"); + return; + } + if (plugin.getSpecialRewardsConfig().isVotePartyOnlyOncePerWeek() + && plugin.getServerData().isLastVotePartySameWeek()) { + plugin.extraDebug("Already had vote party this week"); + return; + } + if (plugin.getSpecialRewardsConfig().isVotePartyResetCount()) { + setTotalVotes(getTotalVotes() - getVotesRequired()); + } + + VotePartyEvent event = new VotePartyEvent(); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + giveRewards(user, forceBungee); + + if (plugin.getSpecialRewardsConfig().getVotePartyIncreaseVotesRequired() > 0) { + plugin.getServerData().setVotePartyExtraRequired(plugin.getServerData().getVotePartyExtraRequired() + + plugin.getSpecialRewardsConfig().getVotePartyIncreaseVotesRequired()); + } + + plugin.getServerData().updateLastVoteParty(); + + if (plugin.getSpecialRewardsConfig().isVotePartyOnlyOncePerWeek()) { + plugin.getServerData().updateLastVotePartyWeek(); + } + } + + /** + * Checks and sends vote reminders if requirements are met. + * + * @param user the voting plugin user + */ + public void checkVoteReminder(VotingPluginUser user) { + if (!user.isVanished()) { + int neededVotes = getNeededVotes(); + + for (Integer num1 : plugin.getSpecialRewardsConfig().getVotePartyVoteReminderAtVotes()) { + int num = num1.intValue(); + if (neededVotes == num) { + String broadcastMessage = plugin.getSpecialRewardsConfig().getVotePartyVoteReminderBroadcast(); + HashMap placeholders = new HashMap<>(); + placeholders.put("player", user.getPlayerName()); + placeholders.put("votesrequired", "" + neededVotes); + MiscUtils.getInstance() + .broadcast(PlaceholderUtils.replacePlaceHolder(broadcastMessage, placeholders)); + + MiscUtils.getInstance().executeConsoleCommands( + plugin.getSpecialRewardsConfig().getVotePartyVoteReminderCommands(), placeholders, false); + } + } + } + } + + /** + * Command vote party. + * + * @param sender the sender + */ + public void commandVoteParty(CommandSender sender) { + if (plugin.getSpecialRewardsConfig().isVotePartyEnabled()) { + ArrayList msg = plugin.getConfigFile().getFormatCommandsVoteParty(); + int votesRequired = getVotesRequired(); + int votes = getTotalVotes(); + int neededVotes = votesRequired - votes; + HashMap placeholders = new HashMap<>(); + placeholders.put("votesrequired", "" + votesRequired); + placeholders.put("neededvotes", "" + neededVotes); + placeholders.put("votes", "" + votes); + msg = ArrayUtils.colorize(PlaceholderUtils.replacePlaceHolder(msg, placeholders)); + if (sender instanceof Player) { + Player p = (Player) sender; + sender.sendMessage(ArrayUtils.convert(PlaceholderUtils.replacePlaceHolders(msg, p))); + } else { + sender.sendMessage(ArrayUtils.convert(msg)); + } + } else { + sender.sendMessage(MessageAPI.colorize("&cVoteParty not enabled")); + } + } + + /** + * Gets the needed votes. + * + * @return the needed votes + */ + public int getNeededVotes() { + return state.getNeededVotes(); + } + + /** + * Gets a random player name from voted users. + * + * @return random player name or "No Player" if none available + */ + public String getRandomPlayerName() { + return rewardHandler.randomOnlinePlayerName(); + } + + /** + * Gets the total votes. + * + * @return the total votes + */ + public int getTotalVotes() { + return state.getTotalVotes(); + } + + /** + * Gets the voted users. + * + * @return the voted users + */ + public List getVotedUsers() { + return state.getVotedUsers(); + } + + /** + * Gets the number of votes required for the vote party. + * + * @return votes required including extra requirements + */ + public int getVotesRequired() { + return state.getVotesRequired(); + } + + /** + * Gives vote party reward to a user. + * + * @param user the voting plugin user + * @param useBungee whether to use Bungee processing + */ + public void giveReward(VotingPluginUser user, boolean useBungee) { + if (plugin.getSpecialRewardsConfig().getVotePartyUserVotesRequired() > 0 + && user.getVotePartyVotes() < plugin.getSpecialRewardsConfig().getVotePartyUserVotesRequired()) { + return; + } + giveReward(user, user.isOnline(), useBungee); + } + + /** + * Gives vote party reward to a user with online status. + * + * @param user the voting plugin user + * @param online whether the user is online + * @param useBungee whether to use Bungee processing + */ + public void giveReward(VotingPluginUser user, boolean online, boolean useBungee) { + rewardHandler.giveReward(user, online, useBungee); + } + + /** + * Gives rewards to all eligible players for the vote party. + * + * @param orgUser the original voting user who triggered the party + * @param forceBungee whether to force Bungee processing + */ + public void giveRewards(VotingPluginUser orgUser, boolean forceBungee) { + rewardHandler.giveRewards(orgUser, forceBungee); + reset(false); + } + + /** + * Handles day change events to reset vote party if configured. + * + * @param event the day change event + */ + @EventHandler + public void onDayChange(DayChangeEvent event) { + if (plugin.getSpecialRewardsConfig().isVotePartyResetEachDay()) { + reset(true); + } + } + + /** + * Handles month change events to reset vote party if configured. + * + * @param event the month change event + */ + @EventHandler + public void onMonthChange(MonthChangeEvent event) { + if (plugin.getSpecialRewardsConfig().isVotePartyResetMonthly()) { + reset(true); + } + + if (plugin.getSpecialRewardsConfig().isVotePartyResetExtraVotesMonthly()) { + plugin.getServerData().setVotePartyExtraRequired(0); + } + } + + /** + * Handles week change events to reset vote party if configured. + * + * @param event the week change event + */ + @EventHandler + public void onWeekChange(WeekChangeEvent event) { + if (plugin.getSpecialRewardsConfig().isVotePartyResetWeekly()) { + reset(true); + } + + if (plugin.getSpecialRewardsConfig().isVotePartyResetExtraVotesWeekly()) { + plugin.getServerData().setVotePartyExtraRequired(0); + } + } + + /** + * Registers this class as an event listener. + */ + public void register() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + /** + * Resets the vote party state. + * + * @param override whether to override total votes to zero + */ + public void reset(boolean override) { + state.reset(override); + } + + /** + * Resets the vote party count for all users. + */ + public void resetVotePartyCount() { + state.resetUserCounts(); + } + + /** + * Sets the total votes. + * + * @param value the new total votes + */ + public void setTotalVotes(int value) { + state.setTotalVotes(value); + } + + /** + * Sets the voted users. + * + * @param value the new voted users + */ + public void setVotedUsers(List value) { + state.setVotedUsers(value); + } + + /** + * Processes a vote for the vote party system. + * + * @param user the voting plugin user + * @param realVote whether this is a real vote + * @param forceBungee whether to force Bungee processing + */ + public synchronized void vote(VotingPluginUser user, boolean realVote, boolean forceBungee) { + if (plugin.getSpecialRewardsConfig().isVotePartyEnabled()) { + if (plugin.getSpecialRewardsConfig().isVotePartyCountFakeVotes() || realVote) { + if (plugin.getSpecialRewardsConfig().isVotePartyCountOfflineVotes() || user.isOnline()) { + addTotal(user); + addVotePlayer(user); + check(user, forceBungee); + checkVoteReminder(user); + } + } + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardHandler.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardHandler.java new file mode 100644 index 000000000..dbcce3c50 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardHandler.java @@ -0,0 +1,128 @@ +package com.bencodez.votingplugin.specialrewards.voteparty; + +import java.util.ArrayList; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.bencodez.advancedcore.api.messages.PlaceholderUtils; +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.advancedcore.api.rewards.RewardBuilder; +import com.bencodez.advancedcore.api.user.UserDataFetchMode; +import com.bencodez.simpleapi.array.ArrayUtils; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.user.VotingPluginUser; + +/** Owns VoteParty reward delivery and global command fan-out. */ +public final class VotePartyRewardHandler { + + private final VotingPluginMain plugin; + private final VotePartyState state; + private final VoteParty voteParty; + + public VotePartyRewardHandler(VotingPluginMain plugin, VotePartyState state, VoteParty voteParty) { + this.plugin = plugin; + this.state = state; + this.voteParty = voteParty; + } + + public void giveReward(VotingPluginUser user, boolean useBungee) { + if (plugin.getSpecialRewardsConfig().getVotePartyUserVotesRequired() > 0 + && user.getVotePartyVotes() < plugin.getSpecialRewardsConfig().getVotePartyUserVotesRequired()) { + return; + } + giveReward(user, user.isOnline(), useBungee); + } + + public void giveReward(VotingPluginUser user, boolean online, boolean useBungee) { + new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), + plugin.getSpecialRewardsConfig().getVotePartyRewardsPath()).setOnline(online) + .withPlaceHolder("VotesRequired", "" + plugin.getSpecialRewardsConfig().getVotePartyVotesRequired()) + .withPlaceHolder("FirstVotePartyToday", "" + !plugin.getServerData().isLastVotePartySameDay()) + .setServer(useBungee).send(user); + } + + public void giveRewards(VotingPluginUser triggeringUser, boolean forceBungee) { + MiscUtils.getInstance().broadcast(plugin.getSpecialRewardsConfig().getVotePartyBroadcast()); + dispatchGlobalCommands(randomOnlinePlayerName()); + + if (plugin.getSpecialRewardsConfig().isVotePartyGiveAllPlayers()) { + giveAllPlayers(triggeringUser, forceBungee); + } else { + giveVotedPlayers(triggeringUser, forceBungee); + } + } + + public String randomOnlinePlayerName() { + ArrayList players = new ArrayList<>(); + for (Player player : Bukkit.getOnlinePlayers()) { + players.add(player.getName()); + } + if (players.isEmpty()) { + return "No Player"; + } + return players.get(ThreadLocalRandom.current().nextInt(players.size())); + } + + private void dispatchGlobalCommands(String randomPlayer) { + for (String command : plugin.getSpecialRewardsConfig().getVotePartyGlobalCommands()) { + plugin.getBukkitScheduler().runTask(plugin, + () -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), + PlaceholderUtils.replacePlaceHolder(command, "randomonlineplayer", randomPlayer))); + } + + ArrayList randomCommands = plugin.getSpecialRewardsConfig().getVotePartyGlobalRandomCommand(); + if (!randomCommands.isEmpty()) { + plugin.getBukkitScheduler().runTask(plugin, () -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), + PlaceholderUtils.replacePlaceHolder( + randomCommands.get(ThreadLocalRandom.current().nextInt(randomCommands.size())), + "randomonlineplayer", randomPlayer))); + } + } + + private void giveAllPlayers(VotingPluginUser triggeringUser, boolean forceBungee) { + plugin.debug("Trying to give all players vote party"); + ArrayList alreadyGiven = new ArrayList<>(); + for (Player player : Bukkit.getOnlinePlayers()) { + VotingPluginUser user = resolveUser(triggeringUser, player.getUniqueId(), player); + if (!plugin.getSpecialRewardsConfig().isVotePartyGiveOnlinePlayersOnly() || user.isOnline()) { + voteParty.giveReward(user, forceBungee); + } + alreadyGiven.add(player.getUniqueId().toString()); + } + + for (String uuid : plugin.getVotingPluginUserManager().getAllUUIDs()) { + if (alreadyGiven.contains(uuid)) { + continue; + } + VotingPluginUser user = resolveUser(triggeringUser, UUID.fromString(uuid), null); + if (!plugin.getSpecialRewardsConfig().isVotePartyGiveOnlinePlayersOnly() || user.isOnline()) { + voteParty.giveReward(user, forceBungee); + } + } + } + + private void giveVotedPlayers(VotingPluginUser triggeringUser, boolean forceBungee) { + plugin.debug("Trying to give all voted players vote party"); + plugin.debug(ArrayUtils.makeStringList(state.getVotedUsers())); + for (String uuid : state.getVotedUsers()) { + VotingPluginUser user = resolveUser(triggeringUser, UUID.fromString(uuid), null); + if (!plugin.getSpecialRewardsConfig().isVotePartyGiveOnlinePlayersOnly() || user.isOnline()) { + voteParty.giveReward(user, forceBungee); + } + } + } + + private VotingPluginUser resolveUser(VotingPluginUser triggeringUser, UUID uuid, Player onlinePlayer) { + if (triggeringUser != null && triggeringUser.getJavaUUID().equals(uuid)) { + return triggeringUser; + } + VotingPluginUser user = onlinePlayer == null + ? plugin.getVotingPluginUserManager().getVotingPluginUser(uuid) + : plugin.getVotingPluginUserManager().getVotingPluginUser(onlinePlayer); + user.userDataFetechMode(UserDataFetchMode.NO_CACHE); + return user; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardRegistrar.java new file mode 100644 index 000000000..50ddd13fe --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardRegistrar.java @@ -0,0 +1,16 @@ +package com.bencodez.votingplugin.specialrewards.voteparty; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-party reward paths. */ +public final class VotePartyRewardRegistrar { + + private VotePartyRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("VoteParty.Rewards", + plugin.getSpecialRewardsConfig())); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyState.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyState.java new file mode 100644 index 000000000..99d31ce86 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyState.java @@ -0,0 +1,62 @@ +package com.bencodez.votingplugin.specialrewards.voteparty; + +import java.util.ArrayList; +import java.util.List; + +import com.bencodez.simpleapi.sql.DataType; +import com.bencodez.votingplugin.VotingPluginMain; + +/** Owns persistent VoteParty totals, participant state, and per-user counters. */ +public final class VotePartyState { + + private static final String TOTAL_PATH = "VoteParty.Total"; + private static final String VOTED_PATH = "VoteParty.Voted"; + private static final String USER_COUNT_KEY = "VotePartyVotes"; + + private final VotingPluginMain plugin; + + public VotePartyState(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public int getTotalVotes() { + return plugin.getServerData().getData().getInt(TOTAL_PATH); + } + + public void setTotalVotes(int value) { + plugin.getServerData().getData().set(TOTAL_PATH, value); + plugin.getServerData().saveData(); + } + + public List getVotedUsers() { + List users = plugin.getServerData().getData().getStringList(VOTED_PATH); + return users == null ? new ArrayList<>() : users; + } + + public void setVotedUsers(List users) { + plugin.getServerData().getData().set(VOTED_PATH, users); + plugin.getServerData().saveData(); + } + + public int getVotesRequired() { + int required = plugin.getSpecialRewardsConfig().getVotePartyVotesRequired(); + int extra = plugin.getServerData().getVotePartyExtraRequired(); + return extra > 0 ? required + extra : required; + } + + public int getNeededVotes() { + return getVotesRequired() - getTotalVotes(); + } + + public void reset(boolean resetTotal) { + if (resetTotal) { + setTotalVotes(0); + } + setVotedUsers(new ArrayList<>()); + resetUserCounts(); + } + + public void resetUserCounts() { + plugin.getUserManager().removeAllKeyValues(USER_COUNT_KEY, DataType.INTEGER); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakRewardRegistrar.java new file mode 100644 index 000000000..f74fb5a9d --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakRewardRegistrar.java @@ -0,0 +1,67 @@ +package com.bencodez.votingplugin.specialrewards.votestreak; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-streak reward paths. */ +public final class VoteStreakRewardRegistrar { + + private VoteStreakRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + registerCurrent(plugin); + registerLegacy(plugin); + } + + private static void registerCurrent(VotingPluginMain plugin) { + ConfigurationSection streaks = plugin.getSpecialRewardsConfig().getData().getConfigurationSection("VoteStreaks"); + if (streaks == null) { + return; + } + + for (String streakId : streaks.getKeys(false)) { + if ("ProgressGroups".equalsIgnoreCase(streakId) || streaks.getConfigurationSection(streakId) == null) { + continue; + } + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreaks." + streakId + ".Rewards", plugin.getSpecialRewardsConfig())); + } + + ConfigurationSection groups = streaks.getConfigurationSection("ProgressGroups"); + if (groups == null) { + return; + } + for (String groupId : groups.getKeys(false)) { + ConfigurationSection group = groups.getConfigurationSection(groupId); + if (group == null) { + continue; + } + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreaks.ProgressGroups." + groupId + ".LostRewards", plugin.getSpecialRewardsConfig())); + ConfigurationSection milestones = group.getConfigurationSection("Milestones"); + if (milestones == null) { + continue; + } + for (String milestoneId : milestones.getKeys(false)) { + if (milestones.getConfigurationSection(milestoneId) == null) { + continue; + } + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreaks.ProgressGroups." + groupId + ".Milestones." + milestoneId + ".Rewards", + plugin.getSpecialRewardsConfig())); + } + } + } + + private static void registerLegacy(VotingPluginMain plugin) { + for (String type : new String[] { "Day", "Week", "Month" }) { + for (String value : plugin.getSpecialRewardsConfig().getVoteStreakVotes(type)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreak." + type + "." + value + ".Rewards", plugin.getSpecialRewardsConfig())); + } + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterHandler.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterHandler.java index 3d02d832a..7a5eee0ea 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterHandler.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterHandler.java @@ -1,1097 +1,847 @@ -package com.bencodez.votingplugin.topvoter; - -import java.io.File; -import java.time.LocalDateTime; -import java.time.Month; -import java.time.YearMonth; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -import com.bencodez.advancedcore.api.time.TimeType; -import com.bencodez.advancedcore.api.time.events.DateChangedEvent; -import com.bencodez.advancedcore.api.time.events.DayChangeEvent; -import com.bencodez.advancedcore.api.time.events.MonthChangeEvent; -import com.bencodez.advancedcore.api.time.events.PreDateChangedEvent; -import com.bencodez.advancedcore.api.time.events.WeekChangeEvent; -import com.bencodez.advancedcore.api.user.UserDataFetchMode; -import com.bencodez.advancedcore.api.user.UserStorage; -import com.bencodez.simpleapi.array.ArrayUtils; -import com.bencodez.simpleapi.file.YMLFileHandler; -import com.bencodez.simpleapi.messages.MessageAPI; -import com.bencodez.simpleapi.sql.Column; -import com.bencodez.simpleapi.sql.DataType; -import com.bencodez.votingplugin.VotingPluginMain; -import com.bencodez.votingplugin.user.VotingPluginUser; - -/** - * Handles top voter rankings and statistics. - */ -public class TopVoterHandler implements Listener { - - private VotingPluginMain plugin; - - /** - * Constructs a new top voter handler. - * @param plugin the plugin instance - */ - public TopVoterHandler(VotingPluginMain plugin) { - this.plugin = plugin; - } - - /** - * Checks if bungee should handle resets. - * @return true if bungee handles resets - */ - public boolean bungeeHandleResets() { - if (plugin.getBungeeSettings().isUseBungeecoord()) { - if (plugin.getBungeeSettings().isGloblalDataEnabled()) { - return true; - } - } - - return false; - } - - /** - * Gets monthly top voters at a specific time. - * @param atTime the time to check - * @return map of top voters and their vote counts - */ - public LinkedHashMap getMonthlyTopVotersAtTime(LocalDateTime atTime) { - // int limitSize = plugin.getConfigFile().getMaxiumNumberOfTopVotersToLoad(); - - LinkedHashMap topVoter = new LinkedHashMap<>(); - - // forEachUserKeys is callback-based; wait for completion because this method - // returns a value. - // NOTE: do not call this from the main thread if forEachUserKeys is async. - CountDownLatch latch = new CountDownLatch(1); - - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - if (plugin != null && plugin.isEnabled() && uuid != null) { - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - int total = user.getTotal(TopVoter.Monthly, atTime); - if (total > 0) { - topVoter.put(user.getTopVoterPlayer(), total); - } - - user.clearTempCache(); - } - }, (count) -> { - latch.countDown(); - }); - - try { - latch.await(10, TimeUnit.MINUTES); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - return sortByValues(topVoter, false); - - } - - /** - * Gets the top voter blacklist. - * @return list of blacklisted players - */ - public ArrayList getTopVoterBlackList() { - return plugin.getConfigFile().getBlackList(); - } - - /** - * Gets top voters for a specific month. - * @param month the year-month to check - * @param cols player data columns - * @return map of top voters and their vote counts - */ - public LinkedHashMap getTopVotersOfMonth(YearMonth month, - HashMap> cols) { - - LinkedHashMap totals = new LinkedHashMap<>(); - LocalDateTime atTime = month.atDay(15).atTime(0, 0); - for (Entry> playerData : cols.entrySet()) { - - String uuid = playerData.getKey().toString(); - if (uuid != null && !uuid.isEmpty()) { - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(uuid), - false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(playerData.getValue()); - int total = 0; - total = user.getTotal(TopVoter.Monthly, atTime); - - if (total > 0) { - totals.put(user.getTopVoterPlayer(), total); - } - user.clearTempCache(); - } - } - - return sortByValues(totals, false); - } - - /** - * Top voters weekly. - * - * @return the string[] - */ - - /** - * Gets weekly top voters as formatted strings. - * @return array of formatted top voter lines - */ - public String[] getTopVotersWeekly() { - ArrayList msg = new ArrayList<>(); - ArrayList users = plugin.convertSet(plugin.getTopVoter(TopVoter.Weekly).keySet()); - for (int i = 0; i < users.size(); i++) { - String line = plugin.getConfigFile().getFormatCommandsVoteTopLine().replace("%num%", "" + (i + 1)) - .replace("%player%", users.get(i).getPlayerName()) - .replace("%votes%", "" + plugin.getTopVoter(TopVoter.Weekly).get(users.get(i))); - msg.add(line); - } - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - private HashMap handlePlaces(Set places) { - HashMap place = new HashMap<>(); - for (String p : places) { - String[] data = p.split("-"); - try { - if (data.length > 1) { - for (int i = Integer.parseInt(data[0]); i < Integer.parseInt(data[1]); i++) { - place.put(i, p); - } - } else { - place.put(Integer.parseInt(data[0]), p); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - return place; - } - - /** - * Loads last month's top voter data. - */ - public void loadLastMonth() { - if (!plugin.getGui().isLastMonthGUI()) { - return; - } - - plugin.getLastMonthTopVoter().clear(); - - // If you don’t need insertion order here, HashMap is faster. - final HashMap totals = new HashMap<>(); - - final LocalDateTime lastMonthTime = plugin.getTimeChecker().getTime().minusMonths(1); - final boolean useDateTotalsPrimary = plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal(); - - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - if (uuid == null || plugin == null || !plugin.isEnabled()) { - return; - } - - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - try { - final int total = useDateTotalsPrimary ? user.getTotal(TopVoter.Monthly, lastMonthTime) - : user.getLastMonthTotal(); - - if (total > 0) { - // Only build tvp if we actually need it - totals.put(user.getTopVoterPlayer(), total); - } - } finally { - user.clearTempCache(); - } - - }, (count) -> { - plugin.getLastMonthTopVoter().putAll(sortByValues(new LinkedHashMap<>(totals), false)); - plugin.debug("Loaded last month top voters"); - }); - } - - /** - * Loads previous month top voters for all configured months. - */ - public void loadPreviousMonthTopVoters() { - if (!plugin.getConfigFile().isStoreMonthTotalsWithDate()) { - return; - } - - final LocalDateTime now = plugin.getTimeChecker().getTime(); - final YearMonth currentYm = YearMonth.of(now.getYear(), now.getMonth()); - - // Discover month columns -> YearMonth list + matching column names - final ArrayList months = new ArrayList<>(); - final ArrayList monthColumns = new ArrayList<>(); - - for (String column : plugin.getUserManager().getAllColumns()) { - if (!column.startsWith("MonthTotal-")) { - continue; - } - - // Expected: MonthTotal-MONTH-YEAR (ex: MonthTotal-JANUARY-2025) - // Faster than split("-") (regex) - final int firstDash = column.indexOf('-'); // after MonthTotal - final int secondDash = column.indexOf('-', firstDash + 1); // after MONTH - if (firstDash < 0 || secondDash < 0) { - continue; - } - - final String monthStr = column.substring(firstDash + 1, secondDash); - final String yearStr = column.substring(secondDash + 1); - - if (!MessageAPI.isInt(yearStr)) { - continue; - } - - try { - final int year = Integer.parseInt(yearStr); - final Month month = Month.valueOf(monthStr); - final YearMonth ym = YearMonth.of(year, month); - - if (ym.isBefore(currentYm)) { - months.add(ym); - monthColumns.add(column); - } - } catch (Exception ignored) { - // bad month string etc. - } - } - - if (months.isEmpty()) { - return; - } - - // Sort months AND keep monthColumns aligned - { - final Integer[] idx = new Integer[months.size()]; - for (int i = 0; i < idx.length; i++) - idx[i] = i; - - java.util.Arrays.sort(idx, (a, b) -> months.get(a).compareTo(months.get(b))); - - final ArrayList monthsSorted = new ArrayList<>(months.size()); - final ArrayList colsSorted = new ArrayList<>(months.size()); - for (int i = 0; i < idx.length; i++) { - monthsSorted.add(months.get(idx[i])); - colsSorted.add(monthColumns.get(idx[i])); - } - - months.clear(); - months.addAll(monthsSorted); - - monthColumns.clear(); - monthColumns.addAll(colsSorted); - } - - // Prepare destination maps once - final LinkedHashMap> prev = plugin - .getPreviousMonthsTopVoters(); - prev.clear(); - - for (YearMonth ym : months) { - plugin.debug("Loading previous month top voters of " + ym); - prev.put(ym, new LinkedHashMap<>()); - } - - @SuppressWarnings("unchecked") - final LinkedHashMap[] monthMaps = new LinkedHashMap[months.size()]; - for (int i = 0; i < months.size(); i++) { - monthMaps[i] = prev.get(months.get(i)); - } - - // Snapshot arrays for faster access inside the loop - final String[] monthColsArr = monthColumns.toArray(new String[0]); - - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - if (uuid == null) { - return; - } - - final VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - try { - TopVoterPlayer tvp = null; - - // Read MonthTotal-* columns directly (FAST) - for (int i = 0; i < monthColsArr.length; i++) { - final int total = user.getData().getInt(monthColsArr[i], 0, UserDataFetchMode.TEMP_ONLY); - if (total > 0) { - if (tvp == null) { - tvp = user.getTopVoterPlayer(); - } - monthMaps[i].put(tvp, total); - } - } - } finally { - user.clearTempCache(); - } - - }, (count) -> { - // Sort each month map once - for (int i = 0; i < months.size(); i++) { - final YearMonth ym = months.get(i); - prev.put(ym, sortByValues(prev.get(ym), false)); - } - - plugin.extraDebug("Previous Months: " + prev.keySet()); - }); - } - - /** - * Handles date change events. - * - * @param event date changed event - */ - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onDateChanged(DateChangedEvent event) { - plugin.setUpdate(true); - plugin.update(); - if (event.getTimeType().equals(TimeType.MONTH)) { - loadLastMonth(); - } - if (plugin.getStorageType().equals(UserStorage.MYSQL)) { - plugin.getMysql().clearCacheBasic(); - } - } - - /** - * Handles day change events. - * @param event the day change event - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onDayChange(DayChangeEvent event) { - synchronized (VotingPluginMain.plugin) { - long startTime = System.currentTimeMillis(); - if (plugin.getConfigFile().isStoreTopVotersDaily()) { - plugin.getLogger().info("Saving TopVoters Daily"); - storeTopVoters(TopVoter.Daily); - } - - plugin.getUserManager().copyColumnData(TopVoter.Daily.getColumnName(), TopVoter.Daily.getLastColumnName()); - if (plugin.getConfigFile().isUseVoteStreaks() || plugin.getConfigFile().isUseHighestTotals()) { - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - if (plugin.getConfigFile().isUseVoteStreaks()) { - if (!user.voteStreakUpdatedToday(LocalDateTime.now().minusDays(1))) { - if (user.getDayVoteStreak() != 0) { - user.setDayVoteStreak(0); - } - } - } - - if (plugin.getConfigFile().isUseHighestTotals()) { - if (user.getHighestDailyTotal() < user.getTotal(TopVoter.Daily)) { - user.setHighestDailyTotal(user.getTotal(TopVoter.Daily)); - } - } - user.clearTempCache(); - }, (count) -> { - // finished - }); - } - - try { - if (plugin.getSpecialRewardsConfig().isEnableDailyRewards()) { - HashMap places = handlePlaces( - plugin.getSpecialRewardsConfig().getDailyPossibleRewardPlaces()); - int i = 0; - int lastTotal = -1; - @SuppressWarnings("unchecked") - LinkedHashMap clone = (LinkedHashMap) plugin - .getTopVoter(TopVoter.Daily).clone(); - for (Entry entry : clone.entrySet()) { - if (plugin.getConfigFile().isTopVoterAwardsTies()) { - if (entry.getValue().intValue() != lastTotal) { - i++; - } - } else { - i++; - } - if (places.containsKey(i)) { - VotingPluginUser user = entry.getKey().getUser(); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - if (!plugin.getConfigFile().isTopVoterIgnorePermission() || !user.isTopVoterIgnore()) { - user.giveDailyTopVoterAward(i, places.get(i)); - plugin.getLogger().info( - "Giving daily top voter reward " + i + " to " + entry.getKey().getPlayerName()); - } - } - lastTotal = entry.getValue().intValue(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - for (String shopIdent : plugin.getShopFile().getShopIdentifiers()) { - if (plugin.getShopFile().getVoteShopResetDaily(shopIdent)) { - resetVoteShopLimit(shopIdent); - } - } - - // give time for other servers to catch up - if (!plugin.getTopVoterHandler().bungeeHandleResets() && plugin.getBungeeSettings().isUseBungeecoord()) { - plugin.debug("Delaying time change 10 seconds for other servers to catchup"); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - if (!bungeeHandleResets()) { - resetTotals(TopVoter.Daily); - } - - if (plugin.getStorageType().equals(UserStorage.MYSQL)) { - plugin.getMysql().clearCacheBasic(); - } - - long now = ((System.currentTimeMillis() - startTime) / 1000); - plugin.getLogger().info("Finished processing day change, took " + now + " seconds"); - } - } - - /** - * Handles month change events. - * @param event the month change event - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onMonthChange(MonthChangeEvent event) { - long startTime = System.currentTimeMillis(); - synchronized (VotingPluginMain.plugin) { - plugin.getLogger().info("Saving TopVoters Monthly"); - storeTopVoters(TopVoter.Monthly); - if (!bungeeHandleResets()) { - plugin.getUserManager().copyColumnData(TopVoter.Monthly.getColumnName(), - TopVoter.Monthly.getLastColumnName()); - } - LocalDateTime lastMonthTime = plugin.getTimeChecker().getTime().minusMonths(1); - if (plugin.getConfigFile().isUseHighestTotals() || plugin.getConfigFile().isUseVoteStreaks()) { - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - if (plugin.getConfigFile().isUseVoteStreaks()) { - if (user.getTotal(TopVoter.Monthly, lastMonthTime) == 0 && user.getMonthVoteStreak() != 0) { - user.setMonthVoteStreak(0); - } else { - if (!plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() - || user.hasPercentageTotal(TopVoter.Monthly, - plugin.getSpecialRewardsConfig().getVoteStreakRequirementMonth(), - lastMonthTime)) { - user.addMonthVoteStreak(); - plugin.getSpecialRewards().checkVoteStreak(null, user, "Month", - plugin.getBungeeSettings().isUseBungeecoord()); - } - } - } - - // using new system - // user.setLastMonthTotal(user.getTotal(TopVoter.Monthly)); - - if (plugin.getConfigFile().isUseHighestTotals()) { - if (user.getHighestMonthlyTotal() < user.getTotal(TopVoter.Monthly, lastMonthTime)) { - user.setHighestMonthlyTotal(user.getTotal(TopVoter.Monthly, lastMonthTime)); - } - } - user.clearTempCache(); - }, (count) -> { - // finished - }); - - } - - try { - if (plugin.getSpecialRewardsConfig().isEnableMonthlyAwards()) { - HashMap places = handlePlaces( - plugin.getSpecialRewardsConfig().getMonthlyPossibleRewardPlaces()); - int i = 0; - int lastTotal = -1; - - LinkedHashMap topVoters = null; - if (plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal()) { - topVoters = getMonthlyTopVotersAtTime(lastMonthTime); - } else { - @SuppressWarnings("unchecked") - LinkedHashMap clone = (LinkedHashMap) plugin - .getTopVoter(TopVoter.Monthly).clone(); - topVoters = clone; - } - - for (Entry entry : topVoters.entrySet()) { - if (plugin.getConfigFile().isTopVoterAwardsTies()) { - if (entry.getValue().intValue() != lastTotal) { - i++; - } - } else { - i++; - } - - if (places.containsKey(i)) { - VotingPluginUser user = entry.getKey().getUser(); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - if (!plugin.getConfigFile().isTopVoterIgnorePermission() || !user.isTopVoterIgnore()) { - user.giveMonthlyTopVoterAward(i, places.get(i)); - plugin.getLogger().info("Giving Monthly top voter reward " + i + " to " - + entry.getKey().getPlayerName()); - } - } - lastTotal = entry.getValue().intValue(); - } - - } - } catch (Exception e) { - e.printStackTrace(); - } - - for (String shopIdent : plugin.getShopFile().getShopIdentifiers()) { - if (plugin.getShopFile().getVoteShopResetMonthly(shopIdent)) { - resetVoteShopLimit(shopIdent); - } - } - - // give time for other servers to catch up - if (!bungeeHandleResets() && plugin.getBungeeSettings().isUseBungeecoord()) { - plugin.debug("Delaying time change 10 seconds for other servers to catchup"); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - if (!bungeeHandleResets()) { - resetTotals(TopVoter.Monthly); - } - - if (plugin.getStorageType().equals(UserStorage.MYSQL)) { - plugin.getMysql().clearCacheBasic(); - } - long now = ((System.currentTimeMillis() - startTime) / 1000); - plugin.getLogger().info("Finished processing day change, took " + now + " seconds"); - } - } - - /** - * Handles pre-date change events. - * @param event the pre-date changed event - */ - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onPreDateChanged(PreDateChangedEvent event) { - if (event.getTimeType().equals(TimeType.DAY)) { - plugin.getBannedPlayers().clear(); - for (OfflinePlayer p : Bukkit.getBannedPlayers()) { - plugin.getBannedPlayers().add(p.getUniqueId().toString()); - } - } - plugin.setUpdate(true); - plugin.update(); - } - - /** - * Handles week change events. - * - * @param event week change event - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onWeekChange(WeekChangeEvent event) { - long startTime = System.currentTimeMillis(); - synchronized (VotingPluginMain.plugin) { - if (plugin.getConfigFile().isStoreTopVotersWeekly()) { - plugin.getLogger().info("Saving TopVoters Weekly"); - storeTopVoters(TopVoter.Weekly); - } - - plugin.getUserManager().copyColumnData(TopVoter.Weekly.getColumnName(), - TopVoter.Weekly.getLastColumnName()); - if (plugin.getConfigFile().isUseVoteStreaks() || plugin.getConfigFile().isUseHighestTotals()) { - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - if (plugin.getConfigFile().isUseVoteStreaks()) { - if (user.getTotal(TopVoter.Weekly) == 0 && user.getWeekVoteStreak() != 0) { - user.setWeekVoteStreak(0); - } else { - if (!plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() - || user.hasPercentageTotal(TopVoter.Weekly, - plugin.getSpecialRewardsConfig().getVoteStreakRequirementWeek(), null)) { - user.addWeekVoteStreak(); - plugin.getSpecialRewards().checkVoteStreak(null, user, "Week", - plugin.getBungeeSettings().isUseBungeecoord()); - } - } - } - - if (plugin.getConfigFile().isUseHighestTotals()) { - if (user.getHighestWeeklyTotal() < user.getTotal(TopVoter.Weekly)) { - user.setHighestWeeklyTotal(user.getTotal(TopVoter.Weekly)); - } - } - user.clearTempCache(); - }, (count) -> { - // finished - }); - } - - try { - if (plugin.getSpecialRewardsConfig().isEnableWeeklyAwards()) { - HashMap places = handlePlaces( - plugin.getSpecialRewardsConfig().getWeeklyPossibleRewardPlaces()); - int i = 0; - int lastTotal = -1; - @SuppressWarnings("unchecked") - LinkedHashMap clone = (LinkedHashMap) plugin - .getTopVoter(TopVoter.Weekly).clone(); - for (Entry entry : clone.entrySet()) { - if (plugin.getConfigFile().isTopVoterAwardsTies()) { - if (entry.getValue().intValue() != lastTotal) { - i++; - } - } else { - i++; - } - if (places.containsKey(i)) { - VotingPluginUser user = entry.getKey().getUser(); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - if (!plugin.getConfigFile().isTopVoterIgnorePermission() || !user.isTopVoterIgnore()) { - user.giveWeeklyTopVoterAward(i, places.get(i)); - plugin.getLogger().info("Giving weekly top voter reward " + i + " to " - + entry.getKey().getPlayerName()); - } - } - lastTotal = entry.getValue().intValue(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - for (String shopIdent : plugin.getShopFile().getShopIdentifiers()) { - if (plugin.getShopFile().getVoteShopResetWeekly(shopIdent)) { - resetVoteShopLimit(shopIdent); - } - } - - // give time for other servers to catch up - if (!plugin.getTopVoterHandler().bungeeHandleResets() && plugin.getBungeeSettings().isUseBungeecoord()) { - plugin.debug("Delaying time change 10 seconds for other servers to catchup"); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - if (!bungeeHandleResets()) { - resetTotals(TopVoter.Weekly); - } - - if (plugin.getStorageType().equals(UserStorage.MYSQL)) { - plugin.getMysql().clearCacheBasic(); - } - - long now = ((System.currentTimeMillis() - startTime) / 1000); - plugin.getLogger().info("Finished processing day change, took " + now + " seconds"); - } - } - - /** - * Registers this handler as a listener. - */ - public void register() { - plugin.getServer().getPluginManager().registerEvents(this, plugin); - } - - /** - * Resets vote totals for a specific period. - * @param topVoter the top voter period to reset - */ - public void resetTotals(TopVoter topVoter) { - plugin.getUserManager().removeAllKeyValues(topVoter.getColumnName(), DataType.INTEGER); - } - - /** - * Resets vote shop limits for a specific shop. - * @param shopIdent the shop identifier - */ - public void resetVoteShopLimit(String shopIdent) { - plugin.getUserManager().removeAllKeyValues("VoteShopLimit" + shopIdent, DataType.INTEGER); - } - - /** - * Sorts top voters by their vote counts. - * @param map the map to sort - * @param order true for ascending order, false for descending - * @return sorted map of top voters - */ - public LinkedHashMap sortByValues(LinkedHashMap map, - final boolean order) { - - List> list = new LinkedList<>(map.entrySet()); - - // Sorting the list based on values - Collections.sort(list, new Comparator>() { - @Override - public int compare(Entry o1, Entry o2) { - if (order) { - int result = (o1.getValue()).compareTo(o2.getValue()); - if (result != 0) { - return result; - } - return o2.getKey().getLastVoteTime().compareTo(o1.getKey().getLastVoteTime()); - } - int result = (o2.getValue()).compareTo(o1.getValue()); - if (result != 0) { - return result; - } - return o1.getKey().getLastVoteTime().compareTo(o2.getKey().getLastVoteTime()); - } - }); - - // Maintaining insertion order with the help of LinkedList - LinkedHashMap sortedMap = new LinkedHashMap<>(); - for (Entry entry : list) { - sortedMap.put(entry.getKey(), entry.getValue()); - } - - return sortedMap; - } - - /** - * Stores top voter data to file. - * @param top the top voter period to store - */ - public void storeTopVoters(TopVoter top) { - LocalDateTime time = LocalDateTime.now().minusDays(1); - String month = time.getMonth().toString(); - int year = time.getYear(); - int week = time.getDayOfYear(); - int day = time.getDayOfMonth(); - String fileName = "TopVoter" + File.separator + top.toString() + File.separator + year + "_" + month; - - if (top.equals(TopVoter.Daily)) { - fileName += "_" + day; - } else if (top.equals(TopVoter.Weekly)) { - fileName += "_" + week; - } - fileName += "_" + System.currentTimeMillis() + ".yml"; - - YMLFileHandler file = new YMLFileHandler(plugin, new File(plugin.getDataFolder(), fileName)); - file.setup(); - file.header("Saving top voters for " + top.toString() + ", file also contains other top voter info as backup"); - for (TopVoter cTop : TopVoter.values()) { - ArrayList topVoters = new ArrayList<>(); - int cTotal = 0; - try { - ArrayList nums = plugin.getUserManager().getNumbersInColumn(cTop.getColumnName()); - for (Integer num : nums) { - cTotal += num.intValue(); - } - topVoters.add("Combined total: " + cTotal); - } catch (Exception e) { - e.printStackTrace(); - } - if (plugin.getTopVoter().containsKey(cTop)) { - - int count = 1; - for (Entry entry : plugin.getTopVoter(cTop).entrySet()) { - topVoters.add(count + ": " + entry.getKey().getPlayerName() + ": " + entry.getValue()); - count++; - } - file.getData().set(cTop.toString(), topVoters); - } - } - file.saveData(); - } - - /** - * Top voter all time - * - * @param page the page - * @return the string[] - */ - public String[] topVoterAllTime(int page) { - int pagesize = plugin.getConfigFile().getFormatPageSize(); - ArrayList msg = new ArrayList<>(); - ArrayList topVoters = new ArrayList<>(); - int count = 1; - for (Entry entry : plugin.getTopVoter(TopVoter.AllTime).entrySet()) { - String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); - line = line.replace("%num%", "" + count); - line = line.replace("%player%", entry.getKey().getPlayerName()); - line = line.replace("%votes%", "" + entry.getValue()); - topVoters.add(line); - count++; - } - - int pageSize = (topVoters.size() / pagesize); - if ((topVoters.size() % pagesize) != 0) { - pageSize++; - } - - String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); - title = title.replace("%page%", "" + page); - title = title.replace("%maxpages%", "" + pageSize); - title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterAllTime()); - msg.add(MessageAPI.colorize(title)); - - for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { - msg.add(topVoters.get(i)); - } - - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - /** - * Gets daily top voters with pagination. - * @param page the page number - * @return array of formatted top voter lines - */ - public String[] topVoterDaily(int page) { - int pagesize = plugin.getConfigFile().getFormatPageSize(); - ArrayList msg = new ArrayList<>(); - ArrayList topVoters = new ArrayList<>(); - int count = 1; - for (Entry entry : plugin.getTopVoter(TopVoter.Daily).entrySet()) { - String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); - line = line.replace("%num%", "" + count); - line = line.replace("%player%", entry.getKey().getPlayerName()); - line = line.replace("%votes%", "" + entry.getValue()); - topVoters.add(line); - count++; - } - - int pageSize = (topVoters.size() / pagesize); - if ((topVoters.size() % pagesize) != 0) { - pageSize++; - } - - String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); - title = title.replace("%page%", "" + page); - title = title.replace("%maxpages%", "" + pageSize); - title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterDaily()); - msg.add(MessageAPI.colorize(title)); - - for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { - msg.add(topVoters.get(i)); - } - - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - /** - * Gets monthly top voters with pagination. - * @param page the page number - * @return array of formatted top voter lines - */ - public String[] topVoterMonthly(int page) { - int pagesize = plugin.getConfigFile().getFormatPageSize(); - ArrayList msg = new ArrayList<>(); - ArrayList topVoters = new ArrayList<>(); - int count = 1; - for (Entry entry : plugin.getTopVoter(TopVoter.Monthly).entrySet()) { - String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); - line = line.replace("%num%", "" + count); - line = line.replace("%player%", entry.getKey().getPlayerName()); - line = line.replace("%votes%", "" + entry.getValue()); - topVoters.add(line); - count++; - } - - int pageSize = (topVoters.size() / pagesize); - if ((topVoters.size() % pagesize) != 0) { - pageSize++; - } - - String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); - title = title.replace("%page%", "" + page); - title = title.replace("%maxpages%", "" + pageSize); - title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterMonthly()); - msg.add(MessageAPI.colorize(title)); - - for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { - msg.add(topVoters.get(i)); - } - - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - /** - * Gets all-time top voters as formatted strings. - * @return array of formatted top voter lines - */ - public String[] topVotersAllTime() { - ArrayList msg = new ArrayList<>(); - List> list = new LinkedList<>(plugin.getTopVoter(TopVoter.AllTime).entrySet()); - int i = 0; - for (Entry entry : list) { - String line = "%num%: %player%, %votes%"; - line = line.replace("%num%", "" + (i + 1)); - try { - line = line.replace("%player%", entry.getKey().getPlayerName()); - } catch (Exception ex) { - VotingPluginMain.plugin.debug(ex); - } - line = line.replace("%votes%", "" + entry.getValue()); - - msg.add(line); - i++; - } - - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - /** - * Gets daily top voters as formatted strings. - * @return array of formatted top voter lines - */ - public String[] topVotersDaily() { - ArrayList msg = new ArrayList<>(); - ArrayList users = plugin.convertSet(plugin.getTopVoter(TopVoter.Daily).keySet()); - for (int i = 0; i < users.size(); i++) { - String line = "%num%: %player%, %votes%"; - line = line.replace("%num%", "" + (i + 1)); - try { - line = line.replace("%player%", users.get(i).getPlayerName()); - } catch (Exception ex) { - VotingPluginMain.plugin.debug(ex); - } - line = line.replace("%votes%", "" + plugin.getTopVoter(TopVoter.Monthly).get(users.get(i))); - msg.add(line); - } - - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - /** - * Gets monthly top voters as formatted strings. - * @return array of formatted top voter lines - */ - public String[] topVotersMonthly() { - ArrayList msg = new ArrayList<>(); - List> list = new LinkedList<>(plugin.getTopVoter(TopVoter.Monthly).entrySet()); - int i = 0; - for (Entry entry : list) { - String line = "%num%: %player%, %votes%"; - line = line.replace("%num%", "" + (i + 1)); - try { - line = line.replace("%player%", entry.getKey().getPlayerName()); - } catch (Exception ex) { - VotingPluginMain.plugin.debug(ex); - } - line = line.replace("%votes%", "" + entry.getValue()); - - msg.add(line); - i++; - } - - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - /** - * Gets weekly top voters with pagination. - * @param page the page number - * @return array of formatted top voter lines - */ - public String[] topVoterWeekly(int page) { - int pagesize = plugin.getConfigFile().getFormatPageSize(); - ArrayList msg = new ArrayList<>(); - ArrayList topVoters = new ArrayList<>(); - int count = 1; - for (Entry entry : plugin.getTopVoter(TopVoter.Weekly).entrySet()) { - String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); - line = line.replace("%num%", "" + count); - line = line.replace("%player%", entry.getKey().getPlayerName()); - line = line.replace("%votes%", "" + entry.getValue()); - topVoters.add(line); - count++; - } - - int pageSize = (topVoters.size() / pagesize); - if ((topVoters.size() % pagesize) != 0) { - pageSize++; - } - - String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); - title = title.replace("%page%", "" + page); - title = title.replace("%maxpages%", "" + pageSize); - title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterWeekly()); - msg.add(MessageAPI.colorize(title)); - - for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { - msg.add(topVoters.get(i)); - } - - msg = ArrayUtils.colorize(msg); - return ArrayUtils.convert(msg); - } - - /** - * Updates top voter rankings for a specific period. - * @param tempTopVoter map of top voter periods to player rankings - */ - public synchronized void updateTopVoters( - LinkedHashMap> tempTopVoter) { - - int limitSize = plugin.getConfigFile().getMaxiumNumberOfTopVotersToLoad(); - for (Entry> entry : tempTopVoter.entrySet()) { - LinkedHashMap map = entry.getValue(); - map = sortByValues(map, false); - if (limitSize > 0) { - ArrayList listKeys = new ArrayList<>(map.keySet()); - if (listKeys.size() > limitSize) { - for (int i = listKeys.size() - 1; i >= 0 && i >= limitSize; i--) { - map.remove(listKeys.get(i)); - } - } - - } - tempTopVoter.put(entry.getKey(), map); - plugin.setTopVoter(tempTopVoter); - plugin.debug(entry.getKey().getName() + " TopVoter loaded, number of players " + map.size()); - - } - - plugin.debug("Updated TopVoter"); - } -} +package com.bencodez.votingplugin.topvoter; + +import java.io.File; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import com.bencodez.advancedcore.api.time.TimeType; +import com.bencodez.advancedcore.api.time.events.DateChangedEvent; +import com.bencodez.advancedcore.api.time.events.DayChangeEvent; +import com.bencodez.advancedcore.api.time.events.MonthChangeEvent; +import com.bencodez.advancedcore.api.time.events.PreDateChangedEvent; +import com.bencodez.advancedcore.api.time.events.WeekChangeEvent; +import com.bencodez.advancedcore.api.user.UserDataFetchMode; +import com.bencodez.advancedcore.api.user.UserStorage; +import com.bencodez.simpleapi.array.ArrayUtils; +import com.bencodez.simpleapi.file.YMLFileHandler; +import com.bencodez.simpleapi.messages.MessageAPI; +import com.bencodez.simpleapi.sql.Column; +import com.bencodez.simpleapi.sql.DataType; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.user.VotingPluginUser; + +/** + * Handles top voter rankings and statistics. + */ +public class TopVoterHandler implements Listener { + + private VotingPluginMain plugin; + private final TopVoterLoader loader; + + /** + * Constructs a new top voter handler. + * @param plugin the plugin instance + */ + public TopVoterHandler(VotingPluginMain plugin) { + this.plugin = plugin; + this.loader = new TopVoterLoader(plugin); + } + + /** + * Checks if bungee should handle resets. + * @return true if bungee handles resets + */ + public boolean bungeeHandleResets() { + if (plugin.getBungeeSettings().isUseBungeecoord()) { + if (plugin.getBungeeSettings().isGloblalDataEnabled()) { + return true; + } + } + + return false; + } + + /** + * Gets monthly top voters at a specific time. + * @param atTime the time to check + * @return map of top voters and their vote counts + */ + public LinkedHashMap getMonthlyTopVotersAtTime(LocalDateTime atTime) { + return loader.getMonthlyTopVotersAtTime(atTime); + } + + + /** + * Gets the top voter blacklist. + * @return list of blacklisted players + */ + public ArrayList getTopVoterBlackList() { + return plugin.getConfigFile().getBlackList(); + } + + /** + * Gets top voters for a specific month. + * @param month the year-month to check + * @param cols player data columns + * @return map of top voters and their vote counts + */ + public LinkedHashMap getTopVotersOfMonth(YearMonth month, + HashMap> cols) { + return loader.getTopVotersOfMonth(month, cols); + } + + + /** + * Top voters weekly. + * + * @return the string[] + */ + + /** + * Gets weekly top voters as formatted strings. + * @return array of formatted top voter lines + */ + public String[] getTopVotersWeekly() { + ArrayList msg = new ArrayList<>(); + ArrayList users = new ArrayList<>(plugin.getTopVoter(TopVoter.Weekly).keySet()); + for (int i = 0; i < users.size(); i++) { + String line = plugin.getConfigFile().getFormatCommandsVoteTopLine().replace("%num%", "" + (i + 1)) + .replace("%player%", users.get(i).getPlayerName()) + .replace("%votes%", "" + plugin.getTopVoter(TopVoter.Weekly).get(users.get(i))); + msg.add(line); + } + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + private HashMap handlePlaces(Set places) { + return TopVoterRanking.mapAwardPlaces(places); + } + + + /** + * Loads last month's top voter data. + */ + public void loadLastMonth() { + loader.loadLastMonth(); + } + + + /** + * Loads previous month top voters for all configured months. + */ + public void loadPreviousMonthTopVoters() { + loader.loadPreviousMonthTopVoters(); + } + + + /** + * Handles date change events. + * + * @param event date changed event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onDateChanged(DateChangedEvent event) { + plugin.setUpdate(true); + plugin.update(); + if (event.getTimeType().equals(TimeType.MONTH)) { + loadLastMonth(); + } + if (plugin.getStorageType().equals(UserStorage.MYSQL)) { + plugin.getMysql().clearCacheBasic(); + } + } + + /** + * Handles day change events. + * @param event the day change event + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDayChange(DayChangeEvent event) { + synchronized (VotingPluginMain.plugin) { + long startTime = System.currentTimeMillis(); + if (plugin.getConfigFile().isStoreTopVotersDaily()) { + plugin.getLogger().info("Saving TopVoters Daily"); + storeTopVoters(TopVoter.Daily); + } + + plugin.getUserManager().copyColumnData(TopVoter.Daily.getColumnName(), TopVoter.Daily.getLastColumnName()); + if (plugin.getConfigFile().isUseVoteStreaks() || plugin.getConfigFile().isUseHighestTotals()) { + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + + if (plugin.getConfigFile().isUseVoteStreaks()) { + if (!user.voteStreakUpdatedToday(LocalDateTime.now().minusDays(1))) { + if (user.getDayVoteStreak() != 0) { + user.setDayVoteStreak(0); + } + } + } + + if (plugin.getConfigFile().isUseHighestTotals()) { + if (user.getHighestDailyTotal() < user.getTotal(TopVoter.Daily)) { + user.setHighestDailyTotal(user.getTotal(TopVoter.Daily)); + } + } + user.clearTempCache(); + }, (count) -> { + // finished + }); + } + + try { + if (plugin.getSpecialRewardsConfig().isEnableDailyRewards()) { + HashMap places = handlePlaces( + plugin.getSpecialRewardsConfig().getDailyPossibleRewardPlaces()); + int i = 0; + int lastTotal = -1; + @SuppressWarnings("unchecked") + LinkedHashMap clone = (LinkedHashMap) plugin + .getTopVoter(TopVoter.Daily).clone(); + for (Entry entry : clone.entrySet()) { + if (plugin.getConfigFile().isTopVoterAwardsTies()) { + if (entry.getValue().intValue() != lastTotal) { + i++; + } + } else { + i++; + } + if (places.containsKey(i)) { + VotingPluginUser user = entry.getKey().getUser(); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + if (!plugin.getConfigFile().isTopVoterIgnorePermission() || !user.isTopVoterIgnore()) { + user.giveDailyTopVoterAward(i, places.get(i)); + plugin.getLogger().info( + "Giving daily top voter reward " + i + " to " + entry.getKey().getPlayerName()); + } + } + lastTotal = entry.getValue().intValue(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + for (String shopIdent : plugin.getShopFile().getShopIdentifiers()) { + if (plugin.getShopFile().getVoteShopResetDaily(shopIdent)) { + resetVoteShopLimit(shopIdent); + } + } + + // give time for other servers to catch up + if (!plugin.getTopVoterHandler().bungeeHandleResets() && plugin.getBungeeSettings().isUseBungeecoord()) { + plugin.debug("Delaying time change 10 seconds for other servers to catchup"); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (!bungeeHandleResets()) { + resetTotals(TopVoter.Daily); + } + + if (plugin.getStorageType().equals(UserStorage.MYSQL)) { + plugin.getMysql().clearCacheBasic(); + } + + long now = ((System.currentTimeMillis() - startTime) / 1000); + plugin.getLogger().info("Finished processing day change, took " + now + " seconds"); + } + } + + /** + * Handles month change events. + * @param event the month change event + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onMonthChange(MonthChangeEvent event) { + long startTime = System.currentTimeMillis(); + synchronized (VotingPluginMain.plugin) { + plugin.getLogger().info("Saving TopVoters Monthly"); + storeTopVoters(TopVoter.Monthly); + if (!bungeeHandleResets()) { + plugin.getUserManager().copyColumnData(TopVoter.Monthly.getColumnName(), + TopVoter.Monthly.getLastColumnName()); + } + LocalDateTime lastMonthTime = plugin.getTimeChecker().getTime().minusMonths(1); + if (plugin.getConfigFile().isUseHighestTotals() || plugin.getConfigFile().isUseVoteStreaks()) { + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + + if (plugin.getConfigFile().isUseVoteStreaks()) { + if (user.getTotal(TopVoter.Monthly, lastMonthTime) == 0 && user.getMonthVoteStreak() != 0) { + user.setMonthVoteStreak(0); + } else { + if (!plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() + || user.hasPercentageTotal(TopVoter.Monthly, + plugin.getSpecialRewardsConfig().getVoteStreakRequirementMonth(), + lastMonthTime)) { + user.addMonthVoteStreak(); + plugin.getSpecialRewards().checkVoteStreak(null, user, "Month", + plugin.getBungeeSettings().isUseBungeecoord()); + } + } + } + + // using new system + // user.setLastMonthTotal(user.getTotal(TopVoter.Monthly)); + + if (plugin.getConfigFile().isUseHighestTotals()) { + if (user.getHighestMonthlyTotal() < user.getTotal(TopVoter.Monthly, lastMonthTime)) { + user.setHighestMonthlyTotal(user.getTotal(TopVoter.Monthly, lastMonthTime)); + } + } + user.clearTempCache(); + }, (count) -> { + // finished + }); + + } + + try { + if (plugin.getSpecialRewardsConfig().isEnableMonthlyAwards()) { + HashMap places = handlePlaces( + plugin.getSpecialRewardsConfig().getMonthlyPossibleRewardPlaces()); + int i = 0; + int lastTotal = -1; + + LinkedHashMap topVoters = null; + if (plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal()) { + topVoters = getMonthlyTopVotersAtTime(lastMonthTime); + } else { + @SuppressWarnings("unchecked") + LinkedHashMap clone = (LinkedHashMap) plugin + .getTopVoter(TopVoter.Monthly).clone(); + topVoters = clone; + } + + for (Entry entry : topVoters.entrySet()) { + if (plugin.getConfigFile().isTopVoterAwardsTies()) { + if (entry.getValue().intValue() != lastTotal) { + i++; + } + } else { + i++; + } + + if (places.containsKey(i)) { + VotingPluginUser user = entry.getKey().getUser(); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + if (!plugin.getConfigFile().isTopVoterIgnorePermission() || !user.isTopVoterIgnore()) { + user.giveMonthlyTopVoterAward(i, places.get(i)); + plugin.getLogger().info("Giving Monthly top voter reward " + i + " to " + + entry.getKey().getPlayerName()); + } + } + lastTotal = entry.getValue().intValue(); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + + for (String shopIdent : plugin.getShopFile().getShopIdentifiers()) { + if (plugin.getShopFile().getVoteShopResetMonthly(shopIdent)) { + resetVoteShopLimit(shopIdent); + } + } + + // give time for other servers to catch up + if (!bungeeHandleResets() && plugin.getBungeeSettings().isUseBungeecoord()) { + plugin.debug("Delaying time change 10 seconds for other servers to catchup"); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (!bungeeHandleResets()) { + resetTotals(TopVoter.Monthly); + } + + if (plugin.getStorageType().equals(UserStorage.MYSQL)) { + plugin.getMysql().clearCacheBasic(); + } + long now = ((System.currentTimeMillis() - startTime) / 1000); + plugin.getLogger().info("Finished processing day change, took " + now + " seconds"); + } + } + + /** + * Handles pre-date change events. + * @param event the pre-date changed event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPreDateChanged(PreDateChangedEvent event) { + if (event.getTimeType().equals(TimeType.DAY)) { + plugin.getBannedPlayers().clear(); + for (OfflinePlayer p : Bukkit.getBannedPlayers()) { + plugin.getBannedPlayers().add(p.getUniqueId().toString()); + } + } + plugin.setUpdate(true); + plugin.update(); + } + + /** + * Handles week change events. + * + * @param event week change event + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onWeekChange(WeekChangeEvent event) { + long startTime = System.currentTimeMillis(); + synchronized (VotingPluginMain.plugin) { + if (plugin.getConfigFile().isStoreTopVotersWeekly()) { + plugin.getLogger().info("Saving TopVoters Weekly"); + storeTopVoters(TopVoter.Weekly); + } + + plugin.getUserManager().copyColumnData(TopVoter.Weekly.getColumnName(), + TopVoter.Weekly.getLastColumnName()); + if (plugin.getConfigFile().isUseVoteStreaks() || plugin.getConfigFile().isUseHighestTotals()) { + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + + if (plugin.getConfigFile().isUseVoteStreaks()) { + if (user.getTotal(TopVoter.Weekly) == 0 && user.getWeekVoteStreak() != 0) { + user.setWeekVoteStreak(0); + } else { + if (!plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() + || user.hasPercentageTotal(TopVoter.Weekly, + plugin.getSpecialRewardsConfig().getVoteStreakRequirementWeek(), null)) { + user.addWeekVoteStreak(); + plugin.getSpecialRewards().checkVoteStreak(null, user, "Week", + plugin.getBungeeSettings().isUseBungeecoord()); + } + } + } + + if (plugin.getConfigFile().isUseHighestTotals()) { + if (user.getHighestWeeklyTotal() < user.getTotal(TopVoter.Weekly)) { + user.setHighestWeeklyTotal(user.getTotal(TopVoter.Weekly)); + } + } + user.clearTempCache(); + }, (count) -> { + // finished + }); + } + + try { + if (plugin.getSpecialRewardsConfig().isEnableWeeklyAwards()) { + HashMap places = handlePlaces( + plugin.getSpecialRewardsConfig().getWeeklyPossibleRewardPlaces()); + int i = 0; + int lastTotal = -1; + @SuppressWarnings("unchecked") + LinkedHashMap clone = (LinkedHashMap) plugin + .getTopVoter(TopVoter.Weekly).clone(); + for (Entry entry : clone.entrySet()) { + if (plugin.getConfigFile().isTopVoterAwardsTies()) { + if (entry.getValue().intValue() != lastTotal) { + i++; + } + } else { + i++; + } + if (places.containsKey(i)) { + VotingPluginUser user = entry.getKey().getUser(); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + if (!plugin.getConfigFile().isTopVoterIgnorePermission() || !user.isTopVoterIgnore()) { + user.giveWeeklyTopVoterAward(i, places.get(i)); + plugin.getLogger().info("Giving weekly top voter reward " + i + " to " + + entry.getKey().getPlayerName()); + } + } + lastTotal = entry.getValue().intValue(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + for (String shopIdent : plugin.getShopFile().getShopIdentifiers()) { + if (plugin.getShopFile().getVoteShopResetWeekly(shopIdent)) { + resetVoteShopLimit(shopIdent); + } + } + + // give time for other servers to catch up + if (!plugin.getTopVoterHandler().bungeeHandleResets() && plugin.getBungeeSettings().isUseBungeecoord()) { + plugin.debug("Delaying time change 10 seconds for other servers to catchup"); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (!bungeeHandleResets()) { + resetTotals(TopVoter.Weekly); + } + + if (plugin.getStorageType().equals(UserStorage.MYSQL)) { + plugin.getMysql().clearCacheBasic(); + } + + long now = ((System.currentTimeMillis() - startTime) / 1000); + plugin.getLogger().info("Finished processing day change, took " + now + " seconds"); + } + } + + /** + * Registers this handler as a listener. + */ + public void register() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + /** + * Resets vote totals for a specific period. + * @param topVoter the top voter period to reset + */ + public void resetTotals(TopVoter topVoter) { + plugin.getUserManager().removeAllKeyValues(topVoter.getColumnName(), DataType.INTEGER); + } + + /** + * Resets vote shop limits for a specific shop. + * @param shopIdent the shop identifier + */ + public void resetVoteShopLimit(String shopIdent) { + plugin.getUserManager().removeAllKeyValues("VoteShopLimit" + shopIdent, DataType.INTEGER); + } + + /** + * Sorts top voters by their vote counts. + * @param map the map to sort + * @param order true for ascending order, false for descending + * @return sorted map of top voters + */ + public LinkedHashMap sortByValues(LinkedHashMap map, + final boolean order) { + return TopVoterRanking.sortByValues(map, order); + } + + + /** + * Stores top voter data to file. + * @param top the top voter period to store + */ + public void storeTopVoters(TopVoter top) { + LocalDateTime time = LocalDateTime.now().minusDays(1); + String month = time.getMonth().toString(); + int year = time.getYear(); + int week = time.getDayOfYear(); + int day = time.getDayOfMonth(); + String fileName = "TopVoter" + File.separator + top.toString() + File.separator + year + "_" + month; + + if (top.equals(TopVoter.Daily)) { + fileName += "_" + day; + } else if (top.equals(TopVoter.Weekly)) { + fileName += "_" + week; + } + fileName += "_" + System.currentTimeMillis() + ".yml"; + + YMLFileHandler file = new YMLFileHandler(plugin, new File(plugin.getDataFolder(), fileName)); + file.setup(); + file.header("Saving top voters for " + top.toString() + ", file also contains other top voter info as backup"); + for (TopVoter cTop : TopVoter.values()) { + ArrayList topVoters = new ArrayList<>(); + int cTotal = 0; + try { + ArrayList nums = plugin.getUserManager().getNumbersInColumn(cTop.getColumnName()); + for (Integer num : nums) { + cTotal += num.intValue(); + } + topVoters.add("Combined total: " + cTotal); + } catch (Exception e) { + e.printStackTrace(); + } + if (plugin.getTopVoter().containsKey(cTop)) { + + int count = 1; + for (Entry entry : plugin.getTopVoter(cTop).entrySet()) { + topVoters.add(count + ": " + entry.getKey().getPlayerName() + ": " + entry.getValue()); + count++; + } + file.getData().set(cTop.toString(), topVoters); + } + } + file.saveData(); + } + + /** + * Top voter all time + * + * @param page the page + * @return the string[] + */ + public String[] topVoterAllTime(int page) { + int pagesize = plugin.getConfigFile().getFormatPageSize(); + ArrayList msg = new ArrayList<>(); + ArrayList topVoters = new ArrayList<>(); + int count = 1; + for (Entry entry : plugin.getTopVoter(TopVoter.AllTime).entrySet()) { + String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); + line = line.replace("%num%", "" + count); + line = line.replace("%player%", entry.getKey().getPlayerName()); + line = line.replace("%votes%", "" + entry.getValue()); + topVoters.add(line); + count++; + } + + int pageSize = (topVoters.size() / pagesize); + if ((topVoters.size() % pagesize) != 0) { + pageSize++; + } + + String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); + title = title.replace("%page%", "" + page); + title = title.replace("%maxpages%", "" + pageSize); + title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterAllTime()); + msg.add(MessageAPI.colorize(title)); + + for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { + msg.add(topVoters.get(i)); + } + + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + /** + * Gets daily top voters with pagination. + * @param page the page number + * @return array of formatted top voter lines + */ + public String[] topVoterDaily(int page) { + int pagesize = plugin.getConfigFile().getFormatPageSize(); + ArrayList msg = new ArrayList<>(); + ArrayList topVoters = new ArrayList<>(); + int count = 1; + for (Entry entry : plugin.getTopVoter(TopVoter.Daily).entrySet()) { + String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); + line = line.replace("%num%", "" + count); + line = line.replace("%player%", entry.getKey().getPlayerName()); + line = line.replace("%votes%", "" + entry.getValue()); + topVoters.add(line); + count++; + } + + int pageSize = (topVoters.size() / pagesize); + if ((topVoters.size() % pagesize) != 0) { + pageSize++; + } + + String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); + title = title.replace("%page%", "" + page); + title = title.replace("%maxpages%", "" + pageSize); + title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterDaily()); + msg.add(MessageAPI.colorize(title)); + + for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { + msg.add(topVoters.get(i)); + } + + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + /** + * Gets monthly top voters with pagination. + * @param page the page number + * @return array of formatted top voter lines + */ + public String[] topVoterMonthly(int page) { + int pagesize = plugin.getConfigFile().getFormatPageSize(); + ArrayList msg = new ArrayList<>(); + ArrayList topVoters = new ArrayList<>(); + int count = 1; + for (Entry entry : plugin.getTopVoter(TopVoter.Monthly).entrySet()) { + String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); + line = line.replace("%num%", "" + count); + line = line.replace("%player%", entry.getKey().getPlayerName()); + line = line.replace("%votes%", "" + entry.getValue()); + topVoters.add(line); + count++; + } + + int pageSize = (topVoters.size() / pagesize); + if ((topVoters.size() % pagesize) != 0) { + pageSize++; + } + + String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); + title = title.replace("%page%", "" + page); + title = title.replace("%maxpages%", "" + pageSize); + title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterMonthly()); + msg.add(MessageAPI.colorize(title)); + + for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { + msg.add(topVoters.get(i)); + } + + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + /** + * Gets all-time top voters as formatted strings. + * @return array of formatted top voter lines + */ + public String[] topVotersAllTime() { + ArrayList msg = new ArrayList<>(); + List> list = new LinkedList<>(plugin.getTopVoter(TopVoter.AllTime).entrySet()); + int i = 0; + for (Entry entry : list) { + String line = "%num%: %player%, %votes%"; + line = line.replace("%num%", "" + (i + 1)); + try { + line = line.replace("%player%", entry.getKey().getPlayerName()); + } catch (Exception ex) { + VotingPluginMain.plugin.debug(ex); + } + line = line.replace("%votes%", "" + entry.getValue()); + + msg.add(line); + i++; + } + + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + /** + * Gets daily top voters as formatted strings. + * @return array of formatted top voter lines + */ + public String[] topVotersDaily() { + ArrayList msg = new ArrayList<>(); + ArrayList users = new ArrayList<>(plugin.getTopVoter(TopVoter.Daily).keySet()); + for (int i = 0; i < users.size(); i++) { + String line = "%num%: %player%, %votes%"; + line = line.replace("%num%", "" + (i + 1)); + try { + line = line.replace("%player%", users.get(i).getPlayerName()); + } catch (Exception ex) { + VotingPluginMain.plugin.debug(ex); + } + line = line.replace("%votes%", "" + plugin.getTopVoter(TopVoter.Monthly).get(users.get(i))); + msg.add(line); + } + + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + /** + * Gets monthly top voters as formatted strings. + * @return array of formatted top voter lines + */ + public String[] topVotersMonthly() { + ArrayList msg = new ArrayList<>(); + List> list = new LinkedList<>(plugin.getTopVoter(TopVoter.Monthly).entrySet()); + int i = 0; + for (Entry entry : list) { + String line = "%num%: %player%, %votes%"; + line = line.replace("%num%", "" + (i + 1)); + try { + line = line.replace("%player%", entry.getKey().getPlayerName()); + } catch (Exception ex) { + VotingPluginMain.plugin.debug(ex); + } + line = line.replace("%votes%", "" + entry.getValue()); + + msg.add(line); + i++; + } + + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + /** + * Gets weekly top voters with pagination. + * @param page the page number + * @return array of formatted top voter lines + */ + public String[] topVoterWeekly(int page) { + int pagesize = plugin.getConfigFile().getFormatPageSize(); + ArrayList msg = new ArrayList<>(); + ArrayList topVoters = new ArrayList<>(); + int count = 1; + for (Entry entry : plugin.getTopVoter(TopVoter.Weekly).entrySet()) { + String line = plugin.getConfigFile().getFormatCommandsVoteTopLine(); + line = line.replace("%num%", "" + count); + line = line.replace("%player%", entry.getKey().getPlayerName()); + line = line.replace("%votes%", "" + entry.getValue()); + topVoters.add(line); + count++; + } + + int pageSize = (topVoters.size() / pagesize); + if ((topVoters.size() % pagesize) != 0) { + pageSize++; + } + + String title = plugin.getConfigFile().getFormatCommandVoteTopTitle(); + title = title.replace("%page%", "" + page); + title = title.replace("%maxpages%", "" + pageSize); + title = title.replace("%Top%", plugin.getConfigFile().getFormatTopVoterWeekly()); + msg.add(MessageAPI.colorize(title)); + + for (int i = (page - 1) * pagesize; (i < topVoters.size()) && (i < (((page - 1) * pagesize) + 10)); i++) { + msg.add(topVoters.get(i)); + } + + msg = ArrayUtils.colorize(msg); + return ArrayUtils.convert(msg); + } + + /** + * Updates top voter rankings for a specific period. + * @param tempTopVoter map of top voter periods to player rankings + */ + public synchronized void updateTopVoters( + LinkedHashMap> tempTopVoter) { + + int limitSize = plugin.getConfigFile().getMaxiumNumberOfTopVotersToLoad(); + for (Entry> entry : tempTopVoter.entrySet()) { + LinkedHashMap map = entry.getValue(); + map = sortByValues(map, false); + if (limitSize > 0) { + ArrayList listKeys = new ArrayList<>(map.keySet()); + if (listKeys.size() > limitSize) { + for (int i = listKeys.size() - 1; i >= 0 && i >= limitSize; i--) { + map.remove(listKeys.get(i)); + } + } + + } + tempTopVoter.put(entry.getKey(), map); + plugin.setTopVoter(tempTopVoter); + plugin.debug(entry.getKey().getName() + " TopVoter loaded, number of players " + map.size()); + + } + + plugin.debug("Updated TopVoter"); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterLoader.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterLoader.java new file mode 100644 index 000000000..c6c6485d0 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterLoader.java @@ -0,0 +1,208 @@ +package com.bencodez.votingplugin.topvoter; + +import java.time.LocalDateTime; +import java.time.Month; +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.bencodez.advancedcore.api.user.UserDataFetchMode; +import com.bencodez.simpleapi.messages.MessageAPI; +import com.bencodez.simpleapi.sql.Column; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.user.VotingPluginUser; + +/** Loads top-voter rankings from user storage. */ +public class TopVoterLoader { + + private final VotingPluginMain plugin; + + public TopVoterLoader(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public LinkedHashMap getMonthlyTopVotersAtTime(LocalDateTime atTime) { + LinkedHashMap topVoters = new LinkedHashMap<>(); + CountDownLatch latch = new CountDownLatch(1); + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + if (plugin != null && plugin.isEnabled() && uuid != null) { + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + int total = user.getTotal(TopVoter.Monthly, atTime); + if (total > 0) { + topVoters.put(user.getTopVoterPlayer(), total); + } + user.clearTempCache(); + } + }, count -> latch.countDown()); + + try { + latch.await(10, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return TopVoterRanking.sortByValues(topVoters, false); + } + + public LinkedHashMap getTopVotersOfMonth(YearMonth month, + HashMap> columnsByPlayer) { + LinkedHashMap totals = new LinkedHashMap<>(); + LocalDateTime atTime = month.atDay(15).atTime(0, 0); + for (Entry> playerData : columnsByPlayer.entrySet()) { + UUID uuid = playerData.getKey(); + if (uuid == null) { + continue; + } + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(playerData.getValue()); + int total = user.getTotal(TopVoter.Monthly, atTime); + if (total > 0) { + totals.put(user.getTopVoterPlayer(), total); + } + user.clearTempCache(); + } + return TopVoterRanking.sortByValues(totals, false); + } + + public void loadLastMonth() { + if (!plugin.getGui().isLastMonthGUI()) { + return; + } + plugin.getTopVoterState().getLastMonthTopVoters().clear(); + HashMap totals = new HashMap<>(); + LocalDateTime lastMonthTime = plugin.getTimeChecker().getTime().minusMonths(1); + boolean useDateTotalsPrimary = plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal(); + + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + if (uuid == null || plugin == null || !plugin.isEnabled()) { + return; + } + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + try { + int total = useDateTotalsPrimary ? user.getTotal(TopVoter.Monthly, lastMonthTime) + : user.getLastMonthTotal(); + if (total > 0) { + totals.put(user.getTopVoterPlayer(), total); + } + } finally { + user.clearTempCache(); + } + }, count -> { + plugin.getTopVoterState().getLastMonthTopVoters() + .putAll(TopVoterRanking.sortByValues(new LinkedHashMap<>(totals), false)); + plugin.debug("Loaded last month top voters"); + }); + } + + public void loadPreviousMonthTopVoters() { + if (!plugin.getConfigFile().isStoreMonthTotalsWithDate()) { + return; + } + + LocalDateTime now = plugin.getTimeChecker().getTime(); + YearMonth currentMonth = YearMonth.of(now.getYear(), now.getMonth()); + ArrayList months = new ArrayList<>(); + ArrayList monthColumns = new ArrayList<>(); + + for (String column : plugin.getUserManager().getAllColumns()) { + YearMonth month = parseMonthColumn(column); + if (month != null && month.isBefore(currentMonth)) { + months.add(month); + monthColumns.add(column); + } + } + if (months.isEmpty()) { + return; + } + + Integer[] order = new Integer[months.size()]; + for (int index = 0; index < order.length; index++) { + order[index] = index; + } + java.util.Arrays.sort(order, (left, right) -> months.get(left).compareTo(months.get(right))); + ArrayList sortedMonths = new ArrayList<>(months.size()); + ArrayList sortedColumns = new ArrayList<>(months.size()); + for (Integer index : order) { + sortedMonths.add(months.get(index)); + sortedColumns.add(monthColumns.get(index)); + } + months.clear(); + months.addAll(sortedMonths); + monthColumns.clear(); + monthColumns.addAll(sortedColumns); + + LinkedHashMap> previous = plugin.getTopVoterState() + .getPreviousMonthsTopVoters(); + previous.clear(); + for (YearMonth month : months) { + plugin.debug("Loading previous month top voters of " + month); + previous.put(month, new LinkedHashMap<>()); + } + + @SuppressWarnings("unchecked") + LinkedHashMap[] monthMaps = new LinkedHashMap[months.size()]; + for (int index = 0; index < months.size(); index++) { + monthMaps[index] = previous.get(months.get(index)); + } + String[] monthColumnNames = monthColumns.toArray(new String[0]); + + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + if (uuid == null) { + return; + } + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + try { + TopVoterPlayer player = null; + for (int index = 0; index < monthColumnNames.length; index++) { + int total = user.getData().getInt(monthColumnNames[index], 0, UserDataFetchMode.TEMP_ONLY); + if (total > 0) { + if (player == null) { + player = user.getTopVoterPlayer(); + } + monthMaps[index].put(player, total); + } + } + } finally { + user.clearTempCache(); + } + }, count -> { + for (int index = 0; index < months.size(); index++) { + YearMonth month = months.get(index); + previous.put(month, TopVoterRanking.sortByValues(previous.get(month), false)); + } + plugin.extraDebug("Previous Months: " + previous.keySet()); + }); + } + + static YearMonth parseMonthColumn(String column) { + if (column == null || !column.startsWith("MonthTotal-")) { + return null; + } + int firstDash = column.indexOf('-'); + int secondDash = column.indexOf('-', firstDash + 1); + if (firstDash < 0 || secondDash < 0) { + return null; + } + String month = column.substring(firstDash + 1, secondDash); + String year = column.substring(secondDash + 1); + if (!MessageAPI.isInt(year)) { + return null; + } + try { + return YearMonth.of(Integer.parseInt(year), Month.valueOf(month)); + } catch (RuntimeException ignored) { + return null; + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRanking.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRanking.java new file mode 100644 index 000000000..49b0cdc5a --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRanking.java @@ -0,0 +1,55 @@ +package com.bencodez.votingplugin.topvoter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +/** Ranking utilities shared by top-voter loading and reward handling. */ +public final class TopVoterRanking { + + private TopVoterRanking() { + } + + public static LinkedHashMap sortByValues( + LinkedHashMap map, boolean ascending) { + List> entries = new ArrayList<>(map.entrySet()); + entries.sort((first, second) -> { + int result = ascending ? first.getValue().compareTo(second.getValue()) + : second.getValue().compareTo(first.getValue()); + if (result != 0) { + return result; + } + return ascending + ? second.getKey().getLastVoteTime().compareTo(first.getKey().getLastVoteTime()) + : first.getKey().getLastVoteTime().compareTo(second.getKey().getLastVoteTime()); + }); + + LinkedHashMap sorted = new LinkedHashMap<>(); + for (Entry entry : entries) { + sorted.put(entry.getKey(), entry.getValue()); + } + return sorted; + } + + public static HashMap mapAwardPlaces(Set places) { + HashMap mapped = new HashMap<>(); + for (String place : places) { + String[] data = place.split("-"); + try { + if (data.length > 1) { + for (int index = Integer.parseInt(data[0]); index < Integer.parseInt(data[1]); index++) { + mapped.put(index, place); + } + } else { + mapped.put(Integer.parseInt(data[0]), place); + } + } catch (RuntimeException e) { + e.printStackTrace(); + } + } + return mapped; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRewardRegistrar.java new file mode 100644 index 000000000..c03120151 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRewardRegistrar.java @@ -0,0 +1,26 @@ +package com.bencodez.votingplugin.topvoter; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined top-voter award reward paths. */ +public final class TopVoterRewardRegistrar { + + private TopVoterRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + for (String path : plugin.getSpecialRewardsConfig().getMonthlyPossibleRewardPlaces()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + plugin.getSpecialRewardsConfig().getMonthlyAwardRewardsPath(path), plugin.getSpecialRewardsConfig())); + } + for (String path : plugin.getSpecialRewardsConfig().getWeeklyPossibleRewardPlaces()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + plugin.getSpecialRewardsConfig().getWeeklyAwardRewardsPath(path), plugin.getSpecialRewardsConfig())); + } + for (String path : plugin.getSpecialRewardsConfig().getDailyPossibleRewardPlaces()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + plugin.getSpecialRewardsConfig().getDailyAwardRewardsPath(path), plugin.getSpecialRewardsConfig())); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/version/VotingPluginVersionInfo.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/version/VotingPluginVersionInfo.java new file mode 100644 index 000000000..666949865 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/version/VotingPluginVersionInfo.java @@ -0,0 +1,60 @@ +package com.bencodez.votingplugin.version; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.security.CodeSource; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.bukkit.configuration.file.YamlConfiguration; + +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +/** Owns VotingPlugin build metadata embedded in votingpluginversion.yml. */ +@Getter +public final class VotingPluginVersionInfo { + + private final VotingPluginMain plugin; + private String profile = ""; + private String buildNumber = "NOTSET"; + private String time = ""; + + public VotingPluginVersionInfo(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public void load() { + YamlConfiguration config = loadVersionFile(); + if (config == null) { + return; + } + time = config.getString("time", ""); + profile = config.getString("profile", ""); + buildNumber = config.getString("buildnumber", "NOTSET"); + } + + private YamlConfiguration loadVersionFile() { + try { + CodeSource source = plugin.getClass().getProtectionDomain().getCodeSource(); + if (source == null) { + return null; + } + URL jar = source.getLocation(); + try (ZipInputStream zip = new ZipInputStream(jar.openStream())) { + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) { + if ("votingpluginversion.yml".equals(entry.getName())) { + Reader reader = new InputStreamReader(zip); + return YamlConfiguration.loadConfiguration(reader); + } + } + } + } catch (Exception e) { + plugin.debug(e); + } + return null; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votelog/VoteLogManager.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votelog/VoteLogManager.java new file mode 100644 index 000000000..8eef82197 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votelog/VoteLogManager.java @@ -0,0 +1,99 @@ +package com.bencodez.votingplugin.votelog; + +import java.util.concurrent.TimeUnit; + +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; +import com.bencodez.votingplugin.VotingPluginMain; + +/** Owns vote-log table setup and maintenance scheduling. */ +public final class VoteLogManager { + + private final VotingPluginMain plugin; + private VoteLogMysqlTable voteLogMysqlTable; + + public VoteLogManager(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public void load() { + if (!plugin.getConfigFile().isVoteLoggingEnabled()) { + plugin.debug("Vote logging MySQL disabled"); + return; + } + + MysqlConfigSpigot config = new MysqlConfigSpigot(plugin.getConfigFile().getVoteLoggingSection()); + if (plugin.getConfigFile().isVoteLoggingUseMainMySQL()) { + voteLogMysqlTable = createMainMysqlTable(config); + } else { + voteLogMysqlTable = createDedicatedMysqlTable(config); + } + + plugin.getTimer().scheduleAtFixedRate( + () -> voteLogMysqlTable.purgeOlderThanDays(plugin.getConfigFile().getVoteLoggingPurgeDays(), 100), + 60, 60 * 60, TimeUnit.SECONDS); + plugin.debug("Vote logging MySQL enabled"); + } + + public VoteLogMysqlTable getVoteLogMysqlTable() { + return voteLogMysqlTable; + } + + private VoteLogMysqlTable createMainMysqlTable(MysqlConfigSpigot config) { + return new VoteLogMysqlTable("votingplugin_votelog", plugin.getMysql().getMysql(), config, + plugin.getOptions().getDebug().isDebug()) { + @Override + public void logSevere(String string) { + plugin.getLogger().severe(string); + } + + @Override + public void logInfo(String string) { + plugin.getLogger().info(string); + } + + @Override + public String getServerName() { + return serverName(); + } + + @Override + public void debug(Throwable throwable) { + debugThrowable(throwable); + } + }; + } + + private VoteLogMysqlTable createDedicatedMysqlTable(MysqlConfigSpigot config) { + return new VoteLogMysqlTable("votingplugin_votelog", config, plugin.getOptions().getDebug().isDebug()) { + @Override + public void logSevere(String string) { + plugin.getLogger().severe(string); + } + + @Override + public void logInfo(String string) { + plugin.getLogger().info(string); + } + + @Override + public String getServerName() { + return serverName(); + } + + @Override + public void debug(Throwable throwable) { + debugThrowable(throwable); + } + }; + } + + private String serverName() { + return plugin.getBungeeSettings().isUseBungeecoord() ? plugin.getBungeeSettings().getServer() : ""; + } + + private void debugThrowable(Throwable throwable) { + if (plugin.getOptions().getDebug().isDebug()) { + plugin.debug(throwable); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votereminding/VoteReminderRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votereminding/VoteReminderRewardRegistrar.java new file mode 100644 index 000000000..0e79779ac --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votereminding/VoteReminderRewardRegistrar.java @@ -0,0 +1,31 @@ +package com.bencodez.votingplugin.votereminding; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.advancedcore.api.rewards.DirectlyDefinedReward; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote reminder reward paths. */ +public final class VoteReminderRewardRegistrar { + + private VoteReminderRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteReminderOptions.Defaults.Rewards") { + @Override public void createSection(String key) { plugin.getConfigFile().saveData(); } + @Override public ConfigurationSection getFileData() { return plugin.getConfigFile().getData(); } + @Override public void save() { plugin.getConfigFile().saveData(); } + @Override public void setData(String path, Object value) { plugin.getConfigFile().setValue(path, value); } + }); + + ConfigurationSection reminders = plugin.getConfigFile().getData().getConfigurationSection("VoteReminders"); + if (reminders != null) { + for (String key : reminders.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("VoteReminders." + key + ".Rewards", + plugin.getConfigFile())); + } + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/voteshop/VoteShopRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/voteshop/VoteShopRewardRegistrar.java new file mode 100644 index 000000000..e477c0854 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/voteshop/VoteShopRewardRegistrar.java @@ -0,0 +1,35 @@ +package com.bencodez.votingplugin.voteshop; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-shop reward paths. */ +public final class VoteShopRewardRegistrar { + + private VoteShopRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + for (String identifier : plugin.getShopFile().getShopIdentifiers()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("Shop." + identifier + ".Rewards", + plugin.getShopFile())); + } + + ConfigurationSection categories = plugin.getShopFile().getCategoriesSection(); + if (categories == null) { + return; + } + for (String category : categories.getKeys(false)) { + ConfigurationSection shop = plugin.getShopFile().getCategoryShopSection(category); + if (shop == null) { + continue; + } + for (String item : shop.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "Categories." + category + "." + item + ".Rewards", plugin.getShopFile())); + } + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteRewardRegistrar.java new file mode 100644 index 000000000..2ce1301c7 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteRewardRegistrar.java @@ -0,0 +1,25 @@ +package com.bencodez.votingplugin.votesites; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-site reward paths. */ +public final class VoteSiteRewardRegistrar { + + private VoteSiteRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("AnySiteRewards", plugin.getSpecialRewardsConfig())); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("EverySiteReward", plugin.getConfigVoteSites())); + + for (VoteSite site : plugin.getVoteSiteManager().getVoteSites()) { + String base = "VoteSites." + site.getKey(); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(base + ".Rewards", plugin.getConfigVoteSites())); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(base + ".WaitUntilVoteDelayRewards", + plugin.getConfigVoteSites())); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(base + ".CoolDownEndRewards", + plugin.getConfigVoteSites())); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/webhook/VotingPluginWebhookManager.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/webhook/VotingPluginWebhookManager.java new file mode 100644 index 000000000..0b307f836 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/webhook/VotingPluginWebhookManager.java @@ -0,0 +1,38 @@ +package com.bencodez.votingplugin.webhook; + +import com.bencodez.votingplugin.VotingPluginMain; + +/** Owns VotingPlugin webhook startup, reload, and shutdown lifecycle. */ +public final class VotingPluginWebhookManager { + + private final VotingPluginMain plugin; + private VotingPluginWebhooks webhooks; + + public VotingPluginWebhookManager(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public void load() { + if (webhooks == null) { + webhooks = new VotingPluginWebhooks(plugin); + } + reload(); + } + + public void reload() { + if (webhooks != null) { + webhooks.reload(plugin.getConfig().getConfigurationSection("Webhooks")); + } + } + + public void shutdown() { + if (webhooks != null) { + webhooks.shutdown(); + webhooks = null; + } + } + + public VotingPluginWebhooks getWebhooks() { + return webhooks; + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backendproxy/cache/ProcessedVoteCacheTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backendproxy/cache/ProcessedVoteCacheTest.java new file mode 100644 index 000000000..f98fce676 --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backendproxy/cache/ProcessedVoteCacheTest.java @@ -0,0 +1,41 @@ +package com.bencodez.votingplugin.tests.backendproxy.cache; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import com.bencodez.votingplugin.backendproxy.cache.ProcessedVoteCache; + +public class ProcessedVoteCacheTest { + + @Test + public void duplicateVoteIdIsRejected() { + ProcessedVoteCache cache = new ProcessedVoteCache(); + UUID voteId = UUID.randomUUID(); + assertTrue(cache.reserve(voteId)); + assertFalse(cache.reserve(voteId)); + assertEquals(1, cache.getProcessedVotes().size()); + } + + @Test + public void nullVoteIdsRemainCompatible() { + ProcessedVoteCache cache = new ProcessedVoteCache(); + assertTrue(cache.reserve(null)); + assertTrue(cache.reserve(null)); + assertTrue(cache.getProcessedVotes().isEmpty()); + } + + @Test + public void expiredVoteIdCanBeReservedAgain() throws Exception { + ProcessedVoteCache cache = new ProcessedVoteCache(1); + UUID voteId = UUID.randomUUID(); + assertTrue(cache.reserve(voteId)); + Thread.sleep(5); + assertTrue(cache.reserve(voteId)); + assertEquals(1, cache.getProcessedVotes().size()); + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backgroundtask/VotingPluginBackgroundTaskTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backgroundtask/VotingPluginBackgroundTaskTest.java new file mode 100644 index 000000000..61a63b59f --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backgroundtask/VotingPluginBackgroundTaskTest.java @@ -0,0 +1,23 @@ +package com.bencodez.votingplugin.tests.backgroundtask; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.Test; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.backgroundtask.VotingPluginBackgroundTask; + +public class VotingPluginBackgroundTaskTest { + + @Test + public void requestedStateCanBeDelegated() { + VotingPluginBackgroundTask task = new VotingPluginBackgroundTask(mock(VotingPluginMain.class)); + assertFalse(task.isRequested()); + task.setRequested(true); + assertTrue(task.isRequested()); + task.setRequested(false); + assertFalse(task.isRequested()); + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/topvoter/TopVoterLoaderTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/topvoter/TopVoterLoaderTest.java new file mode 100644 index 000000000..2fd872f13 --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/topvoter/TopVoterLoaderTest.java @@ -0,0 +1,25 @@ +package com.bencodez.votingplugin.topvoter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.time.YearMonth; + +import org.junit.jupiter.api.Test; + +public class TopVoterLoaderTest { + + @Test + public void parsesStoredMonthColumns() { + assertEquals(YearMonth.of(2025, 1), TopVoterLoader.parseMonthColumn("MonthTotal-JANUARY-2025")); + assertEquals(YearMonth.of(2026, 12), TopVoterLoader.parseMonthColumn("MonthTotal-DECEMBER-2026")); + } + + @Test + public void rejectsMalformedMonthColumns() { + assertNull(TopVoterLoader.parseMonthColumn("MonthTotal-BAD-2025")); + assertNull(TopVoterLoader.parseMonthColumn("MonthTotal-JANUARY-nope")); + assertNull(TopVoterLoader.parseMonthColumn("Other-JANUARY-2025")); + assertNull(TopVoterLoader.parseMonthColumn(null)); + } +}