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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: CI

on:
push:
branches: [master]
pull_request:

jobs:
Expand Down
218 changes: 218 additions & 0 deletions .github/workflows/server-boot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: Server Boot Smoke

on:
push:
branches: [master]
pull_request:

# Boots the built EzAuction plugin on real Paper, Folia, and Spigot servers and fails the
# job if the server log shows boot errors or the plugin never reaches its enabled state.
#
# This complements (does not replace) the MockBukkit `mvn verify` (ci.yml). The FoliaLib-style
# scheduler shim in `compat/Scheduler.java` is what lets the plugin actually boot on Folia's
# regionised threads. Spigot is built from source via BuildTools (no prebuilt jar is published).

jobs:
server-boot:
name: Boot EzAuction on ${{ matrix.server.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
server:
- { name: paper, kind: paper, version: "1.21.1" }
- { name: folia, kind: folia, version: "1.21.11" }
- { name: spigot, kind: spigot, version: "1.21.1" }

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: maven

- name: Build EzAuction
run: mvn -B -q package -DskipTests

- name: Download Paper server jar
if: matrix.server.kind == 'paper'
env:
USER_AGENT: EzAuction-CI/1.0 (https://github.com/skyblockexp/EzAuction)
run: |
VERSION="${{ matrix.server.version }}"
# PaperMC retired the v2 downloads API; Fill v3 requires a descriptive User-Agent
# header and returns the download URL inline (key: server:default). We parse with
# python3 (always present on ubuntu-latest) to avoid a jq dependency.
API="https://fill.papermc.io/v3/projects/paper/versions/${VERSION}/builds"
URL=$(curl -fsSL -H "User-Agent: ${USER_AGENT}" "$API" | python3 -c '
import sys, json
data = json.load(sys.stdin)
stable = [b for b in data if b.get("channel") == "STABLE"]
pool = stable if stable else data
newest = max(pool, key=lambda b: b["id"])
print(newest["downloads"]["server:default"]["url"])
')
echo "Resolved Paper download: $URL"
curl -fsSL -H "User-Agent: ${USER_AGENT}" "$URL" -o paper.jar
test -s paper.jar || { echo "paper.jar is empty" >&2; exit 1; }

- name: Download Folia server jar
if: matrix.server.kind == 'folia'
env:
USER_AGENT: EzAuction-CI/1.0 (https://github.com/skyblockexp/EzAuction)
run: |
VERSION="${{ matrix.server.version }}"
# Folia publishes prebuilt jars via the same Fill v3 service as Paper. Building from
# source is impractical in CI (heavy Gradle build + version-specific task config), so
# we download the newest build for the requested Minecraft version.
API="https://fill.papermc.io/v3/projects/folia/versions/${VERSION}/builds"
URL=$(curl -fsSL -H "User-Agent: ${USER_AGENT}" "$API" | python3 -c '
import sys, json
data = json.load(sys.stdin)
newest = max(data, key=lambda b: b["id"])
print(newest["downloads"]["server:default"]["url"])
')
echo "Resolved Folia download: $URL"
curl -fsSL -H "User-Agent: ${USER_AGENT}" "$URL" -o folia.jar
test -s folia.jar || { echo "folia.jar is empty" >&2; exit 1; }

- name: Build Spigot server jar
if: matrix.server.kind == 'spigot'
id: spigot
timeout-minutes: 90
env:
GIT_AUTHOR_NAME: ci
GIT_AUTHOR_EMAIL: ci@ezauction.local
GIT_COMMITTER_NAME: ci
GIT_COMMITTER_EMAIL: ci@ezauction.local
run: |
VERSION="${{ matrix.server.version }}"
# Spigot publishes no prebuilt jar, so build it from source with BuildTools.
curl -fsSL -o BuildTools.jar "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar"
java -jar BuildTools.jar --rev "$VERSION" --remapped
SPIGOT_JAR=$(ls spigot-*.jar | head -1)
if [ -z "$SPIGOT_JAR" ]; then
echo "Spigot jar not produced by BuildTools" >&2
exit 1
fi
cp "$SPIGOT_JAR" spigot.jar
echo "Built $SPIGOT_JAR"

- name: Prepare server directory
run: |
mkdir -p servers/${{ matrix.server.name }}/plugins
EZ_JAR=$(ls target/EzAuction-*.jar | head -1)
if [ "${{ matrix.server.kind }}" = "folia" ]; then
# Folia refuses to load plugins that do not declare folia support. Inject the flag
# into a copy of the built jar's plugin.yml so the boot actually exercises the plugin.
FOLIA_EZ="servers/${{ matrix.server.name }}/plugins/$(basename "$EZ_JAR")"
python3 - "$EZ_JAR" "$FOLIA_EZ" <<'PY'
import sys, zipfile, io
src, dst = sys.argv[1], sys.argv[2]
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename == "plugin.yml":
text = data.decode("utf-8")
if "folia-supported:" not in text:
text = text.replace("api-version: 1.13\n",
"api-version: 1.13\nfolia-supported: true\n")
data = text.encode("utf-8")
zout.writestr(item, data)
PY
echo "Injected folia-supported: true into $(basename "$FOLIA_EZ")"
else
cp "$EZ_JAR" "servers/${{ matrix.server.name }}/plugins/"
fi
cp plugins/Vault.jar "servers/${{ matrix.server.name }}/plugins/"
cp plugins/ezeconomy-bukkit-3.0.4.jar "servers/${{ matrix.server.name }}/plugins/"
case "${{ matrix.server.kind }}" in
paper) cp paper.jar "servers/${{ matrix.server.name }}/${{ matrix.server.name }}.jar" ;;
folia) cp folia.jar "servers/${{ matrix.server.name }}/${{ matrix.server.name }}.jar" ;;
spigot) cp spigot.jar "servers/${{ matrix.server.name }}/${{ matrix.server.name }}.jar" ;;
esac

- name: Accept EULA
run: echo "eula=true" > "servers/${{ matrix.server.name }}/eula.txt"

- name: Boot server and assert clean enable
working-directory: servers/${{ matrix.server.name }}
run: |
set -euo pipefail
SERVER_JAR="${{ matrix.server.name }}.jar"
LOG="logs/latest.log"
mkdir -p logs
# Boot headless; auto-stop once the server reports "Done".
timeout 300 java -jar "$SERVER_JAR" nogui > "$LOG" 2>&1 &
PID=$!
echo "Server PID: $PID"
# Wait for the "Done" line (server finished loading) or the process to exit.
for i in $(seq 1 600); do
if grep -q "Done (" "$LOG" 2>/dev/null; then
echo "Server reported Done."
break
fi
if ! kill -0 "$PID" 2>/dev/null; then
echo "Server process exited before Done." >&2
break
fi
sleep 1
done
# Give the plugin a moment to finish onEnable after "Done".
sleep 5
if kill -0 "$PID" 2>/dev/null; then
echo "stop" > /proc/$PID/fd/0 2>/dev/null || true
sleep 5
kill "$PID" 2>/dev/null || true
fi
wait "$PID" 2>/dev/null || true

echo "=================== BOOT LOG (EzAuction/Vault/EzEconomy context) ==================="
grep -nE "EzAuction|Vault|EzEconomy|Bootstrap|SEVERE|ERROR" "$LOG" | head -80 || true
echo "=============================================================================="
echo "=================== FULL BOOT LOG ==================="
cat "$LOG" || true
echo "======================================================="

# --- Assertions ---
FAIL=0
if grep -q "EzAuction plugin enabled." "$LOG"; then
echo "PASS: plugin reached enabled state."
else
echo "FAIL: 'EzAuction plugin enabled.' not found in log."
FAIL=1
fi

# Forbidden patterns that indicate a broken boot.
for pattern in "SEVERE" "\[Bootstrap\] FATAL" "IllegalStateException" "async thread access" "Exception in thread"; do
if grep -qF "$pattern" "$LOG"; then
echo "FAIL: forbidden log pattern found: $pattern"
FAIL=1
fi
done

# Folia-specific region scheduler failures.
if [ "${{ matrix.server.kind }}" = "folia" ]; then
if grep -qE "Exception|Error" "$LOG" && grep -qiE "RegionizedServer|region scheduler" "$LOG"; then
echo "FAIL: Folia region scheduler error detected."
FAIL=1
fi
fi

if [ "$FAIL" -ne 0 ]; then
echo "Boot smoke test FAILED for ${{ matrix.server.name }}."
exit 1
fi
echo "Boot smoke test PASSED for ${{ matrix.server.name }}."

- name: Upload server log
if: always()
uses: actions/upload-artifact@v4
with:
name: server-boot-${{ matrix.server.name }}-log
path: servers/${{ matrix.server.name }}/logs/latest.log
if-no-files-found: warn
46 changes: 0 additions & 46 deletions .github/workflows/spigot-smoke.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ out/

# VS Code
.vscode/
.kilo/

# OS files
.DS_Store
Expand Down
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# EzAuction — Agent Guidelines
# EzAuction - Agent Guidelines

A Paper Minecraft plugin providing a fully-featured auction house with GUI menus, live auctions, buy orders, Discord webhooks, hologram displays, and MySQL or YAML storage backends.

Expand All @@ -11,7 +11,7 @@ A Paper Minecraft plugin providing a fully-featured auction house with GUI menus
| Commands | `command/` | `/auction`, `/order`, `/liveauction`, `/auctiondiscord`, etc. |
| GUI | `gui/` | Inventory-based menus (browser, sell, history, orders) |
| Services | `service/`, `claim/`, `live/`, `history/` | Business logic, no Bukkit I/O |
| Storage | `storage/` — `yaml/`, `mysql/` | Persistence behind `AuctionStorage` interface |
| Storage | `storage/` - `yaml/`, `mysql/` | Persistence behind `AuctionStorage` interface |
| Events | `event/` | Bukkit custom events for external plugin hooks |
| Integration | `integration/` | Optional Discord webhook notifier |
| Compat | `compat/` | HologramPlatform abstraction, PDC/Lore item tag strategies |
Expand All @@ -21,7 +21,7 @@ See [docs/api.md](docs/api.md), [docs/events.md](docs/events.md), [docs/permissi

## Configuration

All user-facing settings live in resource YML files — never hardcode values.
All user-facing settings live in resource YML files - never hardcode values.

| File | Purpose |
|------|---------|
Expand All @@ -44,17 +44,17 @@ Messages live in `src/main/resources/messages/`, split into four per-language se
| `menu-interactions_*.yml` | Chat prompts and input instructions |
| `menu-layout_*.yml` | Slot assignments and layout metadata |

**Supported languages:** `en` (default), `es`, `nl`, `zh` — set via `language:` in `auction.yml`.
**Supported languages:** `en` (default), `es`, `nl`, `zh` - set via `language:` in `auction.yml`.

Rules:
- Every new player-visible string **must** be added to all four language files.
- Use MiniMessage / Kyori Adventure components — no legacy `§` colour codes.
- Use MiniMessage / Kyori Adventure components - no legacy `§` colour codes.
- Keys must match exactly across all language files.
- Never concatenate message fragments; use placeholders within a single key.

## Code Conventions

- Java 21; target Paper 1.21+ — use Paper/Adventure APIs, not Bukkit-legacy equivalents.
- Java 21; target Paper 1.21+ - use Paper/Adventure APIs, not Bukkit-legacy equivalents.
- One responsibility per class. Split large classes; do not exceed ~300 lines without good reason.
- Config objects are **immutable records or final-field POJOs** populated at load time by `AuctionConfigurationLoader`.
- Feature entry points belong in dedicated packages (`live/`, `history/`, `integration/`, …). Do not add new feature logic to `EzAuctionPlugin` or `AuctionManager` directly.
Expand All @@ -79,8 +79,8 @@ mvn package -DskipTests # build JAR without tests
## Documentation

- Keep `docs/` in sync with every change to config keys, events, permissions, or the API.
- `docs/configuration.md` — updated when any YML key is added, removed, or changed.
- `docs/events.md` — updated when a new custom event is added.
- `docs/permissions.md` — updated when a permission node changes.
- `docs/api.md` — updated when the public API changes.
- `docs/configuration.md` - updated when any YML key is added, removed, or changed.
- `docs/events.md` - updated when a new custom event is added.
- `docs/permissions.md` - updated when a permission node changes.
- `docs/api.md` - updated when the public API changes.
- Changelog entries go in `README.md` under the relevant version heading.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@ All notable changes to EzAuction are documented here.

Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Release tags use the `v` prefix (e.g. `v2.2.0`).

## [2.3.2] - 2026-06-29
## [2.3.2] - 2026-08-13

### Fixed

- **Hologram placement & removal** — rewrote the TextDisplay hologram platform to use the typed Paper API directly. Previously a `NamespacedKey(JavaPlugin, String)` lookup threw and was swallowed, so the PDC marker was never written and holograms could not be detected or removed. Holograms are now correctly tagged, re-attached after restarts, and persistent.
- **Hologram removal flow** — `/auctionhologram` now supports `list [radius] [--untagged]`, `clear [radius] [--untagged]` (alias `remove`), and `clearall [radius] [--untagged]`. Tagged `clear` removes the nearest EzAuction hologram immediately; `clearall` and any `--untagged` clear use two-step confirmation. Search uses the player's eye location and the new `removal-radius` config (default 10 blocks) instead of the placement-only `search-radius`.
- **Safer `clear --untagged`** — removing untagged displays (which also match other plugins' TextDisplays) now requires a second run within 15 seconds. The first run reports how many untagged displays are in range; the second removes only the nearest one, so a stray bulk delete of another plugin's holograms is avoided.
- **Placement confirmation shows coordinates** — the success message for `/auctionhologram <type>` now reports the exact spawn coordinates and world (e.g. `Placed … hologram at 12,66,-4 (world)`), matching where the hologram actually landed (the targeted block plus the configured `height-offset`).
- **Existing orphans stay orphaned** — holograms placed before this fix carry no marker; reach them with `--untagged` (also matches other plugins' TextDisplays). Run `list --untagged` before `clear --untagged`.
- **Hardened** — spherical distance filtering for removal, removed a latent `ConcurrentModificationException` in the update loop, and made `max-holograms` enforcement live with a distinct denial message.
- **Buy Orders GUI inventory theft vulnerability** - fixed a critical security issue where players could steal items from the Buy Orders overview. Both the `/orders` command and the Buy Orders tab in `/ah` were unprotected, allowing players to drag items out of the GUI into their inventory, or place items into the GUI (which would be deleted on close). All item interactions in buy order displays are now cancelled, and items are cloned so the backing order data is never directly accessible.

### Changed

- **Folia-safe scheduler shim** — all 23 direct scheduler calls now route through `compat/Scheduler`, which sends sync/delayed/repeating tasks to Folia's global region scheduler (and async tasks to its async scheduler) via reflection, falling back to the regular Bukkit scheduler on Paper/Spigot. This lets the plugin boot on Folia without `IllegalStateException` async-thread-access crashes. FoliaLib was intentionally not added as a dependency.
- **Server boot smoke workflow** — added `.github/workflows/server-boot.yml` which boots the built plugin on real Paper, Folia, and Spigot servers, failing the job on `SEVERE`/`FATAL`/async-access errors or if the plugin never reaches its enabled state. The real `Vault` and `EzEconomy` jars (committed under `plugins/`) are dropped into each server's `plugins/` folder so the full enable chain is exercised; for Folia the workflow injects `folia-supported: true` into a copy of the plugin jar so Folia actually loads it. Paper and Folia are downloaded from PaperMC's Fill API; Spigot is built from source via BuildTools (no prebuilt jar is published). The old in-process `spigot-smoke.yml` YAML round-trip workflow was removed in favour of this real-server boot.

## [2.3.1] - 2026-05-27

### Fixed
Expand Down
Loading
Loading