Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
869a8a2
ci: publish to CurseForge now that the project is approved
tastybento Aug 11, 2026
f99ec77
feat: draw the /ch chunks map as a dialog of chunk buttons
tastybento Aug 14, 2026
aea6579
i18n: sync the zh-TW chunk gating keys
tastybento Aug 14, 2026
86faa12
refactor: keep the chunk map's colours in Components, not colour codes
tastybento Aug 14, 2026
009de3a
i18n: convert every locale file to MiniMessage
tastybento Aug 14, 2026
5d987ac
Merge pull request #33 from BentoBoxWorld/chore/minimessage-locales
tastybento Aug 14, 2026
354ee66
Merge pull request #32 from BentoBoxWorld/feature/chunks-map-dialog
tastybento Aug 14, 2026
4a37453
fix: attach glow lichen, vines and other multiface plants to a block
tastybento Aug 15, 2026
f58395c
feat: per-member island activity counters
tastybento Aug 15, 2026
3d99624
Merge pull request #34 from BentoBoxWorld/fix/glow-lichen-attachment
tastybento Aug 15, 2026
1ef9e9c
Update pom.xml
tastybento Aug 15, 2026
312a841
Merge pull request #35 from BentoBoxWorld/feature/activity-counters
tastybento Aug 15, 2026
b18288a
fix: address SonarCloud findings on the counters foundation
tastybento Aug 15, 2026
311ddb4
feat: island trophies and titles
tastybento Aug 15, 2026
20a714f
fix: address SonarCloud findings on the trophy layer
tastybento Aug 15, 2026
1bf8dfa
Merge pull request #36 from BentoBoxWorld/feature/island-trophies
tastybento Aug 15, 2026
f2ba4b5
fix: guard BossBarListener against null playerUUID from NPC/minion br…
tastybento Aug 16, 2026
0ee0b8e
Merge pull request #37 from BentoBoxWorld/fix/null-uuid-minion-bossbar
tastybento Aug 16, 2026
a5618c1
feat: audit log every successful chunk claim
tastybento Aug 16, 2026
9e92f13
fix: replace leftover "OneBlock" with "ChunkBlock" in locale files
tastybento Aug 16, 2026
fb70605
Merge pull request #38 from BentoBoxWorld/feature/claim-audit-logging
tastybento Aug 16, 2026
2eaefed
Merge pull request #39 from BentoBoxWorld/fix/oneblock-typo-in-phases
tastybento Aug 16, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/modrinth-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ jobs:
26.1
26.1.1
26.1.2
26.2

# Path to the built JAR — Maven produces ChunkBlock-<version>.jar in target/
# Note: glob patterns are not supported; use the release tag directly.
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ jobs:
uses: bentoboxworld/.github/.github/workflows/publish-platforms.yml@1f91a0edf72e8c86d671b3b8fdd3121ac6fb88e1 # master
with:
use_release_asset: "true" # publish the jar attached to the release; do not rebuild
# TODO: ChunkBlock is not set up on Hangar or CurseForge yet. When the projects
# exist, set hangar_slug to the Hangar slug and curseforge_id to ChunkBlock's own
# CurseForge project id (1512493 was AOneBlock's — never reuse it), add the
# HANGAR_API_KEY / CURSEFORGE_TOKEN secrets, and re-enable this workflow
# (gh workflow enable publish.yml).
# ChunkBlock's own CurseForge project. Do not reuse 1512493 — that is AOneBlock's.
# Hangar has not approved ChunkBlock yet; a blank slug makes the reusable workflow
# skip the Hangar job entirely, so the release still succeeds. Fill in "ChunkBlock"
# once the project exists.
hangar_slug: "" # blank = skip Hangar
curseforge_id: "" # blank = skip CurseForge
curseforge_id: "1646388"
game_versions: "26.2,26.1.2,26.1.1,26.1,1.21.11,1.21.10,1.21.9,1.21.8,1.21.7,1.21.6,1.21.5"
version: ${{ inputs.version }} # empty on release events -> falls back to the release tag
secrets:
Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ mvn verify + SonarCloud), Jenkins at ci.codemc.org builds release jars on master

