Compare commits
4 Commits
f19b1fc8cb
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| d6efbf3715 | |||
| 5de22ec0ea | |||
| 37f5e19fbd | |||
| e0071ee713 |
@@ -18,8 +18,12 @@ MODRINTH_TOKEN=
|
||||
MODRINTH_PROJECT_ID=metropolis-builder
|
||||
# Optional Markdown override; empty generates notes from commits since the last tag.
|
||||
MODRINTH_CHANGELOG=
|
||||
MODRINTH_GAME_VERSIONS=26.2
|
||||
MODRINTH_GAME_VERSIONS=26.2,26.3
|
||||
MODRINTH_LOADERS=paper
|
||||
MODRINTH_VERSION_TYPE=
|
||||
MODRINTH_DEPENDENCIES=
|
||||
MODRINTH_FILE=
|
||||
# Sync project body/README to Modrinth during publish (true/false)
|
||||
MODRINTH_SYNC_BODY=true
|
||||
MODRINTH_DESCRIPTION_FILE=docs/modrinth-description.md
|
||||
|
||||
|
||||
23
.github/workflows/gradle.yml
vendored
23
.github/workflows/gradle.yml
vendored
@@ -2,9 +2,10 @@ name: Java CI with Gradle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "develop" ]
|
||||
branches: [ "main", "develop" ]
|
||||
tags: [ "v*" ]
|
||||
pull_request:
|
||||
branches: [ "develop" ]
|
||||
branches: [ "main", "develop" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -14,6 +15,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@v6
|
||||
@@ -25,7 +28,21 @@ jobs:
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
gradle-version: '9.8.0'
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/main' }}
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: ./gradlew test
|
||||
|
||||
- name: Upload Test Report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: Metropolis-test-report
|
||||
path: |
|
||||
build/reports/tests/test
|
||||
build/test-results/test
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
|
||||
- name: Build with Gradle Wrapper
|
||||
run: ./tools/build-plugin.sh
|
||||
|
||||
168
.github/workflows/release.yml
vendored
Normal file
168
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git tag to release (e.g., v1.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
name: Validate Tag & Prepare Release Branch
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag_commit: ${{ steps.validate.outputs.tag_commit }}
|
||||
tag_name: ${{ steps.validate.outputs.tag_name }}
|
||||
release_branch: ${{ steps.validate.outputs.release_branch }}
|
||||
version_name: ${{ steps.validate.outputs.version_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure Git identity
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Validate Tag, Develop, Main, and Create Release Branch
|
||||
id: validate
|
||||
run: |
|
||||
TAG="${{ github.event.inputs.tag }}"
|
||||
echo "Target tag: $TAG"
|
||||
|
||||
# Fetch remote branches and tags
|
||||
git fetch origin develop:develop main:main --tags
|
||||
|
||||
# 1. Verify tag exists
|
||||
if ! git rev-parse -q --verify "refs/tags/$TAG" > /dev/null; then
|
||||
echo "::error::Tag '$TAG' does not exist in the repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG_COMMIT=$(git rev-list -n 1 "$TAG")
|
||||
echo "Tag '$TAG' points to commit: $TAG_COMMIT"
|
||||
|
||||
# 2. Verify tag commit is in develop branch
|
||||
if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/develop; then
|
||||
echo "::error::Tag '$TAG' (commit $TAG_COMMIT) is not in the develop branch."
|
||||
exit 1
|
||||
fi
|
||||
echo "Confirmed: Tag '$TAG' is in develop branch."
|
||||
|
||||
# 3. Verify tag commit is in main, or fast-forward merge it to main
|
||||
if git merge-base --is-ancestor "$TAG_COMMIT" origin/main; then
|
||||
echo "Confirmed: Tag '$TAG' is already in main branch."
|
||||
else
|
||||
echo "Tag '$TAG' is not in main. Attempting fast-forward only merge..."
|
||||
git checkout main
|
||||
if ! git merge --ff-only "$TAG_COMMIT"; then
|
||||
echo "::error::Tag '$TAG' cannot be fast-forward merged into main."
|
||||
exit 1
|
||||
fi
|
||||
git push origin main
|
||||
echo "Successfully fast-forward merged '$TAG' into main."
|
||||
fi
|
||||
|
||||
# 4. Create and push the release/v* branch from the tag commit
|
||||
RELEASE_BRANCH="release/${TAG}"
|
||||
echo "Creating release branch '$RELEASE_BRANCH' from commit $TAG_COMMIT..."
|
||||
git checkout -B "$RELEASE_BRANCH" "$TAG_COMMIT"
|
||||
git push -u origin "$RELEASE_BRANCH" --force
|
||||
|
||||
VERSION_NAME="${TAG#v}"
|
||||
|
||||
echo "tag_commit=$TAG_COMMIT" >> $GITHUB_OUTPUT
|
||||
echo "tag_name=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "release_branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT
|
||||
echo "version_name=$VERSION_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
metropolis-release:
|
||||
name: Build, Test & Publish Release
|
||||
needs: prepare-release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout release commit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare-release.outputs.tag_commit }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Ensure tag ref is checked out
|
||||
run: |
|
||||
git checkout -q "refs/tags/${{ needs.prepare-release.outputs.tag_name }}"
|
||||
|
||||
- name: Validate Modrinth Credentials
|
||||
id: check_credentials
|
||||
run: |
|
||||
if [ -z "${{ secrets.MODRINTH_TOKEN }}" ]; then
|
||||
echo "::warning::Modrinth release token was not provided. Release will not be published to Modrinth."
|
||||
echo "::error::Missing MODRINTH_TOKEN secret. Please configure MODRINTH_TOKEN in repository secrets."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
|
||||
- name: Setup Gradle 9.8.0
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
gradle-version: '9.8.0'
|
||||
|
||||
- name: Make Gradle wrapper executable
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: ./gradlew test
|
||||
|
||||
- name: Upload Test Report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Metropolis-test-report-${{ needs.prepare-release.outputs.tag_name }}
|
||||
path: |
|
||||
build/reports/tests/test
|
||||
build/test-results/test
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
|
||||
- name: Build Metropolis Plugin Jar
|
||||
run: ./tools/build-plugin.sh
|
||||
|
||||
- name: Upload Release Jar
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Metropolis-${{ needs.prepare-release.outputs.version_name }}
|
||||
path: build/libs/*.jar
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Publish to Modrinth & Update Description
|
||||
env:
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
MODRINTH_VERSION: ${{ needs.prepare-release.outputs.version_name }}
|
||||
TAG: ${{ needs.prepare-release.outputs.tag_name }}
|
||||
run: |
|
||||
chmod +x ./tools/publish-modrinth.sh
|
||||
./tools/publish-modrinth.sh
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.prepare-release.outputs.tag_name }}
|
||||
name: Metropolis ${{ needs.prepare-release.outputs.tag_name }}
|
||||
files: build/libs/*.jar
|
||||
generate_release_notes: true
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,5 @@ bin/
|
||||
/backups/
|
||||
/server/
|
||||
.env
|
||||
.settings/
|
||||
|
||||
|
||||
35
AGENTS.md
Normal file
35
AGENTS.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Metropolis Working Rules
|
||||
|
||||
Start with [CONTRIBUTING.md](CONTRIBUTING.md) for the first-contribution path,
|
||||
architecture, build/test commands, and operational safety. [README.md](README.md)
|
||||
is the player/server-admin behavior reference. Other files in `docs/` are
|
||||
optional reference material, not competing onboarding guides.
|
||||
|
||||
- Use **Metropolis** in human-facing prose. Preserve machine-facing identifiers,
|
||||
including `Metropolis`, `com.majinnaibu.bukkitplugins.metropolis`, artifact
|
||||
names, plugin IDs, commands, permission nodes, and URLs.
|
||||
- Inspect the current diff before editing and preserve unrelated work. Do not
|
||||
edit generated `build/`, `target/`, or `.gradle/` output. Never read, expose,
|
||||
or commit `.env`, server/player data, or publishing credentials.
|
||||
- Keep claims aligned with source, build configuration, CI, and scripts. State
|
||||
Paper targets separately from versions actually built or tested. Keep
|
||||
implemented behavior, planned work, automated coverage, and manual scenarios
|
||||
distinct.
|
||||
- When editing Java, preserve the existing AGPL header, use Gradle Spotless,
|
||||
and keep Bukkit/Paper state and API access on the main thread. Avoid unrelated
|
||||
gameplay changes.
|
||||
- Before editing `docs/project-description.json`, read
|
||||
`docs/projects.schema.json`. Validate the JSON against that schema without
|
||||
changing the schema to accommodate invalid metadata. Preserve exactly the
|
||||
existing top-level project key, `id`, `title`, `docId`, `image.url`, and
|
||||
`headerImage.url` when present. Preserve `headerImage: null` or its absence.
|
||||
These are fixed consuming-project integration values: do not derive or
|
||||
“correct” their spelling, capitalization, spacing, or slug from repository
|
||||
names. Image paths are relative to the consuming project's root; do not
|
||||
resolve them here, convert them to repository URLs, or replace them with
|
||||
generated/remote URLs. Preserve all other integration fields unless the task
|
||||
requires a change. If schema compliance conflicts with preserving protected
|
||||
values, stop and explain the conflict before editing.
|
||||
- Do not start/stop servers, deploy, restore, publish, tag, or release without
|
||||
explicit authorization. `tools/stop-server.sh` can match unrelated Paper
|
||||
processes; `tools/restore-server.sh` replaces archive top-level items.
|
||||
@@ -1,18 +1,57 @@
|
||||
# Contributing
|
||||
# Contributing to Metropolis
|
||||
|
||||
## Development Setup
|
||||
This is the single onboarding entry point for new contributors. Shared rules
|
||||
for people and coding agents are in [AGENTS.md](AGENTS.md); read them before
|
||||
editing, especially the scraper-metadata preservation and operational-safety
|
||||
rules.
|
||||
|
||||
Use Java 25 or newer. The Gradle wrapper downloads Gradle 9.8; dependencies are declared in `build.gradle` and resolved from Maven Central, PaperMC, and EngineHub.
|
||||
## First Contribution
|
||||
|
||||
```sh
|
||||
./gradlew build
|
||||
```
|
||||
1. Read [README.md](README.md) for supported server versions, installation,
|
||||
configuration, and commands.
|
||||
2. Trace the relevant implementation using the source map below and check
|
||||
nearby tests before changing behavior.
|
||||
3. Make a focused change. Add or update automated tests for behavior changes;
|
||||
update the manual pre-release checklist for behavior that needs a real
|
||||
server.
|
||||
4. Run `./gradlew test` and `./gradlew build`. Report any manual scenarios that
|
||||
were not run rather than describing them as passing.
|
||||
5. Update user-facing descriptions if setup, supported versions, commands,
|
||||
dependencies, or behavior changed.
|
||||
|
||||
The standard build runs JUnit 5 tests without starting Minecraft. Tests use Mockito for Bukkit and external-plugin boundaries. Keep domain and configuration behavior testable without a running server.
|
||||
## Architecture and Source Map
|
||||
|
||||
## Live Server Tests
|
||||
- `MetropolisPlugin` loads configuration, connects Bukkit to WorldGuard and
|
||||
WorldEdit, allocates plots, generates blocks, and manages regions.
|
||||
- `Plot`, `PlayerHome`, grid/layout/support classes, and home stores implement
|
||||
plot geometry, region ownership, allocation, and selected-home persistence.
|
||||
- `commands/` contains the command handlers; `eventlisteners/` contains join
|
||||
behavior.
|
||||
- `src/main/resources/plugin.yml` declares dependencies, commands, and command
|
||||
permissions. `config.yml` is the bundled configuration template.
|
||||
- `src/test/` contains isolated JUnit/Mockito tests. `src/integrationTest/`
|
||||
contains the opt-in tests that require a running server and RCON.
|
||||
- `build.gradle` defines dependencies, Java/Gradle versions, formatting, and
|
||||
test tasks. `.github/workflows/gradle.yml` runs tests and builds on changes
|
||||
to `main` and `develop`, and any `v*` tags.
|
||||
|
||||
Live tests are opt-in and run separately from `build`:
|
||||
## Prerequisites and Checks
|
||||
|
||||
- JDK 25. The Gradle wrapper uses Gradle 9.8 and can download the required
|
||||
Gradle distribution; dependencies resolve from Maven Central, PaperMC, and
|
||||
EngineHub.
|
||||
- `./gradlew test` runs server-free JUnit tests.
|
||||
- `./gradlew build` runs the normal build and verification lifecycle. The
|
||||
plugin jar is written under `build/libs/`.
|
||||
- CI compiles against Paper API 26.2, runs `test`, then builds. It does not
|
||||
start a Minecraft server or run `integrationTest`.
|
||||
|
||||
The opt-in `integrationTest` source set currently has two tests: checking that
|
||||
Metropolis appears in `/plugins`, and creating a coordinate reservation then
|
||||
checking WorldGuard's `regions.yml`. It mutates the configured server's
|
||||
WorldGuard data and needs a running Paper server with RCON, WorldGuard, and
|
||||
WorldEdit. Use a disposable server, bind RCON to a trusted interface, and use
|
||||
a temporary password:
|
||||
|
||||
```sh
|
||||
METROPOLIS_RCON_HOST=127.0.0.1 \
|
||||
@@ -22,12 +61,27 @@ METROPOLIS_SERVER_DIR=/path/to/server \
|
||||
./gradlew integrationTest
|
||||
```
|
||||
|
||||
Use a disposable or backed-up Paper server with compatible WorldGuard and WorldEdit plugin jars installed. Keep RCON local, use a temporary password, and restore the server after tests that modify regions or worlds.
|
||||
Set `METROPOLIS_WORLD` if the test world is not named `world`. These tests do
|
||||
not cover the broader in-game workflows in [human-tests.md](docs/human-tests.md).
|
||||
|
||||
## Pull Requests
|
||||
## Operational Safety
|
||||
|
||||
- Keep changes focused and include tests for behavior changes.
|
||||
- Run `./gradlew build` before submitting.
|
||||
- Run `integrationTest` only when changing behavior that requires Paper or the external plugins; do not add it to the default build.
|
||||
- Update `README.md` and project descriptions when user-facing setup, commands, or dependencies change.
|
||||
- Keep generated build output, server data, credentials, and local `.env` files out of commits.
|
||||
Use the build/test commands above for ordinary contribution work. The scripts
|
||||
under `tools/` also include server start/stop, deployment, backup/restore, and
|
||||
publishing operations; they are not required for a normal build. Read a
|
||||
script before use and get explicit authorization before running operational
|
||||
or release actions. In particular, `stop-server.sh` matches Paper jar
|
||||
processes by command line, and `restore-server.sh` replaces matching top-level
|
||||
server items from an archive. Never commit `.env`, credentials, server state,
|
||||
or generated build output.
|
||||
|
||||
## Optional References
|
||||
|
||||
- [README.md](README.md): player/server-admin usage reference.
|
||||
- [human-tests.md](docs/human-tests.md): manual pre-release checklist, not an
|
||||
automated test report.
|
||||
- [TODO.md](docs/TODO.md): historical backlog; the maintained plan is
|
||||
[modernization-plan.md](docs/modernization-plan.md).
|
||||
- [Modrinth description](docs/modrinth-description.md): hosting copy reference.
|
||||
- [Scraper metadata](docs/project-description.md) and
|
||||
[schema](docs/projects.schema.json): consuming-site project entry.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"id": "metropolis",
|
||||
"title": "Metropolis",
|
||||
"description": "A Paper 26.2/26.3 city-planning plugin that turns player onboarding into procedural urban growth with protected UUID-owned homes, roads, reservations, and configurable infrastructure.",
|
||||
"docId": "Metropolis",
|
||||
"image": {
|
||||
"url": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.",
|
||||
"alt": "Pixel-art aerial view of a Minecraft city with player home plots and connecting roads",
|
||||
"prompt": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"backgroundClassName": "bg-surface"
|
||||
},
|
||||
"url": "/projects/metropolis",
|
||||
"tags": ["open-source"],
|
||||
"source": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/headhunter45/Metropolis"
|
||||
},
|
||||
"feedback": {
|
||||
"url": "https://github.com/headhunter45/Metropolis/issues"
|
||||
}
|
||||
}
|
||||
13
README.md
13
README.md
@@ -1,5 +1,7 @@
|
||||
# Metropolis
|
||||
|
||||
[](https://github.com/headhunter45/Metropolis/actions/workflows/gradle.yml)
|
||||
|
||||
Metropolis expands a protected Minecraft city as players join. Each player can receive a home plot connected by generated roads; WorldGuard protects the city, homes, and reserved plots.
|
||||
|
||||
## Features
|
||||
@@ -16,7 +18,9 @@ Metropolis expands a protected Minecraft city as players join. Each player can r
|
||||
|
||||
## Requirements
|
||||
|
||||
- Paper 26.2 or 26.3, with Java 25 or newer
|
||||
- Paper 26.2 or 26.3, with Java 25 or newer. The build and CI compile against
|
||||
Paper API 26.2; CI does not launch a server to verify runtime behavior on
|
||||
either version.
|
||||
- WorldGuard 7.0.19
|
||||
- WorldEdit 7.4.5
|
||||
|
||||
@@ -112,4 +116,9 @@ Assign these permission nodes to groups or individual users in your permission p
|
||||
|
||||
## License
|
||||
|
||||
GNU General Public License v3.
|
||||
GNU Affero General Public License v3 or later.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for the single developer onboarding
|
||||
path, source map, checks, and operational safety notes.
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# Modernize Metropolis Plugin: Implementation Tasks
|
||||
# Historical Modernization Backlog
|
||||
|
||||
This file preserves the earlier task list for context. Its status labels and
|
||||
acceptance text are historical and are not the current implementation report.
|
||||
Use the maintained [modernization plan](modernization-plan.md) for current
|
||||
implementation status, pending work, and completion criteria.
|
||||
|
||||
| ID | Status | Title |
|
||||
|:-------:|:--------:|:------|
|
||||
@@ -1,6 +1,10 @@
|
||||
# Metropolis Human Testing
|
||||
# Metropolis Manual Pre-release Checklist
|
||||
|
||||
Use this checklist for behavior that depends on a real Paper world, player interactions, or the installed WorldGuard and WorldEdit plugins. Automated tests cover some isolated logic, but they do not establish that the complete in-game workflows behave correctly.
|
||||
This is a manual checklist of expected behavior that needs a real Paper world,
|
||||
player interaction, or installed WorldGuard/WorldEdit plugins. Checklist items
|
||||
are scenarios to run, not test results; no item is considered passed until a
|
||||
person performs it and records the outcome. Automated coverage is summarized
|
||||
separately below.
|
||||
|
||||
## Test Environment
|
||||
|
||||
@@ -10,7 +14,7 @@ Use this checklist for behavior that depends on a real Paper world, player inter
|
||||
- Keep a server console open and check it for errors after startup, commands, joins, and restarts.
|
||||
- Record the Metropolis config, world name, test player UUIDs, command output, and any unexpected region bounds or block changes.
|
||||
|
||||
## Human Checklist
|
||||
## Manual Scenarios
|
||||
|
||||
### Startup and world setup
|
||||
|
||||
@@ -78,34 +82,20 @@ Use this checklist for behavior that depends on a real Paper world, player inter
|
||||
- Test commands as console, permitted player, and player without the relevant permission. Check player-only commands, target-player arguments, unknown names, malformed arguments, and permission denials for useful feedback and no unintended changes.
|
||||
- If upgrading an existing server, verify a legacy name-keyed `currentHomes.yml` entry resolves to the right UUID and the player's selected home is retained.
|
||||
|
||||
## Existing Automated Coverage
|
||||
## Automated Coverage (Separate from This Checklist)
|
||||
|
||||
Run `./gradlew test` for the server-free JUnit suite. It has 62 tests covering:
|
||||
Run `./gradlew test` for the server-free JUnit suite. It covers isolated plot
|
||||
geometry/allocation rules, road and stair layouts/build plans, WorldGuard
|
||||
region occupancy and reservation lookup, spawn/city bounds, home identity and
|
||||
selection persistence, permission overrides, player lookup, and selected
|
||||
command/event behavior. These unit tests do not load a real Paper server.
|
||||
|
||||
- `AvenueStairwayLayoutTest`: centered per-segment street and avenue flights, both border directions, independent cadence, top-tread alignment, inverted backing plan, exact-width openings, and invalid-fit rejection.
|
||||
- `PlotLevelSupportTest`: rejection of upper plots with missing logical support cells and complete coverage requirements across all lower levels.
|
||||
- `CityRegionBoundsTest`: City bounds expand to include elevated plots and Spawn without shrinking existing protection bounds.
|
||||
- `PlotGridLayoutTest`: zero-offset compatibility, offsets on X/Y/Z, axis-specific spacing, and negative logical-index recovery.
|
||||
- `WorldGuardRegionOccupancyTest`: arbitrary saved-region bounds are excluded while City/global regions are treated as non-occupying envelopes.
|
||||
- `PlotWorldBorderBoundsTest`: search bounds follow WorldBorder size/center and require the full candidate footprint inside it.
|
||||
- `SpawnLayoutTest`: default one-cell bounds, independent multipliers, and build-height validation.
|
||||
- `AvenueStairwayBuilderTest`: configured stair material and facing/half data for avenue and street directions, upside-down backing stairs, exact upper-road AIR placements, and no block writes at lower-road Y.
|
||||
- `RoadLayoutTest`: independently sized street/avenue bands, correct road-type tagging, non-overlapping corners, and disabled roads at zero width.
|
||||
- `RoadBlockWriterTest`: no-op writes for existing roads, filling missing road blocks, and preserving stair treads.
|
||||
- `CuboidTest`: WorldEdit bound conversion, vector ordering/null handling, touching intersections, and point containment.
|
||||
- `CurrentHomesStoreTest`: UUID-based persistence and resolving a legacy player-name key.
|
||||
- `PlayerJoinListenerTest`: the welcome bounds message when a home is supplied by a mock.
|
||||
- `PlayerLookupTest`: case-insensitive full-name matching and rejection of prefix matching.
|
||||
- `PlayerHomeTest`: UUID-based region identity and display-name retention.
|
||||
- `HomePermissionOverrideTest`: dotted permission-node parsing, priority and tie resolution, invalid entries, and username/permission/default precedence.
|
||||
- `HomeNumberAllocatorTest`: choosing the first unused positive home number.
|
||||
- `MetropolisHomeMoveCommandTest`: selecting a sender's home, selecting an offline target's home, and rejecting invalid requests.
|
||||
- `PlotReservationLookupTest`: owner/member matching, exclusion of homes/City/Spawn, legacy reservation markers, and region-name aliases.
|
||||
- `MetropolisPlotGoCommandTest`: self and target teleportation, ambiguous memberships, absent membership, and unsafe-destination feedback.
|
||||
- `MetropolisPlotReserveCommandTest`: parsing six coordinates and rejecting malformed coordinates.
|
||||
- `MetropolisHomeAcquireTest`: acquisition below the limit and refusal at the limit.
|
||||
- `MetropolisDebugGenerateTestHomesCommandTest`: asynchronous tick-queued generation, one home per interval, no overlapping batches, and continuing test-player names across repeated runs.
|
||||
|
||||
The two opt-in tests in `src/integrationTest` require a running Paper server and RCON. They check that Metropolis appears in `/plugins` and that a coordinate reservation is persisted by WorldGuard. They do not currently verify restart persistence, join allocation, WorldEdit-selection reservations, permissions, teleports, generated blocks, protection behavior, or the other command workflows above. Run them with the environment variables documented in `README.md`; use a disposable server because the reservation test changes its WorldGuard data.
|
||||
|
||||
The integration scope described in `TODO.md` is broader than the tests currently present. Treat the scenarios in this guide as unverified until a human has run them on the target Paper and plugin versions.
|
||||
The separate `src/integrationTest` source set currently contains two live
|
||||
checks: Metropolis appears in `/plugins`, and a coordinate reservation is
|
||||
persisted in WorldGuard's `regions.yml`. It does not verify restart behavior,
|
||||
join allocation, WorldEdit-selection reservations, permissions, teleport
|
||||
workflows, generated blocks, or protection behavior. See
|
||||
[CONTRIBUTING.md](../CONTRIBUTING.md) for prerequisites and safe invocation.
|
||||
Those integration checks have not been run as part of this documentation
|
||||
update. The manual scenarios above remain unverified until individually run
|
||||
and recorded against the target server/plugins.
|
||||
59
docs/modernization-plan.md
Normal file
59
docs/modernization-plan.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Metropolis Modernization Plan
|
||||
|
||||
This is the maintained status and follow-up plan. Implemented behavior is
|
||||
described in [README.md](../README.md); automated and manual coverage are
|
||||
separated in [human-tests.md](human-tests.md). A feature is not considered
|
||||
runtime-verified merely because its code or an isolated unit test exists.
|
||||
|
||||
## Completed
|
||||
|
||||
- **Build and CI modernization.** Gradle 9.8 builds the Java 25 plugin and
|
||||
expands the version into `plugin.yml`. CI runs `test` and the plugin build
|
||||
for pushes and pull requests to `develop`. Completion check: running
|
||||
`./gradlew test build` succeeds; CI provides the corresponding hosted check.
|
||||
- **Paper/WorldGuard/WorldEdit integration.** The plugin declares Paper API
|
||||
26.2, WorldGuard 7.0.19, and WorldEdit 7.4.5, and uses WorldGuard regions
|
||||
for city, home, and reservation geometry. Paper 26.2/26.3 are project
|
||||
targets; the current build and CI do not perform live server compatibility
|
||||
checks. Completion check for runtime claims: run the applicable manual
|
||||
scenarios in `human-tests.md` on a disposable server.
|
||||
- **Plot, home, and command features.** The implementation includes join-time
|
||||
initial home allocation, explicit additional-home acquisition, UUID-based
|
||||
active-home persistence with legacy name-key loading, user/permission plot
|
||||
overrides, reservations, multi-level support checks, and configured
|
||||
road/stair generation. Isolated tests cover parts of these rules; see
|
||||
`src/test/` and `human-tests.md` for their boundaries.
|
||||
|
||||
## Pending
|
||||
|
||||
- **Expand live-server integration coverage.** The current `integrationTest`
|
||||
suite only checks plugin visibility and persistence of one coordinate-based
|
||||
WorldGuard reservation. Add focused disposable-server checks for join-time
|
||||
home allocation/ownership and selected-home restart persistence. Completion
|
||||
criteria: tests assert the expected region/UUID state before and after a
|
||||
restart, clean up their own test regions, and remain excluded from `build`.
|
||||
- **Choose optional login allocation policies (MET-023/MET-024).** Current
|
||||
behavior allocates the configured initial home count (default one) when a
|
||||
player has no home; later homes require `/metropolis-home-acquire`. Decide
|
||||
whether to offer grant-all-to-limit or one-more-per-login policies before
|
||||
implementing them. Completion criteria: documented config semantics,
|
||||
compatibility defaults, and unit tests for limits, reconnects, and existing
|
||||
home selections.
|
||||
- **Choose an allocation traversal option (MET-025).** The proposal to select
|
||||
a rectangle/cuboid or ring-based traversal remains unimplemented. First
|
||||
document deterministic ordering and how each policy treats occupied regions,
|
||||
levels, and WorldBorder bounds. Completion criteria: configuration and
|
||||
focused allocation tests establish repeatable ordering without overlaps.
|
||||
- **Research conversion of existing worlds (MET-026).** There is no automatic
|
||||
world/config conversion workflow. Document the legacy data/config formats,
|
||||
backups and rollback requirements, and a safe migration strategy before
|
||||
proposing implementation. Completion criteria: a reviewed conversion
|
||||
specification plus disposable-world validation; do not claim migration
|
||||
support before that work is complete.
|
||||
|
||||
## Deferred Ideas
|
||||
|
||||
Economy-based home sales, arbitrary connected plot shapes, and alternate
|
||||
multi-plot footprints remain product ideas, not committed work. Reconsider them
|
||||
only after requirements, compatibility constraints, and acceptance criteria
|
||||
are defined.
|
||||
@@ -11,6 +11,8 @@ Metropolis grows a protected Minecraft city as players join. It assigns home plo
|
||||
WorldGuard and WorldEdit are required dependencies. Install all three plugins in the server's `plugins` directory.
|
||||
|
||||
Metropolis targets Paper only; Bukkit and Spigot servers are not supported.
|
||||
The project targets Paper 26.2 and 26.3, while its build/CI compile against the
|
||||
Paper 26.2 API and do not run a live server compatibility check.
|
||||
|
||||
## Features
|
||||
|
||||
29
docs/project-description.json
Normal file
29
docs/project-description.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"metropolis": {
|
||||
"id": "metropolis",
|
||||
"title": "Metropolis",
|
||||
"description": "City manager that automatically creates protected home plots and roads as players join a Minecraft server.",
|
||||
"docId": "Metropolis",
|
||||
"headerImage": null,
|
||||
"platforms": [
|
||||
{ "name": "Minecraft", "info": "Paper server plugin" }
|
||||
],
|
||||
"image": {
|
||||
"url": "/projects/metropolis/logo.png",
|
||||
"alt": "Metropolis Minecraft plugin logo",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"backgroundClassName": "bg-surface"
|
||||
},
|
||||
"url": "/projects/metropolis",
|
||||
"tags": ["Open-Source", "Game", "Minecraft"],
|
||||
"source": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/headhunter45/Metropolis"
|
||||
},
|
||||
"feedback": {
|
||||
"type": "github-issues",
|
||||
"url": "https://github.com/headhunter45/Metropolis/issues"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
# Metropolis
|
||||
|
||||
The project targets Paper 26.2 and 26.3; Gradle and CI compile against Paper
|
||||
API 26.2, and CI does not run a live server compatibility check.
|
||||
|
||||
Metropolis turns player onboarding into city planning. As players arrive, the Paper plugin finds the next available plot, builds the surrounding roads and infrastructure, and extends a WorldGuard-protected city that feels designed rather than randomly generated.
|
||||
|
||||
The project combines procedural spatial logic, persistent UUID-based ownership, configurable construction rules, WorldEdit-assisted reservations, and a command suite for acquiring and selecting multiple homes. Home limits and plot sizes can be assigned per user or through permission-manager groups using configurable permission tiers. Reservation owners and members can teleport to their reserved plots, with explicit selection when they belong to multiple reservations. Active-home selections persist across reconnects and server restarts. It is also a modernization case study: a legacy plugin brought forward to Paper 26.2 and 26.3 with Gradle, focused unit tests, and opt-in live-server integration tests. Bukkit and Spigot servers are outside the support target.
|
||||
114
docs/projects.schema.json
Normal file
114
docs/projects.schema.json
Normal file
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Projects",
|
||||
"description": "Project metadata keyed by the project route slug.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-z0-9]+(?:-[a-z0-9]+)*$": { "$ref": "#/definitions/project" }
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"image": {
|
||||
"type": "object",
|
||||
"required": ["url", "width", "height"],
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Public asset path or remote image URL. A blank header image URL falls back to image.",
|
||||
"pattern": "^(\\s*|/[^/].*|https?://.+)$"
|
||||
},
|
||||
"alt": { "type": "string" },
|
||||
"prompt": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"width": { "type": "integer", "minimum": 1 },
|
||||
"height": { "type": "integer", "minimum": 1 },
|
||||
"backgroundClassName": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"projectLink": {
|
||||
"type": "object",
|
||||
"required": ["url"],
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"url": { "type": "string", "pattern": "^(\\s*|https?://.+)$" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"platform": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": { "type": "string", "pattern": "\\S" },
|
||||
"info": { "type": "string" },
|
||||
"links": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["label", "url"],
|
||||
"properties": {
|
||||
"label": { "type": "string", "pattern": "\\S" },
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"pattern": "^https?://.+"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"project": {
|
||||
"type": "object",
|
||||
"required": ["id", "title", "description", "tags"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
||||
"title": { "type": "string", "pattern": "\\S" },
|
||||
"description": { "type": "string", "pattern": "\\S" },
|
||||
"docId": { "type": "string", "minLength": 1 },
|
||||
"image": { "$ref": "#/definitions/image" },
|
||||
"headerImage": {
|
||||
"description": "Optional detail-page logo override. Omit, set to null, or use a blank URL to fall back to image. Listing cards continue to use image.",
|
||||
"anyOf": [{ "$ref": "#/definitions/image" }, { "type": "null" }]
|
||||
},
|
||||
"status": { "type": "string" },
|
||||
"platforms": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/platform" }
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"pattern": "^/projects/[a-z0-9]+(?:-[a-z0-9]+)*$"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 },
|
||||
"uniqueItems": true
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/projectLink" },
|
||||
{ "required": ["type"] }
|
||||
]
|
||||
},
|
||||
"feedback": { "$ref": "#/definitions/projectLink" },
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"pattern": "^https?://.+"
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"technology": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,6 @@ set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
if [[ ! -r "$PROJECT_ROOT/.env" ]]; then
|
||||
echo "Error: $PROJECT_ROOT/.env is missing or unreadable. Copy .env.example to .env and set its values." >&2
|
||||
exit 1
|
||||
fi
|
||||
load_project_env() {
|
||||
local env_line env_key env_value first_char last_char
|
||||
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
||||
@@ -36,7 +32,13 @@ load_project_env() {
|
||||
fi
|
||||
done < "$PROJECT_ROOT/.env"
|
||||
}
|
||||
|
||||
if [[ -r "$PROJECT_ROOT/.env" ]]; then
|
||||
load_project_env
|
||||
elif [[ -z "${MODRINTH_TOKEN:-}" ]]; then
|
||||
echo "Error: $PROJECT_ROOT/.env is missing or unreadable and MODRINTH_TOKEN is not set. Copy .env.example to .env and set its values." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MODRINTH_TOKEN="${MODRINTH_TOKEN:-}"
|
||||
MODRINTH_PROJECT_ID="${MODRINTH_PROJECT_ID:-metropolis-builder}"
|
||||
@@ -44,9 +46,10 @@ MODRINTH_VERSION="${MODRINTH_VERSION:-${VERSION:-}}"
|
||||
if [[ -z "$MODRINTH_VERSION" ]]; then
|
||||
MODRINTH_VERSION=$(cd "$PROJECT_ROOT" && ./gradlew -q properties --property version | sed -n 's/^version: //p' | head -n1)
|
||||
fi
|
||||
|
||||
generate_changelog() {
|
||||
local current_tag previous_tag range commits
|
||||
current_tag=$(git -C "$PROJECT_ROOT" describe --tags --exact-match HEAD 2>/dev/null || true)
|
||||
current_tag="${TAG:-$(git -C "$PROJECT_ROOT" describe --tags --exact-match HEAD 2>/dev/null || true)}"
|
||||
if [[ -n "$current_tag" ]]; then
|
||||
previous_tag=$(git -C "$PROJECT_ROOT" describe --tags --abbrev=0 "${current_tag}^" 2>/dev/null || true)
|
||||
range="${previous_tag:+$previous_tag..}$current_tag"
|
||||
@@ -60,12 +63,18 @@ generate_changelog() {
|
||||
fi
|
||||
printf '%s\n\n%s\n' "## What's Changed" "$commits"
|
||||
}
|
||||
|
||||
MODRINTH_CHANGELOG="${MODRINTH_CHANGELOG:-$(generate_changelog)}"
|
||||
MODRINTH_GAME_VERSIONS="${MODRINTH_GAME_VERSIONS:-26.2,26.3}"
|
||||
MODRINTH_LOADERS="${MODRINTH_LOADERS:-paper}"
|
||||
MODRINTH_VERSION_TYPE="${MODRINTH_VERSION_TYPE:-}"
|
||||
MODRINTH_FILE="${MODRINTH_FILE:-}"
|
||||
MODRINTH_DEPENDENCIES="${MODRINTH_DEPENDENCIES:-}"
|
||||
MODRINTH_SYNC_BODY="${MODRINTH_SYNC_BODY:-true}"
|
||||
MODRINTH_DESCRIPTION_FILE="${MODRINTH_DESCRIPTION_FILE:-$PROJECT_ROOT/docs/modrinth-description.md}"
|
||||
if [[ ! -f "$MODRINTH_DESCRIPTION_FILE" ]]; then
|
||||
MODRINTH_DESCRIPTION_FILE="$PROJECT_ROOT/README.md"
|
||||
fi
|
||||
|
||||
if [[ -z "$MODRINTH_VERSION" ]]; then
|
||||
echo "Error: could not determine the project version from Gradle." >&2
|
||||
@@ -89,7 +98,7 @@ if [[ -n "$MODRINTH_FILE" && "$MODRINTH_FILE" != /* ]]; then
|
||||
fi
|
||||
|
||||
if [[ -z "$MODRINTH_TOKEN" ]]; then
|
||||
echo "Error: MODRINTH_TOKEN is not set in $PROJECT_ROOT/.env" >&2
|
||||
echo "Error: MODRINTH_TOKEN is not set (check $PROJECT_ROOT/.env or environment variable)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -159,3 +168,26 @@ if [[ "$HTTP_STATUS" != 2?? ]]; then
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$HTTP_BODY" | jq .
|
||||
|
||||
if [[ "$MODRINTH_SYNC_BODY" == "true" && -f "$MODRINTH_DESCRIPTION_FILE" ]]; then
|
||||
echo "Syncing project description to Modrinth from $MODRINTH_DESCRIPTION_FILE..."
|
||||
BODY_CONTENT=$(cat "$MODRINTH_DESCRIPTION_FILE")
|
||||
PATCH_DATA=$(jq -cn --arg body "$BODY_CONTENT" '{body: $body}')
|
||||
|
||||
PATCH_RESPONSE=$(curl -sS -w $'\n%{http_code}' \
|
||||
-X PATCH "https://api.modrinth.com/v2/project/$MODRINTH_PROJECT_ID" \
|
||||
-H "Authorization: $MODRINTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PATCH_DATA")
|
||||
|
||||
PATCH_STATUS="${PATCH_RESPONSE##*$'\n'}"
|
||||
PATCH_BODY="${PATCH_RESPONSE%$'\n'*}"
|
||||
if [[ "$PATCH_STATUS" != 2?? ]]; then
|
||||
echo "Warning: Modrinth project description update returned HTTP $PATCH_STATUS:" >&2
|
||||
if ! printf '%s\n' "$PATCH_BODY" | jq -r '.description // .error // .' >&2; then
|
||||
printf '%s\n' "$PATCH_BODY" >&2
|
||||
fi
|
||||
else
|
||||
echo "Successfully updated Modrinth project description."
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user