mirror of
https://github.com/BenCodez/VotingPlugin.git
synced 2026-11-04 17:10:03 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39f4717f36 | ||
|
|
46f86f8756 | ||
|
|
f99782117a | ||
|
|
ae65946561 | ||
|
|
e595fd6d40 | ||
|
|
9b585e3d80 | ||
|
|
b4b371f19b | ||
|
|
e3be6f7b83 | ||
|
|
dd1ad376e8 | ||
|
|
25aff53be4 | ||
|
|
37c184285f | ||
|
|
4ee7af2c1f | ||
|
|
7f4a576c27 | ||
|
|
9fa27fc450 | ||
|
|
1ade11ecaf | ||
|
|
41450d5739 | ||
|
|
4c24ca08ae | ||
|
|
5fc36a0981 | ||
|
|
be44c75cca | ||
|
|
aab8791821 | ||
|
|
b08bd76c9c | ||
|
|
8773eb211f | ||
|
|
32f596206e | ||
|
|
e67895531a | ||
|
|
1feef348ab | ||
|
|
7fcbe76922 | ||
|
|
dcab774d26 | ||
|
|
f405794287 | ||
|
|
f47129143e | ||
|
|
a9c773d949 | ||
|
|
67e2fdde51 | ||
|
|
28228cf68f | ||
|
|
5785908401 |
No files matched your search
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.bencodez</groupId>
|
||||
<artifactId>votingplugin</artifactId>
|
||||
<version>7.1</version>
|
||||
<version>7.1.1</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>VotingPlugin</name>
|
||||
<properties>
|
||||
@@ -237,7 +237,7 @@
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>26.1.2-R0.1-SNAPSHOT</version>
|
||||
<version>26.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -285,7 +285,7 @@
|
||||
<dependency>
|
||||
<groupId>com.bencodez</groupId>
|
||||
<artifactId>advancedcore</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.2-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.time.ZoneOffset;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
@@ -51,6 +52,11 @@ import lombok.Getter;
|
||||
* Handler for Bungee/proxy server integration.
|
||||
*/
|
||||
public class BungeeHandler implements Listener {
|
||||
|
||||
private static final long PROCESSED_VOTE_TTL_MILLIS = TimeUnit.MINUTES.toMillis(30);
|
||||
|
||||
@Getter
|
||||
private final ConcurrentHashMap<UUID, Long> processedWireVotes = new ConcurrentHashMap<>();
|
||||
@Getter
|
||||
private ClientHandler clientHandler;
|
||||
|
||||
@@ -575,11 +581,18 @@ public class BungeeHandler implements Listener {
|
||||
|
||||
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);
|
||||
|
||||
VoteTotalsSnapshot text = VoteTotalsSnapshot.parseStorage(v.totals == null ? "" : v.totals);
|
||||
|
||||
bungeeVotePartyCurrent = text.getVotePartyCurrent();
|
||||
bungeeVotePartyRequired = text.getVotePartyRequired();
|
||||
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
|
||||
@@ -587,19 +600,15 @@ public class BungeeHandler implements Listener {
|
||||
|
||||
user.cache();
|
||||
|
||||
boolean bungeeBroadcast = v.broadcast;
|
||||
boolean setTotalsOnBackend = !v.manageTotals;
|
||||
|
||||
// Wire has no "setTotals" flag; treat as always true (so doNotSetTotals =
|
||||
// false)
|
||||
boolean doNotSetTotals = false;
|
||||
|
||||
user.bungeeVotePluginMessaging(service, v.time, text, doNotSetTotals, v.wasOnline, bungeeBroadcast, v.num);
|
||||
user.bungeeVotePluginMessaging(service, v.time, text, setTotalsOnBackend, v.wasOnline, v.broadcast, v.num);
|
||||
|
||||
if (plugin.getBungeeSettings().isPerServerPoints()) {
|
||||
user.addPoints(plugin.getConfigFile().getPointsOnVote());
|
||||
}
|
||||
|
||||
if (v.manageTotals) {
|
||||
if (service != null && !service.isEmpty()) {
|
||||
plugin.getServerData().addServiceSite(service);
|
||||
}
|
||||
|
||||
@@ -607,6 +616,50 @@ public class BungeeHandler implements Listener {
|
||||
int _ignored = v.numberOfVotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves a wire vote for processing.
|
||||
*
|
||||
* @param voteId unique vote identifier
|
||||
* @return true if the vote has not been processed recently
|
||||
*/
|
||||
private boolean reserveWireVote(UUID voteId) {
|
||||
if (voteId == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (currentExpiry > now) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (processedWireVotes.replace(voteId, currentExpiry, expiresAt)) {
|
||||
cleanupProcessedWireVotes(now);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired wire vote identifiers.
|
||||
*
|
||||
* @param now current timestamp
|
||||
*/
|
||||
private void cleanupProcessedWireVotes(long now) {
|
||||
processedWireVotes.entrySet().removeIf(entry -> entry.getValue() <= now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the global MySQL handler for cross-server data synchronization.
|
||||
*/
|
||||
|
||||
@@ -877,34 +877,6 @@ public class VotingPluginMain extends AdvancedCorePlugin {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (plugin.getSpecialRewardsConfig().getData().getConfigurationSection("VoteStreaks") != null) {
|
||||
for (String streak : plugin.getSpecialRewardsConfig().getData().getConfigurationSection("VoteStreaks")
|
||||
.getKeys(false)) {
|
||||
plugin.addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteStreaks." + streak + ".Rewards") {
|
||||
|
||||
@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 getSpecialRewardsConfig().getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
plugin.getSpecialRewardsConfig().saveData();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ConfigurationSection voteMilestonesSection = plugin.getSpecialRewardsConfig().getData()
|
||||
.getConfigurationSection("VoteMilestones");
|
||||
@@ -1293,6 +1265,11 @@ public class VotingPluginMain extends AdvancedCorePlugin {
|
||||
getLogger().info("Loading PlaceholderAPI expansion");
|
||||
}
|
||||
}
|
||||
|
||||
voteMilestonesManager = new VoteMilestonesManager(this);
|
||||
|
||||
voteStreakHandler = new VoteStreakHandler(this);
|
||||
voteStreakHandler.reload();
|
||||
|
||||
registerCommands();
|
||||
checkVotifier();
|
||||
@@ -1337,7 +1314,7 @@ public class VotingPluginMain extends AdvancedCorePlugin {
|
||||
|
||||
topVoterHandler.register();
|
||||
|
||||
voteMilestonesManager = new VoteMilestonesManager(this);
|
||||
|
||||
|
||||
plugin.getBukkitScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
|
||||
@@ -1379,9 +1356,6 @@ public class VotingPluginMain extends AdvancedCorePlugin {
|
||||
// load vote logging if enabled
|
||||
loadVoteLoggingMySQL();
|
||||
|
||||
voteStreakHandler = new VoteStreakHandler(this);
|
||||
voteStreakHandler.reload();
|
||||
|
||||
webhooks = new VotingPluginWebhooks(this);
|
||||
webhooks.reload(getConfig().getConfigurationSection("Webhooks"));
|
||||
|
||||
@@ -1545,7 +1519,7 @@ public class VotingPluginMain extends AdvancedCorePlugin {
|
||||
contains = true;
|
||||
}
|
||||
}
|
||||
if (!contains) {
|
||||
if (!contains && !getConfigFile().isDisableNoServiceSiteMessage()) {
|
||||
issues = false;
|
||||
plugin.getLogger().warning("No vote has been recieved from " + site.getServiceSite()
|
||||
+ ", may be an invalid service site. Please read: https://github.com/BenCodez/VotingPlugin/wiki/Votifier-Troubleshooting");
|
||||
|
||||
+201
-131
@@ -13,14 +13,16 @@ import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import com.bencodez.advancedcore.api.misc.MiscUtils;
|
||||
import com.bencodez.advancedcore.api.messages.PlaceholderUtils;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.simpleapi.player.PlayerUtils;
|
||||
import com.bencodez.simpleapi.time.ParsedDuration;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
@@ -28,42 +30,38 @@ import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
/**
|
||||
* Core broadcast logic.
|
||||
*
|
||||
* Decides WHEN to broadcast and builds lines/messages. Uses your existing
|
||||
* "eligible online players" behavior (disable broadcast respected).
|
||||
* Decides when to broadcast and builds lines and messages. Uses the existing
|
||||
* eligible online players behavior and respects disabled broadcasts.
|
||||
*
|
||||
* NOTE: Backend servers only.
|
||||
* Backend servers only.
|
||||
*/
|
||||
public final class BroadcastHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Only used for day-boundary logic (FIRST_VOTE_OF_DAY).
|
||||
* Only used for day-boundary logic for first vote of day broadcasts.
|
||||
*/
|
||||
private final ZoneId zoneId;
|
||||
|
||||
private volatile BroadcastSettings settings;
|
||||
|
||||
// Per-player cooldown tracking
|
||||
private final ConcurrentHashMap<UUID, Instant> lastBroadcastAt = new ConcurrentHashMap<UUID, Instant>();
|
||||
|
||||
// Batch window tracking
|
||||
private final ConcurrentHashMap<UUID, LinkedHashSet<String>> pendingSites = new ConcurrentHashMap<UUID, LinkedHashSet<String>>();
|
||||
private final ConcurrentHashMap<UUID, BukkitTask> pendingFlush = new ConcurrentHashMap<UUID, BukkitTask>();
|
||||
|
||||
// First vote of day tracking
|
||||
private final ConcurrentHashMap<UUID, LocalDate> firstVoteDay = new ConcurrentHashMap<UUID, LocalDate>();
|
||||
|
||||
// Interval summary tracking (uuid -> set of sites during interval)
|
||||
private final ConcurrentHashMap<UUID, LinkedHashSet<String>> intervalSites = new ConcurrentHashMap<UUID, LinkedHashSet<String>>();
|
||||
private volatile BukkitTask intervalTask;
|
||||
|
||||
/**
|
||||
* Constructs a new BroadcastHandler.
|
||||
* Constructs a new broadcast handler.
|
||||
*
|
||||
* @param plugin the main plugin instance
|
||||
* @param plugin the main plugin instance
|
||||
* @param settings the broadcast settings
|
||||
* @param zoneId the time zone ID, or null to use system default
|
||||
* @param zoneId the time zone ID, or null to use the system default
|
||||
*/
|
||||
public BroadcastHandler(VotingPluginMain plugin, BroadcastSettings settings, ZoneId zoneId) {
|
||||
this.plugin = plugin;
|
||||
@@ -74,7 +72,8 @@ public final class BroadcastHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the broadcast settings and reschedules interval task if needed.
|
||||
* Updates the broadcast settings and reschedules the interval task when
|
||||
* required.
|
||||
*
|
||||
* @param settings the new broadcast settings
|
||||
*/
|
||||
@@ -84,118 +83,131 @@ public final class BroadcastHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Call whenever a vote is received.
|
||||
* Handles a received vote.
|
||||
*
|
||||
* @param uuid player's uuid
|
||||
* @param playerName player name (optional; if null/empty, resolved from Bukkit)
|
||||
* @param siteName vote site name (display name)
|
||||
* @param wasOnline whether the player was online when the vote was received
|
||||
* @param uuid the voted player's UUID
|
||||
* @param playerName the player name, or null to resolve it from Bukkit
|
||||
* @param siteName the vote site display name
|
||||
* @param wasOnline whether the voted player was online when the vote was received
|
||||
*/
|
||||
public void broadcastVote(UUID uuid, String playerName, String siteName, boolean wasOnline) {
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.isDisabled()) {
|
||||
BroadcastSettings currentSettings = settings;
|
||||
if (currentSettings == null || currentSettings.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always collect for interval summaries (even if Type isn't interval right
|
||||
// now).
|
||||
recordInterval(uuid, siteName);
|
||||
|
||||
String name = playerName == null || playerName.isEmpty() ? resolveName(uuid) : playerName;
|
||||
VoteBroadcastType type = currentSettings.getType();
|
||||
|
||||
VoteBroadcastType type = s.getType();
|
||||
|
||||
// New: types that only broadcast if the voting player is online
|
||||
if (type == VoteBroadcastType.EVERY_VOTE_ONLINE_ONLY) {
|
||||
if (!wasOnline) {
|
||||
return;
|
||||
}
|
||||
broadcastNow(name, single(siteName), "vote_online_only", null);
|
||||
broadcastNow(uuid, name, single(siteName), "vote_online_only", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == VoteBroadcastType.EVERY_VOTE) {
|
||||
broadcastNow(name, single(siteName), "vote", null);
|
||||
|
||||
broadcastNow(uuid, name, single(siteName), "vote", null);
|
||||
} else if (type == VoteBroadcastType.COOLDOWN_PER_PLAYER) {
|
||||
if (checkAndMarkCooldown(uuid, s.getDuration())) {
|
||||
broadcastNow(name, single(siteName), "cooldown", null);
|
||||
if (checkAndMarkCooldown(uuid, currentSettings.getDuration())) {
|
||||
broadcastNow(uuid, name, single(siteName), "cooldown", null);
|
||||
}
|
||||
|
||||
} else if (type == VoteBroadcastType.BATCH_WINDOW_PER_PLAYER) {
|
||||
bufferBatch(uuid, name, siteName, s.getDuration());
|
||||
|
||||
bufferBatch(uuid, name, siteName, currentSettings.getDuration());
|
||||
} else if (type == VoteBroadcastType.FIRST_VOTE_OF_DAY) {
|
||||
if (markFirstVoteOfDay(uuid)) {
|
||||
broadcastNow(name, single(siteName), "first_day", null);
|
||||
broadcastNow(uuid, name, single(siteName), "first_day", null);
|
||||
}
|
||||
|
||||
} else if (type == VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
// handled by scheduled task
|
||||
// Handled by the scheduled interval task.
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Broadcast helpers =======================
|
||||
/**
|
||||
* Renders and broadcasts a message.
|
||||
*
|
||||
* All PlaceholderAPI placeholders are evaluated using the voted player before
|
||||
* the message is sent to recipients.
|
||||
*
|
||||
* @param votedPlayerUuid the voted player's UUID, or null when no single player applies
|
||||
* @param playerName the rendered player name
|
||||
* @param sites the vote sites
|
||||
* @param reason the broadcast reason
|
||||
* @param extraContext additional format context, or null
|
||||
*/
|
||||
|
||||
private void broadcastNow(String playerName, List<String> sites, String reason, Map<String, String> extraContext) {
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.isDisabled()) {
|
||||
private void broadcastNow(UUID votedPlayerUuid, String playerName, List<String> sites, String reason,
|
||||
Map<String, String> extraContext) {
|
||||
BroadcastSettings currentSettings = settings;
|
||||
if (currentSettings == null || currentSettings.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> cleaned = applyMaxSites(sites, s);
|
||||
List<String> cleaned = applyMaxSites(sites, currentSettings);
|
||||
cleaned.sort(new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String a, String b) {
|
||||
String aa = a == null ? "" : a.toLowerCase(Locale.ROOT);
|
||||
String bb = b == null ? "" : b.toLowerCase(Locale.ROOT);
|
||||
return aa.compareTo(bb);
|
||||
public int compare(String first, String second) {
|
||||
String firstValue = first == null ? "" : first.toLowerCase(Locale.ROOT);
|
||||
String secondValue = second == null ? "" : second.toLowerCase(Locale.ROOT);
|
||||
return firstValue.compareTo(secondValue);
|
||||
}
|
||||
});
|
||||
|
||||
List<String> lines;
|
||||
if (extraContext == null) {
|
||||
lines = s.getFormat().render(playerName, cleaned, reason);
|
||||
lines = currentSettings.getFormat().render(playerName, cleaned, reason);
|
||||
} else {
|
||||
lines = s.getFormat().render(playerName, cleaned, reason, extraContext);
|
||||
lines = currentSettings.getFormat().render(playerName, cleaned, reason, extraContext);
|
||||
}
|
||||
|
||||
OfflinePlayer votedPlayer = votedPlayerUuid == null ? null : Bukkit.getOfflinePlayer(votedPlayerUuid);
|
||||
|
||||
for (String line : lines) {
|
||||
broadcastToEligiblePlayers(line);
|
||||
String parsedLine = PlaceholderUtils.replacePlaceHolders(votedPlayer, line);
|
||||
broadcastToEligiblePlayers(parsedLine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Your reliable broadcast style: - only sends to online players - respects
|
||||
* per-player disable broadcast toggle - runs PlaceholderAPI style per-player
|
||||
* placeholders (if your PlaceholderUtils does) - supports %newline% / %NewLine%
|
||||
* splitting
|
||||
* Broadcasts a message that has already had PlaceholderAPI placeholders parsed.
|
||||
*
|
||||
* Placeholder parsing is intentionally not performed for each recipient because
|
||||
* all placeholders must be based on the voted player. JSON-style click and hover
|
||||
* formatting is still parsed for each recipient.
|
||||
*
|
||||
* @param broadcastMessage the parsed message
|
||||
*/
|
||||
private void broadcastToEligiblePlayers(String broadcastMsg) {
|
||||
if (broadcastMsg == null || broadcastMsg.isEmpty()) {
|
||||
private void broadcastToEligiblePlayers(String broadcastMessage) {
|
||||
if (broadcastMessage == null || broadcastMessage.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<Player> players = new ArrayList<Player>();
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
VotingPluginUser u = plugin.getVotingPluginUserManager().getVotingPluginUser(p);
|
||||
if (u != null && !u.getDisableBroadcast()) {
|
||||
players.add(p);
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(player);
|
||||
if (user == null || user.getDisableBroadcast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String firstSplit : broadcastMessage.split(Pattern.quote("%newline%"))) {
|
||||
for (String message : firstSplit.split(Pattern.quote("%NewLine%"))) {
|
||||
String colorizedMessage = MessageAPI.colorize(message);
|
||||
PlayerUtils.getServerHandle().sendMessage(player, PlaceholderUtils.parseJson(colorizedMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If you prefer your manual loop, keep it here (not in main)
|
||||
MiscUtils.getInstance().broadcast(broadcastMsg, players);
|
||||
|
||||
Bukkit.getServer().getConsoleSender().sendMessage(MessageAPI.colorize(broadcastMsg));
|
||||
Bukkit.getServer().getConsoleSender().sendMessage(MessageAPI.colorize(broadcastMessage));
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Cooldown logic (fixed millis) =======================
|
||||
/**
|
||||
* Checks and updates a player's broadcast cooldown.
|
||||
*
|
||||
* @param uuid the player's UUID
|
||||
* @param cooldown the cooldown duration
|
||||
* @return true when the broadcast is allowed
|
||||
*/
|
||||
|
||||
private boolean checkAndMarkCooldown(UUID uuid, ParsedDuration cooldown) {
|
||||
if (cooldown == null || cooldown.isEmpty()) {
|
||||
return true;
|
||||
@@ -218,38 +230,50 @@ public final class BroadcastHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Batch window logic (fixed millis)
|
||||
* =======================
|
||||
/**
|
||||
* Adds a vote site to a player's pending batch.
|
||||
*
|
||||
* @param uuid the player's UUID
|
||||
* @param playerName the player's name
|
||||
* @param siteName the vote site
|
||||
* @param window the batch window
|
||||
*/
|
||||
|
||||
private void bufferBatch(UUID uuid, String playerName, String siteName, ParsedDuration window) {
|
||||
if (siteName != null && !siteName.isEmpty()) {
|
||||
LinkedHashSet<String> set = pendingSites.get(uuid);
|
||||
if (set == null) {
|
||||
LinkedHashSet<String> sites = pendingSites.get(uuid);
|
||||
if (sites == null) {
|
||||
LinkedHashSet<String> created = new LinkedHashSet<String>();
|
||||
set = pendingSites.putIfAbsent(uuid, created);
|
||||
if (set == null) {
|
||||
set = created;
|
||||
sites = pendingSites.putIfAbsent(uuid, created);
|
||||
if (sites == null) {
|
||||
sites = created;
|
||||
}
|
||||
}
|
||||
synchronized (set) {
|
||||
set.add(siteName);
|
||||
|
||||
synchronized (sites) {
|
||||
sites.add(siteName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pendingFlush.containsKey(uuid)) {
|
||||
BukkitTask task = scheduleBatchFlush(uuid, playerName, window);
|
||||
BukkitTask prev = pendingFlush.putIfAbsent(uuid, task);
|
||||
if (prev != null) {
|
||||
BukkitTask previous = pendingFlush.putIfAbsent(uuid, task);
|
||||
if (previous != null) {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a pending batch flush.
|
||||
*
|
||||
* @param uuid the player's UUID
|
||||
* @param playerName the player's name
|
||||
* @param window the batch window
|
||||
* @return the scheduled Bukkit task
|
||||
*/
|
||||
private BukkitTask scheduleBatchFlush(final UUID uuid, final String playerName, ParsedDuration window) {
|
||||
long delayMs = window == null ? 1L : window.delayMillisFromNow();
|
||||
long ticks = Math.max(1L, delayMs / 50L);
|
||||
long delayMillis = window == null ? 1L : window.delayMillisFromNow();
|
||||
long ticks = Math.max(1L, delayMillis / 50L);
|
||||
|
||||
return Bukkit.getScheduler().runTaskLater(plugin, new Runnable() {
|
||||
@Override
|
||||
@@ -263,23 +287,32 @@ public final class BroadcastHandler {
|
||||
}, ticks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes a player's pending vote-site batch.
|
||||
*
|
||||
* @param uuid the player's UUID
|
||||
* @param playerName the player's name
|
||||
*/
|
||||
private void flushBatch(UUID uuid, String playerName) {
|
||||
Set<String> set = pendingSites.remove(uuid);
|
||||
if (set == null || set.isEmpty()) {
|
||||
Set<String> sites = pendingSites.remove(uuid);
|
||||
if (sites == null || sites.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> list;
|
||||
synchronized (set) {
|
||||
list = new ArrayList<String>(set);
|
||||
List<String> siteList;
|
||||
synchronized (sites) {
|
||||
siteList = new ArrayList<String>(sites);
|
||||
}
|
||||
broadcastNow(playerName, list, "batch", null);
|
||||
|
||||
broadcastNow(uuid, playerName, siteList, "batch", null);
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= First vote of day (calendar) =======================
|
||||
/**
|
||||
* Marks and checks a player's first vote of the current day.
|
||||
*
|
||||
* @param uuid the player's UUID
|
||||
* @return true when this is the first vote of the day
|
||||
*/
|
||||
|
||||
private boolean markFirstVoteOfDay(UUID uuid) {
|
||||
LocalDate today = LocalDate.now(zoneId);
|
||||
LocalDate last = firstVoteDay.putIfAbsent(uuid, today);
|
||||
@@ -287,30 +320,34 @@ public final class BroadcastHandler {
|
||||
if (last == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!last.equals(today)) {
|
||||
firstVoteDay.put(uuid, today);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Interval summary =======================
|
||||
/**
|
||||
* Reschedules the global interval summary task when required.
|
||||
*/
|
||||
|
||||
private void rescheduleIntervalIfNeeded() {
|
||||
if (intervalTask != null) {
|
||||
intervalTask.cancel();
|
||||
intervalTask = null;
|
||||
}
|
||||
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.getType() != VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
BroadcastSettings currentSettings = settings;
|
||||
if (currentSettings == null
|
||||
|| currentSettings.getType() != VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
long delayMs = s.getDuration() == null ? 1L : s.getDuration().delayMillisFromNow();
|
||||
long ticks = Math.max(1L, delayMs / 50L);
|
||||
long delayMillis = currentSettings.getDuration() == null
|
||||
? 1L
|
||||
: currentSettings.getDuration().delayMillisFromNow();
|
||||
long ticks = Math.max(1L, delayMillis / 50L);
|
||||
|
||||
intervalTask = Bukkit.getScheduler().runTaskTimer(plugin, new Runnable() {
|
||||
@Override
|
||||
@@ -320,36 +357,46 @@ public final class BroadcastHandler {
|
||||
}, ticks, ticks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a vote site for the global interval summary.
|
||||
*
|
||||
* @param uuid the player's UUID
|
||||
* @param siteName the vote site
|
||||
*/
|
||||
private void recordInterval(UUID uuid, String siteName) {
|
||||
if (siteName == null || siteName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedHashSet<String> set = intervalSites.get(uuid);
|
||||
if (set == null) {
|
||||
LinkedHashSet<String> sites = intervalSites.get(uuid);
|
||||
if (sites == null) {
|
||||
LinkedHashSet<String> created = new LinkedHashSet<String>();
|
||||
set = intervalSites.putIfAbsent(uuid, created);
|
||||
if (set == null) {
|
||||
set = created;
|
||||
sites = intervalSites.putIfAbsent(uuid, created);
|
||||
if (sites == null) {
|
||||
sites = created;
|
||||
}
|
||||
}
|
||||
synchronized (set) {
|
||||
set.add(siteName);
|
||||
|
||||
synchronized (sites) {
|
||||
sites.add(siteName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interval summary supports extra context placeholders: - %players% /
|
||||
* %numberofplayers% - %sites% / %numberofsites%
|
||||
* Broadcasts the global interval summary.
|
||||
*
|
||||
* The interval summary has no single voted player, so PlaceholderAPI
|
||||
* placeholders are left unchanged.
|
||||
*/
|
||||
private void broadcastIntervalSummary() {
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.isDisabled() || s.getType() != VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
BroadcastSettings currentSettings = settings;
|
||||
if (currentSettings == null || currentSettings.isDisabled()
|
||||
|| currentSettings.getType() != VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<UUID, LinkedHashSet<String>> snapshot = new ConcurrentHashMap<UUID, LinkedHashSet<String>>(
|
||||
intervalSites);
|
||||
ConcurrentHashMap<UUID, LinkedHashSet<String>> snapshot =
|
||||
new ConcurrentHashMap<UUID, LinkedHashSet<String>>(intervalSites);
|
||||
intervalSites.clear();
|
||||
|
||||
if (snapshot.isEmpty()) {
|
||||
@@ -395,49 +442,72 @@ public final class BroadcastHandler {
|
||||
context.put("sites", sitesCsv);
|
||||
context.put("numberofsites", String.valueOf(uniqueSites.size()));
|
||||
|
||||
broadcastNow("Server", entries, "interval", context);
|
||||
broadcastNow(null, "Server", entries, "interval", context);
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Utilities =======================
|
||||
/**
|
||||
* Creates a single-value site list.
|
||||
*
|
||||
* @param site the site name
|
||||
* @return the site list
|
||||
*/
|
||||
|
||||
private List<String> single(String site) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
List<String> sites = new ArrayList<String>();
|
||||
if (site != null && !site.isEmpty()) {
|
||||
list.add(site);
|
||||
sites.add(site);
|
||||
}
|
||||
return list;
|
||||
return sites;
|
||||
}
|
||||
|
||||
private List<String> applyMaxSites(List<String> sites, BroadcastSettings s) {
|
||||
/**
|
||||
* Applies the configured maximum number of listed sites.
|
||||
*
|
||||
* @param sites the original site list
|
||||
* @param currentSettings the current broadcast settings
|
||||
* @return the limited site list
|
||||
*/
|
||||
private List<String> applyMaxSites(List<String> sites, BroadcastSettings currentSettings) {
|
||||
if (sites == null) {
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
int max = s.getMaxSitesListed();
|
||||
if (max <= 0 || sites.size() <= max) {
|
||||
|
||||
int maximum = currentSettings.getMaxSitesListed();
|
||||
if (maximum <= 0 || sites.size() <= maximum) {
|
||||
return new ArrayList<String>(sites);
|
||||
}
|
||||
return new ArrayList<String>(sites.subList(0, max));
|
||||
|
||||
return new ArrayList<String>(sites.subList(0, maximum));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a player's name from Bukkit.
|
||||
*
|
||||
* @param uuid the player's UUID
|
||||
* @return the resolved player name
|
||||
*/
|
||||
private String resolveName(UUID uuid) {
|
||||
try {
|
||||
OfflinePlayer op = Bukkit.getOfflinePlayer(uuid);
|
||||
String name = op == null ? null : op.getName();
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
|
||||
String name = offlinePlayer == null ? null : offlinePlayer.getName();
|
||||
return name == null || name.isEmpty() ? "Player" : name;
|
||||
} catch (Exception e) {
|
||||
} catch (Exception exception) {
|
||||
return "Player";
|
||||
}
|
||||
}
|
||||
|
||||
private String join(List<String> list) {
|
||||
/**
|
||||
* Joins non-empty strings with a comma and space.
|
||||
*
|
||||
* @param values the values to join
|
||||
* @return the joined string
|
||||
*/
|
||||
private String join(List<String> values) {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (String s : list) {
|
||||
if (s != null && !s.isEmpty()) {
|
||||
joiner.add(s);
|
||||
for (String value : values) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
joiner.add(value);
|
||||
}
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -1,10 +1,13 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.voteshop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
@@ -12,15 +15,21 @@ import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.simpleapi.valuerequest.StringListener;
|
||||
import com.bencodez.simpleapi.valuerequest.ValueRequest;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Admin vote shop GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteShop extends GUIHandler {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
@@ -75,12 +84,47 @@ public class AdminVoteVoteShop extends GUIHandler {
|
||||
@Override
|
||||
public void onInput(Player player, String value) {
|
||||
plugin.getShopFile().createShop(value);
|
||||
player.sendMessage(MessageAPI.colorize("&aShop item created successfully: " + value));
|
||||
plugin.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.HOPPER).setName("&aAdd voteshop item from hand")
|
||||
.addLoreLine("&7Hold an item in your main hand")
|
||||
.addLoreLine("&7then click this button.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
ItemStack item = getHeldItem(clickEvent);
|
||||
if (isEmpty(item)) {
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&cHold an item in your main hand first."));
|
||||
new AdminVoteVoteShop(plugin, clickEvent.getPlayer()).open();
|
||||
return;
|
||||
}
|
||||
|
||||
new ValueRequest(plugin, plugin.getDialogService(), null).requestString(clickEvent.getPlayer(), "", null, true,
|
||||
"Enter item name", new StringListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player player, String value) {
|
||||
createShopFromItem(value, item);
|
||||
player.sendMessage(MessageAPI.colorize("&aShop item created successfully: " + value));
|
||||
plugin.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.CHEST).setName("&bEdit categories")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopCategories(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.REDSTONE_BLOCK).setName("&cRemove item")) {
|
||||
|
||||
@Override
|
||||
@@ -96,4 +140,47 @@ public class AdminVoteVoteShop extends GUIHandler {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new shop item and copies the held item to display and rewards.
|
||||
*
|
||||
* @param identifier the shop identifier
|
||||
* @param itemStack the item stack to copy
|
||||
*/
|
||||
private void createShopFromItem(String identifier, ItemStack itemStack) {
|
||||
String basePath = "Shop." + identifier;
|
||||
plugin.getShopFile().getData().set(basePath, null);
|
||||
plugin.getShopFile().getData().set(basePath + ".Identifier_Name", identifier);
|
||||
plugin.getShopFile().getData().set(basePath + ".Cost", 1);
|
||||
plugin.getShopFile().getData().set(basePath + ".Permission", "");
|
||||
plugin.getShopFile().getData().set(basePath + ".CloseGUI", true);
|
||||
plugin.getShopFile().getData().set(basePath + ".RequireConfirmation", false);
|
||||
ItemBuilder item = new ItemBuilder(itemStack.clone());
|
||||
Map<String, Object> map = item.getConfiguration(true);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
plugin.getShopFile().getData().set(basePath + ".DisplayItem.ItemStack." + entry.getKey(), entry.getValue());
|
||||
plugin.getShopFile().getData().set(basePath + ".Rewards.Items.Item1.ItemStack." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
plugin.getShopFile().saveData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item the player is holding in their main hand.
|
||||
*
|
||||
* @param clickEvent the click event
|
||||
* @return the held item
|
||||
*/
|
||||
private ItemStack getHeldItem(ClickEvent clickEvent) {
|
||||
return clickEvent.getPlayer().getInventory().getItemInMainHand().clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item stack is empty.
|
||||
*
|
||||
* @param itemStack the item stack
|
||||
* @return true if empty
|
||||
*/
|
||||
private boolean isEmpty(ItemStack itemStack) {
|
||||
return itemStack == null || itemStack.getType() == Material.AIR;
|
||||
}
|
||||
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.voteshop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.simpleapi.valuerequest.StringListener;
|
||||
import com.bencodez.simpleapi.valuerequest.ValueRequest;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Admin vote shop categories GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteShopCategories extends GUIHandler {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopCategories.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param player the command sender
|
||||
*/
|
||||
public AdminVoteVoteShopCategories(VotingPluginMain plugin, CommandSender player) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
BInventory inv = new BInventory("Edit VoteShop Categories");
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteShop");
|
||||
|
||||
ConfigurationSection categories = plugin.getShopFile().getCategoriesSection();
|
||||
if (categories != null) {
|
||||
for (String category : categories.getKeys(false)) {
|
||||
ConfigurationSection categorySection = plugin.getShopFile().getCategorySection(category);
|
||||
inv.addButton(new BInventoryButton(getCategoryItem(category, categorySection)) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopCategory(plugin, clickEvent.getPlayer(), (String) getData("category"))
|
||||
.open(GUIMethod.CHEST);
|
||||
}
|
||||
}.addData("category", category));
|
||||
}
|
||||
}
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.EMERALD_BLOCK).setName("&aAdd category")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new ValueRequest(plugin, plugin.getDialogService(), null).requestString(clickEvent.getPlayer(), "", null, true,
|
||||
"Enter category name", new StringListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player player, String value) {
|
||||
createCategory(value, null);
|
||||
player.sendMessage(MessageAPI.colorize("&aCategory created successfully: " + value));
|
||||
plugin.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.HOPPER).setName("&aAdd category from hand")
|
||||
.addLoreLine("&7Drag an item onto this button")
|
||||
.addLoreLine("&7or click while holding an item.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
ItemStack itemStack = getHeldItem(clickEvent);
|
||||
if (isEmpty(itemStack)) {
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&cHold an item in your main hand first."));
|
||||
new AdminVoteVoteShopCategories(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
|
||||
return;
|
||||
}
|
||||
new ValueRequest(plugin, plugin.getDialogService(), null).requestString(clickEvent.getPlayer(), "", null, true,
|
||||
"Enter category name", new StringListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player player, String value) {
|
||||
createCategory(value, itemStack);
|
||||
player.sendMessage(MessageAPI.colorize("&aCategory created successfully: " + value));
|
||||
plugin.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.REDSTONE_BLOCK).setName("&cRemove category")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopCategoryRemove(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cBack")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShop(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a category and matching main shop category button.
|
||||
*
|
||||
* @param category the category identifier
|
||||
* @param itemStack optional display item
|
||||
*/
|
||||
private void createCategory(String category, ItemStack itemStack) {
|
||||
String categoryPath = "Categories." + category;
|
||||
String buttonPath = "Shop." + category;
|
||||
plugin.getShopFile().getData().set(categoryPath + ".Name", "&b" + category);
|
||||
plugin.getShopFile().getData().set(categoryPath + ".BackButton", true);
|
||||
plugin.getShopFile().getData().set(categoryPath + ".BackButtonItem.Material", "BARRIER");
|
||||
plugin.getShopFile().getData().set(categoryPath + ".BackButtonItem.Amount", 1);
|
||||
plugin.getShopFile().getData().set(categoryPath + ".BackButtonItem.Slot", -2);
|
||||
plugin.getShopFile().getData().set(categoryPath + ".BackButtonItem.Name", "&cBack");
|
||||
|
||||
plugin.getShopFile().getData().set(buttonPath + ".Category", category);
|
||||
plugin.getShopFile().getData().set(buttonPath + ".Permission", "");
|
||||
plugin.getShopFile().getData().set(buttonPath + ".HideOnNoPermission", true);
|
||||
|
||||
if (isEmpty(itemStack)) {
|
||||
plugin.getShopFile().getData().set(buttonPath + ".DisplayItem.Material", "CHEST");
|
||||
plugin.getShopFile().getData().set(buttonPath + ".DisplayItem.Amount", 1);
|
||||
plugin.getShopFile().getData().set(buttonPath + ".DisplayItem.Name", "&b" + category);
|
||||
} else {
|
||||
ItemBuilder item = new ItemBuilder(itemStack.clone());
|
||||
Map<String, Object> map = item.getConfiguration(true);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
plugin.getShopFile().getData().set(buttonPath + ".DisplayItem.ItemStack." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin.getShopFile().getData().isConfigurationSection(categoryPath + ".Shop")) {
|
||||
plugin.getShopFile().getData().createSection(categoryPath + ".Shop");
|
||||
}
|
||||
plugin.getShopFile().saveData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a display item for a category.
|
||||
*
|
||||
* @param category the category identifier
|
||||
* @param section the category section
|
||||
* @return the display item
|
||||
*/
|
||||
private ItemBuilder getCategoryItem(String category, ConfigurationSection section) {
|
||||
ConfigurationSection buttonSection = plugin.getShopFile().getData().getConfigurationSection("Shop." + category);
|
||||
if (buttonSection != null) {
|
||||
ConfigurationSection displaySection = plugin.getShopFile().getDisplaySection(buttonSection);
|
||||
if (displaySection != null) {
|
||||
return new ItemBuilder(displaySection).addLoreLine("&7Click to edit category");
|
||||
}
|
||||
}
|
||||
String name = section == null ? "&b" + category : section.getString("Name", "&b" + category);
|
||||
return new ItemBuilder(Material.CHEST).setName(name).addLoreLine("&7Click to edit category");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item the player is holding in their main hand.
|
||||
*
|
||||
* @param clickEvent the click event
|
||||
* @return the held item
|
||||
*/
|
||||
private ItemStack getHeldItem(ClickEvent clickEvent) {
|
||||
return clickEvent.getPlayer().getInventory().getItemInMainHand().clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item stack is empty.
|
||||
*
|
||||
* @param itemStack the item stack
|
||||
* @return true if empty
|
||||
*/
|
||||
private boolean isEmpty(ItemStack itemStack) {
|
||||
return itemStack == null || itemStack.getType() == Material.AIR;
|
||||
}
|
||||
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.voteshop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.inventory.editgui.EditGUI;
|
||||
import com.bencodez.advancedcore.api.inventory.editgui.EditGUIButton;
|
||||
import com.bencodez.advancedcore.api.inventory.editgui.valuetypes.EditGUIValueBoolean;
|
||||
import com.bencodez.advancedcore.api.inventory.editgui.valuetypes.EditGUIValueList;
|
||||
import com.bencodez.advancedcore.api.inventory.editgui.valuetypes.EditGUIValueNumber;
|
||||
import com.bencodez.advancedcore.api.inventory.editgui.valuetypes.EditGUIValueString;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.simpleapi.valuerequest.StringListener;
|
||||
import com.bencodez.simpleapi.valuerequest.ValueRequest;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Admin vote shop category GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteShopCategory extends GUIHandler {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private VotingPluginMain plugin;
|
||||
@Getter
|
||||
@Setter
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopCategory.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param player the command sender
|
||||
* @param category the category identifier
|
||||
*/
|
||||
public AdminVoteVoteShopCategory(VotingPluginMain plugin, CommandSender player, String category) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
EditGUI inv = new EditGUI("Edit Category: " + category);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteShop");
|
||||
|
||||
ConfigurationSection categorySection = getCategorySection();
|
||||
ConfigurationSection backButton = getBackButtonSection();
|
||||
ConfigurationSection buttonDisplay = getCategoryButtonDisplaySection();
|
||||
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.PAPER, 1),
|
||||
new EditGUIValueString("Name", categorySection.getString("Name", "&b" + category)) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
setCategoryData(getKey(), name);
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.BARRIER, 1),
|
||||
new EditGUIValueBoolean("BackButton", categorySection.getBoolean("BackButton", true)) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, boolean value) {
|
||||
setCategoryData(getKey(), value);
|
||||
}
|
||||
}));
|
||||
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(backButton.getString("Material", "BARRIER")),
|
||||
new EditGUIValueString("BackButtonItem.Material", backButton.getString("Material", "BARRIER")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
setCategoryData(getKey(), name);
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.PAPER, 1),
|
||||
new EditGUIValueString("BackButtonItem.Name", backButton.getString("Name", "&cBack")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
setCategoryData(getKey(), name);
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.BOOK, 1),
|
||||
new EditGUIValueList("BackButtonItem.Lore", backButton.getStringList("Lore")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, ArrayList<String> value) {
|
||||
setCategoryData(getKey(), value);
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.ITEM_FRAME, 1),
|
||||
new EditGUIValueNumber("BackButtonItem.Slot", backButton.getInt("Slot", -2)) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, Number num) {
|
||||
setCategoryData(getKey(), num.intValue());
|
||||
}
|
||||
}));
|
||||
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(buttonDisplay.getString("Material", "CHEST")),
|
||||
new EditGUIValueString("CategoryButton.Material", buttonDisplay.getString("Material", "CHEST")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
setCategoryButtonDisplayData("Material", name);
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.PAPER, 1),
|
||||
new EditGUIValueString("CategoryButton.Name", buttonDisplay.getString("Name", "&b" + category)) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
setCategoryButtonDisplayData("Name", name);
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.BOOK, 1),
|
||||
new EditGUIValueList("CategoryButton.Lore", buttonDisplay.getStringList("Lore")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, ArrayList<String> value) {
|
||||
setCategoryButtonDisplayData("Lore", value);
|
||||
}
|
||||
}));
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.HOPPER).setName("&aSet category button from hand")
|
||||
.addLoreLine("&7Hold an item in your main hand")
|
||||
.addLoreLine("&7then click this button.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
ItemStack itemStack = getHeldItem(clickEvent);
|
||||
if (isEmpty(itemStack)) {
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&cHold an item in your main hand first."));
|
||||
new AdminVoteVoteShopCategory(plugin, clickEvent.getPlayer(), category).open(GUIMethod.CHEST);
|
||||
return;
|
||||
}
|
||||
setCategoryButtonItem(itemStack);
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&aUpdated category button item for " + category));
|
||||
new AdminVoteVoteShopCategory(plugin, clickEvent.getPlayer(), category).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.DIAMOND).setName("&cEdit category items")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopItems(plugin, clickEvent.getPlayer(), category).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.EMERALD_BLOCK).setName("&aAdd category item")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new ValueRequest(plugin, plugin.getDialogService(), null).requestString(clickEvent.getPlayer(), "", null, true,
|
||||
"Enter item name", new StringListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player player, String value) {
|
||||
createCategoryShopItem(value, null);
|
||||
player.sendMessage(MessageAPI.colorize("&aCategory shop item created successfully: " + value));
|
||||
plugin.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.HOPPER).setName("&aAdd category item from hand")
|
||||
.addLoreLine("&7Hold an item in your main hand")
|
||||
.addLoreLine("&7then click this button.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
ItemStack itemStack = getHeldItem(clickEvent);
|
||||
if (isEmpty(itemStack)) {
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&cHold an item in your main hand first."));
|
||||
new AdminVoteVoteShopCategory(plugin, clickEvent.getPlayer(), category).open(GUIMethod.CHEST);
|
||||
return;
|
||||
}
|
||||
new ValueRequest(plugin, plugin.getDialogService(), null).requestString(clickEvent.getPlayer(), "", null, true,
|
||||
"Enter item name", new StringListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player player, String value) {
|
||||
createCategoryShopItem(value, itemStack);
|
||||
player.sendMessage(MessageAPI.colorize("&aCategory shop item created successfully: " + value));
|
||||
plugin.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.REDSTONE_BLOCK).setName("&cRemove category item")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopItemRemove(plugin, clickEvent.getPlayer(), category).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cBack to categories")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopCategories(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a category shop item.
|
||||
*
|
||||
* @param identifier the item identifier
|
||||
* @param itemStack optional display item
|
||||
*/
|
||||
private void createCategoryShopItem(String identifier, ItemStack itemStack) {
|
||||
String basePath = "Categories." + category + ".Shop." + identifier;
|
||||
plugin.getShopFile().getData().set(basePath + ".Identifier_Name", identifier);
|
||||
plugin.getShopFile().getData().set(basePath + ".Cost", 1);
|
||||
plugin.getShopFile().getData().set(basePath + ".Permission", "");
|
||||
plugin.getShopFile().getData().set(basePath + ".HideOnNoPermission", true);
|
||||
plugin.getShopFile().getData().set(basePath + ".CloseGUI", true);
|
||||
plugin.getShopFile().getData().set(basePath + ".RequireConfirmation", false);
|
||||
plugin.getShopFile().getData().set(basePath + ".Rewards.Items.Item1.Material", "STONE");
|
||||
plugin.getShopFile().getData().set(basePath + ".Rewards.Items.Item1.Amount", 1);
|
||||
if (isEmpty(itemStack)) {
|
||||
plugin.getShopFile().getData().set(basePath + ".DisplayItem.Material", "STONE");
|
||||
plugin.getShopFile().getData().set(basePath + ".DisplayItem.Amount", 1);
|
||||
plugin.getShopFile().getData().set(basePath + ".DisplayItem.Name", "&cPlaceholder item");
|
||||
} else {
|
||||
ItemBuilder item = new ItemBuilder(itemStack.clone());
|
||||
Map<String, Object> map = item.getConfiguration(true);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
plugin.getShopFile().getData().set(basePath + ".DisplayItem.ItemStack." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
plugin.getShopFile().saveData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets category data.
|
||||
*
|
||||
* @param path the relative path
|
||||
* @param value the value
|
||||
*/
|
||||
private void setCategoryData(String path, Object value) {
|
||||
plugin.getShopFile().getData().set("Categories." + category + "." + path, value);
|
||||
plugin.getShopFile().saveData();
|
||||
plugin.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets category button display data.
|
||||
*
|
||||
* @param path the relative display path
|
||||
* @param value the value
|
||||
*/
|
||||
private void setCategoryButtonDisplayData(String path, Object value) {
|
||||
plugin.getShopFile().getData().set("Shop." + category + ".DisplayItem." + path, value);
|
||||
plugin.getShopFile().saveData();
|
||||
plugin.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main shop category button item from an item stack.
|
||||
*
|
||||
* @param itemStack the item stack
|
||||
*/
|
||||
private void setCategoryButtonItem(ItemStack itemStack) {
|
||||
ItemBuilder item = new ItemBuilder(itemStack.clone());
|
||||
Map<String, Object> map = item.getConfiguration(true);
|
||||
plugin.getShopFile().getData().set("Shop." + category + ".DisplayItem", null);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
plugin.getShopFile().getData().set("Shop." + category + ".DisplayItem.ItemStack." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
plugin.getShopFile().saveData();
|
||||
plugin.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the category section.
|
||||
*
|
||||
* @return the category section
|
||||
*/
|
||||
private ConfigurationSection getCategorySection() {
|
||||
ConfigurationSection section = plugin.getShopFile().getCategorySection(category);
|
||||
if (section == null) {
|
||||
section = plugin.getShopFile().getData().createSection("Categories." + category);
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the back button section.
|
||||
*
|
||||
* @return the back button section
|
||||
*/
|
||||
private ConfigurationSection getBackButtonSection() {
|
||||
ConfigurationSection section = plugin.getShopFile().getData()
|
||||
.getConfigurationSection("Categories." + category + ".BackButtonItem");
|
||||
if (section == null) {
|
||||
section = plugin.getShopFile().getData().createSection("Categories." + category + ".BackButtonItem");
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the category button display section.
|
||||
*
|
||||
* @return the display section
|
||||
*/
|
||||
private ConfigurationSection getCategoryButtonDisplaySection() {
|
||||
ConfigurationSection section = plugin.getShopFile().getData()
|
||||
.getConfigurationSection("Shop." + category + ".DisplayItem");
|
||||
if (section == null) {
|
||||
section = plugin.getShopFile().getData().createSection("Shop." + category + ".DisplayItem");
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item the player is holding in their main hand.
|
||||
*
|
||||
* @param clickEvent the click event
|
||||
* @return the held item
|
||||
*/
|
||||
private ItemStack getHeldItem(ClickEvent clickEvent) {
|
||||
return clickEvent.getPlayer().getInventory().getItemInMainHand().clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item stack is empty.
|
||||
*
|
||||
* @param itemStack the item stack
|
||||
* @return true if empty
|
||||
*/
|
||||
private boolean isEmpty(ItemStack itemStack) {
|
||||
return itemStack == null || itemStack.getType() == Material.AIR;
|
||||
}
|
||||
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.voteshop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.commands.gui.admin.AdminVoteConfirmation;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Admin vote shop category remove GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteShopCategoryRemove extends GUIHandler {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopCategoryRemove.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param player the command sender
|
||||
*/
|
||||
public AdminVoteVoteShopCategoryRemove(VotingPluginMain plugin, CommandSender player) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
BInventory inv = new BInventory("Remove VoteShop Category");
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteShop");
|
||||
|
||||
ConfigurationSection categories = plugin.getShopFile().getCategoriesSection();
|
||||
if (categories != null) {
|
||||
for (final String category : categories.getKeys(false)) {
|
||||
inv.addButton(new BInventoryButton(getCategoryItem(category).addLoreLine("&c&lClick to remove")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteConfirmation(plugin, clickEvent.getPlayer(), "Remove category " + category + "?") {
|
||||
|
||||
@Override
|
||||
public void onConfirm(Player p) {
|
||||
plugin.getShopFile().getData().set("Categories." + category, null);
|
||||
plugin.getShopFile().getData().set("Shop." + category, null);
|
||||
plugin.getShopFile().saveData();
|
||||
p.sendMessage("Removed category " + category);
|
||||
plugin.reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeny(Player p) {
|
||||
new AdminVoteVoteShopCategories(plugin, p).open();
|
||||
}
|
||||
}.open();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a category display item.
|
||||
*
|
||||
* @param category the category identifier
|
||||
* @return the display item
|
||||
*/
|
||||
private ItemBuilder getCategoryItem(String category) {
|
||||
ConfigurationSection buttonSection = plugin.getShopFile().getData().getConfigurationSection("Shop." + category);
|
||||
if (buttonSection != null) {
|
||||
ConfigurationSection displaySection = plugin.getShopFile().getDisplaySection(buttonSection);
|
||||
if (displaySection != null) {
|
||||
return new ItemBuilder(displaySection);
|
||||
}
|
||||
}
|
||||
ConfigurationSection categorySection = plugin.getShopFile().getCategorySection(category);
|
||||
String name = categorySection == null ? "&b" + category : categorySection.getString("Name", "&b" + category);
|
||||
return new ItemBuilder(org.bukkit.Material.CHEST).setName(name);
|
||||
}
|
||||
|
||||
}
|
||||
+167
-27
@@ -8,6 +8,7 @@ import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
@@ -20,15 +21,26 @@ import com.bencodez.advancedcore.api.inventory.editgui.valuetypes.EditGUIValueNu
|
||||
import com.bencodez.advancedcore.api.inventory.editgui.valuetypes.EditGUIValueString;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.advancedcore.command.gui.RewardEditGUI;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Admin vote shop item GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String identifier;
|
||||
@Getter
|
||||
@Setter
|
||||
private VotingPluginMain plugin;
|
||||
@Getter
|
||||
@Setter
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopItem.
|
||||
@@ -38,9 +50,22 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
* @param identifier the item identifier
|
||||
*/
|
||||
public AdminVoteVoteShopItem(VotingPluginMain plugin, CommandSender player, String identifier) {
|
||||
this(plugin, player, identifier, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopItem.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param player the command sender
|
||||
* @param identifier the item identifier
|
||||
* @param category the category identifier
|
||||
*/
|
||||
public AdminVoteVoteShopItem(VotingPluginMain plugin, CommandSender player, String identifier, String category) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
this.identifier = identifier;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,9 +91,15 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
public void onChest(Player player) {
|
||||
EditGUI inv = new EditGUI("Edit VoteShop Item: " + identifier);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteShop");
|
||||
// to add
|
||||
|
||||
ConfigurationSection shopItemData = getShopItemSection();
|
||||
ConfigurationSection displayItemData = plugin.getShopFile().getDisplaySection(shopItemData);
|
||||
if (displayItemData == null) {
|
||||
displayItemData = shopItemData;
|
||||
}
|
||||
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.PAPER, 1), new EditGUIValueString("Identifier_Name",
|
||||
plugin.getShopFile().getShopIdentifierIdentifierName(identifier)) {
|
||||
shopItemData.getString("Identifier_Name", identifier)) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
@@ -76,7 +107,7 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.OAK_DOOR, 1),
|
||||
new EditGUIValueString("Permission", plugin.getShopFile().getVoteShopPermission(identifier)) {
|
||||
new EditGUIValueString("Permission", shopItemData.getString("Permission", "")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
@@ -84,7 +115,7 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.EMERALD, 1),
|
||||
new EditGUIValueNumber("Cost", plugin.getShopFile().getShopIdentifierCost(identifier)) {
|
||||
new EditGUIValueNumber("Cost", shopItemData.getInt("Cost")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, Number num) {
|
||||
@@ -92,7 +123,7 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.BARRIER, 1),
|
||||
new EditGUIValueNumber("Limit", plugin.getShopFile().getShopIdentifierLimit(identifier)) {
|
||||
new EditGUIValueNumber("Limit", shopItemData.getInt("Limit", -1)) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, Number num) {
|
||||
@@ -100,10 +131,8 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
}
|
||||
}));
|
||||
|
||||
// display item
|
||||
ConfigurationSection displayItemData = plugin.getShopFile().getShopIdentifierSection(identifier);
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(displayItemData.getString("Material")).setAmount(1),
|
||||
new EditGUIValueString("Material", displayItemData.getString("Material")) {
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(displayItemData.getString("Material", "STONE")).setAmount(1),
|
||||
new EditGUIValueString(getDisplayPath("Material"), displayItemData.getString("Material", "STONE")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
@@ -111,7 +140,7 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.PAPER, 1),
|
||||
new EditGUIValueString("Name", displayItemData.getString("Name")) {
|
||||
new EditGUIValueString(getDisplayPath("Name"), displayItemData.getString("Name", "")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, String name) {
|
||||
@@ -119,35 +148,42 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
}
|
||||
}));
|
||||
inv.addButton(new EditGUIButton(new ItemBuilder(Material.BOOK, 1),
|
||||
new EditGUIValueList("Lore", displayItemData.getStringList("Lore")) {
|
||||
new EditGUIValueList(getDisplayPath("Lore"), displayItemData.getStringList("Lore")) {
|
||||
|
||||
@Override
|
||||
public void setValue(Player player, ArrayList<String> value) {
|
||||
setPathData(getKey(), value);
|
||||
}
|
||||
}));
|
||||
inv.addButton(new BInventoryButton(
|
||||
new ItemBuilder(player.getInventory().getItemInMainHand().clone()).addLoreLine("&cClick to add")) {
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(getPreviewItem(player)).addLoreLine("&cClick to copy held item display data")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
ItemBuilder item = new ItemBuilder(clickEvent.getPlayer().getInventory().getItemInMainHand().clone());
|
||||
Map<String, Object> map = item.getConfiguration(false);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
setPathData(entry.getKey(), entry.getValue());
|
||||
ItemStack itemStack = getHeldItem(clickEvent);
|
||||
if (isEmpty(itemStack)) {
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&cHold an item in your main hand first."));
|
||||
new AdminVoteVoteShopItem(plugin, clickEvent.getPlayer(), identifier, category).open(GUIMethod.CHEST);
|
||||
return;
|
||||
}
|
||||
setDisplayItemData(itemStack);
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&aUpdated display item data for " + identifier));
|
||||
new AdminVoteVoteShopItem(plugin, clickEvent.getPlayer(), identifier, category).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(player.getInventory().getItemInMainHand().clone())
|
||||
.addLoreLine("&cClick to add with exact data")) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(getPreviewItem(player)).addLoreLine("&cClick to copy held exact item data")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
ItemBuilder item = new ItemBuilder(clickEvent.getPlayer().getInventory().getItemInMainHand().clone());
|
||||
Map<String, Object> map = item.getConfiguration(true);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
setPathData("ItemStack." + entry.getKey(), entry.getValue());
|
||||
ItemStack itemStack = getHeldItem(clickEvent);
|
||||
if (isEmpty(itemStack)) {
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&cHold an item in your main hand first."));
|
||||
new AdminVoteVoteShopItem(plugin, clickEvent.getPlayer(), identifier, category).open(GUIMethod.CHEST);
|
||||
return;
|
||||
}
|
||||
setItemData(itemStack, true);
|
||||
clickEvent.getPlayer().sendMessage(MessageAPI.colorize("&aUpdated exact ItemStack data for " + identifier));
|
||||
new AdminVoteVoteShopItem(plugin, clickEvent.getPlayer(), identifier, category).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -156,12 +192,10 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
RewardEditGUI.getInstance().openRewardGUI(clickEvent.getPlayer(),
|
||||
plugin.getRewardHandler().getDirectlyDefined("Shop." + identifier + ".Rewards"));
|
||||
plugin.getRewardHandler().getDirectlyDefined(getBasePath() + ".Rewards"));
|
||||
}
|
||||
});
|
||||
|
||||
// implement item reward?
|
||||
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
@@ -170,10 +204,116 @@ public class AdminVoteVoteShopItem extends GUIHandler {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets DisplayItem data using exact ItemStack serialization.
|
||||
*
|
||||
* @param itemStack the item stack to copy
|
||||
*/
|
||||
private void setDisplayItemData(ItemStack itemStack) {
|
||||
ItemBuilder item = new ItemBuilder(itemStack.clone());
|
||||
Map<String, Object> map = item.getConfiguration(true);
|
||||
plugin.getShopFile().getData().set(getBasePath() + ".DisplayItem", null);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
setPathData("DisplayItem.ItemStack." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets item display or exact ItemStack data.
|
||||
*
|
||||
* @param itemStack the item stack to copy
|
||||
* @param exactData true to copy exact ItemStack data
|
||||
*/
|
||||
private void setItemData(ItemStack itemStack, boolean exactData) {
|
||||
ItemBuilder item = new ItemBuilder(itemStack.clone());
|
||||
Map<String, Object> map = item.getConfiguration(exactData);
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
setPathData(exactData ? "ItemStack." + entry.getKey() : getDisplayPath(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data for the current shop item.
|
||||
*
|
||||
* @param path the relative path
|
||||
* @param value the value
|
||||
*/
|
||||
private void setPathData(String path, Object value) {
|
||||
plugin.getShopFile().getData().set("Shop." + identifier + "." + path, value);
|
||||
plugin.getShopFile().getData().set(getBasePath() + "." + path, value);
|
||||
plugin.getShopFile().saveData();
|
||||
plugin.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base config path for this item.
|
||||
*
|
||||
* @return the config path
|
||||
*/
|
||||
private String getBasePath() {
|
||||
if (category != null && !category.isEmpty()) {
|
||||
return "Categories." + category + ".Shop." + identifier;
|
||||
}
|
||||
return "Shop." + identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current shop item section.
|
||||
*
|
||||
* @return the shop item section
|
||||
*/
|
||||
private ConfigurationSection getShopItemSection() {
|
||||
ConfigurationSection section = plugin.getShopFile().getData().getConfigurationSection(getBasePath());
|
||||
if (section == null) {
|
||||
section = plugin.getShopFile().getData().createSection(getBasePath());
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display path for this item.
|
||||
*
|
||||
* @param path the display child path
|
||||
* @return the relative display path
|
||||
*/
|
||||
private String getDisplayPath(String path) {
|
||||
if (getShopItemSection().isConfigurationSection("DisplayItem")) {
|
||||
return "DisplayItem." + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a preview item for the current player.
|
||||
*
|
||||
* @param player the player
|
||||
* @return the preview item
|
||||
*/
|
||||
private ItemStack getPreviewItem(Player player) {
|
||||
ItemStack itemStack = player.getInventory().getItemInMainHand();
|
||||
if (isEmpty(itemStack)) {
|
||||
return new ItemStack(Material.HOPPER);
|
||||
}
|
||||
return itemStack.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item the player is holding in their main hand.
|
||||
*
|
||||
* @param clickEvent the click event
|
||||
* @return the held item
|
||||
*/
|
||||
private ItemStack getHeldItem(ClickEvent clickEvent) {
|
||||
return clickEvent.getPlayer().getInventory().getItemInMainHand().clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item stack is empty.
|
||||
*
|
||||
* @param itemStack the item stack
|
||||
* @return true if empty
|
||||
*/
|
||||
private boolean isEmpty(ItemStack itemStack) {
|
||||
return itemStack == null || itemStack.getType() == Material.AIR;
|
||||
}
|
||||
|
||||
}
|
||||
+104
-23
@@ -1,8 +1,11 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.voteshop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
@@ -14,12 +17,20 @@ import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.commands.gui.admin.AdminVoteConfirmation;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Admin vote shop item remove GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteShopItemRemove extends GUIHandler {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private VotingPluginMain plugin;
|
||||
@Getter
|
||||
@Setter
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopItemRemove.
|
||||
@@ -28,8 +39,20 @@ public class AdminVoteVoteShopItemRemove extends GUIHandler {
|
||||
* @param player the command sender
|
||||
*/
|
||||
public AdminVoteVoteShopItemRemove(VotingPluginMain plugin, CommandSender player) {
|
||||
this(plugin, player, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopItemRemove.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param player the command sender
|
||||
* @param category the category identifier
|
||||
*/
|
||||
public AdminVoteVoteShopItemRemove(VotingPluginMain plugin, CommandSender player, String category) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,34 +76,49 @@ public class AdminVoteVoteShopItemRemove extends GUIHandler {
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
BInventory inv = new BInventory("Edit VoteShop Remove Item");
|
||||
BInventory inv = new BInventory(category == null ? "Edit VoteShop Remove Item" : "Remove Category Item: " + category);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteShop");
|
||||
|
||||
for (final String identifier : plugin.getShopFile().getShopIdentifiers()) {
|
||||
inv.addButton(
|
||||
new BInventoryButton(new ItemBuilder(plugin.getShopFile().getShopIdentifierSection(identifier))
|
||||
.addLoreLine("&c&lClick to remove")) {
|
||||
for (final String identifier : getIdentifiers()) {
|
||||
ConfigurationSection section = getItemSection(identifier);
|
||||
if (section == null) {
|
||||
continue;
|
||||
}
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(plugin.getShopFile().getDisplaySection(section))
|
||||
.addLoreLine("&c&lClick to remove")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteConfirmation(plugin, clickEvent.getPlayer(), "Remove shop item " + identifier + "?") {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteConfirmation(plugin, clickEvent.getPlayer(),
|
||||
"Remove shop item " + identifier + "?") {
|
||||
|
||||
@Override
|
||||
public void onConfirm(Player p) {
|
||||
plugin.getShopFile().removeShop(identifier);
|
||||
p.sendMessage("Removed " + identifier);
|
||||
plugin.reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeny(Player p) {
|
||||
new AdminVoteVoteShop(plugin, p).open();
|
||||
}
|
||||
}.open();
|
||||
|
||||
public void onConfirm(Player p) {
|
||||
removeShop(identifier);
|
||||
p.sendMessage("Removed " + identifier);
|
||||
plugin.reload();
|
||||
}
|
||||
}.addData("ident", identifier));
|
||||
|
||||
@Override
|
||||
public void onDeny(Player p) {
|
||||
if (category == null) {
|
||||
new AdminVoteVoteShop(plugin, p).open();
|
||||
} else {
|
||||
new AdminVoteVoteShopCategory(plugin, p, category).open();
|
||||
}
|
||||
}
|
||||
}.open();
|
||||
|
||||
}
|
||||
}.addData("ident", identifier));
|
||||
}
|
||||
if (category != null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cBack to category")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopCategory(plugin, clickEvent.getPlayer(), category).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
}
|
||||
inv.openInventory(player);
|
||||
}
|
||||
@@ -90,4 +128,47 @@ public class AdminVoteVoteShopItemRemove extends GUIHandler {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the identifiers to display.
|
||||
*
|
||||
* @return the identifiers
|
||||
*/
|
||||
private Set<String> getIdentifiers() {
|
||||
if (category != null) {
|
||||
ConfigurationSection section = plugin.getShopFile().getCategoryShopSection(category);
|
||||
if (section != null) {
|
||||
return section.getKeys(false);
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
return plugin.getShopFile().getShopIdentifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item section.
|
||||
*
|
||||
* @param identifier the item identifier
|
||||
* @return the item section
|
||||
*/
|
||||
private ConfigurationSection getItemSection(String identifier) {
|
||||
if (category != null) {
|
||||
return plugin.getShopFile().getData().getConfigurationSection("Categories." + category + ".Shop." + identifier);
|
||||
}
|
||||
return plugin.getShopFile().getShopIdentifierSection(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a shop item.
|
||||
*
|
||||
* @param identifier the identifier
|
||||
*/
|
||||
private void removeShop(String identifier) {
|
||||
if (category == null) {
|
||||
plugin.getShopFile().removeShop(identifier);
|
||||
return;
|
||||
}
|
||||
plugin.getShopFile().getData().set("Categories." + category + ".Shop." + identifier, null);
|
||||
plugin.getShopFile().saveData();
|
||||
}
|
||||
|
||||
}
|
||||
+75
-10
@@ -1,8 +1,11 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.voteshop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
@@ -13,12 +16,20 @@ import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Admin vote shop items GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteShopItems extends GUIHandler {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private VotingPluginMain plugin;
|
||||
@Getter
|
||||
@Setter
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopItems.
|
||||
@@ -27,8 +38,20 @@ public class AdminVoteVoteShopItems extends GUIHandler {
|
||||
* @param player the command sender
|
||||
*/
|
||||
public AdminVoteVoteShopItems(VotingPluginMain plugin, CommandSender player) {
|
||||
this(plugin, player, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteShopItems.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param player the command sender
|
||||
* @param category the category identifier
|
||||
*/
|
||||
public AdminVoteVoteShopItems(VotingPluginMain plugin, CommandSender player, String category) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,20 +75,33 @@ public class AdminVoteVoteShopItems extends GUIHandler {
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
BInventory inv = new BInventory("Edit VoteShop Items");
|
||||
BInventory inv = new BInventory(category == null ? "Edit VoteShop Items" : "Edit Category Items: " + category);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteShop");
|
||||
|
||||
for (String identifier : plugin.getShopFile().getShopIdentifiers()) {
|
||||
inv.addButton(
|
||||
new BInventoryButton(new ItemBuilder(plugin.getShopFile().getShopIdentifierSection(identifier))) {
|
||||
for (String identifier : getIdentifiers()) {
|
||||
ConfigurationSection section = getItemSection(identifier);
|
||||
if (section == null) {
|
||||
continue;
|
||||
}
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(plugin.getShopFile().getDisplaySection(section))) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopItem(plugin, clickEvent.getPlayer(), (String) getData("ident"))
|
||||
.open(GUIMethod.CHEST);
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopItem(plugin, clickEvent.getPlayer(), (String) getData("ident"), category)
|
||||
.open(GUIMethod.CHEST);
|
||||
|
||||
}
|
||||
}.addData("ident", identifier));
|
||||
}
|
||||
}.addData("ident", identifier));
|
||||
}
|
||||
|
||||
if (category != null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cBack to category")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteShopCategory(plugin, clickEvent.getPlayer(), category).open(GUIMethod.CHEST);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inv.openInventory(player);
|
||||
@@ -76,4 +112,33 @@ public class AdminVoteVoteShopItems extends GUIHandler {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the identifiers to display.
|
||||
*
|
||||
* @return the identifiers
|
||||
*/
|
||||
private Set<String> getIdentifiers() {
|
||||
if (category != null) {
|
||||
ConfigurationSection section = plugin.getShopFile().getCategoryShopSection(category);
|
||||
if (section != null) {
|
||||
return section.getKeys(false);
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
return plugin.getShopFile().getShopIdentifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item section.
|
||||
*
|
||||
* @param identifier the item identifier
|
||||
* @return the item section
|
||||
*/
|
||||
private ConfigurationSection getItemSection(String identifier) {
|
||||
if (category != null) {
|
||||
return plugin.getShopFile().getData().getConfigurationSection("Categories." + category + ".Shop." + identifier);
|
||||
}
|
||||
return plugin.getShopFile().getShopIdentifierSection(identifier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public class ServerData {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public ArrayList<String> getServiceSites() {
|
||||
return (ArrayList<String>) getData().getList("ServiceSites", new ArrayList<>());
|
||||
return (ArrayList<String>) getData().getList("GottenServiceSites", new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,7 +434,7 @@ public class ServerData {
|
||||
* @param list the service sites list
|
||||
*/
|
||||
public void setServiceSites(ArrayList<String> list) {
|
||||
getData().set("ServiceSites", list);
|
||||
getData().set("GottenServiceSites", list);
|
||||
saveData();
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -145,7 +145,9 @@ public class PlayerVoteListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
if (voteSite.isWaitUntilVoteDelay() && !user.canVoteSite(voteSite)) {
|
||||
boolean queuedProxyVoteAlreadyRecorded = ProxyVoteDelayCheck.isQueuedVoteAlreadyRecorded(event.isBungee(),
|
||||
event.getTime(), user.getTime(voteSite));
|
||||
if (voteSite.isWaitUntilVoteDelay() && !queuedProxyVoteAlreadyRecorded && !user.canVoteSite(voteSite)) {
|
||||
if (!event.isRealVote()) {
|
||||
plugin.getLogger().info(user.getPlayerName() + " did a not real vote, bypassing WaitUntilVoteDelay");
|
||||
} else {
|
||||
@@ -157,6 +159,10 @@ public class PlayerVoteListener implements Listener {
|
||||
.info(user.getPlayerName() + " has bypass permission for WaitUntilVoteDelay, bypassing");
|
||||
}
|
||||
}
|
||||
if (queuedProxyVoteAlreadyRecorded) {
|
||||
plugin.debug("Allowing queued proxy vote for " + user.getPlayerName() + " on " + voteSite.getKey()
|
||||
+ "; proxy vote time already matches LastVotes: " + event.getTime());
|
||||
}
|
||||
|
||||
UUID voteUUID = UUID.randomUUID();
|
||||
if (event.isBungee() && event.getBungeeTextTotals() != null) {
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.bencodez.votingplugin.listeners;
|
||||
|
||||
/**
|
||||
* Handles the WaitUntilVoteDelay exception for votes queued by a proxy.
|
||||
*/
|
||||
public final class ProxyVoteDelayCheck {
|
||||
|
||||
private ProxyVoteDelayCheck() {
|
||||
}
|
||||
|
||||
/**
|
||||
* A proxy stores the real vote timestamp before its queued message reaches the
|
||||
* backend. The backend must accept that one delivery when the stored timestamp
|
||||
* is the same timestamp carried by the message.
|
||||
*
|
||||
* @param proxyVote true when the vote was forwarded by VotingPlugin's proxy
|
||||
* @param messageVoteTime real vote time included in the proxy message
|
||||
* @param storedVoteTime current backend last-vote time for the site
|
||||
* @return true when the delay check should allow the queued delivery
|
||||
*/
|
||||
public static boolean isQueuedVoteAlreadyRecorded(boolean proxyVote, long messageVoteTime, long storedVoteTime) {
|
||||
return proxyVote && messageVoteTime > 0L && messageVoteTime == storedVoteTime;
|
||||
}
|
||||
}
|
||||
@@ -332,6 +332,15 @@ public class PlaceHolders {
|
||||
}.withDescription("Last month total").updateDataKey("LastMonthTotal"));
|
||||
|
||||
// end of older placeholders
|
||||
|
||||
placeholders.add(new PlaceHolder<VotingPluginUser>("CanLike_NameMC") {
|
||||
|
||||
@Override
|
||||
public String placeholderRequest(VotingPluginUser user, String identifier) {
|
||||
return user.hasClaimedNameMCLikeReward() ? "Complete" : "Incomplete";
|
||||
}
|
||||
}.withDescription("Return Complete/Incomplete depending on whether the player has liked the server on NameMC")
|
||||
.updateDataKey("ClaimedNameMCLikeReward"));
|
||||
|
||||
placeholders.add(new PlaceHolder<VotingPluginUser>("DisableBroadcast") {
|
||||
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ public class VoteSiteJsonPresetManager {
|
||||
String voteURL = getString(values.get("voteURL"));
|
||||
String voteDelay = getString(values.get("voteDelay"));
|
||||
|
||||
Boolean waitUntilDelay = getBoolean(values.get("WaitUnitVoteDelay"));
|
||||
Boolean waitUntilDelay = getBoolean(values.get("WaitUntilVoteDelay"));
|
||||
if (waitUntilDelay == null) {
|
||||
waitUntilDelay = getBoolean(values.get("waitUntilVoteDelay"));
|
||||
}
|
||||
|
||||
@@ -42,11 +42,25 @@ public class VoteTotalsSnapshot {
|
||||
private int dateMonthTotal;
|
||||
@Getter
|
||||
@Setter
|
||||
@Deprecated
|
||||
private UUID voteUUID;
|
||||
|
||||
public VoteTotalsSnapshot() {
|
||||
}
|
||||
|
||||
public VoteTotalsSnapshot(int allTimeTotal, int monthTotal, int weeklyTotal, int dailyTotal, int points,
|
||||
int votePartyCurrent, int votePartyRequired, int dateMonthTotal) {
|
||||
this.allTimeTotal = allTimeTotal;
|
||||
this.monthTotal = monthTotal;
|
||||
this.weeklyTotal = weeklyTotal;
|
||||
this.dailyTotal = dailyTotal;
|
||||
this.points = points;
|
||||
this.votePartyCurrent = votePartyCurrent;
|
||||
this.votePartyRequired = votePartyRequired;
|
||||
this.dateMonthTotal = dateMonthTotal;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public VoteTotalsSnapshot(int allTimeTotal, int monthTotal, int weeklyTotal, int dailyTotal, int points,
|
||||
int votePartyCurrent, int votePartyRequired, int dateMonthTotal, UUID voteUUID) {
|
||||
this.allTimeTotal = allTimeTotal;
|
||||
@@ -102,8 +116,7 @@ public class VoteTotalsSnapshot {
|
||||
|
||||
public String toStorageString() {
|
||||
return "v" + CURRENT_VERSION + SEP + allTimeTotal + SEP + monthTotal + SEP + weeklyTotal + SEP + dailyTotal
|
||||
+ SEP + points + SEP + votePartyCurrent + SEP + votePartyRequired + SEP + dateMonthTotal + SEP
|
||||
+ (voteUUID != null ? voteUUID.toString() : "");
|
||||
+ SEP + points + SEP + votePartyCurrent + SEP + votePartyRequired + SEP + dateMonthTotal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -508,8 +508,8 @@ public abstract class VotingPluginProxy {
|
||||
globalMessageProxyHandler.sendMessage(server, delay,
|
||||
VotingPluginWire.vote(cache.getPlayerName(), cache.getUuid(),
|
||||
cache.getService(), cache.getTime(), false, cache.isRealVote(),
|
||||
cache.getText(), getConfig().getBungeeManageTotals(), broadcastHere,
|
||||
num, numberOfVotes));
|
||||
cache.getText(), cache.getVoteId(), getConfig().getBungeeManageTotals(),
|
||||
broadcastHere, num, numberOfVotes));
|
||||
delay++;
|
||||
num++;
|
||||
removed.add(cache);
|
||||
@@ -553,7 +553,7 @@ public abstract class VotingPluginProxy {
|
||||
|
||||
globalMessageProxyHandler.sendMessage(server, delay,
|
||||
VotingPluginWire.voteOnline(cache.getPlayerName(), cache.getUuid(), cache.getService(),
|
||||
cache.getTime(), false, cache.isRealVote(), cache.getText(),
|
||||
cache.getTime(), false, cache.isRealVote(), cache.getText(), cache.getVoteId(),
|
||||
getConfig().getBungeeManageTotals(), broadcastHere, num, numberOfVotes));
|
||||
delay++;
|
||||
num++;
|
||||
@@ -1342,7 +1342,7 @@ public abstract class VotingPluginProxy {
|
||||
public void processQueue() {
|
||||
while (getVoteCacheHandler().getTimeChangeQueue().size() > 0) {
|
||||
VoteTimeQueue vote = getVoteCacheHandler().getTimeChangeQueue().remove();
|
||||
vote(vote.getName(), vote.getService(), true, false, vote.getTime(), null, null);
|
||||
vote(vote.getName(), vote.getService(), true, false, vote.getTime(), null, null, vote.getVoteId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1597,7 +1597,16 @@ public abstract class VotingPluginProxy {
|
||||
|
||||
public synchronized void vote(String player, String service, boolean realVote, boolean timeQueue, long queueTime,
|
||||
VoteTotalsSnapshot text, String uuid) {
|
||||
vote(player, service, realVote, timeQueue, queueTime, text, uuid, null);
|
||||
}
|
||||
|
||||
private synchronized void vote(String player, String service, boolean realVote, boolean timeQueue, long queueTime,
|
||||
VoteTotalsSnapshot text, String uuid, UUID existingVoteId) {
|
||||
try {
|
||||
UUID voteId = existingVoteId;
|
||||
if (voteId == null) {
|
||||
voteId = UUID.randomUUID();
|
||||
}
|
||||
if (player == null || player.isEmpty()) {
|
||||
log("No name from vote on " + service);
|
||||
return;
|
||||
@@ -1608,7 +1617,7 @@ public abstract class VotingPluginProxy {
|
||||
if (getGlobalDataHandler().isTimeChangedHappened()) {
|
||||
getGlobalDataHandler().checkForFinishedTimeChanges();
|
||||
if (timeQueue && getGlobalDataHandler().isTimeChangedHappened()) {
|
||||
getVoteCacheHandler().getTimeChangeQueue().add(new VoteTimeQueue(player, service,
|
||||
getVoteCacheHandler().getTimeChangeQueue().add(new VoteTimeQueue(voteId, player, service,
|
||||
LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
|
||||
log("Caching vote from " + player + "/" + service
|
||||
+ " because time change is happening right now");
|
||||
@@ -1684,8 +1693,6 @@ public abstract class VotingPluginProxy {
|
||||
final boolean playerOnline = isPlayerOnline(player);
|
||||
final String playerServer = playerOnline ? getCurrentPlayerServer(player) : null;
|
||||
|
||||
final UUID voteId = UUID.randomUUID();
|
||||
|
||||
addVoteParty();
|
||||
|
||||
// Totals processing (primary server OR no multiproxy)
|
||||
@@ -1740,7 +1747,7 @@ public abstract class VotingPluginProxy {
|
||||
}
|
||||
|
||||
text = new VoteTotalsSnapshot(allTimeTotal, monthTotal, weeklyTotal, dailyTotal, points,
|
||||
votePartyVotes, currentVotePartyVotesRequired, dateMonthTotal, voteId);
|
||||
votePartyVotes, currentVotePartyVotesRequired, dateMonthTotal);
|
||||
|
||||
ArrayList<Column> update = new ArrayList<>();
|
||||
update.add(new Column("AllTimeTotal", new DataValueInt(allTimeTotal)));
|
||||
@@ -1752,11 +1759,11 @@ public abstract class VotingPluginProxy {
|
||||
update.add(new Column("DailyTotal", new DataValueInt(dailyTotal)));
|
||||
update.add(new Column("Points", new DataValueInt(points)));
|
||||
|
||||
debug("Setting totals " + text.toString());
|
||||
debug("Setting totals " + text.toString() + ", voteId=" + voteId + " for " + player + "/"
|
||||
+ service);
|
||||
getProxyMySQL().update(uuid, update);
|
||||
} else {
|
||||
text = new VoteTotalsSnapshot(0, 0, 0, 0, 0, votePartyVotes, currentVotePartyVotesRequired, 0,
|
||||
voteId);
|
||||
text = new VoteTotalsSnapshot(0, 0, 0, 0, 0, votePartyVotes, currentVotePartyVotesRequired, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1773,10 +1780,11 @@ public abstract class VotingPluginProxy {
|
||||
if (getConfig().getSendVotesToAllServers()) {
|
||||
for (String s : getAllAvailableServers()) {
|
||||
|
||||
boolean forceCache = false;
|
||||
if (!playerOnline && getConfig().getWaitForUserOnline()) {
|
||||
forceCache = true;
|
||||
debug("Forcing vote to cache");
|
||||
boolean forceCache = getConfig().getWaitForUserOnline()
|
||||
&& (!playerOnline || playerServer == null || !playerServer.equalsIgnoreCase(s));
|
||||
|
||||
if (forceCache) {
|
||||
debug("Forcing vote to cache for server " + s);
|
||||
}
|
||||
|
||||
if ((!isSomeoneOnlineServer(s) && method.requiresPlayerOnline()) || forceCache) {
|
||||
@@ -1793,7 +1801,7 @@ public abstract class VotingPluginProxy {
|
||||
|
||||
globalMessageProxyHandler.sendMessage(s, 2,
|
||||
VotingPluginWire.vote(player, uuid, service, time, true, realVote, text.toString(),
|
||||
!getConfig().getBungeeManageTotals(), broadcastHere, 1, 1));
|
||||
voteId, getConfig().getBungeeManageTotals(), broadcastHere, 1, 1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1810,7 +1818,7 @@ public abstract class VotingPluginProxy {
|
||||
|
||||
globalMessageProxyHandler.sendMessage(server, 1,
|
||||
VotingPluginWire.voteOnline(player, uuid, service, time, true, realVote, text.toString(),
|
||||
!getConfig().getBungeeManageTotals(), broadcastHere, 1, 1));
|
||||
voteId, getConfig().getBungeeManageTotals(), broadcastHere, 1, 1));
|
||||
|
||||
if (getConfig().getProxyBroadcastEnabled()) {
|
||||
Set<String> targets = proxyBroadcastDecider.resolveTargets(true, playerServer);
|
||||
@@ -1863,7 +1871,7 @@ public abstract class VotingPluginProxy {
|
||||
if (!getConfig().getMultiProxyOneGlobalReward()) {
|
||||
debug("Sending global proxy vote envelope");
|
||||
multiProxyHandler.sendMultiProxyEnvelope(VotingPluginWire.vote(player, uuid, service, time, false,
|
||||
realVote, text == null ? "" : text.toString(), false, false, 1, 1));
|
||||
realVote, text == null ? "" : text.toString(), voteId, false, false, 1, 1));
|
||||
} else {
|
||||
// Only send to other proxies if the player DID NOT already receive reward on a
|
||||
// backend
|
||||
@@ -1876,8 +1884,9 @@ public abstract class VotingPluginProxy {
|
||||
|
||||
if (shouldSend) {
|
||||
debug("Sending global proxy voteonline envelope");
|
||||
multiProxyHandler.sendMultiProxyEnvelope(VotingPluginWire.voteOnline(player, uuid, service,
|
||||
time, false, realVote, text == null ? "" : text.toString(), false, false, 1, 1));
|
||||
multiProxyHandler
|
||||
.sendMultiProxyEnvelope(VotingPluginWire.voteOnline(player, uuid, service, time, false,
|
||||
realVote, text == null ? "" : text.toString(), voteId, false, false, 1, 1));
|
||||
} else {
|
||||
debug("Not sending global proxy message for voteonline, player already got reward");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope;
|
||||
|
||||
@@ -63,6 +64,7 @@ public final class VotingPluginWire {
|
||||
public static final String K_WAS_ONLINE = "wasOnline";
|
||||
public static final String K_REAL_VOTE = "realVote";
|
||||
public static final String K_TOTALS = "totals";
|
||||
public static final String K_VOTE_ID = "voteId";
|
||||
public static final String K_SET_TOTALS = "setTotals";
|
||||
public static final String K_MANAGE_TOTALS = "manageTotals";
|
||||
public static final String K_BUNGEE_BROADCAST = "bungeeBroadcast";
|
||||
@@ -84,22 +86,24 @@ public final class VotingPluginWire {
|
||||
}
|
||||
|
||||
public static JsonEnvelope vote(String player, String uuid, String service, long time, boolean wasOnline,
|
||||
boolean realVote, String totals, boolean manageTotals, boolean bungeeBroadcast, int num,
|
||||
boolean realVote, String totals, UUID voteId, boolean manageTotals, boolean bungeeBroadcast, int num,
|
||||
int numberOfVotes) {
|
||||
|
||||
return base(SUB_VOTE).put(K_PLAYER, safe(player)).put(K_UUID, safe(uuid)).put(K_SERVICE, safe(service))
|
||||
.put(K_TIME, time).put(K_WAS_ONLINE, wasOnline).put(K_REAL_VOTE, realVote).put(K_TOTALS, safe(totals))
|
||||
.put(K_VOTE_ID, voteId == null ? "" : voteId.toString())
|
||||
.put(K_SET_TOTALS, true) // backend historically defaulted to true
|
||||
.put(K_MANAGE_TOTALS, manageTotals).put(K_BUNGEE_BROADCAST, bungeeBroadcast).put(K_NUM, num)
|
||||
.put(K_NUMBER_OF_VOTES, numberOfVotes).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope voteOnline(String player, String uuid, String service, long time, boolean wasOnline,
|
||||
boolean realVote, String totals, boolean manageTotals, boolean bungeeBroadcast, int num,
|
||||
boolean realVote, String totals, UUID voteId, boolean manageTotals, boolean bungeeBroadcast, int num,
|
||||
int numberOfVotes) {
|
||||
|
||||
return base(SUB_VOTE_ONLINE).put(K_PLAYER, safe(player)).put(K_UUID, safe(uuid)).put(K_SERVICE, safe(service))
|
||||
.put(K_TIME, time).put(K_WAS_ONLINE, wasOnline).put(K_REAL_VOTE, realVote).put(K_TOTALS, safe(totals))
|
||||
.put(K_VOTE_ID, voteId == null ? "" : voteId.toString())
|
||||
.put(K_SET_TOTALS, true).put(K_MANAGE_TOTALS, manageTotals).put(K_BUNGEE_BROADCAST, bungeeBroadcast)
|
||||
.put(K_NUM, num).put(K_NUMBER_OF_VOTES, numberOfVotes).build();
|
||||
}
|
||||
@@ -174,6 +178,7 @@ public final class VotingPluginWire {
|
||||
public final boolean wasOnline;
|
||||
public final boolean realVote;
|
||||
public final String totals;
|
||||
public final UUID voteId;
|
||||
|
||||
public final boolean setTotals;
|
||||
public final boolean manageTotals;
|
||||
@@ -182,7 +187,7 @@ public final class VotingPluginWire {
|
||||
public final int numberOfVotes;
|
||||
|
||||
private Vote(String subChannel, String player, String uuid, String service, long time, boolean wasOnline,
|
||||
boolean realVote, String totals, boolean setTotals, boolean manageTotals, boolean broadcast, int num,
|
||||
boolean realVote, String totals, UUID voteId, boolean setTotals, boolean manageTotals, boolean broadcast, int num,
|
||||
int numberOfVotes) {
|
||||
this.subChannel = subChannel;
|
||||
this.player = player;
|
||||
@@ -192,6 +197,7 @@ public final class VotingPluginWire {
|
||||
this.wasOnline = wasOnline;
|
||||
this.realVote = realVote;
|
||||
this.totals = totals;
|
||||
this.voteId = voteId;
|
||||
this.setTotals = setTotals;
|
||||
this.manageTotals = manageTotals;
|
||||
this.broadcast = broadcast;
|
||||
@@ -212,6 +218,7 @@ public final class VotingPluginWire {
|
||||
final boolean wasOnline = readBool(f, K_WAS_ONLINE, false);
|
||||
final boolean realVote = readBool(f, K_REAL_VOTE, false);
|
||||
final String totals = safe(f.get(K_TOTALS));
|
||||
final UUID voteId = readUuid(f, K_VOTE_ID);
|
||||
|
||||
final boolean setTotals = readBool(f, K_SET_TOTALS, true);
|
||||
final boolean manageTotals = readBool(f, K_MANAGE_TOTALS, false);
|
||||
@@ -221,7 +228,7 @@ public final class VotingPluginWire {
|
||||
final int num = readInt(f, K_NUM, 1);
|
||||
final int numberOfVotes = readInt(f, K_NUMBER_OF_VOTES, 1);
|
||||
|
||||
return new Vote(sub, player, uuid, service, time, wasOnline, realVote, totals, setTotals, manageTotals,
|
||||
return new Vote(sub, player, uuid, service, time, wasOnline, realVote, totals, voteId, setTotals, manageTotals,
|
||||
broadcast, num, numberOfVotes);
|
||||
}
|
||||
|
||||
@@ -294,6 +301,18 @@ public final class VotingPluginWire {
|
||||
}
|
||||
}
|
||||
|
||||
private static UUID readUuid(Map<String, String> f, String key) {
|
||||
String value = f.get(key);
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return UUID.fromString(value);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static long readLong(Map<String, String> f, String key, long def) {
|
||||
String v = f.get(key);
|
||||
if (v == null) {
|
||||
|
||||
+3
-2
@@ -40,6 +40,7 @@ public class BungeeJsonVoteCache extends BungeeJsonFile implements IVoteCache {
|
||||
setString(path + ".Name", voteTimedQueue.getName());
|
||||
setString(path + ".Service", voteTimedQueue.getService());
|
||||
setLong(path + ".Time", voteTimedQueue.getTime());
|
||||
setString(path + ".VoteId", voteTimedQueue.getVoteId() == null ? null : voteTimedQueue.getVoteId().toString());
|
||||
}
|
||||
|
||||
public void addVote(String server, int num, OfflineBungeeVote voteData) {
|
||||
@@ -50,7 +51,7 @@ public class BungeeJsonVoteCache extends BungeeJsonFile implements IVoteCache {
|
||||
setLong(path + ".Time", voteData.getTime());
|
||||
setBoolean(path + ".Real", voteData.isRealVote());
|
||||
setString(path + ".Text", voteData.getText());
|
||||
setString(path + ".VoteID", voteData.getVoteId() != null ? voteData.getVoteId().toString() : null);
|
||||
setString(path + ".VoteId", voteData.getVoteId() != null ? voteData.getVoteId().toString() : null);
|
||||
}
|
||||
|
||||
public void addVoteOnline(String player, int num, OfflineBungeeVote voteData) {
|
||||
@@ -61,7 +62,7 @@ public class BungeeJsonVoteCache extends BungeeJsonFile implements IVoteCache {
|
||||
setLong(path + ".Time", voteData.getTime());
|
||||
setBoolean(path + ".Real", voteData.isRealVote());
|
||||
setString(path + ".Text", voteData.getText());
|
||||
setString(path + ".VoteID", voteData.getVoteId() != null ? voteData.getVoteId().toString() : null);
|
||||
setString(path + ".VoteId", voteData.getVoteId() != null ? voteData.getVoteId().toString() : null);
|
||||
}
|
||||
|
||||
public void clearData() {
|
||||
|
||||
Vendored
+54
-5
@@ -6,6 +6,7 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
|
||||
import com.bencodez.simpleapi.sql.mysql.DbType;
|
||||
@@ -30,7 +31,8 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
+ qi("id") + " BIGSERIAL PRIMARY KEY, "
|
||||
+ qi("playerName") + " VARCHAR(100), "
|
||||
+ qi("service") + " VARCHAR(100), "
|
||||
+ qi("time") + " BIGINT"
|
||||
+ qi("time") + " BIGINT, "
|
||||
+ qi("voteId") + " VARCHAR(36)"
|
||||
+ ");";
|
||||
}
|
||||
|
||||
@@ -39,6 +41,7 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
+ qi("playerName") + " VARCHAR(100),"
|
||||
+ qi("service") + " VARCHAR(100),"
|
||||
+ qi("time") + " BIGINT,"
|
||||
+ qi("voteId") + " VARCHAR(36),"
|
||||
+ "INDEX idx_time (" + qi("time") + ")"
|
||||
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
}
|
||||
@@ -62,6 +65,7 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_timedvotecache",
|
||||
existingMysql,
|
||||
debug);
|
||||
ensureVoteIdColumn();
|
||||
ensureIndexes();
|
||||
}
|
||||
|
||||
@@ -72,9 +76,28 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
*/
|
||||
public ProxyTimedVoteCacheTable(MysqlConfig config, boolean debug) {
|
||||
super("votingplugin_timedvotecache", config, debug);
|
||||
ensureVoteIdColumn();
|
||||
ensureIndexes();
|
||||
}
|
||||
|
||||
private void ensureVoteIdColumn() {
|
||||
String probeSql = "SELECT " + qi("voteId") + " FROM " + qi(getTableName()) + " WHERE 1 = 0;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(probeSql)) {
|
||||
ps.executeQuery();
|
||||
return;
|
||||
} catch (SQLException ignored) {
|
||||
// Column does not exist yet.
|
||||
}
|
||||
|
||||
try {
|
||||
new Query(mysql, "ALTER TABLE " + qi(getTableName()) + " ADD COLUMN " + qi("voteId")
|
||||
+ " VARCHAR(36);").executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureIndexes() {
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
try {
|
||||
@@ -89,18 +112,20 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
// --- INSERT ---
|
||||
/**
|
||||
* Inserts a timed vote.
|
||||
* @param voteId unique vote identifier
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
*/
|
||||
public void insertTimedVote(String playerName, String service, long time) {
|
||||
public void insertTimedVote(UUID voteId, String playerName, String service, long time) {
|
||||
String sql = "INSERT INTO " + qi(getTableName()) + " (" + qi("playerName") + ", " + qi("service") + ", "
|
||||
+ qi("time") + ") VALUES (?, ?, ?);";
|
||||
+ qi("time") + ", " + qi("voteId") + ") VALUES (?, ?, ?, ?);";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, playerName);
|
||||
ps.setString(2, service);
|
||||
ps.setLong(3, time);
|
||||
ps.setString(4, voteId == null ? null : voteId.toString());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
@@ -189,7 +214,8 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
rs.getInt("id"),
|
||||
rs.getString("playerName"),
|
||||
rs.getString("service"),
|
||||
rs.getLong("time")
|
||||
rs.getLong("time"),
|
||||
parseUuid(rs.getString("voteId"))
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -199,6 +225,17 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
return list;
|
||||
}
|
||||
|
||||
private UUID parseUuid(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return UUID.fromString(value);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a row in the timed vote cache table.
|
||||
*/
|
||||
@@ -207,6 +244,7 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
private final String playerName;
|
||||
private final String service;
|
||||
private final long time;
|
||||
private final UUID voteId;
|
||||
|
||||
/**
|
||||
* Constructor for TimedVoteRow.
|
||||
@@ -214,12 +252,14 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
* @param voteId unique vote identifier
|
||||
*/
|
||||
public TimedVoteRow(int id, String playerName, String service, long time) {
|
||||
public TimedVoteRow(int id, String playerName, String service, long time, UUID voteId) {
|
||||
this.id = id;
|
||||
this.playerName = playerName;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
this.voteId = voteId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,5 +293,14 @@ public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote identifier.
|
||||
*
|
||||
* @return vote identifier or null
|
||||
*/
|
||||
public UUID getVoteId() {
|
||||
return voteId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
-8
@@ -6,6 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@@ -87,7 +88,12 @@ public abstract class VoteCacheHandler {
|
||||
* @param server the server name
|
||||
* @param vote the vote to add
|
||||
*/
|
||||
public void addServerVote(String server, OfflineBungeeVote vote) {
|
||||
public synchronized void addServerVote(String server, OfflineBungeeVote vote) {
|
||||
if (containsServerVote(server, vote.getVoteId())) {
|
||||
debug1("Not caching duplicate vote " + vote.getVoteId() + " for server " + server);
|
||||
return;
|
||||
}
|
||||
|
||||
cachedVotes.putIfAbsent(server, new ArrayList<>());
|
||||
cachedVotes.get(server).add(vote);
|
||||
|
||||
@@ -159,7 +165,12 @@ public abstract class VoteCacheHandler {
|
||||
* @param uuid the player UUID
|
||||
* @param vote the vote to add
|
||||
*/
|
||||
public void addOnlineVote(String uuid, OfflineBungeeVote vote) {
|
||||
public synchronized void addOnlineVote(String uuid, OfflineBungeeVote vote) {
|
||||
if (containsOnlineVote(uuid, vote.getVoteId())) {
|
||||
debug1("Not caching duplicate online vote " + vote.getVoteId() + " for " + uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
cachedOnlineVotes.putIfAbsent(uuid, new ArrayList<>());
|
||||
cachedOnlineVotes.get(uuid).add(vote);
|
||||
|
||||
@@ -226,6 +237,82 @@ public abstract class VoteCacheHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a vote is already cached for a server.
|
||||
*
|
||||
* @param server target server
|
||||
* @param voteId unique vote identifier
|
||||
* @return true if the vote is already cached for the server
|
||||
*/
|
||||
private boolean containsServerVote(String server, UUID voteId) {
|
||||
if (voteId == null) {
|
||||
return false;
|
||||
}
|
||||
for (OfflineBungeeVote vote : getVotes(server)) {
|
||||
if (voteId.equals(vote.getVoteId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a vote is already cached for an online player.
|
||||
*
|
||||
* @param uuid player UUID
|
||||
* @param voteId unique vote identifier
|
||||
* @return true if the vote is already cached for the player
|
||||
*/
|
||||
private boolean containsOnlineVote(String uuid, UUID voteId) {
|
||||
if (voteId == null) {
|
||||
return false;
|
||||
}
|
||||
for (OfflineBungeeVote vote : getOnlineVotes(uuid)) {
|
||||
if (voteId.equals(vote.getVoteId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a vote identifier using the current key and the legacy key.
|
||||
*
|
||||
* @param data cached vote data
|
||||
* @return stored vote identifier or an empty string
|
||||
*/
|
||||
private String readVoteId(DataNode data) {
|
||||
if (data.has("VoteId")) {
|
||||
return data.get("VoteId").asString();
|
||||
}
|
||||
if (data.has("VoteID")) {
|
||||
return data.get("VoteID").asString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an optional UUID from cached data.
|
||||
*
|
||||
* @param data cached data
|
||||
* @param key value key
|
||||
* @return parsed UUID or null
|
||||
*/
|
||||
private UUID readUuid(DataNode data, String key) {
|
||||
if (!data.has(key)) {
|
||||
return null;
|
||||
}
|
||||
String value = data.get(key).asString();
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return UUID.fromString(value);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the vote cache to storage.
|
||||
*/
|
||||
@@ -234,7 +321,7 @@ public abstract class VoteCacheHandler {
|
||||
|
||||
if (!getTimeChangeQueue().isEmpty()) {
|
||||
for (VoteTimeQueue vote : getTimeChangeQueue()) {
|
||||
timedVoteCacheTable.insertTimedVote(vote.getName(), vote.getService(), vote.getTime());
|
||||
timedVoteCacheTable.insertTimedVote(vote.getVoteId(), vote.getName(), vote.getService(), vote.getTime());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -286,8 +373,8 @@ public abstract class VoteCacheHandler {
|
||||
// Load timed votes from MySQL
|
||||
ArrayList<VoteTimeQueue> timedVotes = new ArrayList<>();
|
||||
timedVoteCacheTable.getAllVotes().forEach(timedVoteRow -> {
|
||||
VoteTimeQueue voteTimeQueue = new VoteTimeQueue(timedVoteRow.getPlayerName(), timedVoteRow.getService(),
|
||||
timedVoteRow.getTime());
|
||||
VoteTimeQueue voteTimeQueue = new VoteTimeQueue(timedVoteRow.getVoteId(), timedVoteRow.getPlayerName(),
|
||||
timedVoteRow.getService(), timedVoteRow.getTime());
|
||||
timedVotes.add(voteTimeQueue);
|
||||
});
|
||||
timeChangeQueue.addAll(timedVotes);
|
||||
@@ -303,8 +390,9 @@ public abstract class VoteCacheHandler {
|
||||
String name = data.has("Name") ? data.get("Name").asString() : "";
|
||||
String service = data.has("Service") ? data.get("Service").asString() : "";
|
||||
long time = data.has("Time") ? data.get("Time").asLong() : 0L;
|
||||
UUID voteId = readUuid(data, "VoteId");
|
||||
|
||||
getTimeChangeQueue().add(new VoteTimeQueue(name, service, time));
|
||||
getTimeChangeQueue().add(new VoteTimeQueue(voteId, name, service, time));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +414,7 @@ public abstract class VoteCacheHandler {
|
||||
long time = data.has("Time") ? data.get("Time").asLong() : 0L;
|
||||
boolean real = data.has("Real") && data.get("Real").asBoolean();
|
||||
String text = data.has("Text") ? data.get("Text").asString() : "";
|
||||
String voteId = data.has("VoteId") ? data.get("VoteId").asString() : "";
|
||||
String voteId = readVoteId(data);
|
||||
|
||||
votes.add(new OfflineBungeeVote(voteId, name, uuid, service, time, real, text));
|
||||
}
|
||||
@@ -351,7 +439,7 @@ public abstract class VoteCacheHandler {
|
||||
long time = data.has("Time") ? data.get("Time").asLong() : 0L;
|
||||
boolean real = data.has("Real") && data.get("Real").asBoolean();
|
||||
String text = data.has("Text") ? data.get("Text").asString() : "";
|
||||
String voteId = data.has("VoteId") ? data.get("VoteId").asString() : "";
|
||||
String voteId = readVoteId(data);
|
||||
|
||||
votes.add(new OfflineBungeeVote(voteId, name, uuid, service, time, real, text));
|
||||
}
|
||||
|
||||
+4
-2
@@ -28,6 +28,8 @@ public class VelocityJsonVoteCache extends VelocityJSONFile implements IVoteCach
|
||||
setPath(voteTimedQueue.getName(), "TimedVoteCache", String.valueOf(num), "Name");
|
||||
setPath(voteTimedQueue.getService(), "TimedVoteCache", String.valueOf(num), "Service");
|
||||
setPath(voteTimedQueue.getTime(), "TimedVoteCache", String.valueOf(num), "Time");
|
||||
setPath(voteTimedQueue.getVoteId() == null ? null : voteTimedQueue.getVoteId().toString(), "TimedVoteCache",
|
||||
String.valueOf(num), "VoteId");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,7 +40,7 @@ public class VelocityJsonVoteCache extends VelocityJSONFile implements IVoteCach
|
||||
setPath(voteData.getTime(), "VoteCache", server, String.valueOf(num), "Time");
|
||||
setPath(voteData.isRealVote(), "VoteCache", server, String.valueOf(num), "Real");
|
||||
setPath(voteData.getText(), "VoteCache", server, String.valueOf(num), "Text");
|
||||
setPath(voteData.getVoteId() != null ? voteData.getVoteId().toString() : null, "VoteCache", server, String.valueOf(num), "VoteID");
|
||||
setPath(voteData.getVoteId() != null ? voteData.getVoteId().toString() : null, "VoteCache", server, String.valueOf(num), "VoteId");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,7 +51,7 @@ public class VelocityJsonVoteCache extends VelocityJSONFile implements IVoteCach
|
||||
setPath(voteData.getTime(), "OnlineCache", player, String.valueOf(num), "Time");
|
||||
setPath(voteData.isRealVote(), "OnlineCache", player, String.valueOf(num), "Real");
|
||||
setPath(voteData.getText(), "OnlineCache", player, String.valueOf(num), "Text");
|
||||
setPath(voteData.getVoteId() != null ? voteData.getVoteId().toString() : null, "OnlineCache", player, String.valueOf(num), "VoteID");
|
||||
setPath(voteData.getVoteId() != null ? voteData.getVoteId().toString() : null, "OnlineCache", player, String.valueOf(num), "VoteId");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.bencodez.votingplugin.timequeue;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Represents a vote delayed while a proxy time change is active.
|
||||
*/
|
||||
public class VoteTimeQueue {
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -13,8 +18,31 @@ public class VoteTimeQueue {
|
||||
@Getter
|
||||
@Setter
|
||||
private long time;
|
||||
@Getter
|
||||
@Setter
|
||||
private UUID voteId;
|
||||
|
||||
/**
|
||||
* Creates a legacy-compatible queued vote without an identifier.
|
||||
*
|
||||
* @param name player name
|
||||
* @param service service site
|
||||
* @param time vote timestamp
|
||||
*/
|
||||
public VoteTimeQueue(String name, String service, long time) {
|
||||
this(null, name, service, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a queued vote with its original identifier.
|
||||
*
|
||||
* @param voteId unique vote identifier
|
||||
* @param name player name
|
||||
* @param service service site
|
||||
* @param time vote timestamp
|
||||
*/
|
||||
public VoteTimeQueue(UUID voteId, String name, String service, long time) {
|
||||
this.voteId = voteId;
|
||||
this.name = name;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.bencodez.votingplugin.tests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.bencodez.votingplugin.listeners.ProxyVoteDelayCheck;
|
||||
|
||||
class ProxyVoteDelayCheckTest {
|
||||
|
||||
@Test
|
||||
void acceptsQueuedProxyVoteWhenTheProxyTimestampIsAlreadyStored() {
|
||||
assertTrue(ProxyVoteDelayCheck.isQueuedVoteAlreadyRecorded(true, 12345L, 12345L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotBypassTheDelayForDirectOrStaleProxyVotes() {
|
||||
assertFalse(ProxyVoteDelayCheck.isQueuedVoteAlreadyRecorded(false, 12345L, 12345L));
|
||||
assertFalse(ProxyVoteDelayCheck.isQueuedVoteAlreadyRecorded(true, 12344L, 12345L));
|
||||
assertFalse(ProxyVoteDelayCheck.isQueuedVoteAlreadyRecorded(true, 0L, 0L));
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
package com.bencodez.votingplugin.tests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
|
||||
import com.bencodez.votingplugin.proxy.cache.DataNode;
|
||||
import com.bencodez.votingplugin.proxy.cache.IVoteCache;
|
||||
import com.bencodez.votingplugin.proxy.cache.VoteCacheHandler;
|
||||
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
|
||||
|
||||
/**
|
||||
* Regression tests for proxy vote identity across caches and delayed processing.
|
||||
*/
|
||||
public class VoteCacheHandlerVoteIdTest {
|
||||
|
||||
private IVoteCache storage;
|
||||
private VoteCacheHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
storage = mock(IVoteCache.class);
|
||||
when(storage.getServerVotes("server")).thenReturn(Collections.emptyList());
|
||||
when(storage.getOnlineVotes("player-uuid")).thenReturn(Collections.emptyList());
|
||||
handler = newHandler(storage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverCacheRejectsDuplicateVoteId() {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
|
||||
handler.addServerVote("server", vote(voteId, 100L));
|
||||
handler.addServerVote("server", vote(voteId, 101L));
|
||||
|
||||
assertEquals(1, handler.getVotes("server").size());
|
||||
assertEquals(100L, handler.getVotes("server").get(0).getTime());
|
||||
verify(storage).addVote("server", 0, handler.getVotes("server").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlineCacheRejectsDuplicateVoteId() {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
|
||||
handler.addOnlineVote("player-uuid", vote(voteId, 100L));
|
||||
handler.addOnlineVote("player-uuid", vote(voteId, 101L));
|
||||
|
||||
assertEquals(1, handler.getOnlineVotes("player-uuid").size());
|
||||
verify(storage).addVoteOnline("player-uuid", 0, handler.getOnlineVotes("player-uuid").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentServerDeliveriesReserveVoteIdAtomically() throws Exception {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
|
||||
runConcurrently(() -> handler.addServerVote("server", vote(voteId, 100L)),
|
||||
() -> handler.addServerVote("server", vote(voteId, 101L)));
|
||||
|
||||
assertEquals(1, handler.getVotes("server").size());
|
||||
verify(storage, times(1)).addVote(org.mockito.ArgumentMatchers.eq("server"),
|
||||
org.mockito.ArgumentMatchers.anyInt(), org.mockito.ArgumentMatchers.any(OfflineBungeeVote.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentOnlineDeliveriesReserveVoteIdAtomically() throws Exception {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
|
||||
runConcurrently(() -> handler.addOnlineVote("player-uuid", vote(voteId, 100L)),
|
||||
() -> handler.addOnlineVote("player-uuid", vote(voteId, 101L)));
|
||||
|
||||
assertEquals(1, handler.getOnlineVotes("player-uuid").size());
|
||||
verify(storage, times(1)).addVoteOnline(org.mockito.ArgumentMatchers.eq("player-uuid"),
|
||||
org.mockito.ArgumentMatchers.anyInt(), org.mockito.ArgumentMatchers.any(OfflineBungeeVote.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void distinctVoteIdsAreNotCollapsed() {
|
||||
handler.addServerVote("server", vote(UUID.randomUUID(), 100L));
|
||||
handler.addServerVote("server", vote(UUID.randomUUID(), 101L));
|
||||
|
||||
assertEquals(2, handler.getVotes("server").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingVoteIdsRemainLegacyCompatible() {
|
||||
handler.addServerVote("server", vote(null, 100L));
|
||||
handler.addServerVote("server", vote(null, 101L));
|
||||
|
||||
assertEquals(2, handler.getVotes("server").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void currentVoteIdKeyLoadsFromJsonCache() {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
handler = handlerForStoredVote("VoteId", voteId);
|
||||
|
||||
handler.load();
|
||||
|
||||
assertEquals(voteId, handler.getVotes("server").get(0).getVoteId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyVoteIdKeyLoadsFromJsonCache() {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
handler = handlerForStoredVote("VoteID", voteId);
|
||||
|
||||
handler.load();
|
||||
|
||||
assertEquals(voteId, handler.getVotes("server").get(0).getVoteId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timedVoteIdIsPassedToJsonStorage() {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
VoteTimeQueue queued = new VoteTimeQueue(voteId, "Player", "Service", 100L);
|
||||
handler.addTimeVoteToCache(queued);
|
||||
|
||||
handler.saveVoteCache();
|
||||
|
||||
verify(storage).addTimedVote(eq(0), same(queued));
|
||||
verify(storage).save();
|
||||
assertEquals(voteId, handler.getTimeChangeQueue().element().getVoteId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyTimedVoteConstructorHasNoId() {
|
||||
assertNull(new VoteTimeQueue("Player", "Service", 100L).getVoteId());
|
||||
}
|
||||
|
||||
private VoteCacheHandler handlerForStoredVote(String idKey, UUID voteId) {
|
||||
IVoteCache stored = mock(IVoteCache.class);
|
||||
DataNode voteNode = mock(DataNode.class);
|
||||
when(stored.getTimedVoteCache()).thenReturn(Collections.emptyList());
|
||||
when(stored.getServers()).thenReturn(List.of("server"));
|
||||
when(stored.getServerVotes("server")).thenReturn(List.of("0"));
|
||||
when(stored.getServerVotes("server", "0")).thenReturn(voteNode);
|
||||
when(stored.getPlayers()).thenReturn(Collections.emptyList());
|
||||
when(voteNode.isObject()).thenReturn(true);
|
||||
|
||||
stubString(voteNode, "Name", "Player");
|
||||
stubString(voteNode, "UUID", "player-uuid");
|
||||
stubString(voteNode, "Service", "Service");
|
||||
stubLong(voteNode, "Time", 100L);
|
||||
stubBoolean(voteNode, "Real", true);
|
||||
stubString(voteNode, "Text", "totals");
|
||||
stubString(voteNode, idKey, voteId.toString());
|
||||
if ("VoteID".equals(idKey)) {
|
||||
when(voteNode.has("VoteId")).thenReturn(false);
|
||||
}
|
||||
|
||||
return newHandler(stored);
|
||||
}
|
||||
|
||||
private static void stubString(DataNode parent, String key, String value) {
|
||||
DataNode child = mock(DataNode.class);
|
||||
when(parent.has(key)).thenReturn(true);
|
||||
when(parent.get(key)).thenReturn(child);
|
||||
when(child.asString()).thenReturn(value);
|
||||
}
|
||||
|
||||
private static void stubLong(DataNode parent, String key, long value) {
|
||||
DataNode child = mock(DataNode.class);
|
||||
when(parent.has(key)).thenReturn(true);
|
||||
when(parent.get(key)).thenReturn(child);
|
||||
when(child.asLong()).thenReturn(value);
|
||||
}
|
||||
|
||||
private static void stubBoolean(DataNode parent, String key, boolean value) {
|
||||
DataNode child = mock(DataNode.class);
|
||||
when(parent.has(key)).thenReturn(true);
|
||||
when(parent.get(key)).thenReturn(child);
|
||||
when(child.asBoolean()).thenReturn(value);
|
||||
}
|
||||
|
||||
private static void runConcurrently(Runnable first, Runnable second) throws Exception {
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
CountDownLatch ready = new CountDownLatch(2);
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
try {
|
||||
Future<?> firstFuture = executor.submit(() -> {
|
||||
ready.countDown();
|
||||
await(start);
|
||||
first.run();
|
||||
});
|
||||
Future<?> secondFuture = executor.submit(() -> {
|
||||
ready.countDown();
|
||||
await(start);
|
||||
second.run();
|
||||
});
|
||||
ready.await();
|
||||
start.countDown();
|
||||
firstFuture.get();
|
||||
secondFuture.get();
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private static void await(CountDownLatch latch) {
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static OfflineBungeeVote vote(UUID voteId, long time) {
|
||||
return new OfflineBungeeVote(voteId, "Player", "player-uuid", "Service", time, true, "totals");
|
||||
}
|
||||
|
||||
private static VoteCacheHandler newHandler(IVoteCache storage) {
|
||||
return new VoteCacheHandler(null, false, false, null, false, storage) {
|
||||
@Override
|
||||
public void logInfo1(String msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSevere1(String msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug1(Exception e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug1(Throwable e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug1(String msg) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
-2
@@ -54,7 +54,6 @@ public class VoteTotalsSnapshotStorageTest {
|
||||
assertEquals(3, parsed.getVotePartyCurrent());
|
||||
assertEquals(50, parsed.getVotePartyRequired());
|
||||
assertEquals(9, parsed.getDateMonthTotal());
|
||||
assertEquals(voteId, parsed.getVoteUUID());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,7 +105,6 @@ public class VoteTotalsSnapshotStorageTest {
|
||||
assertEquals(11, reparsed.getVotePartyCurrent());
|
||||
assertEquals(22, reparsed.getVotePartyRequired());
|
||||
assertEquals(33, reparsed.getDateMonthTotal());
|
||||
assertEquals(voteId, reparsed.getVoteUUID());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.bencodez.votingplugin.tests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginWire;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginWire.Vote;
|
||||
|
||||
/**
|
||||
* Tests proxy vote wire encoding and decoding.
|
||||
*/
|
||||
public class VotingPluginWireTest {
|
||||
|
||||
@Test
|
||||
public void voteRoundTripPreservesVoteId() {
|
||||
UUID voteId = UUID.randomUUID();
|
||||
|
||||
JsonEnvelope envelope = VotingPluginWire.vote("Player", UUID.randomUUID().toString(), "Service", 100L,
|
||||
true, true, "totals", voteId, true, false, 1, 1);
|
||||
|
||||
Vote vote = VotingPluginWire.readVote(envelope);
|
||||
|
||||
assertEquals(voteId, vote.voteId);
|
||||
assertEquals("Player", vote.player);
|
||||
assertEquals("Service", vote.service);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void voteOnlineRoundTripAllowsMissingVoteId() {
|
||||
JsonEnvelope envelope = VotingPluginWire.voteOnline("Player", UUID.randomUUID().toString(), "Service",
|
||||
100L, true, true, "totals", null, true, false, 1, 1);
|
||||
|
||||
Vote vote = VotingPluginWire.readVote(envelope);
|
||||
|
||||
assertNull(vote.voteId);
|
||||
}
|
||||
}
|
||||
+49
-102
@@ -1,7 +1,6 @@
|
||||
package com.bencodez.votingplugin.tests.broadcast;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -18,200 +17,148 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import com.bencodez.advancedcore.api.misc.MiscUtils;
|
||||
import com.bencodez.simpleapi.time.ParsedDuration;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.broadcast.BroadcastFormat;
|
||||
import com.bencodez.votingplugin.broadcast.BroadcastHandler;
|
||||
import com.bencodez.votingplugin.broadcast.BroadcastSettings;
|
||||
import com.bencodez.votingplugin.broadcast.VoteBroadcastType;
|
||||
import com.bencodez.votingplugin.user.UserManager;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
public class BroadcastHandlerTest {
|
||||
|
||||
private MockedStatic<Bukkit> bukkitStatic;
|
||||
private MockedStatic<MiscUtils> miscStatic;
|
||||
private ConsoleCommandSender console;
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
if (bukkitStatic != null) {
|
||||
bukkitStatic.close();
|
||||
}
|
||||
if (miscStatic != null) {
|
||||
miscStatic.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static BroadcastSettings settings(VoteBroadcastType type, String duration, int maxSites) {
|
||||
BroadcastFormat fmt = new BroadcastFormat("BM %player% %site% %reason%",
|
||||
BroadcastFormat format = new BroadcastFormat("BM %player% %site% %reason%",
|
||||
"H %player% %sites_count% %sites% %reason%", "L %site%");
|
||||
return new BroadcastSettings(type, ParsedDuration.parse(duration), maxSites, fmt);
|
||||
return new BroadcastSettings(type, ParsedDuration.parse(duration), maxSites, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* BroadcastHandler requires a non-null UserManager and uses console sender.
|
||||
* Stub both so tests don't NPE and so player is eligible
|
||||
* (DisableBroadcast=false).
|
||||
* Stubs Bukkit with no online recipients and a valid console sender.
|
||||
*
|
||||
* The handler always writes the final rendered message to the console, so these
|
||||
* tests can verify broadcast behavior without mocking AdvancedCore's internal
|
||||
* server message handle.
|
||||
*/
|
||||
private void stubEligiblePlayerAndConsole(VotingPluginMain plugin, Player p) {
|
||||
UserManager um = mock(UserManager.class);
|
||||
VotingPluginUser user = mock(VotingPluginUser.class);
|
||||
|
||||
when(plugin.getVotingPluginUserManager()).thenReturn(um);
|
||||
when(um.getVotingPluginUser(p)).thenReturn(user);
|
||||
when(user.getDisableBroadcast()).thenReturn(false);
|
||||
|
||||
private void stubBukkitAndConsole() {
|
||||
Server server = mock(Server.class);
|
||||
ConsoleCommandSender console = mock(ConsoleCommandSender.class);
|
||||
console = mock(ConsoleCommandSender.class);
|
||||
|
||||
bukkitStatic = mockStatic(Bukkit.class);
|
||||
bukkitStatic.when(Bukkit::getOnlinePlayers).thenReturn(Collections.emptyList());
|
||||
bukkitStatic.when(Bukkit::getServer).thenReturn(server);
|
||||
|
||||
when(server.getConsoleSender()).thenReturn(console);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void everyVote_broadcasts_once_per_vote() {
|
||||
VotingPluginMain plugin = mock(VotingPluginMain.class);
|
||||
stubBukkitAndConsole();
|
||||
|
||||
Player p = mock(Player.class);
|
||||
bukkitStatic = mockStatic(Bukkit.class);
|
||||
bukkitStatic.when(Bukkit::getOnlinePlayers).thenReturn(Collections.singletonList(p));
|
||||
stubEligiblePlayerAndConsole(plugin, p);
|
||||
|
||||
MiscUtils misc = mock(MiscUtils.class);
|
||||
miscStatic = mockStatic(MiscUtils.class);
|
||||
miscStatic.when(MiscUtils::getInstance).thenReturn(misc);
|
||||
|
||||
BroadcastHandler h = new BroadcastHandler(plugin, settings(VoteBroadcastType.EVERY_VOTE, "0", 5),
|
||||
BroadcastHandler handler = new BroadcastHandler(plugin, settings(VoteBroadcastType.EVERY_VOTE, "0", 5),
|
||||
ZoneId.systemDefault());
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
h.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
h.broadcastVote(uuid, "Ben", "SiteB", true);
|
||||
handler.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
handler.broadcastVote(uuid, "Ben", "SiteB", true);
|
||||
|
||||
verify(misc, times(2)).broadcast(any(String.class), anyList());
|
||||
verify(console, times(2)).sendMessage(any(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cooldownPerPlayer_suppresses_second_vote_within_window() {
|
||||
VotingPluginMain plugin = mock(VotingPluginMain.class);
|
||||
stubBukkitAndConsole();
|
||||
|
||||
Player p = mock(Player.class);
|
||||
bukkitStatic = mockStatic(Bukkit.class);
|
||||
bukkitStatic.when(Bukkit::getOnlinePlayers).thenReturn(Collections.singletonList(p));
|
||||
stubEligiblePlayerAndConsole(plugin, p);
|
||||
|
||||
MiscUtils misc = mock(MiscUtils.class);
|
||||
miscStatic = mockStatic(MiscUtils.class);
|
||||
miscStatic.when(MiscUtils::getInstance).thenReturn(misc);
|
||||
|
||||
BroadcastHandler h = new BroadcastHandler(plugin, settings(VoteBroadcastType.COOLDOWN_PER_PLAYER, "10m", 5),
|
||||
ZoneId.systemDefault());
|
||||
BroadcastHandler handler = new BroadcastHandler(plugin,
|
||||
settings(VoteBroadcastType.COOLDOWN_PER_PLAYER, "10m", 5), ZoneId.systemDefault());
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
h.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
h.broadcastVote(uuid, "Ben", "SiteB", true); // within 10m => should not broadcast
|
||||
handler.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
handler.broadcastVote(uuid, "Ben", "SiteB", true);
|
||||
|
||||
verify(misc, times(1)).broadcast(any(String.class), anyList());
|
||||
verify(console, times(1)).sendMessage(any(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void firstVoteOfDay_only_first_broadcasts_for_that_day() {
|
||||
VotingPluginMain plugin = mock(VotingPluginMain.class);
|
||||
stubBukkitAndConsole();
|
||||
|
||||
Player p = mock(Player.class);
|
||||
bukkitStatic = mockStatic(Bukkit.class);
|
||||
bukkitStatic.when(Bukkit::getOnlinePlayers).thenReturn(Collections.singletonList(p));
|
||||
stubEligiblePlayerAndConsole(plugin, p);
|
||||
|
||||
MiscUtils misc = mock(MiscUtils.class);
|
||||
miscStatic = mockStatic(MiscUtils.class);
|
||||
miscStatic.when(MiscUtils::getInstance).thenReturn(misc);
|
||||
|
||||
BroadcastHandler h = new BroadcastHandler(plugin, settings(VoteBroadcastType.FIRST_VOTE_OF_DAY, "0", 5),
|
||||
BroadcastHandler handler = new BroadcastHandler(plugin, settings(VoteBroadcastType.FIRST_VOTE_OF_DAY, "0", 5),
|
||||
ZoneId.systemDefault());
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
h.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
h.broadcastVote(uuid, "Ben", "SiteB", true);
|
||||
handler.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
handler.broadcastVote(uuid, "Ben", "SiteB", true);
|
||||
|
||||
verify(misc, times(1)).broadcast(any(String.class), anyList());
|
||||
verify(console, times(1)).sendMessage(any(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batchWindowPerPlayer_flushes_once_and_lists_sites() {
|
||||
VotingPluginMain plugin = mock(VotingPluginMain.class);
|
||||
|
||||
Player p = mock(Player.class);
|
||||
|
||||
BukkitScheduler scheduler = mock(BukkitScheduler.class);
|
||||
BukkitTask task = mock(BukkitTask.class);
|
||||
|
||||
bukkitStatic = mockStatic(Bukkit.class);
|
||||
bukkitStatic.when(Bukkit::getOnlinePlayers).thenReturn(Collections.singletonList(p));
|
||||
stubBukkitAndConsole();
|
||||
bukkitStatic.when(Bukkit::getScheduler).thenReturn(scheduler);
|
||||
stubEligiblePlayerAndConsole(plugin, p);
|
||||
|
||||
AtomicReference<Runnable> scheduled = new AtomicReference<>();
|
||||
when(scheduler.runTaskLater(eq(plugin), any(Runnable.class), anyLong())).thenAnswer(inv -> {
|
||||
scheduled.set(inv.getArgument(1));
|
||||
AtomicReference<Runnable> scheduled = new AtomicReference<Runnable>();
|
||||
when(scheduler.runTaskLater(eq(plugin), any(Runnable.class), anyLong())).thenAnswer(invocation -> {
|
||||
scheduled.set(invocation.getArgument(1));
|
||||
return task;
|
||||
});
|
||||
|
||||
MiscUtils misc = mock(MiscUtils.class);
|
||||
miscStatic = mockStatic(MiscUtils.class);
|
||||
miscStatic.when(MiscUtils::getInstance).thenReturn(misc);
|
||||
|
||||
BroadcastSettings s = new BroadcastSettings(VoteBroadcastType.BATCH_WINDOW_PER_PLAYER,
|
||||
BroadcastSettings broadcastSettings = new BroadcastSettings(VoteBroadcastType.BATCH_WINDOW_PER_PLAYER,
|
||||
ParsedDuration.parse("1s"), 10, new BroadcastFormat("SINGLE %player% %site%",
|
||||
"HEADER %player% %sites_count% %sites% %reason%", " - %site%"));
|
||||
|
||||
BroadcastHandler h = new BroadcastHandler(plugin, s, ZoneId.systemDefault());
|
||||
BroadcastHandler handler = new BroadcastHandler(plugin, broadcastSettings, ZoneId.systemDefault());
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
h.broadcastVote(uuid, "Ben", "SiteB", true);
|
||||
h.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
handler.broadcastVote(uuid, "Ben", "SiteB", true);
|
||||
handler.broadcastVote(uuid, "Ben", "SiteA", true);
|
||||
|
||||
// run the scheduled flush after both votes
|
||||
scheduled.get().run();
|
||||
|
||||
// 2 sites => render produces 3 lines (header + 2 list lines), each broadcast
|
||||
// separately
|
||||
verify(misc, times(3)).broadcast(any(String.class), anyList());
|
||||
|
||||
// header should contain both sites and count=2
|
||||
verify(misc).broadcast(argThat(msg -> msg.contains("HEADER") && msg.contains("Ben") && msg.contains("2")
|
||||
&& msg.contains("SiteA") && msg.contains("SiteB") && msg.contains("batch")), anyList());
|
||||
verify(console, times(3)).sendMessage(any(String.class));
|
||||
verify(console).sendMessage(org.mockito.ArgumentMatchers.<String>argThat(
|
||||
message -> message.contains("HEADER") && message.contains("Ben") && message.contains("2")
|
||||
&& message.contains("SiteA") && message.contains("SiteB") && message.contains("batch")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxSitesListed_truncates_list_in_multi_broadcast() {
|
||||
VotingPluginMain plugin = mock(VotingPluginMain.class);
|
||||
stubBukkitAndConsole();
|
||||
|
||||
Player p = mock(Player.class);
|
||||
bukkitStatic = mockStatic(Bukkit.class);
|
||||
bukkitStatic.when(Bukkit::getOnlinePlayers).thenReturn(Collections.singletonList(p));
|
||||
stubEligiblePlayerAndConsole(plugin, p);
|
||||
|
||||
MiscUtils misc = mock(MiscUtils.class);
|
||||
miscStatic = mockStatic(MiscUtils.class);
|
||||
miscStatic.when(MiscUtils::getInstance).thenReturn(misc);
|
||||
|
||||
BroadcastSettings s = new BroadcastSettings(VoteBroadcastType.EVERY_VOTE, ParsedDuration.parse("0"), 1,
|
||||
BroadcastSettings broadcastSettings = new BroadcastSettings(VoteBroadcastType.EVERY_VOTE,
|
||||
ParsedDuration.parse("0"), 1,
|
||||
new BroadcastFormat("SINGLE %player% %site%", "H %player% %sites_count% %sites%", "L %site%"));
|
||||
|
||||
BroadcastHandler h = new BroadcastHandler(plugin, s, ZoneId.systemDefault());
|
||||
BroadcastHandler handler = new BroadcastHandler(plugin, broadcastSettings, ZoneId.systemDefault());
|
||||
|
||||
h.broadcastVote(UUID.randomUUID(), "Ben", "SiteA", true);
|
||||
handler.broadcastVote(UUID.randomUUID(), "Ben", "SiteA", true);
|
||||
|
||||
verify(misc, times(1)).broadcast(any(String.class), anyList());
|
||||
verify(console, times(1)).sendMessage(any(String.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user