Repository: hannibal002/SkyHanni
Pull request #3886: Feature + Fix: Farming Weight Monthly LB and Fix Upcoming Players
Author: login: ptlthg
Created: 2025-04-25T03:32:28Z | Merged: 2025-05-01T20:35:04Z

## What
This PR does the following:
- Adds monthly weight leaderboard option
- Updates fallback weight values
- Loads more upcoming players when a player is high up in the leaderboard
- Uses the locally calculated rank when fetching new upcoming players

## Changelog New Features
+ Added Monthly Farming Weight Leaderboard option. - Ke5o

## Changelog Fixes
+ Fixed farming weight leaderboard upcoming players repeating. - Ke5o
    * When elitebot.dev doesn't update your data the same upcoming players would be repeated, this fixes that.



Diff:
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/garden/EliteFarmingWeightConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/garden/EliteFarmingWeightConfig.java
index fd65d37ede2b..6c09efb0a9e8 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/garden/EliteFarmingWeightConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/garden/EliteFarmingWeightConfig.java
@@ -29,6 +29,27 @@ public class EliteFarmingWeightConfig {
     @ConfigEditorBoolean
     public boolean leaderboard = true;
 
+    @Expose
+    @ConfigOption(name = "Leaderboard Type", desc = "Select normal or monthly weight leaderboard!")
+    @ConfigEditorDropdown
+    public Property<EliteFarmingWeightLBType> eliteLBType = Property.of(EliteFarmingWeightLBType.DEFAULT);
+
+    public enum EliteFarmingWeightLBType {
+        DEFAULT("All-Time"),
+        MONTHLY("Monthly");
+
+        private final String displayName;
+
+        EliteFarmingWeightLBType(String displayName) {
+            this.displayName = displayName;
+        }
+
+        @Override
+        public String toString() {
+            return displayName;
+        }
+    }
+
     @Expose
     @ConfigOption(name = "Overtake ETA", desc = "Show a timer estimating when you'll move up a spot in the leaderboard! " +
         "Will show an ETA to placement weight required if not on the leaderboard yet.")
@@ -46,7 +67,7 @@ public class EliteFarmingWeightConfig {
     public boolean overtakeETAAlways = true;
 
     @Expose
-    @ConfigOption(name = "Use ETA Goal", desc = "Use the ETA Goal number instead of the next upcoming rank. Useful when your rank is in the" +
+    @ConfigOption(name = "Use ETA Goal", desc = "Use the ETA Goal number instead of the next upcoming rank. Useful when your rank is in the " +
         "ten thousands and you don't want to see small ETAs.")
     @ConfigEditorBoolean
     public Property<Boolean> useEtaGoalRank = Property.of(true);
diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/other/EliteBotJson.kt b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/other/EliteBotJson.kt
index f1638a668d67..374920b16644 100644
--- a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/other/EliteBotJson.kt
+++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/other/EliteBotJson.kt
@@ -24,7 +24,9 @@ data class EliteLeaderboardJson(
 
 data class EliteLeaderboard(
     @Expose val rank: Int,
-    @Expose val minAmount: Int,
+    @Expose val amount: Double,
+    @Expose val minAmount: Double,
+    @Expose val initialAmount: Double,
     @Expose val upcomingRank: Int,
     @Expose val upcomingPlayers: List<UpcomingLeaderboardPlayer>,
 )
diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
index 568e1b04b0ee..6ec6504f541e 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
@@ -6,6 +6,7 @@ import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
 import at.hannibal2.skyhanni.config.commands.CommandCategory
 import at.hannibal2.skyhanni.config.commands.CommandRegistrationEvent
 import at.hannibal2.skyhanni.config.enums.OutsideSBFeature
+import at.hannibal2.skyhanni.config.features.garden.EliteFarmingWeightConfig
 import at.hannibal2.skyhanni.data.HypixelData
 import at.hannibal2.skyhanni.data.jsonobjects.other.EliteLeaderboardJson
 import at.hannibal2.skyhanni.data.jsonobjects.other.ElitePlayerWeightJson
@@ -40,6 +41,7 @@ import at.hannibal2.skyhanni.utils.renderables.Renderable
 import at.hannibal2.skyhanni.utils.renderables.RenderableString
 import com.google.gson.JsonObject
 import kotlinx.coroutines.launch
+import kotlin.math.min
 import kotlin.time.Duration.Companion.minutes
 import kotlin.time.Duration.Companion.seconds
 
@@ -77,6 +79,8 @@ object FarmingWeightDisplay {
         display = emptyList()
         profileId = ""
         weight = -1.0
+        apiWeight = 0.0
+        shWeightDiff = 0.0
 
         nextPlayers.clear()
         rankGoal = -1
@@ -115,15 +119,27 @@ object FarmingWeightDisplay {
     @HandleEvent
     fun onConfigLoad(event: ConfigLoadEvent) {
         if (!isEtaEnabled()) return
-        if (lastupdate.passedSince() < 30.seconds) return
+        if (lastupdate.passedSince() < 10.seconds) return
+
+        ConditionalUtils.onToggle(config.eliteLBType) {
+            // Reset api weight as different lb type will have a different score
+            apiWeight = 0.0
+            onConfigChanged()
+        }
 
         ConditionalUtils.onToggle(config.useEtaGoalRank, config.etaGoalRank) {
-            getRankGoal()
-            loadLeaderboardIfAble()
-            lastupdate = SimpleTimeMark.now()
+            onConfigChanged()
         }
     }
 
+    private fun onConfigChanged() {
+        localCounter.clear()
+        rankGoal = -1
+        getRankGoal()
+        loadLeaderboardIfAble()
+        lastupdate = SimpleTimeMark.now()
+    }
+
     private val config get() = GardenApi.config.eliteFarmingWeights
     private val storage get() = GardenApi.storage?.farmingWeight
     private val localCounter = mutableMapOf<CropType, Long>()
@@ -140,9 +156,16 @@ object FarmingWeightDisplay {
     private var isLoadingWeight = false
     private var isLoadingLeaderboard = false
     private var rankGoal = -1
-    private var minAmount = 0
+    private var minAmount = 0.0
     private var lastupdate: SimpleTimeMark = SimpleTimeMark.farPast()
 
+    // Used to get the difference in weight to subtract for monthly lb
+    // Caused by various inaccuracies, including pest calc
+    private var shWeightDiff = 0.0
+    private var apiWeight = 0.0
+    // Calculated weight number to display
+    private val displayWeight get() = localWeight + weight - shWeightDiff
+
     private val nextPlayers = mutableListOf<UpcomingLeaderboardPlayer>()
     private val nextPlayer get() = nextPlayers.firstOrNull()
 
@@ -183,7 +206,7 @@ object FarmingWeightDisplay {
 
                 isLoadingWeight = true
                 if (display.isEmpty()) {
-                    display = listOf(RenderableString("§6Farming Weight§7: §eLoading.."))
+                    display = listOf(RenderableString("§6${lbName()}§7: §eLoading.."))
                 }
                 SkyHanniMod.coroutineScope.launch {
                     loadWeight(localProfile)
@@ -201,7 +224,7 @@ object FarmingWeightDisplay {
         val list = mutableListOf<Renderable>()
         list.add(
             Renderable.clickable(
-                "§6Farming Weight§7: $weight$leaderboard",
+                "§6${lbName()}§7: $weight$leaderboard",
                 tips = listOf("§eClick to open your Farming Profile."),
                 onLeftClick = { openWebsite(LorenzUtils.getPlayerName()) },
             ),
@@ -240,8 +263,7 @@ object FarmingWeightDisplay {
             }
         }
 
-        val totalWeight = (localWeight + weight)
-        return "§e" + totalWeight.roundTo(2).addSeparators()
+        return "§e" + displayWeight.roundTo(2).addSeparators()
     }
 
     private fun getRankGoal(): Int {
@@ -277,21 +299,20 @@ object FarmingWeightDisplay {
             return Renderable.clickable(
                 "§cWaiting for leaderboard update...",
                 tips = listOf("§eClick here to load new data right now!"),
-                onLeftClick = ::resetData,
+                onLeftClick = ::loadLeaderboardIfAble,
             )
         }
-        val nextWeight = nextPlayer?.weight ?: minAmount.toDouble()
+        val nextWeight = nextPlayer?.weight ?: minAmount
         var nextName = nextPlayer?.name ?: "$nextWeight Weight"
 
         val showRankGoal = (leaderboardPosition == -1 || leaderboardPosition > rankGoal) && config.useEtaGoalRank.get()
         nextName = if (showRankGoal) "#$rankGoal" else nextName
 
-        val totalWeight = (localWeight + weight)
-        var weightUntilOvertake = nextWeight - totalWeight
+        var weightUntilOvertake = nextWeight - displayWeight
 
         if (weightUntilOvertake < 0) {
             if (weightPerSecond > 0) {
-                farmingChatMessage("You passed §b$nextName §ein the Farming Weight Leaderboard!")
+                farmingChatMessage("You passed §b$nextName §ein the §6${lbName()} §eLeaderboard!")
             }
 
             // Lower leaderboard position
@@ -308,14 +329,14 @@ object FarmingWeightDisplay {
             // Display waiting message if nextPlayers list is empty
             // Update values to next player
             nextName = nextPlayer?.name ?: "Loading..."
-            weightUntilOvertake = nextWeight - totalWeight
+            weightUntilOvertake = nextWeight - displayWeight
         }
 
         if (nextWeight == 0.0) {
             return Renderable.clickable(
                 "§cRejoin the garden to show ETA!",
                 tips = listOf("Click here to calculate the data right now!"),
-                onLeftClick = ::resetData,
+                onLeftClick = ::loadLeaderboardIfAble,
             )
         }
 
@@ -328,7 +349,7 @@ object FarmingWeightDisplay {
                     "Error calculating Farming ETA duration",
                     "weightPerSecond" to weightPerSecond,
                     "weightUntilOvertake" to weightUntilOvertake,
-                    "totalWeight" to totalWeight,
+                    "totalWeight" to displayWeight,
                     "nextWeight" to nextWeight,
                 )
                 return null
@@ -337,7 +358,8 @@ object FarmingWeightDisplay {
             " §7(§b$format§7)"
         } else ""
 
-        val weightFormat = weightUntilOvertake.roundTo(2).addSeparators()
+        // Adding 0.0 here to eliminate "-0"
+        val weightFormat = (weightUntilOvertake.roundTo(2) + 0.0).addSeparators()
         val text = "§e$weightFormat$timeFormat §7behind §b$nextName"
         return if (showRankGoal) {
             Renderable.string(text)
@@ -382,6 +404,7 @@ object FarmingWeightDisplay {
     private fun inGardenEnabled() = (LorenzUtils.inSkyBlock && GardenApi.inGarden()) || config.showOutsideGarden
 
     private fun isEtaEnabled() = config.overtakeETA
+    private fun isMonthlyLB() = config.eliteLBType.get() == EliteFarmingWeightConfig.EliteFarmingWeightLBType.MONTHLY
 
     fun addCrop(crop: CropType, addedCounter: Int) {
         // Prevent div-by-0 errors
@@ -444,32 +467,67 @@ object FarmingWeightDisplay {
     private fun showLbChange(direction: String, oldPosition: Int) {
         farmingChatMessage(
             "§7Since your last visit to the §aGarden§7, " +
-                "you have $direction §7on the §dFarming Leaderboard§7. " +
+                "you have $direction §7on the §d${lbName()} Leaderboard§7. " +
                 "§7(§e#${oldPosition.addSeparators()} §7-> §e#${leaderboardPosition.addSeparators()}§7)",
         )
     }
 
+    private fun lbName() = "${if (isMonthlyLB()) "Monthly " else ""}Farming Weight"
+
     private fun loadLeaderboardPosition(): Int {
         val uuid = LorenzUtils.getPlayerUuid()
 
-        val includeUpcoming = if (isEtaEnabled()) "?upcoming=10" else ""
-        val goalRank = getRankGoal() + 1 // API returns upcoming players as if you were at this rank already
-        val atRank = if (isEtaEnabled() && config.useEtaGoalRank.get()) "&atRank=$goalRank" else ""
+        // Fetch more upcoming players when the difference between ranks is expected to be tiny
+        val upcomingPlayersParam = when {
+            !isEnabled() -> ""
+            leaderboardPosition > 10_000 -> "?upcoming=50"
+            leaderboardPosition > 5_000 -> "?upcoming=30"
+            leaderboardPosition > 1_000 -> "?upcoming=20"
+            else -> "?upcoming=10"
+        }
+        // Tell the API to get upcoming players from our local rank (for when new data isn't fetched), or fallback to the
+        // provided eta goal rank from the config
+        val atRank = when {
+            !isEtaEnabled() -> null
+            config.useEtaGoalRank.get() && leaderboardPosition != -1 -> min(getRankGoal() + 1, leaderboardPosition)
+            config.useEtaGoalRank.get() -> getRankGoal() + 1
+            leaderboardPosition != -1 -> leaderboardPosition
+            else -> null
+        }
+        val lbType = if (isMonthlyLB()) "-monthly" else ""
+        val atRankParam = if (atRank != null) "&atRank=$atRank" else ""
 
-        val url = "https://api.elitebot.dev/leaderboard/farmingweight/" +
-            "$uuid/$profileId$includeUpcoming$atRank"
+        val url = "https://api.elitebot.dev/leaderboard/farmingweight$lbType/" +
+            "$uuid/$profileId$upcomingPlayersParam$atRankParam"
         val apiResponse = ApiUtils.getJSONResponse(url, apiName = "Elitebot Farming Leaderboard")
 
         try {
             val apiData = toEliteLeaderboardJson(apiResponse).data
+            val newData = apiWeight < apiData.amount
+
+            minAmount = apiData.minAmount
+
+            if (newData) {
+                shWeightDiff = weight - apiData.amount
+                apiWeight = apiData.amount
+            }
+
+            // Reset weight diff if not a monthly leaderboard
+            if (apiData.initialAmount == 0.0) {
+                shWeightDiff = 0.0
+            }
 
             if (isEtaEnabled()) {
                 nextPlayers.clear()
-                apiData.upcomingPlayers.forEach { nextPlayers.add(it) }
+                apiData.upcomingPlayers.forEach {
+                    if (it.weight > displayWeight) {
+                        nextPlayers.add(it)
+                    }
+                }
             }
-            minAmount = apiData.minAmount
 
-            return apiData.rank
+            // Keep local rank if new data wasn't returned
+            return if (newData) apiData.rank else leaderboardPosition
         } catch (e: Exception) {
             ErrorManager.logErrorWithData(
                 e, "Error getting weight leaderboard position",
@@ -494,7 +552,6 @@ object FarmingWeightDisplay {
         var error: Throwable? = null
 
         try {
-
             val apiData = eliteWeightApiGson.fromJson<ElitePlayerWeightJson>(apiResponse)
 
             val selectedProfileId = apiData.selectedProfileId
@@ -607,15 +664,15 @@ object FarmingWeightDisplay {
     private val backupCropWeights by lazy {
         mapOf(
             CropType.WHEAT to 100_000.0,
-            CropType.CARROT to 302_061.86,
-            CropType.POTATO to 300_000.0,
-            CropType.SUGAR_CANE to 200_000.0,
-            CropType.NETHER_WART to 250_000.0,
-            CropType.PUMPKIN to 98_284.71,
-            CropType.MELON to 485_308.47,
-            CropType.MUSHROOM to 90_178.06,
-            CropType.COCOA_BEANS to 267_174.04,
-            CropType.CACTUS to 177_254.45,
+            CropType.CARROT to 300_000.0,
+            CropType.POTATO to 298_328.17,
+            CropType.SUGAR_CANE to 198_885.45,
+            CropType.NETHER_WART to 248_606.81,
+            CropType.PUMPKIN to 99_236.12,
+            CropType.MELON to 488_435.88,
+            CropType.MUSHROOM to 90_944.27,
+            CropType.COCOA_BEANS to 276_733.75,
+            CropType.CACTUS to 178_730.65,
         )
     }
 


Reviews:
user: login: hannibal002
body: >     Added Monthly Farming Weight leaderboard option. - Ke5o

this is a improvement and no entirely new feature imo
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-04-28T09:54:35Z
user: login: ptlthg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T23:17:51Z
user: login: hannibal002
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-01T19:55:19Z
user: login: hannibal002
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-01T20:29:41Z

Review comments:
diff_hunk: @@ -29,6 +29,27 @@ public class EliteFarmingWeightConfig {
     @ConfigEditorBoolean
     public boolean leaderboard = true;
 
+    @Expose
+    @ConfigOption(name = "Leaderboard Type", desc = "Select normal or monthly weight leaderboard!")
+    @ConfigEditorDropdown
+    public Property<EliteFarmingWeightLbType> eliteLbType = Property.of(EliteFarmingWeightLbType.DEFAULT);
+
+    public enum EliteFarmingWeightLbType {
+        DEFAULT("Normal"),
path: src/main/java/at/hannibal2/skyhanni/config/features/garden/EliteFarmingWeightConfig.java
user: login: hannibal002
body: please have a more descriptive name for default. is this total lb? like, "all time" stats?
created_at: 2025-04-28T09:41:55Z
author_association: OWNER
diff_hunk: @@ -239,7 +251,7 @@ object FarmingWeightDisplay {
             }
         }
 
-        val totalWeight = (localWeight + weight)
+        val totalWeight = (localWeight + weight - shWeightDiff)
path: src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
user: login: hannibal002
body: this formular `localWeight + weight - shWeightDiff` gets used in three spots in the code now. please use a function
created_at: 2025-04-28T09:44:50Z
author_association: OWNER
diff_hunk: @@ -441,34 +455,68 @@ object FarmingWeightDisplay {
     }
 
     private fun showLbChange(direction: String, oldPosition: Int) {
+        val lbType = if (isMonthlyLb()) "Monthly " else ""
         farmingChatMessage(
             "§7Since your last visit to the §aGarden§7, " +
-                "you have $direction §7on the §dFarming Leaderboard§7. " +
+                "you have $direction §7on the §d${lbType}Farming Leaderboard§7. " +
                 "§7(§e#${oldPosition.addSeparators()} §7-> §e#${leaderboardPosition.addSeparators()}§7)",
         )
     }
 
     private fun loadLeaderboardPosition(): Int {
         val uuid = LorenzUtils.getPlayerUuid()
 
-        val includeUpcoming = if (isEtaEnabled()) "?upcoming=10" else ""
-        val goalRank = getRankGoal() + 1 // API returns upcoming players as if you were at this rank already
-        val atRank = if (isEtaEnabled() && config.useEtaGoalRank.get()) "&atRank=$goalRank" else ""
+        // Fetch more upcoming players when the difference between ranks is expected to be tiny
+        val includeUpcoming = when {
+            !isEnabled() -> ""
+            leaderboardPosition > 10_000 -> "?upcoming=50"
+            leaderboardPosition > 5_000 -> "?upcoming=30"
+            leaderboardPosition > 1_000 -> "?upcoming=20"
+            else -> "?upcoming=10"
+        }
+        // Tell the API to get upcoming players from our local rank (for when new data isn't fetched), or fallback to the
+        // provided eta goal rank from the config
+        val atRank = when {
path: src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
user: login: hannibal002
body: please move the string `&atRank=` outside the `when`. rather, use `nullable int` in the `when`, and then build the `atRank` string together after the `when`
created_at: 2025-04-28T09:51:02Z
author_association: OWNER
diff_hunk: @@ -196,11 +207,12 @@ object FarmingWeightDisplay {
 
         if (rankGoal == -1) rankGoal = getRankGoal()
         val leaderboard = getLeaderboard()
+        val lbType = if (isMonthlyLb()) " Monthly" else ""
 
         val list = mutableListOf<Renderable>()
         list.add(
             Renderable.clickable(
-                "§6Farming Weight§7: $weight$leaderboard",
+                "§6Farming Weight$lbType§7: $weight$leaderboard",
path: src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
user: login: hannibal002
body: when you change the wording here to be "Monthly Farming Weight" you can merge three of the four isMonthglyLB checks 
created_at: 2025-04-28T09:52:42Z
author_association: OWNER
diff_hunk: @@ -381,6 +394,7 @@ object FarmingWeightDisplay {
     private fun inGardenEnabled() = (LorenzUtils.inSkyBlock && GardenApi.inGarden()) || config.showOutsideGarden
 
     private fun isEtaEnabled() = config.overtakeETA
+    private fun isMonthlyLb() = config.eliteLbType.get() == EliteFarmingWeightConfig.EliteFarmingWeightLbType.MONTHLY
path: src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
user: login: hannibal002
body: make the last B upper case, as Leaderboard is a acronym and we write them uppercase when 2 chars long. for 3 char or longer we only uppercase the first char. 
created_at: 2025-04-28T09:53:52Z
author_association: OWNER
diff_hunk: @@ -196,11 +207,12 @@ object FarmingWeightDisplay {
 
         if (rankGoal == -1) rankGoal = getRankGoal()
         val leaderboard = getLeaderboard()
+        val lbType = if (isMonthlyLb()) " Monthly" else ""
 
         val list = mutableListOf<Renderable>()
         list.add(
             Renderable.clickable(
-                "§6Farming Weight§7: $weight$leaderboard",
+                "§6Farming Weight$lbType§7: $weight$leaderboard",
path: src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
user: login: ptlthg
body: I have no idea what you mean with this, but I did remove an extra lbType assignment.
created_at: 2025-04-29T23:17:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -196,11 +207,12 @@ object FarmingWeightDisplay {
 
         if (rankGoal == -1) rankGoal = getRankGoal()
         val leaderboard = getLeaderboard()
+        val lbType = if (isMonthlyLb()) " Monthly" else ""
 
         val list = mutableListOf<Renderable>()
         list.add(
             Renderable.clickable(
-                "§6Farming Weight§7: $weight$leaderboard",
+                "§6Farming Weight$lbType§7: $weight$leaderboard",
path: src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
user: login: hannibal002
body: at the time of my review, the variable isMonthglyLB was used four times in the class. three of those four times were qual, with the only change if the empty space is prefix or suffix.
if you now change this line to have the bl type show at the middle and not the end, you can move the emtpy space from prefix to suffix in this example, and you can create a function `getLbTypeDisplay()` in order to reduce three identical lines
created_at: 2025-05-01T19:55:18Z
author_association: OWNER
diff_hunk: @@ -196,11 +207,12 @@ object FarmingWeightDisplay {
 
         if (rankGoal == -1) rankGoal = getRankGoal()
         val leaderboard = getLeaderboard()
+        val lbType = if (isMonthlyLb()) " Monthly" else ""
 
         val list = mutableListOf<Renderable>()
         list.add(
             Renderable.clickable(
-                "§6Farming Weight§7: $weight$leaderboard",
+                "§6Farming Weight$lbType§7: $weight$leaderboard",
path: src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt
user: login: hannibal002
body: [`fe3524b` (#3886)](https://github.com/hannibal002/SkyHanni/pull/3886/commits/fe3524b47f05ea0fe7a51e3d1112be2b162e2511)
created_at: 2025-05-01T20:29:41Z
author_association: OWNER

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-25T03:34:02Z
body: I have detected some issues with your pull request:

Body issues:
Change should start with 'Added' instead of 'Add' in text: `Add Monthly Farming Weight leaderboard option.`

Please fix these issues. For the correct format, refer to the [pull request template](https://github.com/hannibal002/SkyHanni/blob/beta/pull_request_template.md?plain=1).
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-29T10:07:28Z
body: This pull request has conflicts with the base branch "beta". Please resolve those so we can test out your changes.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-29T10:59:02Z
body: Conflicts have been resolved! 🎉
author_association: NONE
user: login: ptlthg
created_at: 2025-04-29T23:20:41Z
body: > > ```
> > Added Monthly Farming Weight leaderboard option. - Ke5o
> > ```
> 
> this is a improvement and no entirely new feature imo

But being able to use the monthly leaderboard is a new thing that the mod couldn't do before. I would say it's a small feature, but still a feature rather than an improvement. If this fits better as an improvement in the changelog just because similar things have been labeled improvements, I can change it, but I do think this is a feature.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-30T19:03:13Z
body: This pull request has conflicts with the base branch "beta". Please resolve those so we can test out your changes.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-01T02:34:06Z
body: Conflicts have been resolved! 🎉
author_association: NONE
user: login: hannibal002
created_at: 2025-05-01T19:56:04Z
body: > But being able to use the monthly leaderboard is a new thing that the mod couldn't do before. I would say it's a small feature, but still a feature rather than an improvement. If this fits better as an improvement in the changelog just because similar things have been labeled improvements, I can change it, but I do think this is a feature.

fair. keep it as a feature.
normally we only use new feature for actual new feature toggles, but here this is a big upgrade to the exisitng feature
author_association: OWNER
user: login: hannibal002
created_at: 2025-05-01T20:23:18Z
body: ![image](https://github.com/user-attachments/assets/1e41330a-8976-45bd-ad4c-649fddf45235)

author_association: OWNER