- Locale keys live under `chunkblock.chunks.*` for gating messages (en-US.yml is the
source of truth; other locales lag until synced).
- **New locale text is MiniMessage** (`<green>`, `<aqua>`); the old `&`-codes still parse,
so files are mixed. Never build colored text yourself and splice it into a translation —
go through `User#getTranslationAsComponent` / `sendMessage(Component)` and keep colors in
Components. Where a formatted fragment really must reach a `[variable]` (the chat map's
`[row]`), serialize it to MiniMessage with `Util.getMiniMessage()` rather than emitting
`&` codes into what may be a MiniMessage line.
- Public API for other plugins: `ChunkUnlockEvent`/`ChunkRelockEvent` (per chunk, carry
claim-order index), request handler `unlocked-chunks`.
- Tests extend `CommonTestSetup` (mocked Bukkit/BentoBox); `ChunkManagerTest` and the
Expand Down
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.1.1</build.version>
<build.version>1.2.0</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_ChunkBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/world/bentobox/chunkblock/ChunkBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import world.bentobox.chunkblock.activity.ActivityManager;
import world.bentobox.chunkblock.chunks.BorderDisplay;
import world.bentobox.chunkblock.chunks.ChunkManager;
import world.bentobox.chunkblock.listeners.ActivityListener;
import world.bentobox.chunkblock.trophies.TrophyManager;
import world.bentobox.chunkblock.commands.admin.AdminCommand;
import world.bentobox.chunkblock.commands.island.PlayerCommand;
import world.bentobox.chunkblock.dataobjects.OneBlockIslands;
Expand All @@ -40,6 +43,7 @@
import world.bentobox.chunkblock.oneblocks.customblock.ItemsAdderCustomBlock;
import world.bentobox.chunkblock.oneblocks.customblock.NexoCustomBlock;
import world.bentobox.chunkblock.requests.IslandStatsHandler;
import world.bentobox.chunkblock.requests.MemberActivityHandler;
import world.bentobox.chunkblock.requests.UnlockedChunksHandler;
import world.bentobox.chunkblock.requests.LocationStatsHandler;
import world.bentobox.bentobox.api.addons.GameModeAddon;
Expand Down Expand Up @@ -88,6 +92,10 @@ public class ChunkBlock extends GameModeAddon {
private OneBlocksManager oneBlockManager;
/** The manager for chunk locking and the unlock spiral */
private ChunkManager chunkManager;
/** The per-member activity counters (the ledger/leaderboard/trophy substrate) */
private ActivityManager activityManager;
/** The config-defined island trophies and titles */
private TrophyManager trophyManager;
/** The placeholder manager for ChunkBlock */
private ChunkBlockPlaceholders phManager;
/** The listener for hologram-related events */
Expand Down Expand Up @@ -244,6 +252,10 @@ public void onEnable() {
oneBlockManager = new OneBlocksManager(this);
// Initialize the chunk lock manager
chunkManager = new ChunkManager(this);
// Initialize the activity counters and the trophies that read them
activityManager = new ActivityManager(this);
trophyManager = new TrophyManager(this);
trophyManager.loadTrophies();
// Load phase data
if (loadData()) {
// Failed to load - don't register anything
Expand All @@ -265,6 +277,7 @@ public void onEnable() {
registerListener(new BlockProtect(this));
registerListener(new JoinLeaveListener(this));
registerListener(new InfoListener(this));
registerListener(new ActivityListener(this));
// Note: bossBar is registered as a listener by the FlagsManager when the
// CHUNKBLOCK_BOSSBAR or CHUNKBLOCK_ACTIONBAR flag is registered in onLoad, so it
// must not be registered here too or events would be handled twice
Expand All @@ -275,6 +288,7 @@ public void onEnable() {
registerRequestHandler(new IslandStatsHandler(this));
registerRequestHandler(new LocationStatsHandler(this));
registerRequestHandler(new UnlockedChunksHandler(this));
registerRequestHandler(new MemberActivityHandler(this));

// Register Holograms
holoListener = new HoloListener(this);
Expand Down Expand Up @@ -309,6 +323,9 @@ public void onDisable() {
if (blockListener != null) {
blockListener.saveCacheNow();
}
if (activityManager != null) {
activityManager.saveCacheNow();
}

// Stop border rendering and restore client-side blocks
if (borderDisplay != null) {
Expand All @@ -325,11 +342,17 @@ public void onDisable() {
public void onReload() {
// save cache
blockListener.saveCache();
if (activityManager != null) {
activityManager.saveCache();
}
// Reload settings and phase data
if (loadSettings()) {
log("Reloaded ChunkBlock settings");
loadData();
}
if (trophyManager != null) {
trophyManager.loadTrophies();
}
}

/**
Expand All @@ -346,6 +369,20 @@ public ChunkManager getChunkManager() {
return chunkManager;
}

/**
* @return the activity counter manager, or null before the addon is enabled
*/
public ActivityManager getActivityManager() {
return activityManager;
}

/**
* @return the trophy and title manager, or null before the addon is enabled
*/
public TrophyManager getTrophyManager() {
return trophyManager;
}

/**
* @return the chunk guard listener (containment and backtracking)
*/
Expand Down Expand Up @@ -481,6 +518,8 @@ public void saveDefaultConfig() {
super.saveDefaultConfig();
// Save default phases panel
this.saveResource("panels/phases_panel.yml", false);
// Save default trophy definitions
this.saveResource("trophies.yml", false);
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,33 @@ 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_title", this::getIslandTitle);
placeholdersManager.registerPlaceholder(addon, "island_trophies", this::getIslandTrophies);
}

/**
* @param user user
* @return the user's island's active trophy title as configured (MiniMessage text),
* or an empty string for no title
*/
public String getIslandTitle(User user) {
if (user == null || user.getUniqueId() == null || addon.getTrophyManager() == null) {
return "";
}
return getUsersIsland(user).map(i -> addon.getTrophyManager().getActiveTitleText(i)).orElse("");
}

/**
* @param user user
* @return how many trophies the user's island has earned
*/
public String getIslandTrophies(User user) {
if (user == null || user.getUniqueId() == null || addon.getTrophyManager() == null) {
return "";
}
return getUsersIsland(user)
.map(i -> String.valueOf(addon.getOneBlocksIsland(i).getEarnedTrophies().size()))
.orElse("");
}

/**
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/world/bentobox/chunkblock/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ public class Settings implements WorldSettings {
@ConfigEntry(path = "chunkblock.eject-players-on-relock")
private boolean ejectPlayersOnRelock = true;

@ConfigComment("How many days of per-day activity buckets to keep per island. Activity counters")
@ConfigComment("(blocks broken, chunks claimed, levels earned... per member) store a lifetime")
@ConfigComment("total plus one bucket per day so time-windowed readouts are possible; buckets")
@ConfigComment("older than this are pruned. Lifetime totals are never pruned. Size this to the")
@ConfigComment("longest window anything reads — a season, a weekly ledger. Minimum 1.")
@ConfigEntry(path = "chunkblock.activity.daily-retention-days")
private int activityRetentionDays = 100;

@ConfigComment("Cancel natural mob spawning inside locked chunks.")
@ConfigEntry(path = "chunkblock.deny-mob-spawns-in-locked")
private boolean denyMobSpawnsInLocked = true;
Expand Down Expand Up @@ -2671,6 +2679,20 @@ public void setRingPlayerCommands(List<String> ringPlayerCommands) {
this.ringPlayerCommands = ringPlayerCommands;
}

/**
* @return how many days of per-day activity buckets are kept, never less than 1
*/
public int getActivityRetentionDays() {
return Math.max(1, activityRetentionDays);
}

/**
* @param activityRetentionDays the activityRetentionDays to set
*/
public void setActivityRetentionDays(int activityRetentionDays) {
this.activityRetentionDays = activityRetentionDays;
}

/**
* @return true if a chunk must be previewed and confirmed before credit is spent
*/
Expand Down
Loading
Loading