From bcc812c06fedd6ad459c417397c6ffb190d0bdeb Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 1 May 2026 19:49:25 -0600 Subject: [PATCH 1/2] Migrate legacy streak progress into new vote streak state --- .../votestreak/VoteStreakDefinition.java | 5 +- .../votestreak/VoteStreakHandler.java | 86 ++- .../src/main/resources/SpecialRewards.yml | 616 +++++++++--------- .../votestreak/VoteStreakHandlerTest.java | 95 ++- 4 files changed, 434 insertions(+), 368 deletions(-) diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakDefinition.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakDefinition.java index fb5000f41..ecfd0d2cb 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakDefinition.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakDefinition.java @@ -20,9 +20,11 @@ public final class VoteStreakDefinition { @Getter private final int votesRequired; + @Getter + private final boolean recurring; public VoteStreakDefinition(String id, VoteStreakType type, boolean enabled, int requiredAmount, int votesRequired, - int allowMissedAmount, int allowMissedPeriod) { + int allowMissedAmount, int allowMissedPeriod, boolean recurring) { this.id = id; this.type = type; this.enabled = enabled; @@ -30,6 +32,7 @@ public VoteStreakDefinition(String id, VoteStreakType type, boolean enabled, int this.allowMissedAmount = Math.max(0, allowMissedAmount); this.allowMissedPeriod = Math.max(0, allowMissedPeriod); this.votesRequired = Math.max(1, votesRequired); + this.recurring = recurring; } } diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java index 314e3bcfd..2eb710538 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java @@ -100,6 +100,38 @@ public void processVote(VotingPluginUser user, long voteTimeMillis, UUID voteUUI } } + + private int getLegacyStreakProgress(VotingPluginUser user, VoteStreakType type) { + switch (type) { + case DAILY: + return Math.max(0, user.getDayVoteStreak()); + case WEEKLY: + return Math.max(0, user.getWeekVoteStreak()); + case MONTHLY: + return Math.max(0, user.getMonthVoteStreak()); + default: + return 0; + } + } + + private void migrateLegacyProgressIfNeeded(VotingPluginUser user, VoteStreakDefinition def, StreakState state, + String currentPeriodKey) { + if (state.periodKey != null && !state.periodKey.isEmpty()) { + return; + } + int legacyProgress = getLegacyStreakProgress(user, def.getType()); + if (legacyProgress <= 0) { + return; + } + state.periodKey = currentPeriodKey; + state.streakCount = legacyProgress; + state.votesThisPeriod = 0; + state.countedThisPeriod = false; + state.missWindowStartKey = ""; + state.missesUsed = 0; + plugin.extraDebug("[VoteStreak] migrated legacy progress for " + def.getId() + ": " + legacyProgress); + } + private void processVoteForDefinition(VotingPluginUser user, VoteStreakDefinition def, long voteTimeMillis, UUID voteUUID) { final String col = getColumnName(def); @@ -107,6 +139,7 @@ private void processVoteForDefinition(VotingPluginUser user, VoteStreakDefinitio StreakState state = StreakState.deserialize(rawBefore); final String currentPeriodKey = periodKey(def.getType(), voteTimeMillis); + migrateLegacyProgressIfNeeded(user, def, state, currentPeriodKey); plugin.extraDebug("[VoteStreak] def=" + def.getId() + " idKey=" + def.getId() + " type=" + def.getType() + " col=" + col + " period=" + currentPeriodKey + " votesReq=" + def.getVotesRequired() + " interval=" @@ -146,7 +179,9 @@ private void processVoteForDefinition(VotingPluginUser user, VoteStreakDefinitio state.streakCount++; int interval = Math.max(1, def.getRequiredAmount()); - boolean shouldReward = state.streakCount > 0 && (state.streakCount % interval) == 0; + boolean shouldReward = def.isRecurring() ? + (state.streakCount > 0 && (state.streakCount % interval) == 0) + : (state.streakCount == interval); plugin.extraDebug("[VoteStreak] period satisfied: streakCount=" + state.streakCount + " interval=" + interval + " shouldReward=" + shouldReward); @@ -507,6 +542,43 @@ private static boolean parseBool(String s, boolean def) { public final class VoteStreakConfigLoader { + + private boolean loadLegacy(ConfigurationSection root) { + ConfigurationSection legacy = root.getConfigurationSection("VoteStreak"); + if (legacy == null) { + return false; + } + boolean any = false; + any |= loadLegacyType(legacy, "Day", VoteStreakType.DAILY); + any |= loadLegacyType(legacy, "Week", VoteStreakType.WEEKLY); + any |= loadLegacyType(legacy, "Month", VoteStreakType.MONTHLY); + return any; + } + + private boolean loadLegacyType(ConfigurationSection legacy, String key, VoteStreakType type) { + ConfigurationSection sec = legacy.getConfigurationSection(key); + if (sec == null) return false; + boolean any = false; + for (String streakKey : sec.getKeys(false)) { + ConfigurationSection defSec = sec.getConfigurationSection(streakKey); + if (defSec == null) continue; + String normalized = streakKey.replace("-", ""); + if (!MessageAPI.isInt(normalized)) continue; + int amount = Integer.parseInt(normalized); + if (amount <= 0) continue; + boolean recurring = streakKey.contains("-"); + boolean enabled = defSec.getBoolean("Enabled", true); + defSec.set("Enabled", false); + String id = "Legacy" + type.name() + amount + (recurring ? "Recurring" : "OneTime"); + VoteStreakDefinition def = new VoteStreakDefinition(id, type, enabled, amount, 1, 0, 0, recurring); + plugin.getUserManager().getDataManager() + .addKey(new UserDataKeyString(getColumnName(def)).setColumnType("MEDIUMTEXT")); + byId.put(id, def); + ordered.add(def); + any = true; + } + return any; + } private final Pattern idPattern = Pattern.compile("^[A-Za-z0-9_\\-]+$"); // no spaces public void load(ConfigurationSection root) { @@ -516,7 +588,11 @@ public void load(ConfigurationSection root) { } ConfigurationSection voteStreaks = root.getConfigurationSection("VoteStreaks"); - if (voteStreaks == null) { + if (voteStreaks == null || voteStreaks.getKeys(false).isEmpty()) { + if (loadLegacy(root)) { + plugin.getLogger().info("Loaded " + ordered.size() + " VoteStreak definitions from legacy VoteStreak section."); + return; + } plugin.getLogger().warning("VoteStreaks is missing or not a section; no streaks loaded."); return; } @@ -559,6 +635,7 @@ public void load(ConfigurationSection root) { } boolean enabled = defSec.getBoolean("Enabled", true); + defSec.set("Enabled", false); int amountInterval = 0; int votesRequired = 1; @@ -579,11 +656,10 @@ public void load(ConfigurationSection root) { int allowMissedAmount = Math.max(0, defSec.getInt("AllowMissedAmount", 0)); int allowMissedPeriod = Math.max(0, defSec.getInt("AllowMissedPeriod", 0)); - - // ConfigurationSection editableTarget = getOrCreateVoteStreakSection(id); + boolean recurring = defSec.getBoolean("Recurring", true); VoteStreakDefinition def = new VoteStreakDefinition(id, type, enabled, amountInterval, votesRequired, - allowMissedAmount, allowMissedPeriod); + allowMissedAmount, allowMissedPeriod, recurring); plugin.getUserManager().getDataManager() .addKey(new UserDataKeyString(getColumnName(def)).setColumnType("MEDIUMTEXT")); diff --git a/VotingPlugin/src/main/resources/SpecialRewards.yml b/VotingPlugin/src/main/resources/SpecialRewards.yml index e436a0dcf..06db0c90d 100644 --- a/VotingPlugin/src/main/resources/SpecialRewards.yml +++ b/VotingPlugin/src/main/resources/SpecialRewards.yml @@ -1,307 +1,309 @@ -#################################################################################################### -# VotingPlugin – Special Rewards Configuration (SpecialRewards.yml) -# -# Quick-disable rule: -# - To disable any reward section: set it to {} (single line) or remove it. -# Example: FirstVote: {} -# -# VoteMilestones (NEW / Recommended): -# - Each entry is evaluated on every vote. -# - Triggers: -# • At: (exact totals; supports ranges like 10..50 step 10) -# • Every: (fires at multiples: 10,20,30,...) -# - First vote note: -# • The first vote total is 1, so use At: 1 for “first vote”. -# -# Limits (optional, per milestone): -# - NONE (default) : can trigger whenever it matches -# - WINDOW_DAY/WEEK/MONTH : only once per calendar window -# - COOLDOWN : once per duration (30m, 12h, 2w, 1mo, 5000ms) -# -# Groups (optional): -# - Use Group: on a milestone to put multiple milestones into a selection bucket. -# - Selection modes per group are configured under VoteMilestonesOptions.Groups: -# • ALL : execute all matched milestones in that group -# • FIRST : execute first matched milestone (file order) -# • HIGHEST : execute “best” matched milestone (prefers AT > EVERY; higher values win) -#################################################################################################### - -# ----------------------------- -# VoteMilestones global options -# ----------------------------- -VoteMilestonesOptions: - Groups: - # Group id and setting - # Set group id on each votemilestone (default id is default if not set) - # Valid options: - # ALL - Allow giving all vote milestones - # FIRST - Only give the first vote milestone that is met - # HIGHEST - Only give the highest vote milestone that is met - Default: ALL - ExampleId: FIRST - - -# ----------------------------- -# VoteMilestones -# ----------------------------- -VoteMilestones: - - # First vote ever (all-time) - # Total becomes 1 on the player's first vote. - FirstVoteAllTime: - Enabled: true - Total: AllTime # also works: ALLTIME_VOTES, ALLTIME, AllTime, etc - At: 1 - Group: first - Rewards: - Messages: - Player: "&aThanks for your first vote ever!" - - # Every 25 all-time votes - Every25Votes: - Enabled: false - Total: ALLTIME_VOTES - Every: 25 - Rewards: - Commands: - - "say %player% hit %amount% votes!" - - # Exact milestones + range expansion - MilestoneMix: - Enabled: true - Total: Daily # Daily / DAILY / DAILY_VOTES all work - At: - - 5 - - 10..50 step 10 - - 100 - Rewards: - Messages: - Player: "&bDaily milestone reached: %amount%!" - - # Points milestones - Every10Points: - Enabled: false - Total: POINTS - Every: 10 - Rewards: - Commands: - - "give %player% diamond 1" - - # AllSitesToday - # Rewards when the player votes on ALL sites in one day. - AllSitesToday_AllDone: - Enabled: true - Total: ALLSITES_TODAY - At: 5 # <-- set this to YOUR number of vote sites - Limit: - Type: WINDOW_DAY - Rewards: - Messages: - Player: "&eYou voted on all sites today!" - - # Cooldown example - # Rewards every 3 votes, but only once every 12 hours. - SomeCooldownExample: - Enabled: false - Total: ALLTIME_VOTES - Every: 3 - Limit: - Type: COOLDOWN - Duration: 12h # supports: 30m, 12h, 1d, 2w, 1mo, 5000ms - Rewards: - Messages: - Player: "&aYou hit another %amount% votes, but only once every 12 hours!" - - -# ----------------------------- -# Vote party configuration -# ----------------------------- -VoteParty: - # Whether or not vote party is enabled - Enabled: false - # Number of votes required to give rewards - VotesRequired: 20 - # Increase the amount of votes required on each vote party reached - #IncreaseVotesRequired: 10 - # If true, players who did not vote to reach the votes required will - # receive the reward - # If false, only players who voted get rewards - GiveAllPlayers: false - # If true, give online players the vote party reward - # Can also use RewardType: ONLINE if you wanted - GiveOnlinePlayersOnly: true - # If true, the vote count will reset each day - ResetEachDay: false - # If true, the vote count will reset each week - ResetWeekly: false - # Reset at the end of the month - ResetMonthly: false - # If true, will give voteparty only once per day - OnlyOncePerDay: false - # If true, will give voteparty once per week only - OnlyOncePerWeek: false - # Reset extra votes weekly - ResetExtraVotesWeekly: false - # Reset extra votes monthly - ResetExtraVotesMonthly: false - # Count votes from /av vote? - CountFakeVotes: true - # Number of user votes that apply to vote party total the user needs to get rewards - UserVotesRequired: 0 - # Count offline votes - CountOfflineVotes: true - # Broadcast when vote party reached - Broadcast: '&cReached the vote party amount!' - - # Send vote reminder at number of votes to go - VoteReminder: - AtVotes: [] - # Send a broadcast at a certain number of votes left and send a vote reminder - Broadcast: '%votesrequired% left to go, go vote!' - # Commands to run instead - Commands: [] - - # List of commands to execute, these only execute once. - # %player% does not work here - GlobalCommands: [] - # List of commands to execute once (picks one randomly) - # Only runs once, %player% does not work here - GlobalRandomCommand: [] - # Rewards to give per player - Rewards: - # Uncomment below for online players to be rewarded only - #RewardType: ONLINE - Commands: - - say %player% - Items: - DIAMOND: - Material: 'DIAMOND' - Amount: 1 - - -# ----------------------------- -# Vote streak rewards (NEW) -# ----------------------------- -VoteStreaks: - # Id name, data is stored under this name - # On proxy setups keep different for each server unless you want one global streak/reward - Daily3Votes: - Type: DAILY - Enabled: false - Requirements: - Amount: 3 - VotesRequired: 3 - AllowMissedAmount: 1 - AllowMissedPeriod: 7 - Rewards: - Commands: - - say test - -NameMCLikeReward: - Enabled: false - - # Server ip/domain used in the NameMC API URL - Url: "play.example.com" - - # How often to check NameMC in minutes - CheckIntervalMinutes: 10 - - Rewards: - Messages: - Player: "&aThanks for liking the server on NameMC!" - Commands: - - "say %player% liked the server on NameMC!" - -# ----------------------------- -# Legacy vote rewards (DEPRECATED) -# ----------------------------- -# These are fully disabled and kept only for reference. -# VoteMilestones above replace all of this functionality. - -FirstVote: {} - -FirstVoteToday: {} - -AllSites: {} - -OnlyOneCumulative: false - -Cumulative: - '20': - Enabled: false - TotalToUse: AllTime - Rewards: - Messages: - Player: '&aYou got %cumulative% cumulative votes!' - -ResetMilestonesMonthly: false - -MileStones: - '20': - Enabled: false - Rewards: - Messages: - Player: '&aYou got %milestone% milestone votes!' - -VoteStreak: - Day: - '2': - Enabled: false - Rewards: - Messages: - Player: "&aYou voted for %Streak% %Type%'s in a row!" - Week: - '2': - Enabled: false - Rewards: - Messages: - Player: "&aYou voted for %Streak% %Type%'s in a row!" - Month: - '2': - Enabled: false - Rewards: - Messages: - Player: "&aYou voted for %Streak% %Type%'s in a row!" - Requirement: - UsePercentage: false - Day: 50 - Week: 50 - Month: 50 - - -# ----------------------------- -# Top voter awards -# ----------------------------- -EnableMonthlyAwards: true - -MonthlyAwards: - 1: - Rewards: - Messages: - Player: '&aYou came in first place in %TopVoter%!' - 2: - Rewards: - Messages: - Player: '&aYou came in second place in %TopVoter%!' - -EnableWeeklyAwards: false -WeeklyAwards: - 1: - Rewards: - Messages: - Player: '&aYou came in first place in %TopVoter%!' - 2: - Rewards: - Messages: - Player: '&aYou came in second place in %TopVoter%!' - -EnableDailyRewards: false -DailyAwards: - 1: - Rewards: - Messages: - Player: '&aYou came in first place in %TopVoter%!' - 2: - Rewards: - Messages: - Player: '&aYou came in second place in %TopVoter%!' +#################################################################################################### +# VotingPlugin – Special Rewards Configuration (SpecialRewards.yml) +# +# Quick-disable rule: +# - To disable any reward section: set it to {} (single line) or remove it. +# Example: FirstVote: {} +# +# VoteMilestones (NEW / Recommended): +# - Each entry is evaluated on every vote. +# - Triggers: +# • At: (exact totals; supports ranges like 10..50 step 10) +# • Every: (fires at multiples: 10,20,30,...) +# - First vote note: +# • The first vote total is 1, so use At: 1 for “first vote”. +# +# Limits (optional, per milestone): +# - NONE (default) : can trigger whenever it matches +# - WINDOW_DAY/WEEK/MONTH : only once per calendar window +# - COOLDOWN : once per duration (30m, 12h, 2w, 1mo, 5000ms) +# +# Groups (optional): +# - Use Group: on a milestone to put multiple milestones into a selection bucket. +# - Selection modes per group are configured under VoteMilestonesOptions.Groups: +# • ALL : execute all matched milestones in that group +# • FIRST : execute first matched milestone (file order) +# • HIGHEST : execute “best” matched milestone (prefers AT > EVERY; higher values win) +#################################################################################################### + +# ----------------------------- +# VoteMilestones global options +# ----------------------------- +VoteMilestonesOptions: + Groups: + # Group id and setting + # Set group id on each votemilestone (default id is default if not set) + # Valid options: + # ALL - Allow giving all vote milestones + # FIRST - Only give the first vote milestone that is met + # HIGHEST - Only give the highest vote milestone that is met + Default: ALL + ExampleId: FIRST + + +# ----------------------------- +# VoteMilestones +# ----------------------------- +VoteMilestones: + + # First vote ever (all-time) + # Total becomes 1 on the player's first vote. + FirstVoteAllTime: + Enabled: true + Total: AllTime # also works: ALLTIME_VOTES, ALLTIME, AllTime, etc + At: 1 + Group: first + Rewards: + Messages: + Player: "&aThanks for your first vote ever!" + + # Every 25 all-time votes + Every25Votes: + Enabled: false + Total: ALLTIME_VOTES + Every: 25 + Rewards: + Commands: + - "say %player% hit %amount% votes!" + + # Exact milestones + range expansion + MilestoneMix: + Enabled: true + Total: Daily # Daily / DAILY / DAILY_VOTES all work + At: + - 5 + - 10..50 step 10 + - 100 + Rewards: + Messages: + Player: "&bDaily milestone reached: %amount%!" + + # Points milestones + Every10Points: + Enabled: false + Total: POINTS + Every: 10 + Rewards: + Commands: + - "give %player% diamond 1" + + # AllSitesToday + # Rewards when the player votes on ALL sites in one day. + AllSitesToday_AllDone: + Enabled: true + Total: ALLSITES_TODAY + At: 5 # <-- set this to YOUR number of vote sites + Limit: + Type: WINDOW_DAY + Rewards: + Messages: + Player: "&eYou voted on all sites today!" + + # Cooldown example + # Rewards every 3 votes, but only once every 12 hours. + SomeCooldownExample: + Enabled: false + Total: ALLTIME_VOTES + Every: 3 + Limit: + Type: COOLDOWN + Duration: 12h # supports: 30m, 12h, 1d, 2w, 1mo, 5000ms + Rewards: + Messages: + Player: "&aYou hit another %amount% votes, but only once every 12 hours!" + + +# ----------------------------- +# Vote party configuration +# ----------------------------- +VoteParty: + # Whether or not vote party is enabled + Enabled: false + # Number of votes required to give rewards + VotesRequired: 20 + # Increase the amount of votes required on each vote party reached + #IncreaseVotesRequired: 10 + # If true, players who did not vote to reach the votes required will + # receive the reward + # If false, only players who voted get rewards + GiveAllPlayers: false + # If true, give online players the vote party reward + # Can also use RewardType: ONLINE if you wanted + GiveOnlinePlayersOnly: true + # If true, the vote count will reset each day + ResetEachDay: false + # If true, the vote count will reset each week + ResetWeekly: false + # Reset at the end of the month + ResetMonthly: false + # If true, will give voteparty only once per day + OnlyOncePerDay: false + # If true, will give voteparty once per week only + OnlyOncePerWeek: false + # Reset extra votes weekly + ResetExtraVotesWeekly: false + # Reset extra votes monthly + ResetExtraVotesMonthly: false + # Count votes from /av vote? + CountFakeVotes: true + # Number of user votes that apply to vote party total the user needs to get rewards + UserVotesRequired: 0 + # Count offline votes + CountOfflineVotes: true + # Broadcast when vote party reached + Broadcast: '&cReached the vote party amount!' + + # Send vote reminder at number of votes to go + VoteReminder: + AtVotes: [] + # Send a broadcast at a certain number of votes left and send a vote reminder + Broadcast: '%votesrequired% left to go, go vote!' + # Commands to run instead + Commands: [] + + # List of commands to execute, these only execute once. + # %player% does not work here + GlobalCommands: [] + # List of commands to execute once (picks one randomly) + # Only runs once, %player% does not work here + GlobalRandomCommand: [] + # Rewards to give per player + Rewards: + # Uncomment below for online players to be rewarded only + #RewardType: ONLINE + Commands: + - say %player% + Items: + DIAMOND: + Material: 'DIAMOND' + Amount: 1 + + +# ----------------------------- +# Vote streak rewards (NEW) +# ----------------------------- +VoteStreaks: + # Id name, data is stored under this name + # On proxy setups keep different for each server unless you want one global streak/reward + Daily3Votes: + Type: DAILY + Enabled: false + Requirements: + Amount: 3 + VotesRequired: 3 + AllowMissedAmount: 1 + AllowMissedPeriod: 7 + # true = reward every N streak completions, false = reward only once at N + Recurring: true + Rewards: + Commands: + - say test + +NameMCLikeReward: + Enabled: false + + # Server ip/domain used in the NameMC API URL + Url: "play.example.com" + + # How often to check NameMC in minutes + CheckIntervalMinutes: 10 + + Rewards: + Messages: + Player: "&aThanks for liking the server on NameMC!" + Commands: + - "say %player% liked the server on NameMC!" + +# ----------------------------- +# Legacy vote rewards (DEPRECATED) +# ----------------------------- +# These are fully disabled and kept only for reference. +# VoteMilestones above replace all of this functionality. + +FirstVote: {} + +FirstVoteToday: {} + +AllSites: {} + +OnlyOneCumulative: false + +Cumulative: + '20': + Enabled: false + TotalToUse: AllTime + Rewards: + Messages: + Player: '&aYou got %cumulative% cumulative votes!' + +ResetMilestonesMonthly: false + +MileStones: + '20': + Enabled: false + Rewards: + Messages: + Player: '&aYou got %milestone% milestone votes!' + +VoteStreak: + Day: + '2': + Enabled: false + Rewards: + Messages: + Player: "&aYou voted for %Streak% %Type%'s in a row!" + Week: + '2': + Enabled: false + Rewards: + Messages: + Player: "&aYou voted for %Streak% %Type%'s in a row!" + Month: + '2': + Enabled: false + Rewards: + Messages: + Player: "&aYou voted for %Streak% %Type%'s in a row!" + Requirement: + UsePercentage: false + Day: 50 + Week: 50 + Month: 50 + + +# ----------------------------- +# Top voter awards +# ----------------------------- +EnableMonthlyAwards: true + +MonthlyAwards: + 1: + Rewards: + Messages: + Player: '&aYou came in first place in %TopVoter%!' + 2: + Rewards: + Messages: + Player: '&aYou came in second place in %TopVoter%!' + +EnableWeeklyAwards: false +WeeklyAwards: + 1: + Rewards: + Messages: + Player: '&aYou came in first place in %TopVoter%!' + 2: + Rewards: + Messages: + Player: '&aYou came in second place in %TopVoter%!' + +EnableDailyRewards: false +DailyAwards: + 1: + Rewards: + Messages: + Player: '&aYou came in first place in %TopVoter%!' + 2: + Rewards: + Messages: + Player: '&aYou came in second place in %TopVoter%!' diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java index 2262f4380..36eabff83 100644 --- a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java @@ -30,7 +30,6 @@ import com.bencodez.votingplugin.user.VotingPluginUser; class VoteStreakHandlerTest { - private VotingPluginMain plugin; private TimeChecker timeChecker; private VoteStreakHandler handler; @@ -39,13 +38,9 @@ class VoteStreakHandlerTest { void setup() { plugin = mock(VotingPluginMain.class, RETURNS_DEEP_STUBS); timeChecker = mock(TimeChecker.class); - when(plugin.getTimeChecker()).thenReturn(timeChecker); when(timeChecker.getTime()).thenReturn(LocalDateTime.of(2026, 1, 10, 12, 0)); - when(plugin.getLogger()).thenReturn(Logger.getLogger("VoteStreakHandlerTest")); - when(plugin.getLogger()).thenReturn(silentLogger()); - handler = new VoteStreakHandler(plugin); } @@ -53,53 +48,39 @@ private static VotingPluginUser mapBackedUser(UUID uuid, String name, Map backing.get(inv.getArgument(0, String.class))); - doAnswer(inv -> { backing.put(inv.getArgument(0, String.class), inv.getArgument(1, String.class)); return null; }).when(user).setVoteStreakState(anyString(), anyString()); - return user; } - private static Logger silentLogger() { - Logger l = Logger.getLogger("silent-test-logger"); - l.setUseParentHandlers(false); // <- key: stops ConsoleHandler printing - l.setLevel(Level.OFF); // or Level.WARNING if you want warnings - // also remove any handlers that might already be attached - java.util.logging.Handler[] hs = l.getHandlers(); - for (java.util.logging.Handler h : hs) { - l.removeHandler(h); - } - return l; + Logger l = Logger.getLogger("silent-test-logger"); + l.setUseParentHandlers(false); + l.setLevel(Level.OFF); + for (java.util.logging.Handler h : l.getHandlers()) { + l.removeHandler(h); + } + return l; } - private static MemoryConfiguration rootWithOneStreak(String id, String type, boolean enabled, int intervalAmount, - int votesRequired, int allowMissedAmount, int allowMissedPeriod) { - + int votesRequired, int allowMissedAmount, int allowMissedPeriod, boolean recurring) { MemoryConfiguration root = new MemoryConfiguration(); - ConfigurationSection def = root.createSection("VoteStreaks").createSection(id); def.set("Type", type); def.set("Enabled", enabled); - ConfigurationSection req = def.createSection("Requirements"); req.set("Amount", intervalAmount); req.set("VotesRequired", votesRequired); - def.set("AllowMissedAmount", allowMissedAmount); def.set("AllowMissedPeriod", allowMissedPeriod); - + def.set("Recurring", recurring); return root; } - /** - * periodKey|streakCount|votesThisPeriod|countedThisPeriod|missWindowStartKey|missesUsed - */ private static String[] parseState(String raw) { assertNotNull(raw); assertFalse(raw.isEmpty()); @@ -109,20 +90,15 @@ private static String[] parseState(String raw) { } private void loadFromRoot(ConfigurationSection root) { - // IMPORTANT: no plugin.getSpecialRewardsConfig() involved. handler.new VoteStreakConfigLoader().load(root); } @Test void configLoader_loadsDefinition_andGetDefinitionWorks() { - MemoryConfiguration root = rootWithOneStreak("DailyStreak", "DAILY", true, 5, 2, 0, 0); + MemoryConfiguration root = rootWithOneStreak("DailyStreak", "DAILY", true, 5, 2, 0, 0, true); loadFromRoot(root); - - // getDefinition() is broken due to key casing mismatch in handler: byId.put(id) - // vs lookup lowercased VoteStreakDefinition def = handler.getById().get("DailyStreak"); assertNotNull(def); - assertEquals("DailyStreak", def.getId()); assertEquals(VoteStreakType.DAILY, def.getType()); assertTrue(def.isEnabled()); @@ -132,50 +108,59 @@ void configLoader_loadsDefinition_andGetDefinitionWorks() { @Test void processVote_whenAlreadyCountedThisPeriod_doesNotIncrementStreakAgain() { - MemoryConfiguration root = rootWithOneStreak("test", "DAILY", true, 9999, 2, 0, 0); + MemoryConfiguration root = rootWithOneStreak("test", "DAILY", true, 9999, 2, 0, 0, true); loadFromRoot(root); - VoteStreakDefinition def = handler.getById().get("test"); - assertNotNull(def); - String col = handler.getColumnName(def); - Map backing = new HashMap<>(); VotingPluginUser user = mapBackedUser(UUID.randomUUID(), "Ben", backing); - backing.put(col, "2026-01-10|5|1|true||0"); - handler.processVote(user, System.currentTimeMillis(), UUID.randomUUID()); - String[] p = parseState(handler.readStateString(user, col)); - assertEquals("5", p[1], "streakCount must not increment again when already counted"); - assertEquals("1", p[2], "votesThisPeriod should NOT increment once already countedThisPeriod=true"); + assertEquals("5", p[1]); + assertEquals("1", p[2]); assertEquals("true", p[3]); } @Test void processVote_acceptsOldFiveFieldFormat_andUpgradesState() { - MemoryConfiguration root = rootWithOneStreak("test", "DAILY", true, 9999, 2, 0, 0); + MemoryConfiguration root = rootWithOneStreak("test", "DAILY", true, 9999, 2, 0, 0, true); loadFromRoot(root); - VoteStreakDefinition def = handler.getById().get("test"); - assertNotNull(def); - String col = handler.getColumnName(def); - Map backing = new HashMap<>(); VotingPluginUser user = mapBackedUser(UUID.randomUUID(), "Ben", backing); - backing.put(col, "2026-01-10|5|true||2"); - handler.processVote(user, System.currentTimeMillis(), UUID.randomUUID()); - String[] p = parseState(handler.readStateString(user, col)); assertEquals("2026-01-10", p[0]); - assertEquals("5", p[1], "streakCount should not increment (already counted)"); - assertEquals("1", p[2], "votesThisPeriod upgraded-from-old (counted=true => 1) and ignored on vote"); + assertEquals("5", p[1]); + assertEquals("1", p[2]); assertEquals("true", p[3]); - assertEquals("2", p[5], "missesUsed preserved"); + assertEquals("2", p[5]); + } + + @Test + void processVote_migratesLegacyProgressIntoNewState() { + MemoryConfiguration root = rootWithOneStreak("daily", "DAILY", true, 99, 1, 0, 0, true); + loadFromRoot(root); + VoteStreakDefinition def = handler.getById().get("daily"); + String col = handler.getColumnName(def); + Map backing = new HashMap<>(); + VotingPluginUser user = mapBackedUser(UUID.randomUUID(), "Ben", backing); + when(user.getDayVoteStreak()).thenReturn(7); + handler.processVote(user, System.currentTimeMillis(), UUID.randomUUID()); + String[] p = parseState(handler.readStateString(user, col)); + assertEquals("8", p[1]); } + @Test + void configLoader_migratesLegacyVoteStreakSection() { + MemoryConfiguration root = new MemoryConfiguration(); + ConfigurationSection day = root.createSection("VoteStreak").createSection("Day"); + day.createSection("2").set("Enabled", true); + day.createSection("-3").set("Enabled", true); + loadFromRoot(root); + assertEquals(2, handler.getDefinitions().size()); + } } From 07c71182060ad85af9d531f7db476c887c5388e5 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 1 May 2026 20:01:53 -0600 Subject: [PATCH 2/2] Create VoteStreaks config entries during legacy migration --- .../votestreak/VoteStreakHandler.java | 30 +++++++++++++++---- .../votestreak/VoteStreakHandlerTest.java | 4 +++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java index 2eb710538..be20df3bb 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakHandler.java @@ -548,14 +548,21 @@ private boolean loadLegacy(ConfigurationSection root) { if (legacy == null) { return false; } + ConfigurationSection voteStreaks = root.getConfigurationSection("VoteStreaks"); + if (voteStreaks == null) { + voteStreaks = root.createSection("VoteStreaks"); + } boolean any = false; - any |= loadLegacyType(legacy, "Day", VoteStreakType.DAILY); - any |= loadLegacyType(legacy, "Week", VoteStreakType.WEEKLY); - any |= loadLegacyType(legacy, "Month", VoteStreakType.MONTHLY); + any |= loadLegacyType(voteStreaks, legacy, "Day", VoteStreakType.DAILY); + any |= loadLegacyType(voteStreaks, legacy, "Week", VoteStreakType.WEEKLY); + any |= loadLegacyType(voteStreaks, legacy, "Month", VoteStreakType.MONTHLY); + if (any) { + plugin.getSpecialRewardsConfig().saveData(); + } return any; } - private boolean loadLegacyType(ConfigurationSection legacy, String key, VoteStreakType type) { + private boolean loadLegacyType(ConfigurationSection voteStreaks, ConfigurationSection legacy, String key, VoteStreakType type) { ConfigurationSection sec = legacy.getConfigurationSection(key); if (sec == null) return false; boolean any = false; @@ -570,6 +577,20 @@ private boolean loadLegacyType(ConfigurationSection legacy, String key, VoteStre boolean enabled = defSec.getBoolean("Enabled", true); defSec.set("Enabled", false); String id = "Legacy" + type.name() + amount + (recurring ? "Recurring" : "OneTime"); + if (voteStreaks.getConfigurationSection(id) == null) { + ConfigurationSection migrated = voteStreaks.createSection(id); + migrated.set("Type", type.name()); + migrated.set("Enabled", enabled); + migrated.set("Recurring", recurring); + ConfigurationSection req = migrated.createSection("Requirements"); + req.set("Amount", amount); + req.set("VotesRequired", 1); + migrated.set("AllowMissedAmount", 0); + migrated.set("AllowMissedPeriod", 0); + if (defSec.getConfigurationSection("Rewards") != null) { + migrated.set("Rewards", defSec.getConfigurationSection("Rewards").getValues(true)); + } + } VoteStreakDefinition def = new VoteStreakDefinition(id, type, enabled, amount, 1, 0, 0, recurring); plugin.getUserManager().getDataManager() .addKey(new UserDataKeyString(getColumnName(def)).setColumnType("MEDIUMTEXT")); @@ -635,7 +656,6 @@ public void load(ConfigurationSection root) { } boolean enabled = defSec.getBoolean("Enabled", true); - defSec.set("Enabled", false); int amountInterval = 0; int votesRequired = 1; diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java index 36eabff83..8f32fbb92 100644 --- a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votestreak/VoteStreakHandlerTest.java @@ -162,5 +162,9 @@ void configLoader_migratesLegacyVoteStreakSection() { day.createSection("-3").set("Enabled", true); loadFromRoot(root); assertEquals(2, handler.getDefinitions().size()); + assertNotNull(root.getConfigurationSection("VoteStreaks.LegacyDAILY2OneTime")); + assertNotNull(root.getConfigurationSection("VoteStreaks.LegacyDAILY3Recurring")); + assertFalse(root.getBoolean("VoteStreak.Day.2.Enabled")); + assertFalse(root.getBoolean("VoteStreak.Day.-3.Enabled")); } }