309 lines
16 KiB
Markdown
309 lines
16 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.
|
|
|
|
### 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>` | (Optional) place a display block here that mirrors the current target color |
|
|
| `/blockparty delbillboard <arena>` | Remove the billboard, if one is set |
|
|
| `/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 |
|
|
|
|
## 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`
|
|
to place a big display block that mirrors the round's target color — purely cosmetic and
|
|
entirely optional (see "Billboard" 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.
|
|
|
|
### Billboard
|
|
|
|
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. It is
|
|
entirely optional:
|
|
|
|
- Per-arena: only spawned if `/bp setbillboard <arena>` has been run (stored as `billboard`
|
|
in the arena's yml). `/bp delbillboard <arena>` removes it.
|
|
- Per-arena toggle: `billboard-enabled` in the arena's yml (default `true`) lets you keep the
|
|
location set but temporarily turn it off without deleting it.
|
|
- Global toggle: `ui.billboard` in `config.yml` (default `true`) disables billboards across
|
|
every arena at once, e.g. for performance-constrained servers.
|
|
|
|
The entity is non-persistent (not saved to the world, so it never survives a server restart on
|
|
its own — BlockParty re-creates it on demand) and is despawned automatically when the arena is
|
|
disabled, deleted, or the plugin shuts down, so it never leaks entities.
|
|
|
|
## 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 become spectators at the configured spectator location.
|
|
- 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), `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.
|
|
|
|
### 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.
|