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/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 { 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);