diff --git a/pom.xml b/pom.xml index aa19b4c..5d4fcc5 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ -LOCAL - 1.2.2 + 1.3.0 BentoBoxWorld_ChunkBlock bentobox-world diff --git a/src/main/java/world/bentobox/chunkblock/ChunkBlockPlaceholders.java b/src/main/java/world/bentobox/chunkblock/ChunkBlockPlaceholders.java index 902c0ec..14ea94d 100644 --- a/src/main/java/world/bentobox/chunkblock/ChunkBlockPlaceholders.java +++ b/src/main/java/world/bentobox/chunkblock/ChunkBlockPlaceholders.java @@ -68,6 +68,7 @@ public ChunkBlockPlaceholders(ChunkBlock addon, placeholdersManager.registerPlaceholder(addon, "island_chunk_credit", this::getIslandChunkCredit); placeholdersManager.registerPlaceholder(addon, "island_ring", this::getIslandRing); placeholdersManager.registerPlaceholder(addon, "island_rings_complete", this::getIslandRingsComplete); + placeholdersManager.registerPlaceholder(addon, "island_next_ring_remaining", this::getNextRingRemaining); placeholdersManager.registerPlaceholder(addon, "island_title", this::getIslandTitle); placeholdersManager.registerPlaceholder(addon, "island_trophies", this::getIslandTrophies); } @@ -167,6 +168,24 @@ public String getIslandRingsComplete(User user) { return getUsersIsland(user).map(i -> String.valueOf(addon.getChunkManager().completedRings(i))).orElse(""); } + /** + * @param user user + * @return how many chunks remain to complete the user's island's next ring, or "0" if + * all rings are done + */ + public String getNextRingRemaining(User user) { + if (user == null || user.getUniqueId() == null) { + return ""; + } + return getUsersIsland(user).map(i -> { + int next = addon.getChunkManager().completedRings(i) + 1; + if (next > addon.getChunkManager().maxRingRadius(i)) { + return "0"; + } + return String.valueOf(addon.getChunkManager().chunksRemainingInRing(i, next)); + }).orElse(""); + } + /** * Get the user's owned island. Returns the island owned by the user, not a team * island they may be visiting as a member. If the user owns more than one island, diff --git a/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java b/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java index 0fb1b74..f461f73 100644 --- a/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java +++ b/src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java @@ -207,6 +207,28 @@ public int completedRings(Island island) { return ring; } + /** + * @param island the island + * @param ring the ring radius to check + * @return how many chunks in the ring are still locked; 0 means the ring is complete + */ + public int chunksRemainingInRing(Island island, int ring) { + if (ring <= 0) { + return 0; + } + OneBlockIslands data = addon.getOneBlocksIsland(island); + int missing = 0; + for (int d = -ring; d <= ring; d++) { + if (!data.isChunkUnlocked(d, -ring)) missing++; + if (!data.isChunkUnlocked(d, ring)) missing++; + if (d != -ring && d != ring) { + if (!data.isChunkUnlocked(-ring, d)) missing++; + if (!data.isChunkUnlocked(ring, d)) missing++; + } + } + return missing; + } + /** * @param island the island * @return the island's unlocked chunk offsets in unlock order (x and z are chunk diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java index caa2938..89a6685 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java @@ -80,8 +80,17 @@ public boolean execute(User user, String label, List args) { user.sendMessage("chunkblock.chunks.info", "[unlocked]", String.valueOf(unlocked), "[max]", String.valueOf(max), "[credit]", String.valueOf(credit), "[cost]", String.valueOf(cm.getChunkCost())); - user.sendMessage("chunkblock.chunks.rings", "[rings]", String.valueOf(cm.completedRings(island)), "[max]", - String.valueOf(cm.maxRingRadius(island))); + int completedRings = cm.completedRings(island); + int maxRing = cm.maxRingRadius(island); + user.sendMessage("chunkblock.chunks.rings", "[rings]", String.valueOf(completedRings), "[max]", + String.valueOf(maxRing)); + int nextRing = completedRings + 1; + if (nextRing <= maxRing) { + int remaining = cm.chunksRemainingInRing(island, nextRing); + int total = 8 * nextRing; + user.sendMessage("chunkblock.chunks.ring-progress", "[ring]", String.valueOf(nextRing), + "[remaining]", String.valueOf(remaining), "[total]", String.valueOf(total)); + } showMap(user, island, unlocked, max); return true; } diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java new file mode 100644 index 0000000..76a61d7 --- /dev/null +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommand.java @@ -0,0 +1,127 @@ +package world.bentobox.chunkblock.commands.island; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.chunkblock.ChunkBlock; +import world.bentobox.chunkblock.activity.ActivityManager; +import world.bentobox.chunkblock.activity.CounterType; + +/** + * /ch ledger — shows each team member's contributions to the island: blocks broken, + * chunks claimed, and rings completed. Reads from the activity counters foundation; + * the ledger itself adds no persistence. + * + * @author tastybento + */ +public class IslandLedgerCommand extends CompositeCommand { + + private static final String REF = "chunkblock.commands.ledger."; + + private ChunkBlock addon; + + public IslandLedgerCommand(CompositeCommand islandCommand, String label, String[] aliases) { + super(islandCommand, label, aliases); + } + + @Override + public void setup() { + setDescription(REF + "description"); + setParametersHelp(REF + "parameters"); + setOnlyPlayer(true); + setPermission("island.ledger"); + addon = getAddon(); + } + + @Override + public boolean canExecute(User user, String label, List args) { + if (!Util.sameWorld(getWorld(), user.getWorld())) { + user.sendMessage("general.errors.wrong-world"); + return false; + } + return true; + } + + @Override + public boolean execute(User user, String label, List args) { + Optional optionalIsland = getIslands().getIslandAt(user.getLocation()); + if (optionalIsland.isEmpty()) { + user.sendMessage("general.errors.not-on-island"); + return false; + } + Island island = optionalIsland.get(); + ActivityManager am = addon.getActivityManager(); + if (am == null) { + return false; + } + + int window = parseWindow(args); + String windowLabel = window <= 0 + ? user.getTranslation(REF + "all-time") + : user.getTranslation(REF + "last-days", "[days]", String.valueOf(window)); + + user.sendMessage(REF + "header", "[window]", windowLabel); + + List rows = buildRows(island, am, window); + if (rows.isEmpty()) { + user.sendMessage(REF + "no-activity"); + return true; + } + + for (MemberRow row : rows) { + user.sendMessage(REF + "row", + "[name]", row.name, + "[blocks]", String.valueOf(row.blocks), + "[chunks]", String.valueOf(row.chunks), + "[rings]", String.valueOf(row.rings)); + } + + long totalBlocks = rows.stream().mapToLong(r -> r.blocks).sum(); + long totalChunks = rows.stream().mapToLong(r -> r.chunks).sum(); + long totalRings = rows.stream().mapToLong(r -> r.rings).sum(); + user.sendMessage(REF + "total", + "[blocks]", String.valueOf(totalBlocks), + "[chunks]", String.valueOf(totalChunks), + "[rings]", String.valueOf(totalRings)); + + return true; + } + + private int parseWindow(List args) { + if (args.isEmpty()) { + return 0; + } + try { + return Math.max(0, Integer.parseInt(args.get(0))); + } catch (NumberFormatException e) { + return 0; + } + } + + private List buildRows(Island island, ActivityManager am, int window) { + List rows = new ArrayList<>(); + for (UUID uuid : am.getContributors(island)) { + long blocks = am.getCount(island, uuid, CounterType.MAGIC_BLOCKS, window); + long chunks = am.getCount(island, uuid, CounterType.CHUNKS_CLAIMED, window) + + am.getCount(island, uuid, CounterType.CHUNKS_RECLAIMED, window); + long rings = am.getCount(island, uuid, CounterType.RINGS_COMPLETED, window); + if (blocks > 0 || chunks > 0 || rings > 0) { + String name = addon.getPlayers().getName(uuid); + rows.add(new MemberRow(name == null || name.isEmpty() ? uuid.toString() : name, + blocks, chunks, rings)); + } + } + rows.sort(Comparator.comparingLong(MemberRow::blocks).reversed()); + return rows; + } + + private record MemberRow(String name, long blocks, long chunks, long rings) { + } +} diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java index 7ea83af..6ac089e 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandTitleCommand.java @@ -60,6 +60,9 @@ public boolean execute(User user, String label, List args) { return false; } if (args.isEmpty()) { + return toggleTitle(user, island); + } + if ("list".equalsIgnoreCase(args.get(0))) { showTitles(user, island); return true; } @@ -105,6 +108,25 @@ private void showTitles(User user, Island island) { } } + private boolean toggleTitle(User user, Island island) { + String active = addon.getTrophyManager().getActiveTitleText(island); + if (!active.isEmpty()) { + addon.getTrophyManager().setActiveTitle(island, null); + user.sendMessage("chunkblock.commands.title.toggled-off", TITLE_VAR, active); + return true; + } + // No active title — try to activate the first earned trophy that carries one + Optional first = addon.getTrophyManager().getEarned(island).stream() + .filter(t -> t.title() != null).findFirst(); + if (first.isEmpty()) { + user.sendMessage("chunkblock.commands.title.none-earned-yet"); + return false; + } + addon.getTrophyManager().setActiveTitle(island, first.get().id()); + user.sendMessage("chunkblock.commands.title.toggled-on", TITLE_VAR, first.get().title()); + return true; + } + @Override public Optional> tabComplete(User user, String alias, List args) { Island island = getIslands().getIsland(getWorld(), user); @@ -112,6 +134,7 @@ public Optional> tabComplete(User user, String alias, List return Optional.empty(); } List options = new ArrayList<>(); + options.add("list"); options.add(CLEAR); addon.getTrophyManager().getEarned(island).stream().filter(t -> t.title() != null) .map(Trophy::id).forEach(options::add); diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java index b99d6db..ffbf840 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/PlayerCommand.java @@ -33,6 +33,8 @@ public void setup() { settings.getSetCountCommand().split(" ")); // Chunk territory info and map new IslandChunksCommand(this, "chunks", new String[] {"chunks"}); + // Contribution ledger + new IslandLedgerCommand(this, "ledger", new String[] {"ledger"}); // Trophy titles new IslandTitleCommand(this, "title", new String[] {"title"}); // Force block respawn diff --git a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java index 9610f78..a065544 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java @@ -198,10 +198,19 @@ private void tryToShowBossBar(UUID uuid, Island island) { int numBlocksToGo = addon.getOneBlockManager().getNextPhaseBlocks(obi); int phaseBlocks = addon.getOneBlockManager().getPhaseBlocks(obi); int done = phaseBlocks - numBlocksToGo; + String titlePrefix = ""; + if (addon.getTrophyManager() != null) { + String activeTitle = addon.getTrophyManager().getActiveTitleText(island); + if (!activeTitle.isEmpty()) { + titlePrefix = user.getTranslationOrNothing("chunkblock.bossbar.title-prefix", + "[title]", activeTitle); + } + } String translation = user.getTranslationOrNothing("chunkblock.bossbar.status", "[togo]", String.valueOf(numBlocksToGo), "[total]", String.valueOf(phaseBlocks), "[done]", String.valueOf(done), "[phase-name]", obi.getPhaseName(), "[percent-done]", Math.round(addon.getOneBlockManager().getPercentageDone(obi)) + "%"); + translation = translation.replace("[island-title]", titlePrefix); bar.setTitle(translation); // Add to user if they don't have it already Player player = Bukkit.getPlayer(uuid); diff --git a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java index e491057..fa1d085 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/LevelListener.java @@ -248,6 +248,10 @@ private void rewardRing(Island island, int ring) { "[chunks]", chunkText)); } celebrateRing(island, ring); + if (addon.getActivityManager() != null) { + addon.getActivityManager().recordActivity(island, null, + world.bentobox.chunkblock.activity.CounterType.RINGS_COMPLETED, 1); + } List ownerCommands = addon.getSettings().getRingCommands(); if (!ownerCommands.isEmpty()) { runCommands(ownerCommands, ringText, chunkText, "[owner]", playerName(island.getOwner())); diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index bb8d7b0..483e52b 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -54,6 +54,7 @@ chunkblock: ring-complete: "Ring [ring] complete! The whole ring around your island is yours — [chunks] chunks in all." ring-broadcast: "[name]'s island has closed ring [ring] [chunks] chunks and still growing!" rings: "Rings completed: [rings] of [max]." + ring-progress: "Ring [ring]: [remaining]/[total] chunks to go." sethome-denied: "You can't set a home in a locked chunk." info: "Chunks: [unlocked]/[max]. Credit: [credit] level(s) — a chunk costs [cost]." map: @@ -76,7 +77,8 @@ chunkblock: title: "Blocks remaining" # status: "&a Phase blocks &b [total]. Blocks left: [todo]" # status: "&a [phase-name] : [percent-done]" - status: "Phase blocks [done] / [total]" + title-prefix: "[title] | " + status: "[island-title]Phase blocks [done] / [total]" # RED, WHITE, PINK, BLUE, GREEN, YELLOW, or PURPLE color: RED # SOLID, SEGMENTED_6, SEGMENTED_10, SEGMENTED_12, SEGMENTED_20 @@ -87,13 +89,22 @@ chunkblock: not-active: "Action Bar is not active for this island" trophies: awarded: "Trophy earned: [name]!" - title-available: "Your island can now show the title [title] — pick it with /ch title." + title-available: "Your island earned the title [title] — toggle it on and off using /ch title." commands: chunks: description: "show your unlocked chunks and a map of your territory" + ledger: + description: "show team member contributions" + parameters: "[days]" + header: "Island Contribution Ledger ([window])" + all-time: "all time" + last-days: "last [days] days" + row: " - [name]: [blocks] blocks, [chunks] chunks, [rings] rings" + total: "Total: [blocks] blocks, [chunks] chunks, [rings] rings" + no-activity: "No activity recorded yet." title: - description: "show your island's trophies and choose its title" - parameters: "[trophy-id | none]" + description: "toggle your island's title on/off, or choose one" + parameters: "[list | trophy-id | none]" header: "Your island's trophies:" trophy-entry: " - [name]" title-entry: " - [name] — title [title] (/ch title [id])" @@ -102,6 +113,8 @@ chunkblock: no-active: "No title is active. Pick one with /ch title." set: "Your island's title is now [title]." cleared: "Your island no longer shows a title." + toggled-on: "Island title [title] is now showing." + toggled-off: "Island title [title] is now hidden." not-earned: "Your island has not earned a trophy with that title." admin: chunks: diff --git a/src/main/resources/trophies.yml b/src/main/resources/trophies.yml index 1df9d47..ca087df 100644 --- a/src/main/resources/trophies.yml +++ b/src/main/resources/trophies.yml @@ -39,10 +39,26 @@ trophies: name: "First Ring" description: "Complete the first ring of chunks around your magic block." icon: GOLD_INGOT - title: "Ring Bearer" + title: "The Outpost" criteria: type: RING ring: 1 + second-ring: + name: "Second Ring" + description: "Complete the second ring of chunks." + icon: GOLD_BLOCK + title: "The Stronghold" + criteria: + type: RING + ring: 2 + third-ring: + name: "Third Ring" + description: "Complete the third ring of chunks." + icon: DIAMOND + title: "The Citadel" + criteria: + type: RING + ring: 3 homesteader: name: "Homesteader" description: "Claim ten different chunks for your island." @@ -56,7 +72,7 @@ trophies: name: "Ten Thousand Blocks" description: "Break ten thousand magic blocks as an island." icon: DIAMOND_PICKAXE - title: "Block Breaker" + title: "The Forge" criteria: type: COUNTER counter: MAGIC_BLOCKS diff --git a/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java b/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java index c31659e..8a6680c 100644 --- a/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java +++ b/src/test/java/world/bentobox/chunkblock/chunks/ChunkManagerTest.java @@ -280,6 +280,37 @@ private void claimRingOne() { } } + @Test + void testChunksRemainingInRingZero() { + assertEquals(0, cm.chunksRemainingInRing(island, 0)); + } + + @Test + void testChunksRemainingInRingOneFresh() { + assertEquals(8, cm.chunksRemainingInRing(island, 1)); + } + + @Test + void testChunksRemainingInRingOnePartial() { + level = 3; + cm.claim(island, 1, 0); + cm.claim(island, 0, 1); + cm.claim(island, -1, 0); + assertEquals(5, cm.chunksRemainingInRing(island, 1)); + } + + @Test + void testChunksRemainingInRingOneComplete() { + level = 8; + claimRingOne(); + assertEquals(0, cm.chunksRemainingInRing(island, 1)); + } + + @Test + void testChunksRemainingInRingTwo() { + assertEquals(16, cm.chunksRemainingInRing(island, 2)); + } + @Test void testGetUnlockedOffsets() { level = 2; diff --git a/src/test/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommandTest.java b/src/test/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommandTest.java new file mode 100644 index 0000000..97c2f5d --- /dev/null +++ b/src/test/java/world/bentobox/chunkblock/commands/island/IslandLedgerCommandTest.java @@ -0,0 +1,147 @@ +package world.bentobox.chunkblock.commands.island; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Location; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.chunkblock.ChunkBlock; +import world.bentobox.chunkblock.CommonTestSetup; +import world.bentobox.chunkblock.Settings; +import world.bentobox.chunkblock.activity.ActivityManager; +import world.bentobox.chunkblock.activity.CounterType; +import world.bentobox.chunkblock.chunks.ChunkManager; +import world.bentobox.chunkblock.dataobjects.OneBlockIslands; +import world.bentobox.chunkblock.listeners.BlockListener; + +class IslandLedgerCommandTest extends CommonTestSetup { + + @Mock + private CompositeCommand ac; + @Mock + private User user; + @Mock + private ChunkBlock addon; + @Mock + private ActivityManager activityManager; + @Mock + private Location playerLocation; + @Mock + private PlayersManager playersManager; + + private IslandLedgerCommand command; + private UUID member1; + private UUID member2; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(ac.getAddon()).thenReturn(addon); + Settings settings = new Settings(); + when(addon.getSettings()).thenReturn(settings); + OneBlockIslands data = new OneBlockIslands("test"); + when(addon.getOneBlocksIsland(island)).thenReturn(data); + when(addon.getBlockListener()).thenReturn(mock(BlockListener.class)); + when(addon.getActivityManager()).thenReturn(activityManager); + when(addon.getPlayers()).thenReturn(playersManager); + ChunkManager cm = new ChunkManager(addon); + when(addon.getChunkManager()).thenReturn(cm); + + when(island.getCenter()).thenReturn(location); + when(island.getWorld()).thenReturn(world); + when(world.getName()).thenReturn("chunkblock_world"); + when(location.getBlockX()).thenReturn(8); + when(location.getBlockZ()).thenReturn(8); + when(island.getProtectionRange()).thenReturn(240); + + when(playerLocation.getWorld()).thenReturn(world); + when(user.getLocation()).thenReturn(playerLocation); + when(user.getWorld()).thenReturn(world); + when(user.getTranslation(anyString(), any(String[].class))).thenAnswer(inv -> inv.getArgument(0, String.class)); + when(im.getIslandAt(playerLocation)).thenReturn(Optional.of(island)); + + member1 = UUID.randomUUID(); + member2 = UUID.randomUUID(); + when(playersManager.getName(member1)).thenReturn("Alice"); + when(playersManager.getName(member2)).thenReturn("Bob"); + + command = new IslandLedgerCommand(ac, "ledger", new String[] { "ledger" }); + } + + @Test + void testSetup() { + assertEquals("island.ledger", command.getPermission()); + assertEquals("chunkblock.commands.ledger.description", command.getDescription()); + assertTrue(command.isOnlyPlayer()); + } + + @Test + void testExecuteNoIsland() { + when(im.getIslandAt(any())).thenReturn(Optional.empty()); + assertFalse(command.execute(user, "ledger", Collections.emptyList())); + verify(user).sendMessage("general.errors.not-on-island"); + } + + @Test + void testExecuteNoActivity() { + when(activityManager.getContributors(island)).thenReturn(Collections.emptySet()); + assertTrue(command.execute(user, "ledger", Collections.emptyList())); + verify(user).sendMessage(eq("chunkblock.commands.ledger.header"), anyString(), anyString()); + verify(user).sendMessage("chunkblock.commands.ledger.no-activity"); + } + + @Test + void testExecuteWithContributors() { + when(activityManager.getContributors(island)).thenReturn(Set.of(member1, member2)); + when(activityManager.getCount(island, member1, CounterType.MAGIC_BLOCKS, 0)).thenReturn(100L); + when(activityManager.getCount(island, member1, CounterType.CHUNKS_CLAIMED, 0)).thenReturn(3L); + when(activityManager.getCount(island, member1, CounterType.CHUNKS_RECLAIMED, 0)).thenReturn(1L); + when(activityManager.getCount(island, member1, CounterType.RINGS_COMPLETED, 0)).thenReturn(1L); + when(activityManager.getCount(island, member2, CounterType.MAGIC_BLOCKS, 0)).thenReturn(50L); + when(activityManager.getCount(island, member2, CounterType.CHUNKS_CLAIMED, 0)).thenReturn(0L); + when(activityManager.getCount(island, member2, CounterType.CHUNKS_RECLAIMED, 0)).thenReturn(0L); + when(activityManager.getCount(island, member2, CounterType.RINGS_COMPLETED, 0)).thenReturn(0L); + + assertTrue(command.execute(user, "ledger", Collections.emptyList())); + verify(user).sendMessage(eq("chunkblock.commands.ledger.header"), anyString(), anyString()); + verify(user, never()).sendMessage("chunkblock.commands.ledger.no-activity"); + verify(user).sendMessage(eq("chunkblock.commands.ledger.total"), + eq("[blocks]"), eq("150"), + eq("[chunks]"), eq("4"), + eq("[rings]"), eq("1")); + } + + @Test + void testExecuteWithWindowDays() { + when(activityManager.getContributors(island)).thenReturn(Set.of(member1)); + when(activityManager.getCount(island, member1, CounterType.MAGIC_BLOCKS, 7)).thenReturn(20L); + when(activityManager.getCount(island, member1, CounterType.CHUNKS_CLAIMED, 7)).thenReturn(1L); + when(activityManager.getCount(island, member1, CounterType.CHUNKS_RECLAIMED, 7)).thenReturn(0L); + when(activityManager.getCount(island, member1, CounterType.RINGS_COMPLETED, 7)).thenReturn(0L); + + assertTrue(command.execute(user, "ledger", List.of("7"))); + verify(user).sendMessage(eq("chunkblock.commands.ledger.header"), anyString(), anyString()); + } +} diff --git a/src/test/java/world/bentobox/chunkblock/commands/island/IslandTitleCommandTest.java b/src/test/java/world/bentobox/chunkblock/commands/island/IslandTitleCommandTest.java index e0652fd..d1a159f 100644 --- a/src/test/java/world/bentobox/chunkblock/commands/island/IslandTitleCommandTest.java +++ b/src/test/java/world/bentobox/chunkblock/commands/island/IslandTitleCommandTest.java @@ -69,17 +69,35 @@ void testSetup() { } @Test - void testListWithNothingEarned() { + void testToggleOnWhenNothingEarned() { + when(tm.getActiveTitleText(island)).thenReturn(""); when(tm.getEarned(island)).thenReturn(List.of()); - assertTrue(command.execute(user, "title", List.of())); + assertFalse(command.execute(user, "title", List.of())); verify(user).sendMessage("chunkblock.commands.title.none-earned-yet"); } + @Test + void testToggleOffWhenActive() { + when(tm.getActiveTitleText(island)).thenReturn("The Outpost"); + assertTrue(command.execute(user, "title", List.of())); + verify(tm).setActiveTitle(island, null); + verify(user).sendMessage("chunkblock.commands.title.toggled-off", "[title]", "The Outpost"); + } + + @Test + void testToggleOnWhenInactive() { + when(tm.getActiveTitleText(island)).thenReturn(""); + when(tm.getEarned(island)).thenReturn(List.of(TITLED, UNTITLED)); + when(tm.setActiveTitle(island, "first-ring")).thenReturn(true); + assertTrue(command.execute(user, "title", List.of())); + verify(user).sendMessage("chunkblock.commands.title.toggled-on", "[title]", "Ring Bearer"); + } + @Test void testListShowsTrophiesTitlesAndActiveTitle() { when(tm.getEarned(island)).thenReturn(List.of(TITLED, UNTITLED)); when(tm.getActiveTitleText(island)).thenReturn("Ring Bearer"); - assertTrue(command.execute(user, "title", List.of())); + assertTrue(command.execute(user, "title", List.of("list"))); verify(user).sendMessage("chunkblock.commands.title.header"); verify(user).sendMessage("chunkblock.commands.title.title-entry", "[name]", "First Ring", "[title]", "Ring Bearer", "[id]", "first-ring"); @@ -91,7 +109,7 @@ void testListShowsTrophiesTitlesAndActiveTitle() { void testListMentionsWhenNoTitleIsActive() { when(tm.getEarned(island)).thenReturn(List.of(UNTITLED)); when(tm.getActiveTitleText(island)).thenReturn(""); - assertTrue(command.execute(user, "title", List.of())); + assertTrue(command.execute(user, "title", List.of("list"))); verify(user).sendMessage("chunkblock.commands.title.no-active"); } @@ -122,6 +140,7 @@ void testTabCompleteOffersNoneAndTitledTrophies() { when(tm.getEarned(island)).thenReturn(List.of(TITLED, UNTITLED)); Optional> options = command.tabComplete(user, "title", List.of("")); assertTrue(options.isPresent()); + assertTrue(options.get().contains("list")); assertTrue(options.get().contains("none")); assertTrue(options.get().contains("first-ring")); // A trophy with no title is not offered