From e80614d9627237c7346ae2513cdb132c125ecbfa Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:56:17 +0000 Subject: [PATCH 1/2] Show the next weekly reward on the activity points icon. Players can see which reward is next, including each possible pool draw, and the icon says there are no more rewards this week once every milestone is claimed. Co-authored-by: Cursor --- .../activitytf/gui/ActivityGui.java | 62 ++++++++++++++++--- src/main/resources/messages.yml | 5 +- .../config/DefaultResourcesTest.java | 8 ++- .../activitytf/gui/ActivityGuiTest.java | 59 ++++++++++++++++++ 4 files changed, 122 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java b/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java index 9c3c67d..7608262 100644 --- a/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java +++ b/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java @@ -22,12 +22,14 @@ import net.tfminecraft.activitytf.managers.ActivityManager; import net.tfminecraft.activitytf.models.ActivityDef; import net.tfminecraft.activitytf.models.PlayerData; +import net.tfminecraft.activitytf.models.RewardEntry; import net.tfminecraft.activitytf.utils.Bar; import net.tfminecraft.activitytf.utils.ItemPath; import net.tfminecraft.activitytf.utils.Utils; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.function.Function; public class ActivityGui implements Listener { @@ -117,25 +119,67 @@ private void fillEmptySlots(Inventory inventory, Messages messages) { private ItemStack barItem(ActivityConfiguration config, Messages messages, PlayerData data) { List milestones = config.milestones(); String bar = Bar.render(data.points(), config.barMax(), config.barLength(), milestones); - int due = data.claimable(milestones); - Integer next = nextMilestone(data, milestones); List lore = new ArrayList<>(); lore.add(Utils.colorize(bar)); lore.add(" "); - if (due > 0) { - lore.add(messages.get("gui.reward-click", "%count%", due)); - } else if (next != null) { - lore.add(messages.get("gui.bar-lore-next", "%points%", next)); - } else { - lore.add(messages.get("gui.bar-lore-done")); - } + lore.addAll(weeklyRewardLore(messages, data, milestones, config.milestoneDrops(), + config.rewardMultiplier(), config::rewardPool)); return item(Material.EXPERIENCE_BOTTLE, messages.get("gui.bar-name", "%points%", data.points(), "%max%", config.barMax()), lore); } + static List weeklyRewardLore(Messages messages, PlayerData data, List milestones, + Map drops, int multiplier, + Function> pools) { + List lore = new ArrayList<>(); + int due = data.claimable(milestones); + Integer next = nextMilestone(data, milestones); + if (next == null) { + lore.add(messages.get("gui.bar-lore-none")); + return lore; + } + + List rewards = nextRewardDisplays(next, drops, multiplier, pools); + if (rewards.size() == 1) { + lore.add(messages.get("gui.bar-lore-reward", "%points%", next, "%reward%", + Utils.colorize(rewards.get(0)))); + } else { + lore.add(messages.get("gui.bar-lore-next-options", "%points%", next)); + for (String reward : rewards) { + lore.add(Utils.colorize(reward)); + } + } + if (due > 0) { + lore.add(messages.get("gui.reward-click", "%count%", due)); + } + return lore; + } + + static List nextRewardDisplays(int milestone, Map drops, int multiplier, + Function> pools) { + RewardEntry drop = drops.get(milestone); + String pool = drop == null ? ActivityConfiguration.DEFAULT_POOL + : ActivityConfiguration.referencedPool(drop); + if (pool == null) { + RewardEntry.Item item = drop.items().get(0); + return List.of("#50d990x" + item.amount() * multiplier + " #b8906e" + drop.display()); + } + List entries = pools.apply(pool); + if (entries == null || entries.isEmpty()) { + return List.of(); + } + List displays = new ArrayList<>(); + for (RewardEntry entry : entries) { + if (entry.display() != null && !entry.display().isBlank()) { + displays.add(entry.display()); + } + } + return displays; + } + private static Integer nextMilestone(PlayerData data, List milestones) { for (Integer milestone : milestones) { if (milestone > data.claimedPoints()) { diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index d2f0d05..94866f1 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -14,8 +14,9 @@ reroll-locked: "&7Rerolling daily tasks is an #823FFF&l[#8939EE&lA#9033DD&ls#972 gui: bar-name: "#e6ca40&lWeekly Activity&e: #50d990%points%&7/&f%max%" daily-bar-name: "#e6ca40&lDaily Activity&e: #50d990%points%&7/&f%max%" - bar-lore-next: "#d97b66Next reward at&e: &6%points% #b8906epoints" - bar-lore-done: "&8All rewards claimed this week" + bar-lore-reward: "#d97b66Next reward&e: %reward% #b8906eat &6%points% #b8906epoints" + bar-lore-next-options: "#d97b66Next reward at&e: &6%points% #b8906epoints" + bar-lore-none: "&7No more rewards this week" reward-click: "#6b9c68[#fafa16Click to claim %count% reward(s)#6b9c68]" activity-lore-progress: "#af97bdProgress&e: %bar%" activity-lore-today: "#d97b66Today&e: &6%today%" diff --git a/src/test/java/net/tfminecraft/activitytf/config/DefaultResourcesTest.java b/src/test/java/net/tfminecraft/activitytf/config/DefaultResourcesTest.java index 4421db9..d23520f 100644 --- a/src/test/java/net/tfminecraft/activitytf/config/DefaultResourcesTest.java +++ b/src/test/java/net/tfminecraft/activitytf/config/DefaultResourcesTest.java @@ -48,7 +48,13 @@ void guiMessagesKeepTheirPlaceholders() { assertTrue(messages.getString("gui.activity-lore-today-capped").contains("%today%")); assertTrue(messages.getString("gui.activity-lore-today-capped").contains("%cap%")); assertNull(messages.getString("gui.bar-lore-milestones")); - assertTrue(messages.getString("gui.bar-lore-next").contains("%points%")); + assertNull(messages.getString("gui.bar-lore-next")); + assertNull(messages.getString("gui.bar-lore-done")); + assertTrue(messages.getString("gui.bar-lore-reward").contains("%points%")); + assertTrue(messages.getString("gui.bar-lore-reward").contains("%reward%")); + assertTrue(messages.getString("gui.bar-lore-next-options").contains("%points%")); + assertTrue(messages.getString("gui.bar-lore-none").toLowerCase(java.util.Locale.ROOT) + .contains("no more rewards this week")); } private static final Pattern HEX_MARKER = Pattern.compile("#[0-9a-fA-F]{6}"); diff --git a/src/test/java/net/tfminecraft/activitytf/gui/ActivityGuiTest.java b/src/test/java/net/tfminecraft/activitytf/gui/ActivityGuiTest.java index 97b130c..9ea6bae 100644 --- a/src/test/java/net/tfminecraft/activitytf/gui/ActivityGuiTest.java +++ b/src/test/java/net/tfminecraft/activitytf/gui/ActivityGuiTest.java @@ -261,6 +261,65 @@ void anActivityWithoutADescriptionRendersTheSameTwoLines() { messages.get("gui.activity-lore-today-capped", "%today%", 0, "%cap%", 5)), lore); } + @Test + void theWeeklyIconNamesTheNextReward() { + Messages messages = shippedMessages(); + PlayerData data = new PlayerData(5, 0, "2026-W38", "2026-09-17", 0, java.util.Map.of()); + RewardEntry steel = new RewardEntry(1, "Steel", List.of(), + List.of(new RewardEntry.Item("m.material.steel", 3))); + + List lore = ActivityGui.weeklyRewardLore(messages, data, List.of(10, 20), + Map.of(10, steel), 1, pool -> List.of()); + + assertEquals(List.of(messages.get("gui.bar-lore-reward", "%points%", 10, "%reward%", + Utils.colorize("#50d990x3 #b8906eSteel"))), lore); + assertTrue(String.join(" ", lore).contains("Steel"), lore.toString()); + } + + @Test + void theWeeklyIconListsEachDrawInTheNextPool() { + Messages messages = shippedMessages(); + PlayerData data = new PlayerData(12, 0, "2026-W38", "2026-09-17", 10, java.util.Map.of()); + List pool = List.of( + new RewardEntry(65, "#50d990x2 #7f7d80Ignitium", List.of(), List.of()), + new RewardEntry(3, "#50d990x8 #7f7d80Ignitium", List.of(), List.of())); + + List lore = ActivityGui.weeklyRewardLore(messages, data, List.of(10, 20), + Map.of(20, ActivityConfiguration.poolRef("pool_prologue")), 1, name -> pool); + + assertEquals(messages.get("gui.bar-lore-next-options", "%points%", 20), lore.get(0)); + assertTrue(lore.get(1).contains("x2"), lore.toString()); + assertTrue(lore.get(2).contains("x8"), lore.toString()); + assertFalse(lore.get(1).contains("#50d990"), lore.toString()); + } + + @Test + void theWeeklyIconSaysThereAreNoMoreRewardsThisWeek() { + Messages messages = shippedMessages(); + PlayerData data = new PlayerData(40, 0, "2026-W38", "2026-09-17", 40, java.util.Map.of()); + + List lore = ActivityGui.weeklyRewardLore(messages, data, List.of(10, 20, 40), + Map.of(), 1, pool -> List.of()); + + assertEquals(List.of(messages.get("gui.bar-lore-none")), lore); + assertTrue(lore.get(0).toLowerCase(java.util.Locale.ROOT).contains("no more rewards this week"), + lore.toString()); + } + + @Test + void aReadyRewardStillSaysWhatItIsAndThatItCanBeClaimed() { + Messages messages = shippedMessages(); + PlayerData data = new PlayerData(10, 0, "2026-W38", "2026-09-17", 0, java.util.Map.of()); + RewardEntry steel = new RewardEntry(1, "Steel", List.of(), + List.of(new RewardEntry.Item("DIAMOND", 1))); + + List lore = ActivityGui.weeklyRewardLore(messages, data, List.of(10), + Map.of(10, steel), 2, pool -> List.of()); + + assertTrue(lore.get(0).contains("x2"), lore.toString()); + assertEquals(messages.get("gui.reward-click", "%count%", 1), lore.get(1)); + } + @Test void descriptionLinesAreColorized() { List lore = ActivityGui.activityLore(shippedMessages(), described(List.of("#e6ca40&lX")), 0); From 44377286ea28f440a2736ee4f9eb7616802a74bb Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:10:15 +0000 Subject: [PATCH 2/2] Capture reward-config warnings on the plugin logger itself. CI was missing lines that java.util.logging handlers did not record, so those assertions failed even though the warnings were printed. Co-authored-by: Cursor --- .../ActivityConfigurationRewardsTest.java | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/test/java/net/tfminecraft/activitytf/config/ActivityConfigurationRewardsTest.java b/src/test/java/net/tfminecraft/activitytf/config/ActivityConfigurationRewardsTest.java index d5a9563..06a69af 100644 --- a/src/test/java/net/tfminecraft/activitytf/config/ActivityConfigurationRewardsTest.java +++ b/src/test/java/net/tfminecraft/activitytf/config/ActivityConfigurationRewardsTest.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -34,13 +33,31 @@ class ActivityConfigurationRewardsTest { private static final class TestPlugin extends JavaPlugin { } + private static final ThreadLocal> LOGGED = ThreadLocal.withInitial(ArrayList::new); + + private static final class CapturingLogger extends Logger { + CapturingLogger() { + super("ActivityConfigurationRewardsTest", null); + setLevel(Level.ALL); + } + + @Override + public void log(LogRecord record) { + String message = record.getMessage(); + if (message != null) { + LOGGED.get().add(message); + } + super.log(record); + } + } + private static JavaPlugin stubPlugin() { try { JavaPlugin plugin = new ObjenesisStd().newInstance(TestPlugin.class); Field loggerField = JavaPlugin.class.getDeclaredField("logger"); loggerField.setAccessible(true); - loggerField.set(plugin, Logger.getLogger("ActivityConfigurationRewardsTest")); + loggerField.set(plugin, new CapturingLogger()); return plugin; } catch (ReflectiveOperationException e) { @@ -48,37 +65,22 @@ private static JavaPlugin stubPlugin() { } } - private final List logged = new ArrayList<>(); - - private final Handler capture = new Handler() { - @Override - public void publish(LogRecord record) { - logged.add(record.getMessage()); - } - - @Override - public void flush() { - } - - @Override - public void close() { - } - }; - @BeforeEach void captureLog() { - Logger logger = Logger.getLogger("ActivityConfigurationRewardsTest"); - logger.setLevel(Level.ALL); - logger.addHandler(capture); + LOGGED.get().clear(); } @AfterEach void releaseLog() { - Logger.getLogger("ActivityConfigurationRewardsTest").removeHandler(capture); + LOGGED.remove(); + } + + private static List logged() { + return LOGGED.get(); } private boolean loggedContains(String fragment) { - return logged.stream().anyMatch(message -> message.contains(fragment)); + return logged().stream().anyMatch(message -> message != null && message.contains(fragment)); } private static FileConfiguration yaml(String content) { @@ -487,20 +489,20 @@ void aFixedDropLoadsAgainstItsMilestoneWithPathAmountAndName() { List.of(new RewardEntry.Item("m.material.steel", 3))), 30, new RewardEntry(1, "Diamond", List.of(), List.of(new RewardEntry.Item("DIAMOND", 1)))), drops); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test void aMissingDropsSectionMeansEveryMilestoneDrawsFromThePool() { assertTrue(loadDrops(List.of(10, 20), "rewards:\n multiplier: 1\n").isEmpty()); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test void poolIsCaseInsensitive() { assertTrue(loadDrops(List.of(10, 20), "rewards:\n drops:\n drop_1: POOL\n drop_2: Pool\n") .isEmpty()); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -616,14 +618,14 @@ void dailyRewardGroupsLoadInConfigOrderWithPathAmountAndName() { groups.get("vip")); assertEquals(new RewardEntry(1, "Steel", List.of(), List.of(new RewardEntry.Item("m.material.steel", 3))), groups.get("ascended")); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test void noDailyRewardSectionOrAnEmptyOnePaysNothingQuietly() { assertTrue(loadDailyRewards("rewards:\n multiplier: 1\n").isEmpty()); assertTrue(loadDailyRewards("daily-reward:\n groups: {}\n").isEmpty()); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -659,7 +661,7 @@ void aPoolDailyRewardGroupLoadsBesideAnItemGroup() throws ReflectiveOperationExc assertSame(ActivityConfiguration.DAILY_POOL, groups.get("old")); assertEquals(new RewardEntry(1, "Diamond", List.of(), List.of(new RewardEntry.Item("DIAMOND", 2))), groups.get("noble")); - assertTrue(logged.isEmpty(), logged.toString()); + assertTrue(logged().isEmpty(), logged().toString()); } @Test @@ -674,7 +676,7 @@ void aPoolDailyRewardGroupWithAnEmptyPoolIsKeptAndWarnedAbout() { @Test void anEmptyPoolIsNotWarnedAboutWithoutAPoolGroup() { loadDailyRewards("daily-reward:\n groups:\n vip: DIAMOND\n"); - assertTrue(logged.isEmpty(), logged.toString()); + assertTrue(logged().isEmpty(), logged().toString()); } @Test @@ -695,7 +697,7 @@ void aBlockDropLoadsWithItsPathAndAmount() { assertEquals(Map.of(10, new RewardEntry(1, "Steel", List.of(), List.of(new RewardEntry.Item("m.material.steel", 3)))), drops); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -705,7 +707,7 @@ void aBlockDropWithNoAmountDefaultsToOne() { assertEquals(Map.of(10, new RewardEntry(1, "Diamond", List.of(), List.of(new RewardEntry.Item("DIAMOND", 1)))), drops); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -715,7 +717,7 @@ void aBlockDropAmountMayBeAQuotedString() { assertEquals(Map.of(10, new RewardEntry(1, "Diamond", List.of(), List.of(new RewardEntry.Item("DIAMOND", 3)))), drops); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -790,7 +792,7 @@ void aStringDropStillLoadsAlongsideBlockForm() { List.of(new RewardEntry.Item("m.material.steel", 3))), 20, new RewardEntry(1, "Diamond", List.of(), List.of(new RewardEntry.Item("DIAMOND", 1)))), drops); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -800,7 +802,7 @@ void aBlockDailyRewardLoadsWithItsPathAndAmount() { assertEquals(Map.of("legacy", new RewardEntry(1, "Rare Item Skin Scroll", List.of(), List.of(new RewardEntry.Item("m.loot.rare_item_skin_scroll", 1)))), groups); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -810,7 +812,7 @@ void aBlockDailyRewardWithNoAmountDefaultsToOne() { assertEquals(Map.of("vip", new RewardEntry(1, "Diamond", List.of(), List.of(new RewardEntry.Item("DIAMOND", 1)))), groups); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -820,7 +822,7 @@ void aBlockDailyRewardAmountMayBeAQuotedString() { assertEquals(Map.of("vip", new RewardEntry(1, "Diamond", List.of(), List.of(new RewardEntry.Item("DIAMOND", 3)))), groups); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test @@ -897,7 +899,7 @@ void aStringDailyRewardStillLoadsAlongsideBlockForm() { groups.get("vip")); assertEquals(new RewardEntry(1, "Steel", List.of(), List.of(new RewardEntry.Item("m.material.steel", 3))), groups.get("ascended")); - assertTrue(logged.isEmpty()); + assertTrue(logged().isEmpty()); } @Test void namedPoolsDoNotInheritPackagedRewardsAndReloadReplacesThem() throws Exception {