Skip to content
Merged
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
16 changes: 8 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
uses: actions/setup-java@v3
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'adopt'
java-version: '17'
distribution: 'temurin'
java-version: '21'
- name: Cache SonarCloud packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
Expand All @@ -35,4 +35,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
60 changes: 60 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

TopBlock is a BentoBox addon that produces a Top Ten ranking for the AOneBlock game mode based on how many magic blocks each island has mined. It is **not** standalone — it depends on the BentoBox plugin and the AOneBlock addon being present at runtime, and refuses to enable otherwise.

## Build & Test

Maven project, Java 21, Paper 1.21.11 API, BentoBox 3.14.0, AOneBlock 1.18.0.

- Build (default goal is `clean package`): `mvn package` — produces a shaded jar in `target/` named `TopBlock-<revision><build.number>.jar`. The shade plugin bundles only `lv.id.bonne:panelutils`; everything else is `provided`.
- Run tests: `mvn test`
- Run a single test class: `mvn test -Dtest=TopBlockManagerTest`
- Run a single test method: `mvn test -Dtest=TopBlockManagerTest#testFormatLevelShorthandKilo`
- The Surefire config sets a long list of `--add-opens` JVM flags — required for Mockito + MockBukkit reflection on Java 21; do not remove them when tweaking the build.

Version handling is driven by Maven properties: `build.version` is the human version (currently 1.1.0), `revision` resolves to `${build.version}-SNAPSHOT` locally and to `${build.version}` under the `master` profile (activated by `GIT_BRANCH=origin/master` on Jenkins). `build.number` is `-LOCAL` locally, `-b<num>` on CI, empty on master. Don't hand-edit `<version>` — bump `build.version`.

## Runtime entry points (Pladdon pattern)

There are **two** main classes and the distinction matters:

- `TopBlockPladdon` (referenced by `plugin.yml`) is the Bukkit-facing `Pladdon`. Spigot loads this; its only job is `getAddon() → new TopBlock()`.
- `TopBlock` (referenced by `addon.yml`) is the BentoBox `Addon`. All real lifecycle (`onLoad`, `onEnable`, `onDisable`) lives here.

`onEnable` looks up the AOneBlock addon via `getPlugin().getAddonsManager().getAddonByName("aoneblock")`; if missing or not a `GameModeAddon`, the addon disables itself. The `/<gamemode> topblock` command is registered against AOneBlock's player command, not as a top-level command.

## Data flow

`TopBlockManager` is a `Listener` that reacts to `BentoBoxReadyEvent` (handler is `public void onBentoBoxReady` — Bukkit silently skips private @EventHandler methods, which is what broke the addon historically) to start a repeating Bukkit task. The task period is `settings.getRefreshTime() * 20L * 60` ticks (minutes → ticks). Each tick of the task:

1. Calls `AOneBlock.getBlockListener().getAllIslands()` — this reads every island, so the refresh interval is intentionally coarse (default 5 min, min 1 min).
2. Builds a fresh `List<TopTenData>` (record of island + blockNumber + lifetime + phaseName) — sorted at read time via `Comparator` on `lifetime` then `blockNumber`.
3. Updates `PlaceholderManager`'s cached snapshot.

