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