Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.2.0</build.version>
<build.version>1.3.0</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_ChunkBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,17 @@ public boolean execute(User user, String label, List<String> 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> args) {
Optional<Island> 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<MemberRow> 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<String> args) {
if (args.isEmpty()) {
return 0;
}
try {
return Math.max(0, Integer.parseInt(args.get(0)));
} catch (NumberFormatException e) {
return 0;
}
}

private List<MemberRow> buildRows(Island island, ActivityManager am, int window) {
List<MemberRow> 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) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public boolean execute(User user, String label, List<String> args) {
return false;
}
if (args.isEmpty()) {
return toggleTitle(user, island);
}
if ("list".equalsIgnoreCase(args.get(0))) {
showTitles(user, island);
return true;
}
Expand Down Expand Up @@ -105,13 +108,33 @@ 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<Trophy> 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<List<String>> tabComplete(User user, String alias, List<String> args) {
Island island = getIslands().getIsland(getWorld(), user);
if (island == null) {
return Optional.empty();
}
List<String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,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<String> ownerCommands = addon.getSettings().getRingCommands();
if (!ownerCommands.isEmpty()) {
runCommands(ownerCommands, ringText, chunkText, "[owner]", playerName(island.getOwner()));
Expand Down
21 changes: 17 additions & 4 deletions src/main/resources/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ chunkblock:
ring-complete: "<gold><bold>Ring [ring] complete! </bold>The whole ring around your island is yours — </gold><aqua>[chunks] </aqua><gold>chunks in all.</gold>"
ring-broadcast: "<gold>[name]'s island has closed ring </gold><aqua>[ring] </aqua><gold>— </gold><aqua>[chunks] </aqua><gold>chunks and still growing!</gold>"
rings: "<green>Rings completed: </green><aqua>[rings] </aqua><green>of </green><aqua>[max]</aqua><green>.</green>"
ring-progress: "<yellow>Ring [ring]: </yellow><aqua>[remaining]</aqua><yellow>/</yellow><aqua>[total]</aqua><yellow> chunks to go.</yellow>"
sethome-denied: "<red>You can't set a home in a locked chunk.</red>"
info: "<green>Chunks: </green><aqua>[unlocked]</aqua><green>/</green><aqua>[max]</aqua><green>. Credit: </green><aqua>[credit] </aqua><green>level(s) — a chunk costs </green><aqua>[cost]</aqua><green>.</green>"
map:
Expand All @@ -76,7 +77,8 @@ chunkblock:
title: "Blocks remaining"
# status: "&a Phase blocks &b [total]. Blocks left: [todo]"
# status: "&a [phase-name] : [percent-done]"
status: "<green>Phase blocks </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total]</aqua>"
title-prefix: "[title] | "
status: "[island-title]<green>Phase blocks </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total]</aqua>"
# RED, WHITE, PINK, BLUE, GREEN, YELLOW, or PURPLE
color: RED
# SOLID, SEGMENTED_6, SEGMENTED_10, SEGMENTED_12, SEGMENTED_20
Expand All @@ -87,13 +89,22 @@ chunkblock:
not-active: "<red>Action Bar is not active for this island</red>"
trophies:
awarded: "<gold><bold>Trophy earned: </bold></gold>[name]<gold>!</gold>"
title-available: "<yellow>Your island can now show the title </yellow>[title]<yellow> — pick it with </yellow><aqua>/ch title</aqua><yellow>.</yellow>"
title-available: "<yellow>Your island earned the title </yellow>[title]<yellow> — toggle it on and off using </yellow><aqua>/ch title</aqua><yellow>.</yellow>"
commands:
chunks:
description: "show your unlocked chunks and a map of your territory"
ledger:
description: "show team member contributions"
parameters: "[days]"
header: "<green>Island Contribution Ledger </green><gray>([window])</gray>"
all-time: "all time"
last-days: "last [days] days"
row: "<gray> - </gray><aqua>[name]</aqua><gray>: </gray><green>[blocks]</green><gray> blocks, </gray><green>[chunks]</green><gray> chunks, </gray><green>[rings]</green><gray> rings</gray>"
total: "<gold>Total: </gold><green>[blocks]</green><gold> blocks, </gold><green>[chunks]</green><gold> chunks, </gold><green>[rings]</green><gold> rings</gold>"
no-activity: "<gray>No activity recorded yet.</gray>"
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: "<green>Your island's trophies:</green>"
trophy-entry: "<gray> - </gray>[name]"
title-entry: "<gray> - </gray>[name]<gray> — title </gray>[title]<gray> (</gray><aqua>/ch title [id]</aqua><gray>)</gray>"
Expand All @@ -102,6 +113,8 @@ chunkblock:
no-active: "<gray>No title is active. Pick one with </gray><aqua>/ch title</aqua><gray>.</gray>"
set: "<green>Your island's title is now </green>[title]<green>.</green>"
cleared: "<green>Your island no longer shows a title.</green>"
toggled-on: "<green>Island title </green>[title]<green> is now showing.</green>"
toggled-off: "<yellow>Island title </yellow>[title]<yellow> is now hidden.</yellow>"
not-earned: "<red>Your island has not earned a trophy with that title.</red>"
admin:
chunks:
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ api-version: "1.21"
authors: [tastybento]
contributors: ["The BentoBoxWorld Community"]
website: https://bentobox.world
description: ${project.description}
description: "${project.description}"
20 changes: 18 additions & 2 deletions src/main/resources/trophies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,26 @@ trophies:
name: "<gold>First Ring"
description: "<gray>Complete the first ring of chunks around your magic block."
icon: GOLD_INGOT
title: "<gold>Ring Bearer"
title: "<gold>The Outpost"
criteria:
type: RING
ring: 1
second-ring:
name: "<yellow>Second Ring"
description: "<gray>Complete the second ring of chunks."
icon: GOLD_BLOCK
title: "<yellow>The Stronghold"
criteria:
type: RING
ring: 2
third-ring:
name: "<red>Third Ring"
description: "<gray>Complete the third ring of chunks."
icon: DIAMOND
title: "<red>The Citadel"
criteria:
type: RING
ring: 3
homesteader:
name: "<green>Homesteader"
description: "<gray>Claim ten different chunks for your island."
Expand All @@ -56,7 +72,7 @@ trophies:
name: "<aqua>Ten Thousand Blocks"
description: "<gray>Break ten thousand magic blocks as an island."
icon: DIAMOND_PICKAXE
title: "<aqua>Block Breaker"
title: "<aqua>The Forge"
criteria:
type: COUNTER
counter: MAGIC_BLOCKS
Expand Down
Loading