Placeholders are registered once via a `runTaskLater` 10-tick delay after the first ready event (so PAPI / BentoBox's `PlaceholdersManager` is up). Names follow `island_<field>_top_<1..10>` and are scoped to the AOneBlock `GameModeAddon`. The `TopBlock.TEN` constant is the source of truth for the list size.

## Panel

`TopLevelPanel` uses BentoBox's `TemplatedPanelBuilder`. The template file is shipped in `src/main/resources/panels/top_panel.yml` and copied to the data folder on load via `saveResource("panels/top_panel.yml", false)` — players' edits to the on-disk file persist across restarts. Localization keys live under `topblock.gui.buttons.island.*` in `src/main/resources/locales/en-US.yml`. The icon material can be overridden per-player via the `<permissionPrefix>topblock.icon.<MATERIAL>` permission.

The panel has no click actions (TopBlock doesn't bundle Warp/Visit hooks like Level does). The YAML still declares `warp`/`visit` actions with tooltips, but no click handler is registered — clicking does nothing.

## Resource filtering

`pom.xml` filters `src/main/resources` (so `${version}` etc. in `addon.yml` / `plugin.yml` get substituted) **except** `src/main/resources/locales`, which is copied verbatim to `./locales` to avoid Maven mangling YAML colons / placeholder syntax in translations.

## Tests

JUnit 5 + Mockito + MockBukkit. Test classes extend `CommonTestSetup` which:
- Mocks `Bukkit` statically and provides a real `MockBukkit.mock()` server (needed for Tag/Material initialisation).
- Injects the BentoBox singleton via `WhiteBox.setInternalState(BentoBox.class, "instance", plugin)`.
- Sets up the standard graph of mocks: `IslandWorldManager`, `IslandsManager`, `PlayersManager`, `LocalesManager`, `PlaceholdersManager`, `Notifier`, `HooksManager`, `BlueprintsManager`.
- Calls `User.setPlugin(plugin)` and pre-creates a `User` instance for `mockPlayer` (uuid `tastybento`).

`TestWorldSettings` returns `"TopBlock"` for friendly name and `"topblock."` for permission prefix. The addon test (`TopBlockTest`) builds an in-memory `addon.jar` containing `config.yml` + `panels/top_panel.yml` because `Addon.saveResource` reads from a real JarFile.

JaCoCo excludes `**/*Names*` to avoid synthetic-field issues on JavaBeans — keep that exclusion if adding similar classes.
74 changes: 57 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,77 @@

## About

Add-on for BentoBox to calculate island levels for AOneBlock specifically. Ranks are determined by how many magic blocks have been mined - the count.
TopBlock is a [BentoBox](https://github.com/BentoBoxWorld/BentoBox) addon that produces a Top Ten ranking for the [AOneBlock](https://github.com/BentoBoxWorld/AOneBlock) game mode based on how many magic blocks each island has mined.

## Requirements

- Paper 1.21.x (Spigot is no longer supported)
- Java 21
- BentoBox 3.14.0 or later
- AOneBlock 1.18.0 or later

## How to use

1. Place the level addon jar in the addons folder of the BentoBox plugin. Make sure you have AOneBlock installed too!
2. Restart the server
3. The addon will create a data folder and inside the folder will be a config.yml
4. Edit the config.yml how you want.
5. Restart the server if you make a change
1. Drop the TopBlock jar into your server's `plugins/BentoBox/addons/` folder. AOneBlock must already be installed there too.
2. Restart the server. TopBlock will create `addons/TopBlock/config.yml` and `addons/TopBlock/panels/top_panel.yml`.
3. Edit `config.yml` if you want to tune anything (see below) and restart the server again to apply.

## Configuration

`addons/TopBlock/config.yml`:

| Option | Default | Description |
|----------------|---------|-------------------------------------------------------------------------------------------------------------------|
| `refresh-time` | `5` | How often the Top Ten is recalculated, in minutes. Minimum 1. Each refresh reads every island, so don't set it too low. |
| `shorthand` | `false` | If `true`, format large counts using units — `10,500` becomes `10.5k`, `1,527,314` becomes `1.5M`, etc. |

The panel layout lives in `addons/TopBlock/panels/top_panel.yml`. Edits are preserved across restarts.

## Commands

`/ob topblock` - this shows the Top Ten
`/ob topblock` (alias: `/oneblock topblock`) — opens the Top Ten panel.

To get into the top ten, a player just needs to mine at least one magic block on their AOneBlock island. The list refreshes every `refresh-time` minutes; a player who just started mining may need to wait that long before appearing.

## Permissions
Permissions are given automatically to players as listed below. If your permissions plugin strips permissions then you may have to allocate these manually. Note that if a player doesn't have the `intopten` permission, they will not be listed in the top ten.

```
permissions:
Permissions are given automatically to players. If your permissions plugin strips defaults, allocate them manually:

```yaml
permissions:
'aoneblock.island.topblock':
description: Player can use TopBlock command
description: Player can use the TopBlock command
default: true
'aoneblock.intopten':
description: Player's island will be listed in the top ten. Remove from admins or testers to hide them.
default: true
```

If an island owner is **online** and lacks `aoneblock.intopten`, their island is excluded from the top ten panel and from placeholders. **Offline** owners are always included — to hide an admin or tester, remove the perm from the player who can actually log in. Removing the perm from an entire group (e.g. ops) excludes everyone in that group while online.

The icon shown for each rank can be overridden per player by granting `aoneblock.topblock.icon.<MATERIAL>` (for example `aoneblock.topblock.icon.diamond_block`). Without an override, the rank icon is the player's head.

## Placeholders

```
%aoneblock_island_player_name_top_RANK% where RANK is 1 to 10 - Island owner's name
%aoneblock_island_member_names_top_RANK% where RANK is 1 to 10 - Name of island team members
%aoneblock_island_phase_name_top_RANK% where RANK is 1 to 10 - Name of the phase they have reached
%aoneblock_island_phase_number_top_RANK% where RANK is 1 to 10 - Phase number, e.g. Plains is 1, Underground is 2, etc.
%aoneblock_island_count_top_RANK% where RANK is 1 to 10 - Block Count of magic blocks mined this round
%aoneblock_island_lifetime_top_RANK% where RANK is 1 to 10 - Lifetime count of magic blocks mined
%aoneblock_island_player_name_top_RANK% - Island owner's name
%aoneblock_island_member_names_top_RANK% - Comma-separated team members (highest rank first)
%aoneblock_island_phase_name_top_RANK% - Name of the phase the island has reached
%aoneblock_island_phase_number_top_RANK% - Phase number, e.g. Plains is 1, Underground is 2
%aoneblock_island_count_top_RANK% - Block count of magic blocks mined this round
%aoneblock_island_lifetime_top_RANK% - Lifetime count of magic blocks mined
```

`RANK` is `1` to `10`. If fewer than `RANK` islands qualify for the top ten, the placeholder returns an empty string.

## Building from source

```bash
mvn clean package
```

Produces `target/TopBlock-<version>.jar`.

## License

[EPL-2.0](LICENSE)
Loading
Loading