403 lines
22 KiB
Markdown
403 lines
22 KiB
Markdown
# BlockParty
|
|
|
|
BlockParty is a colored-floor elimination minigame for PaperMC servers. Players stand on a
|
|
floor of colored blocks; each round a target color is announced and players must move onto
|
|
it before time runs out. All other blocks vanish, anyone not standing on the target color
|
|
(or standing on nothing) falls and is eliminated, and the floor is restored for the next,
|
|
slightly faster round. Last player standing wins.
|
|
|
|
## Requirements
|
|
|
|
- PaperMC 1.21.x (API version `1.21`)
|
|
- Java 21
|
|
- No other plugins required. Optional soft-dependencies: PlaceholderAPI, Vault, WorldEdit.
|
|
|
|
## Installation
|
|
|
|
1. Download `BlockParty-<version>.jar` (see [Building from source](#building-from-source))
|
|
or grab it from the CI build artifacts.
|
|
2. Drop it into your server's `plugins/` folder.
|
|
3. Start/restart the server. `config.yml`, `messages.yml` and the `arenas/` folder are
|
|
created automatically under `plugins/BlockParty/`.
|
|
|
|
## Commands
|
|
|
|
All admin/config commands live under `/blockparty` (alias `/bp`) and work from console
|
|
where it makes sense (`create`, `delete`, `enable`, `disable`, `info`, `list`, `reload`).
|
|
Commands that require a physical location (`setlobby`, `setspawn`, `setspectator`,
|
|
`setbillboard`, `pos1`, `pos2`) require a player sender. `delbillboard`, `billboards`, `info`
|
|
work fine from console.
|
|
|
|
### Admin
|
|
| Command | Description |
|
|
|---|---|
|
|
| `/blockparty help` | Show command help |
|
|
| `/blockparty create <arena>` | Create a new, disabled arena |
|
|
| `/blockparty delete <arena>` | Delete an arena and its config file |
|
|
| `/blockparty enable <arena>` | Validate and enable an arena |
|
|
| `/blockparty disable <arena>` | Disable an arena, kicking anyone inside |
|
|
| `/blockparty setlobby <arena>` | Set the waiting-room location |
|
|
| `/blockparty setspawn <arena>` | Set the in-game spawn location |
|
|
| `/blockparty setspectator <arena>` | Set the spectator/eliminated location |
|
|
| `/blockparty setbillboard <arena> [name]` | (Optional) place a named display block here that mirrors the current target color; `name` defaults to `default`, so you can have any number by giving each a unique name |
|
|
| `/blockparty delbillboard <arena> [name]` | Remove a named billboard (defaults to `default`) |
|
|
| `/blockparty billboards <arena>` | List an arena's billboards and whether each is enabled |
|
|
| `/blockparty pos1 <arena>` | Set floor region corner 1 |
|
|
| `/blockparty pos2 <arena>` | Set floor region corner 2 |
|
|
| `/blockparty setfloor <arena> <mat1,mat2,...>` | Set the allowed floor materials |
|
|
| `/blockparty generate <arena>` | Randomly generate and persist the floor layout |
|
|
| `/blockparty info <arena>` | Show arena configuration/status and validation errors |
|
|
| `/blockparty list` | List all arenas |
|
|
| `/blockparty reload` | Reload config.yml, messages.yml and arena definitions |
|
|
|
|
### Player
|
|
| Command | Description |
|
|
|---|---|
|
|
| `/blockparty join <arena>` | Join an arena's waiting lobby |
|
|
| `/blockparty leave` | Leave your current arena |
|
|
| `/blockparty arenas` | List arenas (alias of `list`) |
|
|
| `/blockparty stats [player]` | View your or another player's stats |
|
|
| `/blockparty musicvote` | (Only if the jukebox is enabled) get a one-time code to vote for the next track on the jukebox web page |
|
|
|
|
## Permissions
|
|
|
|
| Permission | Default | Description |
|
|
|---|---|---|
|
|
| `blockparty.play` | true | Baseline permission to use player commands |
|
|
| `blockparty.join` | true | Allows joining arenas |
|
|
| `blockparty.spectate` | true | Allows spectating |
|
|
| `blockparty.admin` | op | Parent permission, grants all `blockparty.admin.*` |
|
|
| `blockparty.admin.create` | op | Create arenas |
|
|
| `blockparty.admin.delete` | op | Delete arenas |
|
|
| `blockparty.admin.setup` | op | Configure lobby/spawn/floor/etc, enable/disable |
|
|
| `blockparty.admin.reload` | op | Reload the plugin |
|
|
|
|
## Creating an arena — full walkthrough
|
|
|
|
1. `/bp create lobby1` — creates `plugins/BlockParty/arenas/lobby1.yml`.
|
|
2. Stand where players should wait and run `/bp setlobby lobby1`.
|
|
3. Stand where players should spawn on the floor and run `/bp setspawn lobby1`.
|
|
4. Stand where eliminated players/spectators should go and run `/bp setspectator lobby1`.
|
|
5. Stand at one corner of the floor area and run `/bp pos1 lobby1`.
|
|
6. Stand at the opposite corner and run `/bp pos2 lobby1`.
|
|
7. Either:
|
|
- Run `/bp setfloor lobby1 RED_CONCRETE,BLUE_CONCRETE,GREEN_CONCRETE,YELLOW_CONCRETE` to
|
|
choose the color palette (defaults to the full concrete rainbow if you skip this and
|
|
just generate), then `/bp generate lobby1` to lay out and persist a random floor, **or**
|
|
- Build the floor by hand out of allowed materials, then run `/bp generate lobby1` is
|
|
still required once so BlockParty captures/serializes the exact layout it should restore
|
|
each round (BlockParty does not scan the world every round — it restores from the saved
|
|
layout for performance and determinism).
|
|
8. Optionally, stand somewhere visible near/above the floor and run `/bp setbillboard lobby1`
|
|
(or `/bp setbillboard lobby1 <name>` to add more than one, e.g. one per side of the arena)
|
|
to place a big display block that mirrors the round's target color — purely cosmetic and
|
|
entirely optional (see "Billboards" below).
|
|
9. `/bp info lobby1` to confirm there are no validation errors.
|
|
10. `/bp enable lobby1`.
|
|
11. Players run `/bp join lobby1`. Once `min-players` is reached the countdown starts
|
|
automatically; joining an arena mid-countdown is allowed by default
|
|
(`allow-join-while-waiting: true`).
|
|
|
|
### Floor selection details
|
|
|
|
- `pos1` / `pos2` define the two opposite corners of the rectangular floor region (any Y).
|
|
If [WorldEdit](https://enginehub.org/worldedit) is installed and you have an active WorldEdit
|
|
selection (made with the wand — left-click for position 1, right-click for position 2, or
|
|
`//pos1` / `//pos2`), running `/bp pos1 <arena>` / `/bp pos2 <arena>` adopts that selection's
|
|
corners directly instead of your standing location — no need to physically stand at each
|
|
corner. This is entirely optional: without a WorldEdit selection (or without WorldEdit
|
|
installed at all), the commands fall back to your current location exactly as before. Disable
|
|
it with `integrations.worldedit: false` in `config.yml` if you'd rather always use your
|
|
location even when WorldEdit is present.
|
|
- `setfloor` only changes which materials are *allowed* to be used/regenerated — it does not
|
|
touch blocks in the world.
|
|
- `generate` is the step that actually writes a random layout (biased evenly across the
|
|
configured palette) across the whole region and serializes it into the arena's yml file
|
|
(`generated-layout`) so it can be restored exactly, every round, without re-scanning or
|
|
re-randomizing the world.
|
|
- By default the *positions* from that saved layout are reused every round, but which color
|
|
sits at which position is reshuffled (`floor.shuffle-each-round: true` in `config.yml`) at
|
|
the start of the match and before every round's restore — so the board isn't a static,
|
|
memorizable pattern for the whole game. The shuffle only permutes materials in memory; the
|
|
saved `generated-layout` itself is never modified, so a restart or another `/bp generate`
|
|
still starts from the original. Set `shuffle-each-round: false` to always restore the exact
|
|
saved layout unchanged instead.
|
|
|
|
### Billboards
|
|
|
|
An arena billboard is a `BlockDisplay` entity that visually shows the currently selected
|
|
target color/block each round, in addition to the title/actionbar/hotbar-item cues. An arena
|
|
may have any number of them (e.g. one visible from each side of the floor), each independently
|
|
named, positioned and toggled. Entirely optional:
|
|
|
|
- Per-billboard: `/bp setbillboard <arena> [name]` places/updates one at your current location;
|
|
`name` defaults to `default` if omitted, so the simple single-billboard flow needs no name at
|
|
all. Give each a distinct `name` to have several at once (e.g. `/bp setbillboard lobby1 north`,
|
|
`/bp setbillboard lobby1 south`). `/bp delbillboard <arena> [name]` removes one. `/bp billboards
|
|
<arena>` lists an arena's billboards and whether each is enabled.
|
|
- Per-billboard toggle: stored per name in the arena's yml (`billboards.<name>.enabled`, default
|
|
`true`) — set to `false` to keep the location saved but temporarily stop displaying it, without
|
|
running `delbillboard`.
|
|
- Global toggle: `ui.billboard` in `config.yml` (default `true`) disables every billboard on
|
|
every arena at once, e.g. for performance-constrained servers.
|
|
|
|
Every billboard shows the same current target color in sync — they're just multiple vantage
|
|
points on the same information. Entities are non-persistent (not saved to the world, so they
|
|
never survive a server restart on their own — BlockParty re-creates them on demand) and are all
|
|
despawned automatically when the arena is disabled, deleted, or the plugin shuts down, so
|
|
nothing leaks.
|
|
|
|
## Player usage
|
|
|
|
- `/bp join <arena>` to enter the waiting lobby. Your inventory, armor, offhand, health,
|
|
food, XP, game mode, flight state, location and potion effects are all captured and will
|
|
be restored exactly when you leave, get eliminated-and-the-match-ends, or the match ends.
|
|
- `/bp leave` at any time to exit and be restored.
|
|
- During a round, a title/subtitle/action bar/sound announce the target color, and the
|
|
target block is placed in your hotbar (slot 9) as a visual reminder. A boss bar (if
|
|
enabled) shows the remaining time as a shrinking progress bar.
|
|
- Get eliminated by not standing on the target color when the floor clears, or by falling
|
|
off entirely. Eliminated players are teleported to the configured spectator location and
|
|
marked as spectators for gameplay purposes (no damage, can't interfere), but stay in
|
|
adventure mode rather than true spectator mode — no noclip/flight, just watching from where
|
|
they landed.
|
|
- Last player standing wins; rewards, stats and celebration effects (fireworks, titles,
|
|
sounds) fire automatically.
|
|
- **Solo play**: if a match starts with only one player (e.g. `min-players: 1` for testing),
|
|
"one player remaining" is the expected steady state, not a win — the match keeps running
|
|
rounds, each shorter than the last, until that player is actually eliminated or leaves.
|
|
With two or more players, the match ends the moment only one remains.
|
|
|
|
## Configuration overview
|
|
|
|
- **`config.yml`** — global settings: `ui.*` toggles for scoreboard/bossbar/titles/actionbar,
|
|
`sounds.*` (Bukkit `Sound` enum names, played directly to each participating player rather
|
|
than at one world location, so distance never mutes them; invalid names are logged and
|
|
skipped, never crash the plugin), `performance.floor-blocks-per-tick` (reserved for
|
|
future/optional batched floor operations — round-end removal and each round's floor reset
|
|
are always instantaneous, all blocks at once, by design),
|
|
`defaults.*` (used only when `/bp create` seeds a new arena), `default-floor-materials`
|
|
(the full 16-color concrete palette by default), `floor.shuffle-each-round`, `music.*`
|
|
(see "Music" below), `rewards.winner.commands`, and `integrations.placeholderapi`.
|
|
- **`messages.yml`** — every user-facing string, in [MiniMessage](https://docs.advntr.dev/minimessage/format.html)
|
|
format (Adventure, bundled with Paper). Placeholders like `%player%`, `%arena%`, `%round%`,
|
|
`%time%`, `%color%` are substituted per-message.
|
|
- **`arenas/<name>.yml`** — one file per arena; a broken file only disables that one arena
|
|
(the rest still load) and the error is logged.
|
|
|
|
### Music
|
|
|
|
Optional background music for the duration of a match, off by default:
|
|
|
|
```yaml
|
|
music:
|
|
enabled: true
|
|
track: MUSIC_DISC_PIGSTEP
|
|
volume: 1.0
|
|
```
|
|
|
|
`track` is any Bukkit `Sound` enum name that's actually a track (one of the `MUSIC_DISC_*`
|
|
sounds, or a `MUSIC_*` ambient track) — invalid names are logged and skipped, never crash the
|
|
plugin. It plays on the client's dedicated **Music** volume slider (via `SoundCategory.MUSIC`),
|
|
separately from sound effects, starting when a match begins. It's paused the instant the floor
|
|
wipes each round and resumed once the floor has fully reset for the next round. Note this is a
|
|
stop/replay-from-the-start rather than a true mid-track pause/resume — vanilla Minecraft's sound
|
|
API has no seek/resume-from-position, so each "resume" restarts the track from the beginning.
|
|
It's stopped entirely at match end, arena disable, or plugin shutdown.
|
|
|
|
### Jukebox (web-based track voting)
|
|
|
|
Optional: let players vote for an arena's music track from a web page, live, instead of (or as
|
|
a live override to) the fixed `music.track`. **Off by default** — enabling it opens a real HTTP
|
|
port on your server. Config:
|
|
|
|
```yaml
|
|
jukebox:
|
|
enabled: true
|
|
bind-address: 127.0.0.1
|
|
port: 8642
|
|
allowed-origin: "*"
|
|
vote-code-minutes: 10
|
|
tracks:
|
|
- "MUSIC_DISC_PIGSTEP|Pigstep"
|
|
- "MUSIC_DISC_CAT|Cat"
|
|
# ... id|label pairs; "id" must be a valid Bukkit Sound enum name
|
|
```
|
|
|
|
**How it works:**
|
|
|
|
1. A player runs `/blockparty musicvote` in-game and gets a short numeric code (valid for
|
|
`vote-code-minutes`). This is the only way to get a code — it proves you're a real player on
|
|
the server, without needing any separate account/password system on the web page.
|
|
2. On the voting page, they enter the code plus their arena and pick a track. Each vote is a
|
|
simple HTTP `POST /api/vote?arena=<arena>&code=<code>&track=<id>` — the plugin resolves the
|
|
code back to their player UUID, so re-voting just changes their pick rather than adding a
|
|
duplicate.
|
|
3. The plugin re-checks the current vote leader every time music resumes after a floor-wipe
|
|
pause, so the crowd can change the track between rounds within a match, not just before it
|
|
starts. Votes for an arena are cleared when it returns to `WAITING`.
|
|
4. `GET /api/tracks` lists the configured, valid tracks; `GET /api/arenas` lists each arena's
|
|
state, current track, and live vote tally, for the page to render.
|
|
|
|
**Security model and requirements — read before enabling:**
|
|
|
|
- This is a **plain, unauthenticated-by-default HTTP API** on your server. `bind-address:
|
|
127.0.0.1` (the default) means it's only reachable from the same machine — you must put a
|
|
reverse proxy (Caddy, nginx, Cloudflare Tunnel, etc.) with a real TLS certificate in front of
|
|
it to expose it to the internet safely. Do **not** set `bind-address` to `0.0.0.0` and forward
|
|
the raw port directly without TLS in front of it.
|
|
- A web voting page is normally served over HTTPS (e.g. a published page); browsers block an
|
|
HTTPS page from calling a plain `http://` address ("mixed content"), so the API being behind
|
|
HTTPS isn't optional if you want a hosted page to reach it — it's required.
|
|
- The only "auth" is the short-lived numeric code from `/blockparty musicvote`; anyone who
|
|
obtains a valid code (e.g. a player shares it) can vote as that player until it expires.
|
|
There's no rate-limiting on the HTTP endpoints — treat this as a fun, low-stakes feature, not
|
|
a hardened public API.
|
|
- `allowed-origin` sets the `Access-Control-Allow-Origin` response header; tighten it from `*`
|
|
to your actual voting page's origin if you want to restrict which sites can call it.
|
|
- Changes to any `jukebox.*` setting require a full server restart to take effect — the HTTP
|
|
server isn't restarted by `/blockparty reload`.
|
|
|
|
### Reload behavior
|
|
|
|
`/blockparty reload` re-reads `config.yml`, `messages.yml`, and every arena file. Arenas
|
|
currently in the `RUNNING` or `ENDING` state are **skipped** during arena reload (their
|
|
config keeps running unmodified) to avoid corrupting an active match; a warning is logged
|
|
per skipped arena. Arenas that are `WAITING`, `STARTING` or `DISABLED` are reloaded fully
|
|
from disk. This is a deliberate design choice: rather than queuing a reload for when the
|
|
arena goes idle, in-progress matches simply finish on their old configuration and pick up
|
|
the new configuration the next time they return to `WAITING`.
|
|
|
|
## Rewards
|
|
|
|
```yaml
|
|
rewards:
|
|
winner:
|
|
money: 100
|
|
commands:
|
|
- "give %player% diamond 3"
|
|
- "say %player% won BlockParty in %arena% after %round% rounds!"
|
|
|
|
integrations:
|
|
vault: true
|
|
```
|
|
|
|
Two independent reward mechanisms fire on a win, both optional:
|
|
|
|
- **`money`** is deposited straight into the winner's balance through the
|
|
[Vault](https://www.spigotmc.org/resources/vault.34315/) Economy API — no console command
|
|
string parsing involved. This requires Vault **and** an economy plugin that registers a
|
|
Vault `Economy` provider (e.g. EssentialsX, CMI). If Vault or an economy provider isn't
|
|
present, BlockParty logs one warning and simply skips the money portion — it never crashes
|
|
and console-command rewards still run normally. Set `money: 0` to disable this entirely, or
|
|
`integrations.vault: false` to force-disable it even with Vault installed.
|
|
- **`commands`** run as the console with `%player%`, `%arena%` and `%round%` substituted, for
|
|
anything beyond currency (items, permissions, external plugin hooks, announcements, etc.).
|
|
|
|
Vault is declared as a `softdepend` in `plugin.yml`, so BlockParty starts up fine whether or
|
|
not it's installed; the Economy service lookup happens fresh each time a reward is dispatched
|
|
(not cached at startup), so plugin load order relative to your economy plugin doesn't matter.
|
|
|
|
## Statistics storage
|
|
|
|
By default player statistics are stored in a local SQLite file (`plugins/BlockParty/blockparty.db`) —
|
|
zero setup required. To use a shared MySQL database instead (useful across a network of servers),
|
|
set `storage.type: mysql` in `config.yml`:
|
|
|
|
```yaml
|
|
storage:
|
|
type: mysql
|
|
mysql:
|
|
host: localhost
|
|
port: 3306
|
|
database: blockparty
|
|
username: blockparty
|
|
password: password
|
|
table-prefix: "bp_"
|
|
connection-parameters: "sslMode=PREFERRED&verifyServerCertificate=false"
|
|
```
|
|
|
|
`connection-parameters` is appended verbatim to the JDBC URL, so TLS behavior is
|
|
controlled with real MySQL Connector/J flags rather than a single `use-ssl` toggle.
|
|
Common values:
|
|
|
|
- `sslMode=DISABLED` — no TLS.
|
|
- `sslMode=PREFERRED&verifyServerCertificate=false` (default) — TLS if the server offers
|
|
it, without validating the certificate; good for a private/internal database host.
|
|
- `sslMode=REQUIRED&verifyServerCertificate=false` — TLS mandatory, certificate not validated.
|
|
- `sslMode=VERIFY_IDENTITY` — TLS mandatory and the server certificate is validated
|
|
against a trust store (production-grade; requires a properly signed/trusted cert).
|
|
|
|
MySQL and MariaDB are both supported through the same `mysql` driver/config. If a stats
|
|
query ever fails (bad credentials, dropped connection, etc.), BlockParty logs the full error
|
|
once for diagnosis and then throttles repeats of that same failure to once every 5 minutes —
|
|
it never spams a stack trace on every round, and gameplay is unaffected either way since all
|
|
stats I/O is async and best-effort.
|
|
|
|
The MySQL JDBC driver (`mysql-connector-j`) is bundled in the plugin jar, so no extra
|
|
download is required. The stats table is created automatically on startup if it doesn't
|
|
exist. All queries run asynchronously off the main thread regardless of storage type.
|
|
Changing `storage.type` requires a server restart (it's read once at startup, not on
|
|
`/blockparty reload`).
|
|
|
|
## PlaceholderAPI support
|
|
|
|
If PlaceholderAPI is installed, BlockParty registers a `%blockparty_...%` expansion
|
|
automatically (soft-dependency — startup never fails if PAPI is absent):
|
|
|
|
- `%blockparty_wins%`
|
|
- `%blockparty_games_played%`
|
|
- `%blockparty_best_round%`
|
|
- `%blockparty_arena%`
|
|
- `%blockparty_players_<arena>%`
|
|
- `%blockparty_state_<arena>%`
|
|
|
|
## Building from source
|
|
|
|
```bash
|
|
./gradlew clean build
|
|
```
|
|
|
|
The shaded plugin jar (with `sqlite-jdbc` and `mysql-connector-j` bundled) is produced at
|
|
`build/libs/BlockParty-<version>.jar`.
|
|
|
|
Run unit tests only:
|
|
|
|
```bash
|
|
./gradlew test
|
|
```
|
|
|
|
Unit tests cover the pure-logic package (`us.tss3.blockparty.logic`) with no Bukkit
|
|
dependency: arena state transitions, countdown start/cancel logic, round-time
|
|
progression/clamping math, target-color selection (deterministic via an injectable RNG),
|
|
elimination/winner determination, arena config validation, and player-count/join logic.
|
|
|
|
## Troubleshooting
|
|
|
|
- **"Arena failed validation" on enable** — run `/bp info <arena>` to see exactly which
|
|
fields are missing (lobby/spawn/spectator/region/materials/etc).
|
|
- **World unloaded warning** — if an arena's world is unloaded while the plugin is running,
|
|
BlockParty detects it, safely disables that arena and restores any players inside it.
|
|
- **Invalid sound/material in config** — logged as a warning at startup/use and skipped;
|
|
it will not crash the plugin.
|
|
- **Nothing happens on `/bp join`** — check the arena is enabled (`/bp info <arena>`), not
|
|
full, and that you are not already in another arena (`/bp leave` first).
|
|
- **Floor doesn't look right** — remember `setfloor` only changes the allowed palette; you
|
|
must run `/bp generate <arena>` to actually (re)generate and persist a layout.
|
|
|
|
## Architecture
|
|
|
|
- `logic/` — pure, Bukkit-free game logic (unit tested).
|
|
- `arena/` — `Arena` (per-arena runtime state machine + round loop) and `ArenaManager`.
|
|
- `config/` — `ArenaConfig`, `ArenaConfigLoader`, `ConfigManager`, `MessagesManager`.
|
|
- `floor/` — `FloorManager`, tick-batched floor generation/removal/restoration.
|
|
- `session/` — `PlayerSession`, `SessionManager` (single-arena-membership enforcement).
|
|
- `model/` — `PlayerState` snapshot/restore.
|
|
- `command/` — `BlockPartyCommand` (single entry point, dispatches to handlers).
|
|
- `listener/` — connection/quit handling and gameplay restrictions.
|
|
- `scoreboard/` — interval-based sidebar scoreboard.
|
|
- `reward/` — console-command reward dispatch.
|
|
- `persistence/` — async stats storage over SQLite or MySQL (`StatsDatabase`, `StatsManager`).
|
|
- `placeholder/` — optional PlaceholderAPI expansion, loaded only when PAPI is present.
|
|
- `util/` — sound/location helpers.
|