Compare commits
28 Commits
0.5.1
...
d6efbf3715
| Author | SHA1 | Date | |
|---|---|---|---|
| d6efbf3715 | |||
| 5de22ec0ea | |||
| 37f5e19fbd | |||
| e0071ee713 | |||
| f19b1fc8cb | |||
| 4e459ec9fa | |||
| 99f6195542 | |||
| 60c3d41e50 | |||
| 8d99c2e67f | |||
| d68f7543ea | |||
| 917dac9c8e | |||
| 30c31c185b | |||
| fd5bf785d3 | |||
| 04a0c1d959 | |||
| f68d043d02 | |||
| 9be629d82e | |||
| d012d9e645 | |||
| 87b5efed8c | |||
| de3651f22c | |||
| da46d3e4b9 | |||
| b6b30621f0 | |||
| efc39365c3 | |||
| a13f512be6 | |||
| 013dc6749c | |||
| 08d27ede40 | |||
| 8dbbdbac91 | |||
| 4ada53ba76 | |||
| 69cc0f8677 |
@@ -16,9 +16,14 @@ MINECRAFT_BACKUP_PATH=./backups
|
|||||||
# Release version is read from Gradle's Git-sensitive semantic version and checked against the jar's plugin.yml.
|
# Release version is read from Gradle's Git-sensitive semantic version and checked against the jar's plugin.yml.
|
||||||
MODRINTH_TOKEN=
|
MODRINTH_TOKEN=
|
||||||
MODRINTH_PROJECT_ID=metropolis-builder
|
MODRINTH_PROJECT_ID=metropolis-builder
|
||||||
|
# Optional Markdown override; empty generates notes from commits since the last tag.
|
||||||
MODRINTH_CHANGELOG=
|
MODRINTH_CHANGELOG=
|
||||||
MODRINTH_GAME_VERSIONS=26.2
|
MODRINTH_GAME_VERSIONS=26.2,26.3
|
||||||
MODRINTH_LOADERS=paper
|
MODRINTH_LOADERS=paper
|
||||||
MODRINTH_VERSION_TYPE=
|
MODRINTH_VERSION_TYPE=
|
||||||
MODRINTH_DEPENDENCIES=
|
MODRINTH_DEPENDENCIES=
|
||||||
MODRINTH_FILE=
|
MODRINTH_FILE=
|
||||||
|
# Sync project body/README to Modrinth during publish (true/false)
|
||||||
|
MODRINTH_SYNC_BODY=true
|
||||||
|
MODRINTH_DESCRIPTION_FILE=docs/modrinth-description.md
|
||||||
|
|
||||||
|
|||||||
62
.github/workflows/gradle.yml
vendored
Normal file
62
.github/workflows/gradle.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: Java CI with Gradle
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main", "develop" ]
|
||||||
|
tags: [ "v*" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main", "develop" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up JDK 25
|
||||||
|
uses: actions/setup-java@v6
|
||||||
|
with:
|
||||||
|
java-version: '25'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Gradle 9.8.0
|
||||||
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
with:
|
||||||
|
gradle-version: '9.8.0'
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Extract Project Version
|
||||||
|
id: project_info
|
||||||
|
run: |
|
||||||
|
VERSION=$(./gradlew properties --no-daemon -q | awk -F ': ' '$1 == "version" {print $2}')
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload Plugin Artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: Metropolis-${{ steps.project_info.outputs.version }}
|
||||||
|
path: build/libs/*.jar
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
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/
|
/backups/
|
||||||
/server/
|
/server/
|
||||||
.env
|
.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
|
1. Read [README.md](README.md) for supported server versions, installation,
|
||||||
./gradlew build
|
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
|
```sh
|
||||||
METROPOLIS_RCON_HOST=127.0.0.1 \
|
METROPOLIS_RCON_HOST=127.0.0.1 \
|
||||||
@@ -22,12 +61,27 @@ METROPOLIS_SERVER_DIR=/path/to/server \
|
|||||||
./gradlew integrationTest
|
./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.
|
Use the build/test commands above for ordinary contribution work. The scripts
|
||||||
- Run `./gradlew build` before submitting.
|
under `tools/` also include server start/stop, deployment, backup/restore, and
|
||||||
- Run `integrationTest` only when changing behavior that requires Paper or the external plugins; do not add it to the default build.
|
publishing operations; they are not required for a normal build. Read a
|
||||||
- Update `README.md` and project descriptions when user-facing setup, commands, or dependencies change.
|
script before use and get explicit authorization before running operational
|
||||||
- Keep generated build output, server data, credentials, and local `.env` files out of commits.
|
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": "Paper plugin that automatically creates protected home plots and connecting roads as players join a Minecraft server.",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Metropolis
|
|
||||||
|
|
||||||
Metropolis is a Paper plugin that expands a protected Minecraft city as players join. It generates home plots and connecting roads, uses WorldGuard to protect the city and homes, and supports reserved plots and home teleportation.
|
|
||||||
|
|
||||||
## Platform
|
|
||||||
|
|
||||||
- Paper 26.2, Java 25+
|
|
||||||
- WorldGuard 7.0.19 and WorldEdit 7.4.5
|
|
||||||
- Built with Gradle 9.8 and Git-sensitive semantic versioning
|
|
||||||
- Configuration: `plugins/Metropolis/config.yml`
|
|
||||||
- Unit tests run without a server; live integration tests use the separate Gradle `integrationTest` task.
|
|
||||||
- License: GNU General Public License v3
|
|
||||||
87
README.md
87
README.md
@@ -1,10 +1,26 @@
|
|||||||
# Metropolis
|
# 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.
|
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
|
||||||
|
|
||||||
|
- Assigns a protected home plot when a player joins by default and supports acquiring additional homes up to the configured player limit; set `plot.initial` to `0` to require `/metropolis-home-acquire` for the first home.
|
||||||
|
- Adds stacked plot levels with `plot.maxLevels`, while `plot.sizeY` continues to define the full height of each individual plot.
|
||||||
|
- Supports per-player plot sizes and home limits through `userOverrides` or configurable permission tiers; acquired homes can be selected as the active home.
|
||||||
|
- Expands the City region as plots are occupied, with optional spawn generation, world-spawn placement, floors, support blocks, signs, and perimeter walls.
|
||||||
|
- Generates roads around plots with configurable width, level, material, clearance, and supports.
|
||||||
|
- Creates named protected reservations from coordinates or a WorldEdit selection so reserved plots are not assigned as homes.
|
||||||
|
- Lets WorldGuard reservation owners and members teleport to their reservations, with explicit selection when they belong to several.
|
||||||
|
- Persists selected homes by player UUID and recognizes legacy name-based home data during migration.
|
||||||
|
- Supports online/offline player administration for home generation, movement, and eviction.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Paper 26.2, 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
|
- WorldGuard 7.0.19
|
||||||
- WorldEdit 7.4.5
|
- WorldEdit 7.4.5
|
||||||
|
|
||||||
@@ -41,27 +57,68 @@ Copy `.env.example` to `.env` and configure the Paper version/build and server p
|
|||||||
- `tools/start-server.sh` downloads the configured Paper build if needed and starts it.
|
- `tools/start-server.sh` downloads the configured Paper build if needed and starts it.
|
||||||
- `tools/backup-server.sh` and `tools/restore-server.sh` back up or restore the configured server.
|
- `tools/backup-server.sh` and `tools/restore-server.sh` back up or restore the configured server.
|
||||||
- `tools/publish-local.sh` publishes the artifact to the local Maven repository.
|
- `tools/publish-local.sh` publishes the artifact to the local Maven repository.
|
||||||
- `tools/publish-modrinth.sh` uploads a built jar after checking its version.
|
- `tools/publish-modrinth.sh` uploads a built jar after checking its version and generates a Markdown changelog from Git commits since the previous version tag. Set `MODRINTH_CHANGELOG` to override it with curated notes.
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The default configuration is in `src/main/resources/config.yml` and is copied to `plugins/Metropolis/config.yml` on first startup. Materials use Bukkit names; the previous numeric IDs for stone, grass, cobblestone, and bedrock are still accepted when loading a configuration.
|
`plugins/Metropolis/config.yml` controls plot dimensions and limits, floor and support generation, signs, road width/level/material/clearance, spawn behavior, wall generation, world name, and per-player overrides. The per-level stack is configured with `plot.sizeY` and `plot.maxLevels`: `sizeY` is the full height of one plot level, and `maxLevels` is the number of stacked levels that can be allocated without exceeding the world build height. `plot.multiplier` sets the default plot-size multiplier; `plot.maxPerPlayer` sets the default home limit; `plot.initial` sets how many homes are auto-generated on join, and `0` requires `/metropolis-home-acquire` for the player's first home.
|
||||||
|
|
||||||
Player home ownership and selected-home data use UUIDs. Existing name-keyed `currentHomes.yml` entries and name-based WorldGuard home regions are recognized during migration.
|
`plot.offsetX`, `plot.offsetY`, and `plot.offsetZ` shift the logical plot grid in blocks. Positive or negative offsets apply consistently to plot/road placement, vertical levels, spawn alignment, and logical-cell indexing. Changing offsets does not move existing saved WorldGuard regions or automatically convert an existing world.
|
||||||
|
|
||||||
|
Before allocating, Metropolis treats every saved WorldGuard region except the enclosing `City` and special `__global__` region as occupied. It uses each region's bounding cuboid; for non-cuboid regions this can conservatively leave extra gaps to ensure new plot regions do not overlap them.
|
||||||
|
|
||||||
|
Plot allocation continues outward up to the configured WorldBorder. The complete logical plot-and-road footprint must fit inside the border; if no supported, unoccupied location remains, Metropolis logs an error and creates no region.
|
||||||
|
|
||||||
|
Set `spawn.sizeX`, `spawn.sizeY`, and `spawn.sizeZ` to positive logical-grid multipliers. X and Z include the matching plot dimension plus road spacing; Y sets the spawn volume's height upward from `road.level` in multiples of `plot.sizeY`. All default to `1`. A newly created Spawn region must fit within the world's build height, and generation clears only within that region's configured vertical bounds. Existing saved Spawn regions retain their saved bounds.
|
||||||
|
|
||||||
|
The `road.streets.*` and `road.avenues.*` sections independently configure width, level, material, clearance, supports, and stairs. Streets run along X; avenues run along Z. Each section's stair settings (`generate`, `material`, `width`, and `everyNBlocks`) use that section's road width and cadence. Street stair runs ascend along positive X at the north/south borders; avenue runs ascend along positive Z at the east/west borders. Runs are centered within each eligible logical plot-sized segment. The lower road remains the normal-material landing; stairs begin one block above and forward from it. Connected following treads use upside-down stairs facing back down the run as backing, and the final tread reaches the upper road Y. The three blocks before each top tread remain open only across the stair width.
|
||||||
|
|
||||||
|
Upper-level plots require complete generated support below: every logical plot cell in their footprint must be occupied by a generated home or spawn at every lower level. If a footprint has gaps below, allocation skips that upper candidate and keeps searching for a supported lower-level location.
|
||||||
|
|
||||||
|
The WorldGuard `City` region expands in all three dimensions to contain its previous bounds, the requested city footprint, Spawn, and generated homes/reservations. New upper-level plots therefore remain inside City protection; adding a home or reservation also triggers a resize.
|
||||||
|
|
||||||
|
The breaking multi-level schema is documented in the example config at `src/main/resources/examples/multi-level.yml`. It uses split road sections (`road.streets.*` and `road.avenues.*`), a level-aware `plot.maxLevels` field, and the valid stair material `COBBLESTONE_STAIRS`.
|
||||||
|
|
||||||
|
Roads are generated at their full configured width when a plot is created. Generating an adjacent plot does not rewrite road blocks that are already correct; it only fills missing blocks, including gaps left by older half-width generation. Existing stair treads and upper-road openings are preserved.
|
||||||
|
|
||||||
|
Use `permissionOverrides` to assign plot size and home limits through any permission manager. Each permission node maps to a `priority`, `plotMultiplier`, and `maxPlots`; the matching node with the highest priority wins, and the last matching entry in YAML order wins ties. An explicit username entry in `userOverrides` takes precedence over permission rules. If no override matches, the global `plot.*` defaults apply. Permission rules are checked for online players; offline home generation uses username overrides or global defaults. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
permissionOverrides:
|
||||||
|
metropolis.plots.vip:
|
||||||
|
priority: 10
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 2
|
||||||
|
metropolis.plots.elite:
|
||||||
|
priority: 20
|
||||||
|
plotMultiplier: 3
|
||||||
|
maxPlots: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Assign these permission nodes to groups or individual users in your permission plugin. Bukkit permissions are boolean, so the configured values determine the numeric limits. The bundled configuration provides the defaults. Metropolis targets Paper 26.2 and 26.3 only; Bukkit and Spigot servers are not supported.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- `/metropolis` displays the plugin version.
|
| Command | Permission | Description |
|
||||||
- `/metropolis-home-acquire` acquires an available home plot.
|
| --- | --- | --- |
|
||||||
- `/metropolis-home-generate <player>` generates a home for a known online or cached offline player.
|
| `/metropolis` | None | Displays the plugin version. |
|
||||||
- `/metropolis-home-list` lists Metropolis plots.
|
| `/metropolis-home-acquire` | Player only | Acquires an available home if the player's home limit is not reached. |
|
||||||
- `/metropolis-home-go [player]` teleports to a home.
|
| `/metropolis-home-generate <playerName>` | `metropolis.home.generate` | Generates a home for an online or cached offline player. |
|
||||||
- `/metropolis-home-move <home-number> [player]` changes a player's selected home.
|
| `/metropolis-home-list` | `metropolis.home.list` | Lists the occupied and reserved Metropolis plots with their bounds. |
|
||||||
- `/metropolis-home-evict <player>` removes a player's home ownership.
|
| `/metropolis-home-go [playerName]` | `metropolis.home.go` | Teleports the sender, or a permitted target player, to their home. |
|
||||||
- `/metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>` reserves coordinates; a player may instead provide a WorldEdit selection.
|
| `/metropolis-home-move <homeNumber> [playerName]` | `metropolis.home.move` | Selects one of a player's existing homes as active. The player argument defaults to the sender; console and authorized admins can target online or cached offline players. The selection persists across restarts. |
|
||||||
- `/metropolis-plot-go <plot-name> [player]` teleports to a plot.
|
| `/metropolis-home-evict <playerName>` | `metropolis.home.evict` | Removes the player's ownership from their selected home region. |
|
||||||
- `/metropolis-flag-reset` reapplies Metropolis protection flags.
|
| `/metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>` | `metropolis.plot.reserve` | Creates a named reservation from explicit bounds. A player can instead run `/metropolis-plot-reserve <name>` with a WorldEdit selection. |
|
||||||
|
| `/metropolis-plot-go [playerName]` | Reservation owner/member; `metropolis.plot.go` to target others or bypass membership | Teleports the sender or an online target to a reservation they own or belong to. If they have several, specify one with `/metropolis-plot-go <reservationName> [playerName]`. |
|
||||||
|
| `/metropolis-flag-reset` | `metropolis.flag.reset` | Reapplies Metropolis protection flags to the City and home regions. |
|
||||||
|
| `/metropolis-debug-generatetesthomes <count>` | `metropolis.debug` | Queues test-home generation on the main thread, creating one home every two ticks; intended for development servers. |
|
||||||
|
|
||||||
## License
|
## 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.
|
||||||
|
|||||||
119
TODO.md
119
TODO.md
@@ -1,119 +0,0 @@
|
|||||||
# Modernize Metropolis Plugin: Implementation Tasks
|
|
||||||
|
|
||||||
| ID | Status | Title |
|
|
||||||
|:-------:|:--------:|:------|
|
|
||||||
| MET-001 | Done | Initialize Gradle in the project root, add the Gradle wrapper, and create the project build file. |
|
|
||||||
| MET-002 | Done | Set Gradle project metadata and configure a Java toolchain compatible with the target modern Paper version. |
|
|
||||||
| MET-003 | Done | Configure repositories and compatible PaperMC, WorldEdit, WorldGuard, and other required or optional dependencies; add build plugins such as Shadow only if needed. |
|
|
||||||
| MET-004 | Done | Move or verify plugin.yml under src/main/resources, configure Gradle resource processing, and update its Paper api-version, dependencies, commands, permissions, and required fields. |
|
|
||||||
| MET-005 | Done | Add JUnit 5 test infrastructure and keep the default Gradle `test` task server-free. Use an in-memory Paper harness such as MockBukkit for Bukkit/Paper behavior and mocks or test doubles for WorldEdit, WorldGuard, and narrow API boundaries where appropriate. |
|
|
||||||
| MET-006 | Done | Build the initial Gradle project and run its tests; resolve build setup issues before removing the existing Maven build. |
|
|
||||||
| MET-007 | Done | Update .gitignore for Gradle and remove obsolete Maven and Eclipse project files after the Gradle build succeeds. |
|
|
||||||
| MET-008 | Done | Analyze Java sources for deprecated or removed Bukkit/Paper APIs and legacy patterns, including logging, event registration, configuration, player lookup, and player-keyed storage. |
|
|
||||||
| MET-009 | Done | Review custom scripts and resources for required updates to the Gradle build and modern Paper server workflow. |
|
|
||||||
| MET-010 | Done | Refactor plugin logging to use JavaPlugin.getLogger(). |
|
|
||||||
| MET-011 | Done | Refactor event listeners and registration to use Listener, @EventHandler, and the modern plugin manager API. |
|
|
||||||
| MET-012 | Done | Replace deprecated configuration APIs with the modern Bukkit/Paper configuration API, including load, save, and reload behavior. |
|
|
||||||
| MET-013 | Done | Replace Player-keyed storage with UUID-based storage wherever player identity is persisted or tracked. |
|
|
||||||
| MET-014 | Done | Review player lookup logic and use exact or otherwise explicit name-matching behavior where appropriate. |
|
|
||||||
| MET-015 | Done | Refactor remaining code that uses deprecated or removed Bukkit, Spigot, or Paper APIs so it is compatible with the target Paper API. |
|
|
||||||
| MET-016 | Done | Add fast unit tests for plot and home allocation, reservation validation, player lookup and UUID-based ownership, configuration persistence, command validation, and event behavior. Keep domain rules independent of a running Minecraft server and mock external plugin boundaries as needed. |
|
|
||||||
| MET-017 | Done | Add a separate Gradle `integrationTest` source set/task for tests requiring an actual Paper server and compatible WorldGuard/WorldEdit plugins. Verify plugin loading, command registration and dispatch, region-backed reservations and home allocation, and persistence across a server restart. Run it only when explicitly requested; keep it out of the normal `test` and `build` tasks. |
|
|
||||||
| MET-018 | Done | Build the plugin with Gradle and use the provided server scripts to smoke-test it on modern Paper with its required plugins installed. |
|
|
||||||
| MET-019 | Done | Address bugs and incompatibilities found by unit tests, integration tests, or modern Paper server testing. |
|
|
||||||
| MET-020 | Done | Adopt the git-sensitive semantic versioning Gradle plugin used by MobScores and ScoreKeeper, using the existing 0.5-SNAPSHOT version as the migration baseline; verify version/tag behavior and enable the Gradle configuration cache when compatible. |
|
|
||||||
| MET-021 | Done | Update README.md, CONTRIBUTING.md, other documentation, and project scripts with the Gradle build, test, versioning, and modern Paper server workflow. |
|
|
||||||
| MET-022 | Planning | (Optional) Add new features, quality-of-life improvements, or CI configuration after the modernization and documentation tasks are complete. |
|
|
||||||
| GIT-009 | Planning | i have three reserve plots and one just got overwritten when a new player joined. |
|
|
||||||
| GIT-008 | Ready | Implement multiple plot sizes by granting multiple plots around each other.
|
|
||||||
| GIT-007 | Ready | Allow buying and selling of homes via an ecnoomy plugin. |
|
|
||||||
| GIT-006 | Ready | Add levels support. |
|
|
||||||
| GIT-005 | Planning | Add the possibility to create multiple plot homes in shapes. |
|
|
||||||
| GIT-004 | Ready | Add a command to request home allocation. |
|
|
||||||
| GIT-003 | Ready | Add support for multiple homes. |
|
|
||||||
| GIT-002 | Ready | Add a command to teleport to a reservation you are part of. |
|
|
||||||
| GIT-001 | Ready | Add a command to move to a different home. |
|
|
||||||
|
|
||||||
# GIT-001 - Add a command to move to a different home.
|
|
||||||
**Status:** Ready
|
|
||||||
**Descriptioon:**
|
|
||||||
|
|
||||||
/metropolis-home-move [playerName]
|
|
||||||
The target player will be the callee or if specified [playerName].
|
|
||||||
This command should set the target player's home to the plot defined by .
|
|
||||||
|
|
||||||
# GIT-002 - Add a command to teleport to a reservation you are part of.
|
|
||||||
**Status:** Ready
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
/metropolis-plot-go [playerName]
|
|
||||||
The target player should be [playerName] if specified and otherwise should be the callee.
|
|
||||||
The reservation to teleport to is
|
|
||||||
|
|
||||||
### Comments
|
|
||||||
|
|
||||||
Or better yet just use uhome or create your own, integrated that records the home location at the height set in the configuration file, but on the road next to the plot. Using existing plugins to insert into SQL say uhome, would allow privatization and minimal work on your end.
|
|
||||||
|
|
||||||
# GIT-003 - Add support for multiple homes
|
|
||||||
|
|
||||||
**Status:** Ready
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
The max number of homes per player should be defined by a permission such as
|
|
||||||
metropolis.maxhomes: 2
|
|
||||||
|
|
||||||
A config option should be added to determine when the homes are added with at least the following options
|
|
||||||
|
|
||||||
Allocate all homes that aren't allocated on login
|
|
||||||
Allocate at most 1 home per login
|
|
||||||
Only allocate the first home on login, and require a command to allocate additional homes.
|
|
||||||
|
|
||||||
# GIT-004 - Add a command to request home allocation.
|
|
||||||
**Status:** Ready
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
Add a command that users can type to get their first or subsequent homes.
|
|
||||||
Add a config option to not generate any homes automatically, but to require this command to generate a user's first home.
|
|
||||||
|
|
||||||
This could be used so that homes won't be generated until a user has read the server rules which would include telling them to type the command towards the end.
|
|
||||||
|
|
||||||
# GIT-005 - Add the possibility to create multiple plot homes in shapes.
|
|
||||||
**Status:** Planning
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
Add the possibility to create multiple plot homes in shapes
|
|
||||||
|
|
||||||
|
|
||||||
# GIT-006 - Add levels support.
|
|
||||||
**Status:** Ready
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
Allow generating multilevel cities with stairs or interchanges to go between levels (streets are level but avenues are ramps.
|
|
||||||
|
|
||||||
# GIT-007 - Allow buying and selling of homes via an ecnoomy plugin.
|
|
||||||
**Status:** Ready
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
Allow buying and selling of homes via an ecnoomy plugin. Include decent real estate support including wtb and wts listings as well as an auction facility.
|
|
||||||
|
|
||||||
This should also support not giving free plots to users with none. This way one would have to go out and mine to earn money which could be spent to purchase a plot.
|
|
||||||
|
|
||||||
# GIT-008 - Implement multiple plot sizes by granting multiple plots around each other.
|
|
||||||
**Status:** Ready
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
a= user a
|
|
||||||
x= empty plot
|
|
||||||
s= spawn
|
|
||||||
|
|
||||||
user a get's a small plot which is defined as 1 plot
|
|
||||||
a x x
|
|
||||||
x s x
|
|
||||||
x x x
|
|
||||||
|
|
||||||
user b gets a large plot which is defined as 4 plots
|
|
||||||
x x b b x
|
|
||||||
x a b b x
|
|
||||||
x x s x x
|
|
||||||
x x x x x
|
|
||||||
x x x x x
|
|
||||||
14
build.gradle
14
build.gradle
@@ -1,4 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'com.diffplug.spotless' version '8.10.3'
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'org.danilopianini.git-sensitive-semantic-versioning' version '7.0.24'
|
id 'org.danilopianini.git-sensitive-semantic-versioning' version '7.0.24'
|
||||||
@@ -9,7 +10,7 @@ description = 'Metropolis'
|
|||||||
|
|
||||||
gitSemVer {
|
gitSemVer {
|
||||||
minimumVersion.set('0.5.0')
|
minimumVersion.set('0.5.0')
|
||||||
versionPrefix.set('')
|
versionPrefix.set('v')
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -87,4 +88,15 @@ tasks.named('wrapper') {
|
|||||||
distributionPath = '.gradle/wrapper/dists'
|
distributionPath = '.gradle/wrapper/dists'
|
||||||
archiveBase = Wrapper.PathBase.PROJECT
|
archiveBase = Wrapper.PathBase.PROJECT
|
||||||
archivePath = '.gradle/wrapper/dists'
|
archivePath = '.gradle/wrapper/dists'
|
||||||
|
}
|
||||||
|
|
||||||
|
spotless {
|
||||||
|
java {
|
||||||
|
googleJavaFormat('1.36.1')
|
||||||
|
importOrder 'java', 'javax', 'com', 'net', 'org', ''
|
||||||
|
licenseHeaderFile 'config/license-header.txt'
|
||||||
|
target 'src/**/*.java'
|
||||||
|
removeUnusedImports()
|
||||||
|
toggleOffOn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
17
config/license-header.txt
Normal file
17
config/license-header.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
140
docs/TODO.md
Normal file
140
docs/TODO.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# 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 |
|
||||||
|
|:-------:|:--------:|:------|
|
||||||
|
| MET-001 | Done | Initialize Gradle in the project root, add the Gradle wrapper, and create the project build file. |
|
||||||
|
| MET-002 | Done | Set Gradle project metadata and configure a Java toolchain compatible with the target modern Paper version. |
|
||||||
|
| MET-003 | Done | Configure repositories and compatible PaperMC, WorldEdit, WorldGuard, and other required or optional dependencies; add build plugins such as Shadow only if needed. |
|
||||||
|
| MET-004 | Done | Move or verify plugin.yml under src/main/resources, configure Gradle resource processing, and update its Paper api-version, dependencies, commands, permissions, and required fields. |
|
||||||
|
| MET-005 | Done | Add JUnit 5 test infrastructure and keep the default Gradle `test` task server-free. Use an in-memory Paper harness such as MockBukkit for Bukkit/Paper behavior and mocks or test doubles for WorldEdit, WorldGuard, and narrow API boundaries where appropriate. |
|
||||||
|
| MET-006 | Done | Build the initial Gradle project and run its tests; resolve build setup issues before removing the existing Maven build. |
|
||||||
|
| MET-007 | Done | Update .gitignore for Gradle and remove obsolete Maven and Eclipse project files after the Gradle build succeeds. |
|
||||||
|
| MET-008 | Done | Analyze Java sources for deprecated or removed Bukkit/Paper APIs and legacy patterns, including logging, event registration, configuration, player lookup, and player-keyed storage. |
|
||||||
|
| MET-009 | Done | Review custom scripts and resources for required updates to the Gradle build and modern Paper server workflow. |
|
||||||
|
| MET-010 | Done | Refactor plugin logging to use JavaPlugin.getLogger(). |
|
||||||
|
| MET-011 | Done | Refactor event listeners and registration to use Listener, @EventHandler, and the modern plugin manager API. |
|
||||||
|
| MET-012 | Done | Replace deprecated configuration APIs with the modern Bukkit/Paper configuration API, including load, save, and reload behavior. |
|
||||||
|
| MET-013 | Done | Replace Player-keyed storage with UUID-based storage wherever player identity is persisted or tracked. |
|
||||||
|
| MET-014 | Done | Review player lookup logic and use exact or otherwise explicit name-matching behavior where appropriate. |
|
||||||
|
| MET-015 | Done | Refactor remaining code that uses deprecated or removed Bukkit, Spigot, or Paper APIs so it is compatible with the target Paper API. |
|
||||||
|
| MET-016 | Done | Add fast unit tests for plot and home allocation, reservation validation, player lookup and UUID-based ownership, configuration persistence, command validation, and event behavior. Keep domain rules independent of a running Minecraft server and mock external plugin boundaries as needed. |
|
||||||
|
| MET-017 | Done | Add a separate Gradle `integrationTest` source set/task for tests requiring an actual Paper server and compatible WorldGuard/WorldEdit plugins. Verify plugin loading, command registration and dispatch, region-backed reservations and home allocation, and persistence across a server restart. Run it only when explicitly requested; keep it out of the normal `test` and `build` tasks. |
|
||||||
|
| MET-018 | Done | Build the plugin with Gradle and use the provided server scripts to smoke-test it on modern Paper with its required plugins installed. |
|
||||||
|
| MET-019 | Done | Address bugs and incompatibilities found by unit tests, integration tests, or modern Paper server testing. |
|
||||||
|
| MET-020 | Done | Adopt the git-sensitive semantic versioning Gradle plugin used by MobScores and ScoreKeeper, using the existing 0.5-SNAPSHOT version as the migration baseline; verify version/tag behavior and enable the Gradle configuration cache when compatible. |
|
||||||
|
| MET-021 | Done | Update README.md, CONTRIBUTING.md, other documentation, and project scripts with the Gradle build, test, versioning, and modern Paper server workflow. |
|
||||||
|
| MET-022 | Planning | (Optional) Add new features, quality-of-life improvements, or CI configuration after the modernization and documentation tasks are complete. |
|
||||||
|
| MET-023 | Planning | Add an optional home-allocation policy that grants all remaining homes up to a player's limit when they join. |
|
||||||
|
| MET-024 | Planning | Add an optional home-allocation policy that grants at most one additional home per login until the player's limit is reached. |
|
||||||
|
| MET-025 | Planning | Add a configuration option to choose the plot-allocation traversal algorithm: concentric rectangles/cuboids or concentric rings that prefer a free location near the center. |
|
||||||
|
| MET-026 | Planning | Research a world-conversion option for existing Metropolis worlds, including how to safely adapt existing regions and generated roads/plots. |
|
||||||
|
| GIT-009 | Planning | i have three reserve plots and one just got overwritten when a new player joined. |
|
||||||
|
| GIT-008 | Ready | Implement multiple plot sizes by granting multiple plots around each other.
|
||||||
|
| GIT-007 | Planning | Allow buying and selling of homes via an ecnoomy plugin. |
|
||||||
|
| GIT-006 | Done | Add levels support. |
|
||||||
|
| GIT-005 | Planning | Add the possibility to create multiple plot homes in shapes. |
|
||||||
|
| GIT-004 | Done | Add a command to request home allocation. |
|
||||||
|
| GIT-003 | Ready | Add support for multiple homes. |
|
||||||
|
| GIT-002 | Done | Add a command to teleport to a reservation you are part of. |
|
||||||
|
| GIT-001 | Done | Add a command to move to a different home. |
|
||||||
|
|
||||||
|
# GIT-001 - Add a command to move to a different home.
|
||||||
|
**Status:** Done
|
||||||
|
**Depends On:** [GIT-003](#git-003---add-support-for-multiple-homes)
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
`/metropolis-home-move <homeNumber> [playerName]` selects one of a player's existing homes as active. Without a player name, the command acts on the player who ran it; console and authorized administrators can specify an online or cached offline player. Reject non-positive or malformed numbers, unknown players, nonexistent homes, and extra arguments with clear feedback. Persist the selection by player UUID and confirm the change to the sender and, when online, the target player.
|
||||||
|
|
||||||
|
# GIT-002 - Add a command to teleport to a reservation you are part of.
|
||||||
|
**Status:** Done
|
||||||
|
**Depends On:** [GIT-003](#git-003---add-support-for-multiple-homes)
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
`/metropolis-plot-go [playerName]` teleports the sender, or the specified online player, to a Metropolis WorldGuard cuboid reservation where that player is an owner or member. If the player belongs to more than one reservation, report the available region names and require an explicit choice with `/metropolis-plot-go <reservationName> [playerName]`. The explicit-name form only permits destinations where the target is a member/owner, unless the sender has `metropolis.plot.go`. Remove the command-level permission gate so reservation members can use self-service teleportation; enforce admin-only target and membership bypass checks in the command. Report unknown players, missing membership, ambiguous reservations, and reservations without a safe teleport location. Do not report success unless teleportation succeeds.
|
||||||
|
|
||||||
|
### Comments
|
||||||
|
|
||||||
|
Or better yet just use uhome or create your own, integrated that records the home location at the height set in the configuration file, but on the road next to the plot. Using existing plugins to insert into SQL say uhome, would allow privatization and minimal work on your end.
|
||||||
|
|
||||||
|
# GIT-003 - Add support for multiple homes
|
||||||
|
|
||||||
|
**Status:** Done
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
Add a top-level `permissionOverrides` map keyed by permission node. Each rule defines `priority`, `plotMultiplier`, and `maxPlots`; any permission manager can grant the boolean nodes to groups or individual players. For online players, the matching rule with the highest priority wins, with the last matching entry in YAML order breaking ties. An explicit `userOverrides` entry takes precedence over permission rules. Without either override, use `plot.multiplier` and `plot.maxPerPlayer`. Offline-player generation uses username overrides or global defaults because permissions are only queried for online players.
|
||||||
|
|
||||||
|
On join, automatically allocate one home only when the player has no existing home, and restore a valid existing home as active if the saved selection is missing. Additional homes must be explicitly requested with `/metropolis-home-acquire`, up to the player's effective limit. Acquisition allocates the first unused positive home number and makes the new home active.
|
||||||
|
|
||||||
|
The alternative login allocation policies are tracked separately in MET-023 and MET-024; they are not part of the default policy in this task.
|
||||||
|
|
||||||
|
# GIT-004 - Add a command to request home allocation.
|
||||||
|
**Status:** Done
|
||||||
|
**Depends On:** [GIT-003](#git-003---add-support-for-multiple-homes)
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
`/metropolis-home-acquire` gives players their next available home, including the first home when `plot.initial` is `0`. The command allocates the lowest unused positive home number, makes it active, and refuses requests once the player has reached their effective limit. The default policy remains automatic first-home generation, but servers can disable it by setting `plot.initial: 0` and requiring the command for new players.
|
||||||
|
|
||||||
|
# GIT-005 - Add the possibility to create multiple plot homes in shapes.
|
||||||
|
**Status:** Planning
|
||||||
|
**Depends On:** [GIT-006](#git-006---add-levels-support), [MET-025](#met-025---add-a-configuration-option-to-choose-the-plot-allocation-traversal-algorithm-concentric-rectanglescuboids-or-concentric-rings-that-prefer-a-free-location-near-the-center)
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
Add the possibility to create multiple plot homes in contiguous shapes.
|
||||||
|
|
||||||
|
* Line all plots in a line x, y, or z.
|
||||||
|
* Rectangle with width (x), height(y), and length (z).
|
||||||
|
* Sphere closest to a point.
|
||||||
|
|
||||||
|
# GIT-006 - Add levels support.
|
||||||
|
**Status:** Done
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
Add multiple vertical levels to the city. Use the existing `road.level` as the base Y: the bottom of a ground-level plot is at that Y, and `plot.sizeY` defines the full plot height. For example, a five-block-high plot includes its floor at road level, three usable blocks, and a roof block; all five blocks belong to the plot region. Use the same plot-dimension-plus-road-spacing grid pitch vertically as horizontally; when road width is zero, adjacent plot volumes may touch. Do not allocate a plot if its full extent would exceed the world's buildable area. If the search cannot find any valid plot, log an allocation error and do not create the plot or region. The current config example is not intended for a multilevel world; request an updated `config.yml` before implementation. Existing `plot.offsetX`, `plot.offsetY`, and `plot.offsetZ` nudge the grid; `plot.multiplier`, `userOverrides[username].plotMultiplier`, and future `permissionOverrides[permission_name].plotMultiplier` affect plot size.
|
||||||
|
|
||||||
|
Add a maximum-level config value. If unset, default to 1; administrators can increase it to enable more levels. Allocate plots in X, Z, Y axis order: scan X from negative to positive, then increment Z, and only after scanning the X/Z area increment Y to the next level (Y outermost, Z middle, X innermost). For equal-distance candidates, use the same negative-to-positive X ordering. The selectable X/Z traversal shapes are tracked separately in MET-025.
|
||||||
|
|
||||||
|
Streets run along X and avenues run along Z. Streets remain level; avenues connect levels with stairs placed beside plots at a configurable cadence. Add `stairs.*`, `road.streets.*`, and `road.avenues.*` configuration. The street and avenue sections should each contain the current `road.*` settings plus `generateStairs`. Configure stair material, width, and `everyNBlocks`; this cadence is measured in plot-sized avenue blocks, so 3 means stairs beside every third plot along an avenue. Allow stairs to be configured independently for each direction, including different widths. Validate each enabled direction against its corresponding road width; if its stairs are not thinner than that road, log an error but continue plugin startup.
|
||||||
|
|
||||||
|
Keep spawn on its current level. Add independent X, Y, and Z spawn-size multipliers, each expressed as a multiple of the corresponding plot dimension and road spacing so roads line up with differently sized plots. The Y multiplier controls spawn's vertical height upward from `road.level`.
|
||||||
|
|
||||||
|
When allocating plots in an existing world, treat saved WorldGuard regions as occupied: never overwrite a region or create a new region that overlaps an existing one, even if avoiding overlaps leaves irregular gaps between plots and roads. Document this behavior so users know to expect it. Research a possible world-conversion option separately under MET-026; conversion requires further investigation.
|
||||||
|
|
||||||
|
This intentionally breaks the existing config schema: do not add a legacy `road.*` fallback or automatic migration. Document the config changes in release notes and make the breaking change in the 0.6.x release line.
|
||||||
|
|
||||||
|
# GIT-007 - Allow buying and selling of homes via an ecnoomy plugin.
|
||||||
|
**Status:** Blocked
|
||||||
|
**Blockers:** Figure out how to integrate with economy plugins.
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
Allow buying and selling of homes via an ecnoomy plugin. Include decent real estate support including wtb and wts listings as well as an auction facility.
|
||||||
|
|
||||||
|
This should also support not giving free plots to users with none. This way one would have to go out and mine to earn money which could be spent to purchase a plot.
|
||||||
|
|
||||||
|
# GIT-008 - Implement multiple plot sizes by granting multiple plots around each other.
|
||||||
|
**Status:** Ready
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
a= user a
|
||||||
|
x= empty plot
|
||||||
|
s= spawn
|
||||||
|
|
||||||
|
user a get's a small plot which is defined as 1 plot
|
||||||
|
a x x
|
||||||
|
x s x
|
||||||
|
x x x
|
||||||
|
|
||||||
|
user b gets a large plot which is defined as 4 plots
|
||||||
|
x x b b x
|
||||||
|
x a b b x
|
||||||
|
x x s x x
|
||||||
|
x x x x x
|
||||||
|
x x x x x
|
||||||
|
|
||||||
|
# MET-025 - Add a configuration option to choose the plot-allocation traversal algorithm: concentric rectangles/cuboids or concentric rings that prefer a free location near the center.
|
||||||
|
**Status:** Planning
|
||||||
|
**Depends On:** [MET-023], [MET-024]
|
||||||
101
docs/human-tests.md
Normal file
101
docs/human-tests.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Metropolis Manual Pre-release Checklist
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- Use a disposable Paper 26.2 or 26.3 server with Java 25+, WorldGuard 7.0.19, WorldEdit 7.4.5, and Metropolis installed.
|
||||||
|
- Back up the server before testing. Home generation, reservations, and configuration can change world blocks and WorldGuard regions.
|
||||||
|
- Use at least two non-operator player accounts for ownership and protection checks. Operators can bypass protection and hide permission problems.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Manual Scenarios
|
||||||
|
|
||||||
|
### Startup and world setup
|
||||||
|
|
||||||
|
- Start with the required plugins enabled. Confirm Metropolis enables without errors and its commands are registered. Also confirm startup fails clearly if a required dependency or configured world is missing, then restore the valid setup.
|
||||||
|
- On a fresh test world, inspect the `City` and `Spawn` WorldGuard regions. Check their bounds, flags, and priority; verify the configured spawn generation and world-spawn location.
|
||||||
|
- Restart once with existing regions. Confirm Metropolis recognizes them instead of duplicating or resetting them, and that existing city bounds and player homes remain intact.
|
||||||
|
|
||||||
|
### Join and plot allocation
|
||||||
|
|
||||||
|
- Join as a new player. Confirm one home is allocated and the welcome message reports its actual bounds. Check the corresponding `h_1_<UUID>` region has the player as owner and its bounds match the configured plot size.
|
||||||
|
- Set `plot.initial: 0` on a disposable server, restart, and join as a fresh player. Confirm no home is created until the player runs `/metropolis-home-acquire`; then verify the command creates the initial home and makes it active.
|
||||||
|
- Join with a second new player. Confirm the new home is distinct, does not overlap the first home or spawn, and the City region grows to include it.
|
||||||
|
- Start from a disposable world configured with `plot.sizeY` and `plot.maxLevels` from the multi-level example. Confirm the level count is honored, the city does not exceed the world's build height, and upper plots are only allocated above footprints fully supported at every lower level. With a 1×1 spawn and a 2×2 home size, confirm a new home is allocated at base level rather than above the partially supported spawn.
|
||||||
|
- Generate homes at base and elevated levels, then verify each full home cuboid is inside the WorldGuard `City` region. Add a home/reservation extending the city bounds and confirm City expands without shrinking its existing bounds or flags.
|
||||||
|
- Set positive and negative `plot.offsetX`, `plot.offsetY`, and `plot.offsetZ` values on a disposable world. Verify plot regions, road bands, stair flights, and Spawn share the shifted grid origin; allocate multiple plots across negative coordinates and confirm logical-cell spacing stays consistent. Confirm changing offsets does not move existing saved regions; Metropolis-managed homes and reservations should retain their bounds and remain recognized. Treat automatic conversion of other saved regions as unverified.
|
||||||
|
- Set `spawn.sizeX`, `spawn.sizeY`, and `spawn.sizeZ` to distinct positive values on a fresh disposable world. Confirm the Spawn region spans the corresponding plot-plus-road logical cells horizontally and the configured plot-height multiple vertically. Check blocks outside those bounds remain unchanged; configure a height exceeding the world's build limit and confirm startup rejects it clearly.
|
||||||
|
- Join again as an existing player. Confirm the same home is selected and no duplicate region or plot is created.
|
||||||
|
- Reserve an area before allocating another home. Confirm future allocations skip it. Repeat with reservations near the city edge and near existing roads to look for overlaps or gaps.
|
||||||
|
- Create an unrelated saved WorldGuard region in the next candidate area, then allocate a home and confirm it is skipped. Repeat with a non-cuboid region and verify the full bounding cuboid is avoided; confirm `City` itself does not prevent allocation.
|
||||||
|
- Set a small WorldBorder in a disposable world, occupy or reserve the remaining valid grid cells, and request another home. Confirm search grows beyond 256 rings but never places a plot/road footprint outside the border; when no supported cell remains, check the error log and confirm no region is created.
|
||||||
|
- Test a user override that grants a different plot multiplier or home limit. Confirm the generated bounds and allowed number of homes match the override, and that the configured global defaults still apply to other players.
|
||||||
|
|
||||||
|
### Generated blocks and geometry
|
||||||
|
|
||||||
|
- Inspect a newly allocated home and adjoining roads. Check road width, level, material, clearance, plot floor, support blocks, and optional sign against the active config.
|
||||||
|
- Generate one plot in a disposable world and confirm each adjoining road is fully paved to the configured width. Generate a plot directly beside it: confirm already-correct shared road blocks are untouched, any missing half-width legacy section is filled, and stair treads/openings remain intact.
|
||||||
|
- Configure streets and avenues with different widths, materials, clearances, and support settings. Verify streets along X use only the street options and avenues along Z use only avenue options.
|
||||||
|
- Enable stairs for both sections with different widths/cadences, each narrower than its road. Generate a plot on a level below `plot.maxLevels`; verify street stairs ascend along positive X at north/south borders and avenue stairs ascend along positive Z at east/west borders. Confirm lower roads remain unchanged, the first treads start one block above and forward from the road-level landing, connected treads use inverted backing, and top treads reach the upper road Y. Check each upper road leaves exactly three blocks open before its top tread only across that stair width. For a 2-plot-wide/long home with cadence 1, confirm two centered runs along each eligible edge. Repeat with higher levels to confirm existing flights remain open.
|
||||||
|
- Test generation over varied terrain, including water, caves, uneven ground, and existing structures. Confirm generated floors and roads do not leave unsafe gaps or erase blocks outside their intended area.
|
||||||
|
- Enable and disable optional floor, support, sign, spawn, world-spawn, and perimeter-wall settings one at a time on a disposable world. Confirm each setting changes only its intended behavior.
|
||||||
|
- Allocate plots in several directions and near the world origin. Check road joins, plot dimensions, City region expansion, and boundary coordinates for symmetry and off-by-one errors.
|
||||||
|
|
||||||
|
### Reservations and protection
|
||||||
|
|
||||||
|
- Reserve one plot with explicit coordinates. Then make a WorldEdit selection and reserve a second plot using `/metropolis-plot-reserve <name>`.
|
||||||
|
- Try invalid coordinates, a duplicate region name, and bounds that intersect a home or another reservation. Confirm the command reports a useful failure and does not silently overwrite an existing region.
|
||||||
|
- Check `/metropolis-home-list` and `/metropolis-plot-go <plotName>` for both occupied homes and reservations; confirm teleport destinations are safe and inside the requested plot.
|
||||||
|
- As the home owner, build and interact inside the home. As another non-op player, try the same actions. Verify ownership and protection behave as intended in homes, reservations, City, and Spawn.
|
||||||
|
- Verify representative protection flags in-game, including PvP, mob damage/spawning, explosions, and fire/lava behavior where applicable. Run `/metropolis-flag-reset` and confirm the expected flags are restored without changing region bounds or ownership.
|
||||||
|
- Restart the server and confirm reservations and their protection are still present in WorldGuard and remain excluded from home allocation.
|
||||||
|
- Add a non-op test player as a WorldGuard member of a Metropolis reservation. Run `/metropolis-plot-go` and confirm they arrive inside that reservation at a safe location; repeat with an owner.
|
||||||
|
- Confirm a player who is neither owner nor member cannot use a reservation name to teleport there. Check that a region they own/member outside homes, City, and Spawn is treated as expected by the Metropolis reservation lookup.
|
||||||
|
- Add a player to two reservations. Confirm the no-argument form reports both names without teleporting, then specify one reservation and verify the chosen destination.
|
||||||
|
- As an authorized admin, target another online member with `/metropolis-plot-go <playerName>`. Confirm the target receives a notification. Repeat without `metropolis.plot.go` and verify targeting another player is denied.
|
||||||
|
- Test a reservation with no safe destination. Confirm the command reports failure and does not claim teleportation succeeded. Also test console use with an online player name and an unknown/offline name.
|
||||||
|
|
||||||
|
### Commands, permissions, and player state
|
||||||
|
|
||||||
|
- For a new player with no overrides, join and confirm exactly one `h_1_<UUID>` home is created. Rejoin and confirm no additional home is created.
|
||||||
|
- Configure two `permissionOverrides` tiers in `plugins/Metropolis/config.yml`, then restart. Grant each permission node to a test user and to a test group through the server's permission manager. Confirm the player's effective `plotMultiplier` and `maxPlots` match the granted rule.
|
||||||
|
- Give one player multiple configured permission nodes with different priorities. Confirm the highest-priority rule controls both values; then give two matching rules the same priority and confirm the last matching YAML entry wins.
|
||||||
|
- Give a player both a permission tier and a matching `userOverrides` entry. Confirm the username override wins. Remove both overrides from another player and confirm `plot.multiplier` and `plot.maxPerPlayer` are used as defaults.
|
||||||
|
- Change or revoke a permission while the player is online, then acquire another home. Confirm the next acquisition uses the currently granted tier without requiring a restart.
|
||||||
|
- With plot multipliers of 1 and 2, acquire homes and inspect their WorldGuard bounds. Confirm size follows the tier, a larger footprint does not overlap an existing home, and the multiplier is applied to the X and Z footprint only once.
|
||||||
|
- Exercise `/metropolis-home-acquire` below and at each effective home limit. Confirm every successful request creates a distinct next-numbered home, creates none beyond the limit, and does not change other players' homes.
|
||||||
|
- After acquiring a second home, verify it becomes active: check the player's selected home in `plugins/Metropolis/currentHomes.yml` and use `/metropolis-home-go` to confirm travel to that home. Switch homes with `/metropolis-home-move` and verify the selection changes as expected.
|
||||||
|
- Run `/metropolis-home-move <homeNumber>` as a player with multiple homes. Verify the command confirms the selected home, updates `currentHomes.yml`, and `/metropolis-home-go` now travels to it.
|
||||||
|
- Run `/metropolis-home-move <homeNumber> <playerName>` as an authorized administrator, then repeat from console. Check an online target is notified and a cached offline target uses the selected home on their next join.
|
||||||
|
- Try zero, negative, non-numeric, nonexistent home numbers, unknown player names, and extra arguments. Confirm each request is rejected with useful feedback and does not change the stored active home.
|
||||||
|
- Restart after acquiring multiple homes. Confirm all home regions remain owned by the player, the active home selection persists, and joining does not allocate another first home.
|
||||||
|
- Generate a home for an offline player administratively. Confirm permission tiers are not assumed for offline players; the username override or global settings determine plot size and limit.
|
||||||
|
- Exercise `/metropolis-home-go` with a safe bed spawn inside the home and with no such bed spawn. Confirm teleportation chooses a valid location inside the home and reports a clear failure if no safe location exists.
|
||||||
|
- Exercise `/metropolis-home-move` for a player with multiple homes. Confirm the selected home persists across reconnect and restart; reject nonexistent home numbers.
|
||||||
|
- As an administrator, test `/metropolis-home-generate`, `/metropolis-home-evict`, and `/metropolis-home-list` with online and previously seen offline players. Confirm the intended region ownership changes, persistence, and subsequent allocation behavior.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Automated Coverage (Separate from This Checklist)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
50
docs/modrinth-description.md
Normal file
50
docs/modrinth-description.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
**Metropolis**
|
||||||
|
|
||||||
|
Metropolis grows a protected Minecraft city as players join. It assigns home plots connected by roads, expands the protected city region, and lets players and administrators manage homes and reserve plots.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Paper 26.2 or 26.3.
|
||||||
|
- Java 25 or newer.
|
||||||
|
- WorldGuard 7.0.19 and WorldEdit 7.4.5.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- Automatically assigns a protected home plot when a player joins.
|
||||||
|
- Allows players to acquire additional homes up to their effective limit, configured globally, per user, or through permission-manager groups.
|
||||||
|
- Supports permission-based plot-size and home-limit tiers with configurable priorities; explicit per-user overrides take precedence.
|
||||||
|
- Lets players and authorized administrators select an existing home as active; selections persist by player UUID across restarts.
|
||||||
|
- Expands the protected city region as plots are occupied.
|
||||||
|
- Configures plot dimensions, roads, floors, supports, signs, spawn behavior, and perimeter walls.
|
||||||
|
- Reserves named plots by coordinates or a WorldEdit selection.
|
||||||
|
- Lets WorldGuard owners and members teleport to reservations they belong to, with a named choice when they have multiple.
|
||||||
|
- Tracks home ownership and selected homes by player UUID.
|
||||||
|
- Supports home and plot teleportation, administration, and WorldGuard protection flags.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Metropolis creates `plugins/Metropolis/config.yml` on first startup. Configure plot limits and dimensions, roads, floors and supports, signs, spawn behavior, walls, world name, and per-player overrides. Use Bukkit material names; legacy numeric material IDs are also accepted for existing configurations.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- `/metropolis` - Displays the plugin version.
|
||||||
|
- `/metropolis-home-acquire` - Acquires another available home plot.
|
||||||
|
- `/metropolis-home-generate <playerName>` - Generates a home for a known online or cached offline player.
|
||||||
|
- `/metropolis-home-list` - Lists occupied and reserved plots.
|
||||||
|
- `/metropolis-home-go [playerName]` - Teleports to a home.
|
||||||
|
- `/metropolis-home-move <homeNumber> [playerName]` - Selects an existing home as active. The player name defaults to the sender; authorized administrators and console can target online or cached offline players.
|
||||||
|
- `/metropolis-home-evict <playerName>` - Removes a player's ownership from their selected home region.
|
||||||
|
- `/metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>` - Reserves a region by coordinates; players can use `/metropolis-plot-reserve <name>` with a WorldEdit selection.
|
||||||
|
- `/metropolis-plot-go [playerName]` - Teleports to the sender's or an online player's reservation. Specify a reservation name to choose among multiple memberships; `metropolis.plot.go` allows targeting others or bypassing membership.
|
||||||
|
- `/metropolis-flag-reset` - Reapplies Metropolis protection flags.
|
||||||
|
- `/metropolis-debug-generatetesthomes <count>` - Generates test homes; intended for development servers.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `./gradlew build` from the project directory. The plugin jar is created in `build/libs/`.
|
||||||
13
docs/modrinth.json
Normal file
13
docs/modrinth.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "Metropolis",
|
||||||
|
"url": "https://modrinth.com/plugin/metropolis-builder",
|
||||||
|
"summary": "Grow a protected Minecraft city with automatic UUID-owned homes, configurable roads, WorldGuard protection, and reserved plots.",
|
||||||
|
"visibility": "public",
|
||||||
|
"links": {
|
||||||
|
"issueTracker": "https://github.com/headhunter45/Metropolis/issues",
|
||||||
|
"sourceCode": "https://github.com/headhunter45/Metropolis",
|
||||||
|
"wikiPage": null,
|
||||||
|
"discordInvite": null,
|
||||||
|
"donationLinks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
docs/project-description.md
Normal file
8
docs/project-description.md
Normal file
@@ -0,0 +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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@@ -15,110 +32,114 @@ import org.bukkit.configuration.file.YamlConfiguration;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class MetropolisServerIntegrationTest {
|
class MetropolisServerIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
void runningPaperLoadsMetropolis() throws IOException {
|
void runningPaperLoadsMetropolis() throws IOException {
|
||||||
String installedPlugins;
|
String installedPlugins;
|
||||||
try (RconClient rcon = connectToConfiguredServer()) {
|
try (RconClient rcon = connectToConfiguredServer()) {
|
||||||
installedPlugins = rcon.execute("plugins");
|
installedPlugins = rcon.execute("plugins");
|
||||||
}
|
}
|
||||||
assertTrue(installedPlugins.contains("Metropolis"), installedPlugins);
|
assertTrue(installedPlugins.contains("Metropolis"), installedPlugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void plotReservationIsPersistedByTheLoadedWorldGuardPlugin() throws IOException {
|
void plotReservationIsPersistedByTheLoadedWorldGuardPlugin() throws IOException {
|
||||||
String regionId = "metropolis-it-" + UUID.randomUUID().toString().substring(0, 8);
|
String regionId = "metropolis-it-" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
try (RconClient rcon = connectToConfiguredServer()) {
|
try (RconClient rcon = connectToConfiguredServer()) {
|
||||||
String response = rcon.execute("metropolis-plot-reserve " + regionId + " 100 60 100 105 70 105");
|
String response =
|
||||||
assertTrue(response.contains("Reserved plot region " + regionId), response);
|
rcon.execute("metropolis-plot-reserve " + regionId + " 100 60 100 105 70 105");
|
||||||
}
|
assertTrue(response.contains("Reserved plot region " + regionId), response);
|
||||||
|
}
|
||||||
|
|
||||||
Path serverDirectory = Path.of(requiredEnvironment("METROPOLIS_SERVER_DIR"));
|
Path serverDirectory = Path.of(requiredEnvironment("METROPOLIS_SERVER_DIR"));
|
||||||
String worldName = System.getenv().getOrDefault("METROPOLIS_WORLD", "world");
|
String worldName = System.getenv().getOrDefault("METROPOLIS_WORLD", "world");
|
||||||
Path regionsFile = serverDirectory.resolve("plugins/WorldGuard/worlds")
|
Path regionsFile =
|
||||||
.resolve(worldName)
|
serverDirectory
|
||||||
.resolve("regions.yml");
|
.resolve("plugins/WorldGuard/worlds")
|
||||||
YamlConfiguration regions = YamlConfiguration.loadConfiguration(regionsFile.toFile());
|
.resolve(worldName)
|
||||||
|
.resolve("regions.yml");
|
||||||
|
YamlConfiguration regions = YamlConfiguration.loadConfiguration(regionsFile.toFile());
|
||||||
|
|
||||||
assertTrue(regions.contains("regions." + regionId), "WorldGuard did not persist region " + regionId);
|
assertTrue(
|
||||||
}
|
regions.contains("regions." + regionId), "WorldGuard did not persist region " + regionId);
|
||||||
|
}
|
||||||
|
|
||||||
private static RconClient connectToConfiguredServer() throws IOException {
|
private static RconClient connectToConfiguredServer() throws IOException {
|
||||||
String host = requiredEnvironment("METROPOLIS_RCON_HOST");
|
String host = requiredEnvironment("METROPOLIS_RCON_HOST");
|
||||||
int port = Integer.parseInt(requiredEnvironment("METROPOLIS_RCON_PORT"));
|
int port = Integer.parseInt(requiredEnvironment("METROPOLIS_RCON_PORT"));
|
||||||
String password = requiredEnvironment("METROPOLIS_RCON_PASSWORD");
|
String password = requiredEnvironment("METROPOLIS_RCON_PASSWORD");
|
||||||
return new RconClient(host, port, password);
|
return new RconClient(host, port, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String requiredEnvironment(String name) {
|
private static String requiredEnvironment(String name) {
|
||||||
String value = System.getenv(name);
|
String value = System.getenv(name);
|
||||||
if (value == null || value.isBlank()) {
|
if (value == null || value.isBlank()) {
|
||||||
throw new IllegalStateException("Set " + name + " to run integrationTest.");
|
throw new IllegalStateException("Set " + name + " to run integrationTest.");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class RconClient implements AutoCloseable {
|
private static final class RconClient implements AutoCloseable {
|
||||||
private final Socket socket = new Socket();
|
private final Socket socket = new Socket();
|
||||||
private final DataInputStream input;
|
private final DataInputStream input;
|
||||||
private final DataOutputStream output;
|
private final DataOutputStream output;
|
||||||
private int requestId = 1;
|
private int requestId = 1;
|
||||||
|
|
||||||
private RconClient(String host, int port, String password) throws IOException {
|
private RconClient(String host, int port, String password) throws IOException {
|
||||||
socket.connect(new InetSocketAddress(host, port), 5000);
|
socket.connect(new InetSocketAddress(host, port), 5000);
|
||||||
socket.setSoTimeout(5000);
|
socket.setSoTimeout(5000);
|
||||||
input = new DataInputStream(socket.getInputStream());
|
input = new DataInputStream(socket.getInputStream());
|
||||||
output = new DataOutputStream(socket.getOutputStream());
|
output = new DataOutputStream(socket.getOutputStream());
|
||||||
int authId = requestId++;
|
int authId = requestId++;
|
||||||
Packet response = send(authId, 3, password);
|
Packet response = send(authId, 3, password);
|
||||||
if (response.id != authId) {
|
if (response.id != authId) {
|
||||||
throw new IOException("RCON authentication failed.");
|
throw new IOException("RCON authentication failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String execute(String command) throws IOException {
|
private String execute(String command) throws IOException {
|
||||||
return send(requestId++, 2, command).payload;
|
return send(requestId++, 2, command).payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Packet send(int id, int type, String payload) throws IOException {
|
private Packet send(int id, int type, String payload) throws IOException {
|
||||||
byte[] data = payload.getBytes(StandardCharsets.UTF_8);
|
byte[] data = payload.getBytes(StandardCharsets.UTF_8);
|
||||||
writeLittleEndianInt(data.length + 10);
|
writeLittleEndianInt(data.length + 10);
|
||||||
writeLittleEndianInt(id);
|
writeLittleEndianInt(id);
|
||||||
writeLittleEndianInt(type);
|
writeLittleEndianInt(type);
|
||||||
output.write(data);
|
output.write(data);
|
||||||
output.writeByte(0);
|
output.writeByte(0);
|
||||||
output.writeByte(0);
|
output.writeByte(0);
|
||||||
output.flush();
|
output.flush();
|
||||||
|
|
||||||
int length = readLittleEndianInt();
|
int length = readLittleEndianInt();
|
||||||
byte[] packet = input.readNBytes(length);
|
byte[] packet = input.readNBytes(length);
|
||||||
if (packet.length != length || length < 10) {
|
if (packet.length != length || length < 10) {
|
||||||
throw new IOException("Received an invalid RCON packet.");
|
throw new IOException("Received an invalid RCON packet.");
|
||||||
}
|
}
|
||||||
int responseId = littleEndianInt(packet, 0);
|
int responseId = littleEndianInt(packet, 0);
|
||||||
String response = new String(packet, 8, length - 10, StandardCharsets.UTF_8);
|
String response = new String(packet, 8, length - 10, StandardCharsets.UTF_8);
|
||||||
return new Packet(responseId, response);
|
return new Packet(responseId, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLittleEndianInt(int value) throws IOException {
|
private void writeLittleEndianInt(int value) throws IOException {
|
||||||
output.writeInt(Integer.reverseBytes(value));
|
output.writeInt(Integer.reverseBytes(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int readLittleEndianInt() throws IOException {
|
private int readLittleEndianInt() throws IOException {
|
||||||
return Integer.reverseBytes(input.readInt());
|
return Integer.reverseBytes(input.readInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int littleEndianInt(byte[] bytes, int offset) {
|
private static int littleEndianInt(byte[] bytes, int offset) {
|
||||||
return (bytes[offset] & 0xff)
|
return (bytes[offset] & 0xff)
|
||||||
| ((bytes[offset + 1] & 0xff) << 8)
|
| ((bytes[offset + 1] & 0xff) << 8)
|
||||||
| ((bytes[offset + 2] & 0xff) << 16)
|
| ((bytes[offset + 2] & 0xff) << 16)
|
||||||
| (bytes[offset + 3] << 24);
|
| (bytes[offset + 3] << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Packet(int id, String payload) {}
|
private record Packet(int id, String payload) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.AvenueStairwayLayout.Flight;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.AvenueStairwayLayout.Step;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.block.data.Bisected;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.block.data.type.Stairs;
|
||||||
|
|
||||||
|
public final class AvenueStairwayBuilder {
|
||||||
|
private AvenueStairwayBuilder() {}
|
||||||
|
|
||||||
|
public static void build(World world, Flight flight, Material stairMaterial) {
|
||||||
|
BlockFace ascendingFacing = flight.side().runsAlongX() ? BlockFace.EAST : BlockFace.SOUTH;
|
||||||
|
BlockFace descendingFacing = ascendingFacing.getOppositeFace();
|
||||||
|
for (Step step : flight.stairBlocks()) {
|
||||||
|
Block stairBlock = world.getBlockAt(step.x(), step.y(), step.z());
|
||||||
|
stairBlock.setType(stairMaterial, false);
|
||||||
|
BlockData blockData = stairBlock.getBlockData();
|
||||||
|
if (blockData instanceof Stairs stairs) {
|
||||||
|
stairs.setFacing(ascendingFacing);
|
||||||
|
stairs.setHalf(Bisected.Half.BOTTOM);
|
||||||
|
stairs.setShape(Stairs.Shape.STRAIGHT);
|
||||||
|
stairBlock.setBlockData(stairs, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Step backing : flight.invertedBackingBlocks()) {
|
||||||
|
Block supportBlock = world.getBlockAt(backing.x(), backing.y(), backing.z());
|
||||||
|
supportBlock.setType(stairMaterial, false);
|
||||||
|
BlockData supportData = supportBlock.getBlockData();
|
||||||
|
if (supportData instanceof Stairs stairs) {
|
||||||
|
stairs.setFacing(descendingFacing);
|
||||||
|
stairs.setHalf(Bisected.Half.TOP);
|
||||||
|
stairs.setShape(Stairs.Shape.STRAIGHT);
|
||||||
|
supportBlock.setBlockData(stairs, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Step opening : flight.roadOpenings()) {
|
||||||
|
world.getBlockAt(opening.x(), opening.y(), opening.z()).setType(Material.AIR, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class AvenueStairwayLayout {
|
||||||
|
private AvenueStairwayLayout() {}
|
||||||
|
|
||||||
|
public static List<Step> centeredEastAvenue(
|
||||||
|
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise) {
|
||||||
|
return stairBlocks(plot, roadWidth, stairWidth, lowerY, rise, Side.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Flight> forUpperRoadSegments(
|
||||||
|
Cuboid plot,
|
||||||
|
int plotSizeZ,
|
||||||
|
int gridSizeZ,
|
||||||
|
int firstBlockIndex,
|
||||||
|
int everyNBlocks,
|
||||||
|
int avenueWidth,
|
||||||
|
int stairWidth,
|
||||||
|
int lowerY,
|
||||||
|
int rise,
|
||||||
|
int openingLength) {
|
||||||
|
if (plotSizeZ < 1 || gridSizeZ < plotSizeZ || everyNBlocks < 1) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Flight> flights = new ArrayList<>();
|
||||||
|
int segmentIndex = 0;
|
||||||
|
for (int segmentStart = plot.minZ;
|
||||||
|
segmentStart <= plot.maxZ;
|
||||||
|
segmentStart += gridSizeZ, segmentIndex++) {
|
||||||
|
int avenueBlockIndex = firstBlockIndex + segmentIndex;
|
||||||
|
if (Math.floorMod(avenueBlockIndex + 1, everyNBlocks) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cuboid segment =
|
||||||
|
new Cuboid(
|
||||||
|
plot.minX,
|
||||||
|
plot.minY,
|
||||||
|
segmentStart,
|
||||||
|
plot.maxX,
|
||||||
|
plot.maxY,
|
||||||
|
Math.min(segmentStart + plotSizeZ - 1, plot.maxZ));
|
||||||
|
for (Side side : List.of(Side.EAST, Side.WEST)) {
|
||||||
|
List<Step> stairs = stairBlocks(segment, avenueWidth, stairWidth, lowerY, rise, side);
|
||||||
|
if (stairs.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<Step> backings = invertedBackings(stairs, lowerY);
|
||||||
|
List<Step> openings =
|
||||||
|
upperRoadOpening(stairs, avenueWidth, stairWidth, lowerY, rise, openingLength, side);
|
||||||
|
flights.add(new Flight(side, stairs, backings, openings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(flights);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Flight> forUpperStreetSegments(
|
||||||
|
Cuboid plot,
|
||||||
|
int plotSizeX,
|
||||||
|
int gridSizeX,
|
||||||
|
int firstBlockIndex,
|
||||||
|
int everyNBlocks,
|
||||||
|
int streetWidth,
|
||||||
|
int stairWidth,
|
||||||
|
int lowerY,
|
||||||
|
int rise,
|
||||||
|
int openingLength) {
|
||||||
|
if (plotSizeX < 1 || gridSizeX < plotSizeX || everyNBlocks < 1) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Flight> flights = new ArrayList<>();
|
||||||
|
int segmentIndex = 0;
|
||||||
|
for (int segmentStart = plot.minX;
|
||||||
|
segmentStart <= plot.maxX;
|
||||||
|
segmentStart += gridSizeX, segmentIndex++) {
|
||||||
|
if (Math.floorMod(firstBlockIndex + segmentIndex + 1, everyNBlocks) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cuboid segment =
|
||||||
|
new Cuboid(
|
||||||
|
segmentStart,
|
||||||
|
plot.minY,
|
||||||
|
plot.minZ,
|
||||||
|
Math.min(segmentStart + plotSizeX - 1, plot.maxX),
|
||||||
|
plot.maxY,
|
||||||
|
plot.maxZ);
|
||||||
|
for (Side side : List.of(Side.NORTH, Side.SOUTH)) {
|
||||||
|
List<Step> stairs = stairBlocks(segment, streetWidth, stairWidth, lowerY, rise, side);
|
||||||
|
if (stairs.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
flights.add(
|
||||||
|
new Flight(
|
||||||
|
side,
|
||||||
|
stairs,
|
||||||
|
invertedBackings(stairs, lowerY),
|
||||||
|
upperRoadOpening(
|
||||||
|
stairs, streetWidth, stairWidth, lowerY, rise, openingLength, side)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(flights);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Step> upperRoadOpening(
|
||||||
|
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
|
||||||
|
List<Step> stairs = centeredEastAvenue(plot, roadWidth, stairWidth, lowerY, rise);
|
||||||
|
return upperRoadOpening(stairs, roadWidth, stairWidth, lowerY, rise, openingLength, Side.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Step> upperRoadOpening(
|
||||||
|
List<Step> stairs,
|
||||||
|
int roadWidth,
|
||||||
|
int stairWidth,
|
||||||
|
int lowerY,
|
||||||
|
int rise,
|
||||||
|
int openingLength,
|
||||||
|
Side side) {
|
||||||
|
if (stairs.isEmpty() || openingLength < 1) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
Step topStair = stairs.get(stairs.size() - 1);
|
||||||
|
Step firstStair = stairs.getFirst();
|
||||||
|
int upperRoadY = lowerY + rise;
|
||||||
|
List<Step> opening = new ArrayList<>(stairWidth * openingLength);
|
||||||
|
for (int widthIndex = 0; widthIndex < stairWidth; widthIndex++) {
|
||||||
|
for (int lengthIndex = openingLength; lengthIndex > 0; lengthIndex--) {
|
||||||
|
opening.add(
|
||||||
|
side.runsAlongX()
|
||||||
|
? new Step(topStair.x() - lengthIndex, upperRoadY, firstStair.z() + widthIndex)
|
||||||
|
: new Step(firstStair.x() + widthIndex, upperRoadY, topStair.z() - lengthIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(opening);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Flight forUpperRoad(
|
||||||
|
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
|
||||||
|
List<Step> stairs = centeredEastAvenue(plot, roadWidth, stairWidth, lowerY, rise);
|
||||||
|
if (stairs.isEmpty()) {
|
||||||
|
return new Flight(Side.EAST, List.of(), List.of(), List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Flight(
|
||||||
|
Side.EAST,
|
||||||
|
stairs,
|
||||||
|
invertedBackings(stairs, lowerY),
|
||||||
|
upperRoadOpening(stairs, roadWidth, stairWidth, lowerY, rise, openingLength, Side.EAST));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Step> invertedBackings(List<Step> stairs, int lowerY) {
|
||||||
|
List<Step> invertedBackings = new ArrayList<>();
|
||||||
|
for (Step stair : stairs) {
|
||||||
|
if (stair.y() > lowerY + 1) {
|
||||||
|
invertedBackings.add(new Step(stair.x(), stair.y() - 1, stair.z()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(invertedBackings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Step> stairBlocks(
|
||||||
|
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, Side side) {
|
||||||
|
int plotLength = side.runsAlongX() ? plot.maxX - plot.minX + 1 : plot.maxZ - plot.minZ + 1;
|
||||||
|
if (roadWidth < 1
|
||||||
|
|| stairWidth < 1
|
||||||
|
|| stairWidth >= roadWidth
|
||||||
|
|| rise < 1
|
||||||
|
|| rise + 2 > plotLength) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
int firstRun = (side.runsAlongX() ? plot.minX : plot.minZ) + (plotLength - rise) / 2 + 1;
|
||||||
|
int firstCross =
|
||||||
|
switch (side) {
|
||||||
|
case EAST -> plot.maxX + 1 + (roadWidth - stairWidth) / 2;
|
||||||
|
case WEST -> plot.minX - roadWidth + (roadWidth - stairWidth) / 2;
|
||||||
|
case NORTH -> plot.minZ - roadWidth + (roadWidth - stairWidth) / 2;
|
||||||
|
case SOUTH -> plot.maxZ + 1 + (roadWidth - stairWidth) / 2;
|
||||||
|
};
|
||||||
|
List<Step> steps = new ArrayList<>(stairWidth * rise);
|
||||||
|
for (int riseIndex = 0; riseIndex < rise; riseIndex++) {
|
||||||
|
for (int widthIndex = 0; widthIndex < stairWidth; widthIndex++) {
|
||||||
|
steps.add(
|
||||||
|
side.runsAlongX()
|
||||||
|
? new Step(firstRun + riseIndex, lowerY + riseIndex + 1, firstCross + widthIndex)
|
||||||
|
: new Step(firstCross + widthIndex, lowerY + riseIndex + 1, firstRun + riseIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Side {
|
||||||
|
EAST,
|
||||||
|
WEST,
|
||||||
|
NORTH,
|
||||||
|
SOUTH;
|
||||||
|
|
||||||
|
public boolean runsAlongX() {
|
||||||
|
return this == NORTH || this == SOUTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Step(int x, int y, int z) {}
|
||||||
|
|
||||||
|
public record Flight(
|
||||||
|
Side side,
|
||||||
|
List<Step> stairBlocks,
|
||||||
|
List<Step> invertedBackingBlocks,
|
||||||
|
List<Step> roadOpenings) {
|
||||||
|
public Flight {
|
||||||
|
stairBlocks = List.copyOf(stairBlocks);
|
||||||
|
invertedBackingBlocks = List.copyOf(invertedBackingBlocks);
|
||||||
|
roadOpenings = List.copyOf(roadOpenings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
public final class CityRegionBounds {
|
||||||
|
private CityRegionBounds() {}
|
||||||
|
|
||||||
|
public static Cuboid expandToContain(
|
||||||
|
Cuboid existing, Cuboid requested, Iterable<Cuboid> occupiedRegions) {
|
||||||
|
int minX = Math.min(existing.minX, requested.minX);
|
||||||
|
int minY = Math.min(existing.minY, requested.minY);
|
||||||
|
int minZ = Math.min(existing.minZ, requested.minZ);
|
||||||
|
int maxX = Math.max(existing.maxX, requested.maxX);
|
||||||
|
int maxY = Math.max(existing.maxY, requested.maxY);
|
||||||
|
int maxZ = Math.max(existing.maxZ, requested.maxZ);
|
||||||
|
|
||||||
|
for (Cuboid occupied : occupiedRegions) {
|
||||||
|
minX = Math.min(minX, occupied.minX);
|
||||||
|
minY = Math.min(minY, occupied.minY);
|
||||||
|
minZ = Math.min(minZ, occupied.minZ);
|
||||||
|
maxX = Math.max(maxX, occupied.maxX);
|
||||||
|
maxY = Math.max(maxY, occupied.maxY);
|
||||||
|
maxZ = Math.max(maxZ, occupied.maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,220 +1,265 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
|
||||||
import org.bukkit.Location;
|
|
||||||
|
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
|
||||||
public class Cuboid implements Comparable<Cuboid> {
|
public class Cuboid implements Comparable<Cuboid> {
|
||||||
private int id;
|
private int id;
|
||||||
|
|
||||||
public int minX;
|
|
||||||
public int minY;
|
|
||||||
public int minZ;
|
|
||||||
|
|
||||||
public int maxX;
|
|
||||||
public int maxY;
|
|
||||||
public int maxZ;
|
|
||||||
|
|
||||||
public int getId(){
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(int id){
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cuboid(int minX, int minY, int minZ, int maxX, int maxY, int maxZ){
|
|
||||||
this.minX = minX;
|
|
||||||
this.minY = minY;
|
|
||||||
this.minZ = minZ;
|
|
||||||
this.maxX = maxX;
|
|
||||||
this.maxY = maxY;
|
|
||||||
this.maxZ = maxZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cuboid(BlockVector3 min, BlockVector3 max) {
|
|
||||||
this.minX = min.x();
|
|
||||||
this.minY = min.y();
|
|
||||||
this.minZ = min.z();
|
|
||||||
this.maxX = max.x();
|
|
||||||
this.maxY = max.y();
|
|
||||||
this.maxZ = max.z();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cuboid() {
|
public int minX;
|
||||||
this.minX = 0;
|
public int minY;
|
||||||
this.minY = 0;
|
public int minZ;
|
||||||
this.minZ = 0;
|
|
||||||
this.maxX = 0;
|
|
||||||
this.maxY = 0;
|
|
||||||
this.maxZ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cuboid(Region selection) {
|
public int maxX;
|
||||||
this.minX = selection.getMinimumPoint().x();
|
public int maxY;
|
||||||
this.minY = selection.getMinimumPoint().y();
|
public int maxZ;
|
||||||
this.minZ = selection.getMinimumPoint().z();
|
|
||||||
this.maxX = selection.getMaximumPoint().x();
|
|
||||||
this.maxY = selection.getMaximumPoint().y();
|
|
||||||
this.maxZ = selection.getMaximumPoint().z();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockVector3 getMin(){
|
public int getId() {
|
||||||
return BlockVector3.at(minX, minY, minZ);
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector3 getMax(){
|
|
||||||
return BlockVector3.at(maxX, maxY, maxZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public void setId(int id) {
|
||||||
public int compareTo(Cuboid o) {
|
this.id = id;
|
||||||
BlockVector3 min = getMin();
|
}
|
||||||
BlockVector3 otherMin = o.getMin();
|
|
||||||
|
|
||||||
if(min.x() < otherMin.x()){
|
|
||||||
return -1;
|
|
||||||
}else if(min.x() > otherMin.x()){
|
|
||||||
return 1;
|
|
||||||
}else if(min.z() < otherMin.z()){
|
|
||||||
return -1;
|
|
||||||
}else if(min.z() > otherMin.z()){
|
|
||||||
return 1;
|
|
||||||
}else if(min.y() < otherMin.y()){
|
|
||||||
return -1;
|
|
||||||
}else if(min.y() > otherMin.y()){
|
|
||||||
return 1;
|
|
||||||
}else{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int compareBlockVectors(BlockVector3 v1, BlockVector3 v2){
|
public Cuboid(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||||
if(v1 == null){
|
this.minX = minX;
|
||||||
if(v2 == null){
|
this.minY = minY;
|
||||||
return 0;
|
this.minZ = minZ;
|
||||||
}else{
|
this.maxX = maxX;
|
||||||
return -1;
|
this.maxY = maxY;
|
||||||
}
|
this.maxZ = maxZ;
|
||||||
}else if(v2 == null){
|
}
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(v1.x() < v2.x()){
|
|
||||||
return -1;
|
|
||||||
}else if(v1.x() > v2.x()){
|
|
||||||
return 1;
|
|
||||||
}else if(v1.z() < v2.z()){
|
|
||||||
return -1;
|
|
||||||
}else if(v1.z() > v2.z()){
|
|
||||||
return 1;
|
|
||||||
}else if(v1.y() < v2.y()){
|
|
||||||
return -1;
|
|
||||||
}else if(v1.y() > v2.y()){
|
|
||||||
return 1;
|
|
||||||
}else{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isBlockLessThan(BlockVector3 v1, BlockVector3 v2) {
|
|
||||||
return compareBlockVectors(v1, v2) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cuboid inset(int amount){
|
|
||||||
return inset(amount, amount, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cuboid inset(int x, int z){
|
|
||||||
return inset(x, 0, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cuboid inset(int x, int y, int z){
|
|
||||||
return new Cuboid(this.minX + x, this.minY, this.minZ + z, this.maxX - x, this.maxY, this.maxZ - z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVolume() {
|
public Cuboid(BlockVector3 min, BlockVector3 max) {
|
||||||
return (this.maxX - this.minX) * (this.maxY - this.minY) * (this.maxZ - this.minZ);
|
this.minX = min.x();
|
||||||
}
|
this.minY = min.y();
|
||||||
public int getMinX(){return minX;}
|
this.minZ = min.z();
|
||||||
public void setMinX(int minX){this.minX = minX;}
|
this.maxX = max.x();
|
||||||
public int getMinY(){return minY;}
|
this.maxY = max.y();
|
||||||
public void setMinY(int minY){this.minY = minY;}
|
this.maxZ = max.z();
|
||||||
public int getMinZ(){return minZ;}
|
}
|
||||||
public void setMinZ(int minZ){this.minZ = minZ;}
|
|
||||||
public int getMaxX(){return maxX;}
|
|
||||||
public void setMaxX(int maxX){this.maxX = maxX;}
|
|
||||||
public int getMaxY(){return maxY;}
|
|
||||||
public void setMaxY(int maxY){this.maxY = maxY;}
|
|
||||||
public int getMaxZ(){return maxZ;}
|
|
||||||
public void setMaxZ(int maxZ){this.maxZ = maxZ;}
|
|
||||||
|
|
||||||
public Cuboid outset(int amount) {
|
public Cuboid() {
|
||||||
return outset(amount, amount, amount);
|
this.minX = 0;
|
||||||
}
|
this.minY = 0;
|
||||||
|
this.minZ = 0;
|
||||||
|
this.maxX = 0;
|
||||||
|
this.maxY = 0;
|
||||||
|
this.maxZ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
public Cuboid outset(int x, int z){
|
public Cuboid(Region selection) {
|
||||||
return outset(x, 0, z);
|
this.minX = selection.getMinimumPoint().x();
|
||||||
}
|
this.minY = selection.getMinimumPoint().y();
|
||||||
|
this.minZ = selection.getMinimumPoint().z();
|
||||||
public Cuboid outset(int x, int y, int z) {
|
this.maxX = selection.getMaximumPoint().x();
|
||||||
return new Cuboid(this.minX - x, this.minY - y, this.minZ - z, this.maxX + x, this.maxY + y, this.maxZ + z);
|
this.maxY = selection.getMaximumPoint().y();
|
||||||
}
|
this.maxZ = selection.getMaximumPoint().z();
|
||||||
|
}
|
||||||
public int getCenterX(){
|
|
||||||
return (this.minX + this.maxX) /2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCenterY(){
|
|
||||||
return (this.minY + this.maxY)/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCenterZ(){
|
|
||||||
return (this.minZ + this.maxZ)/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean intersects(Cuboid other) {
|
public BlockVector3 getMin() {
|
||||||
if (this.maxX >= other.minX && this.minX <= other.maxX)
|
return BlockVector3.at(minX, minY, minZ);
|
||||||
{
|
}
|
||||||
if (this.maxZ >= other.minZ && this.minZ <= other.maxZ)
|
|
||||||
{
|
public BlockVector3 getMax() {
|
||||||
if (this.maxY >= other.minY && this.minY <= other.maxY)
|
return BlockVector3.at(maxX, maxY, maxZ);
|
||||||
{
|
}
|
||||||
return true;
|
|
||||||
}
|
@Override
|
||||||
else
|
public int compareTo(Cuboid o) {
|
||||||
{
|
BlockVector3 min = getMin();
|
||||||
return false;
|
BlockVector3 otherMin = o.getMin();
|
||||||
}
|
|
||||||
}
|
if (min.x() < otherMin.x()) {
|
||||||
else
|
return -1;
|
||||||
{
|
} else if (min.x() > otherMin.x()) {
|
||||||
return false;
|
return 1;
|
||||||
}
|
} else if (min.z() < otherMin.z()) {
|
||||||
|
return -1;
|
||||||
|
} else if (min.z() > otherMin.z()) {
|
||||||
|
return 1;
|
||||||
|
} else if (min.y() < otherMin.y()) {
|
||||||
|
return -1;
|
||||||
|
} else if (min.y() > otherMin.y()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int compareBlockVectors(BlockVector3 v1, BlockVector3 v2) {
|
||||||
|
if (v1 == null) {
|
||||||
|
if (v2 == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (v2 == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (v1.x() < v2.x()) {
|
||||||
|
return -1;
|
||||||
|
} else if (v1.x() > v2.x()) {
|
||||||
|
return 1;
|
||||||
|
} else if (v1.z() < v2.z()) {
|
||||||
|
return -1;
|
||||||
|
} else if (v1.z() > v2.z()) {
|
||||||
|
return 1;
|
||||||
|
} else if (v1.y() < v2.y()) {
|
||||||
|
return -1;
|
||||||
|
} else if (v1.y() > v2.y()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBlockLessThan(BlockVector3 v1, BlockVector3 v2) {
|
||||||
|
return compareBlockVectors(v1, v2) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cuboid inset(int amount) {
|
||||||
|
return inset(amount, amount, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cuboid inset(int x, int z) {
|
||||||
|
return inset(x, 0, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cuboid inset(int x, int y, int z) {
|
||||||
|
return new Cuboid(
|
||||||
|
this.minX + x, this.minY, this.minZ + z, this.maxX - x, this.maxY, this.maxZ - z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVolume() {
|
||||||
|
return (this.maxX - this.minX) * (this.maxY - this.minY) * (this.maxZ - this.minZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinX() {
|
||||||
|
return minX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinX(int minX) {
|
||||||
|
this.minX = minX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinY() {
|
||||||
|
return minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinY(int minY) {
|
||||||
|
this.minY = minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinZ() {
|
||||||
|
return minZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinZ(int minZ) {
|
||||||
|
this.minZ = minZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxX() {
|
||||||
|
return maxX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxX(int maxX) {
|
||||||
|
this.maxX = maxX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxY() {
|
||||||
|
return maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxY(int maxY) {
|
||||||
|
this.maxY = maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxZ() {
|
||||||
|
return maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxZ(int maxZ) {
|
||||||
|
this.maxZ = maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cuboid outset(int amount) {
|
||||||
|
return outset(amount, amount, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cuboid outset(int x, int z) {
|
||||||
|
return outset(x, 0, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cuboid outset(int x, int y, int z) {
|
||||||
|
return new Cuboid(
|
||||||
|
this.minX - x, this.minY - y, this.minZ - z, this.maxX + x, this.maxY + y, this.maxZ + z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCenterX() {
|
||||||
|
return (this.minX + this.maxX) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCenterY() {
|
||||||
|
return (this.minY + this.maxY) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCenterZ() {
|
||||||
|
return (this.minZ + this.maxZ) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean intersects(Cuboid other) {
|
||||||
|
if (this.maxX >= other.minX && this.minX <= other.maxX) {
|
||||||
|
if (this.maxZ >= other.minZ && this.minZ <= other.maxZ) {
|
||||||
|
if (this.maxY >= other.minY && this.minY <= other.maxY) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
} else {
|
||||||
{
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} else {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean contains(Location bedSpawn) {
|
public boolean contains(Location bedSpawn) {
|
||||||
if(this.minX > bedSpawn.getBlockX() || this.maxX < bedSpawn.getBlockX()){
|
if (this.minX > bedSpawn.getBlockX() || this.maxX < bedSpawn.getBlockX()) {
|
||||||
return false;
|
return false;
|
||||||
}else if(this.minZ > bedSpawn.getBlockZ() || this.maxZ < bedSpawn.getBlockZ()){
|
} else if (this.minZ > bedSpawn.getBlockZ() || this.maxZ < bedSpawn.getBlockZ()) {
|
||||||
return false;
|
return false;
|
||||||
}else if(this.minY > bedSpawn.getBlockY() || this.maxY < bedSpawn.getBlockY()){
|
} else if (this.minY > bedSpawn.getBlockY() || this.maxY < bedSpawn.getBlockY()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return true;
|
||||||
public String toString() {
|
}
|
||||||
return String.format("{Cuboid minX=%d, minY=%d, minZ=%d, maxX=%d, maxY=%d, maxZ=%d}", minX, minY, minZ, maxX, maxY, maxZ);
|
|
||||||
}
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format(
|
||||||
|
"{Cuboid minX=%d, minY=%d, minZ=%d, maxX=%d, maxY=%d, maxZ=%d}",
|
||||||
|
minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -11,40 +28,40 @@ import org.bukkit.configuration.InvalidConfigurationException;
|
|||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
final class CurrentHomesStore {
|
final class CurrentHomesStore {
|
||||||
private CurrentHomesStore() {}
|
private CurrentHomesStore() {}
|
||||||
|
|
||||||
static Map<UUID, Integer> load(File file, Function<String, UUID> legacyOwnerResolver)
|
static Map<UUID, Integer> load(File file, Function<String, UUID> legacyOwnerResolver)
|
||||||
throws IOException, InvalidConfigurationException {
|
throws IOException, InvalidConfigurationException {
|
||||||
Map<UUID, Integer> homesByOwner = new HashMap<>();
|
Map<UUID, Integer> homesByOwner = new HashMap<>();
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
return homesByOwner;
|
return homesByOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
YamlConfiguration yaml = new YamlConfiguration();
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
yaml.load(file);
|
yaml.load(file);
|
||||||
for (String ownerKey : yaml.getKeys(false)) {
|
for (String ownerKey : yaml.getKeys(false)) {
|
||||||
UUID ownerId;
|
UUID ownerId;
|
||||||
try {
|
try {
|
||||||
ownerId = UUID.fromString(ownerKey);
|
ownerId = UUID.fromString(ownerKey);
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
ownerId = legacyOwnerResolver.apply(ownerKey);
|
ownerId = legacyOwnerResolver.apply(ownerKey);
|
||||||
}
|
}
|
||||||
int homeNumber = yaml.getInt(ownerKey, 0);
|
int homeNumber = yaml.getInt(ownerKey, 0);
|
||||||
if (ownerId != null && homeNumber > 0) {
|
if (ownerId != null && homeNumber > 0) {
|
||||||
homesByOwner.put(ownerId, homeNumber);
|
homesByOwner.put(ownerId, homeNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return homesByOwner;
|
return homesByOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void save(File file, Map<UUID, Integer> homesByOwner) throws IOException {
|
static void save(File file, Map<UUID, Integer> homesByOwner) throws IOException {
|
||||||
File parent = file.getParentFile();
|
File parent = file.getParentFile();
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.mkdirs();
|
parent.mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
YamlConfiguration yaml = new YamlConfiguration();
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
homesByOwner.forEach((ownerId, homeNumber) -> yaml.set(ownerId.toString(), homeNumber));
|
homesByOwner.forEach((ownerId, homeNumber) -> yaml.set(ownerId.toString(), homeNumber));
|
||||||
yaml.save(file);
|
yaml.save(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class HomeNumberAllocator {
|
||||||
|
private HomeNumberAllocator() {}
|
||||||
|
|
||||||
|
public static int nextHomeNumber(List<Plot> plots) {
|
||||||
|
Set<Integer> allocatedNumbers = new HashSet<>();
|
||||||
|
for (Plot plot : plots) {
|
||||||
|
if (plot instanceof PlayerHome home) {
|
||||||
|
allocatedNumbers.add(home.getNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextHomeNumber = 1;
|
||||||
|
while (allocatedNumbers.contains(nextHomeNumber)) {
|
||||||
|
nextHomeNumber++;
|
||||||
|
}
|
||||||
|
return nextHomeNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
|
||||||
|
public final class HomePermissionOverride {
|
||||||
|
private final String _permission;
|
||||||
|
private final int _priority;
|
||||||
|
private final int _plotMultiplier;
|
||||||
|
private final int _maxPlots;
|
||||||
|
|
||||||
|
private HomePermissionOverride(
|
||||||
|
String permission, int priority, int plotMultiplier, int maxPlots) {
|
||||||
|
_permission = permission;
|
||||||
|
_priority = priority;
|
||||||
|
_plotMultiplier = plotMultiplier;
|
||||||
|
_maxPlots = maxPlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPermission() {
|
||||||
|
return _permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return _priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlotMultiplier() {
|
||||||
|
return _plotMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxPlots() {
|
||||||
|
return _maxPlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<HomePermissionOverride> load(
|
||||||
|
ConfigurationSection section, Consumer<String> warningLogger) {
|
||||||
|
if (section == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HomePermissionOverride> overrides = new ArrayList<>();
|
||||||
|
collect(section, "", overrides, warningLogger);
|
||||||
|
return List.copyOf(overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collect(
|
||||||
|
ConfigurationSection section,
|
||||||
|
String permissionPrefix,
|
||||||
|
List<HomePermissionOverride> overrides,
|
||||||
|
Consumer<String> warningLogger) {
|
||||||
|
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||||
|
String permission =
|
||||||
|
permissionPrefix.isEmpty() ? entry.getKey() : permissionPrefix + "." + entry.getKey();
|
||||||
|
Map<?, ?> values = getValues(entry.getValue());
|
||||||
|
if (values != null && !hasRuleValues(values)) {
|
||||||
|
collect((ConfigurationSection) entry.getValue(), permission, overrides, warningLogger);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.isBlank()
|
||||||
|
|| values == null
|
||||||
|
|| !(values.get("priority") instanceof Number priority)
|
||||||
|
|| !(values.get("plotMultiplier") instanceof Number plotMultiplier)
|
||||||
|
|| !(values.get("maxPlots") instanceof Number maxPlots)
|
||||||
|
|| priority.intValue() != priority.doubleValue()
|
||||||
|
|| plotMultiplier.intValue() != plotMultiplier.doubleValue()
|
||||||
|
|| maxPlots.intValue() != maxPlots.doubleValue()
|
||||||
|
|| plotMultiplier.intValue() < 1
|
||||||
|
|| maxPlots.intValue() < 0) {
|
||||||
|
warningLogger.accept("Ignoring invalid permissionOverrides entry '" + permission + "'.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
overrides.add(
|
||||||
|
new HomePermissionOverride(
|
||||||
|
permission, priority.intValue(), plotMultiplier.intValue(), maxPlots.intValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasRuleValues(Map<?, ?> values) {
|
||||||
|
return values.containsKey("priority")
|
||||||
|
|| values.containsKey("plotMultiplier")
|
||||||
|
|| values.containsKey("maxPlots");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HomePermissionOverride findMatch(
|
||||||
|
List<HomePermissionOverride> overrides, Predicate<String> hasPermission) {
|
||||||
|
HomePermissionOverride match = null;
|
||||||
|
for (HomePermissionOverride override : overrides) {
|
||||||
|
if (hasPermission.test(override.getPermission())
|
||||||
|
&& (match == null || override.getPriority() >= match.getPriority())) {
|
||||||
|
match = override;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int resolveMaxPlots(
|
||||||
|
UserOverride userOverride,
|
||||||
|
List<HomePermissionOverride> overrides,
|
||||||
|
Predicate<String> hasPermission,
|
||||||
|
int fallback) {
|
||||||
|
if (userOverride != null) {
|
||||||
|
return userOverride.getMaxPlots();
|
||||||
|
}
|
||||||
|
HomePermissionOverride match = findMatch(overrides, hasPermission);
|
||||||
|
return match == null ? fallback : match.getMaxPlots();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int resolvePlotMultiplier(
|
||||||
|
UserOverride userOverride,
|
||||||
|
List<HomePermissionOverride> overrides,
|
||||||
|
Predicate<String> hasPermission,
|
||||||
|
int fallback) {
|
||||||
|
if (userOverride != null) {
|
||||||
|
return userOverride.getPlotMultiplier();
|
||||||
|
}
|
||||||
|
HomePermissionOverride match = findMatch(overrides, hasPermission);
|
||||||
|
return match == null ? fallback : match.getPlotMultiplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<?, ?> getValues(Object value) {
|
||||||
|
if (value instanceof ConfigurationSection section) {
|
||||||
|
return section.getValues(false);
|
||||||
|
}
|
||||||
|
if (value instanceof Map<?, ?> map) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,107 +1,144 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
import org.bukkit.Bukkit;
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
import org.bukkit.OfflinePlayer;
|
(at your option) any later version.
|
||||||
|
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
public class PlayerHome extends Plot{
|
|
||||||
private UUID playerId;
|
You should have received a copy of the GNU Affero General Public License
|
||||||
private String playerName;
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
public UUID getPlayerId(){return this.playerId;}
|
*/
|
||||||
public String getPlayerName(){return this.playerName;}
|
|
||||||
public void setPlayerName(String playerName){this.playerName = playerName;}
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
private int number;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
public PlayerHome(UUID ownerId, String ownerName, int homeNumber, BlockVector3 min, BlockVector3 max) {
|
|
||||||
super(String.format("h_%d_%s", homeNumber, ownerId), min, max);
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
this.playerId = ownerId;
|
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||||
this.playerName = ownerName;
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
this.number = homeNumber;
|
|
||||||
}
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
public PlayerHome() {
|
|
||||||
this.playerId = null;
|
public class PlayerHome extends Plot {
|
||||||
this.playerName = "";
|
private UUID playerId;
|
||||||
}
|
private String playerName;
|
||||||
|
|
||||||
public PlayerHome(ProtectedRegion homeRegion){
|
public UUID getPlayerId() {
|
||||||
String regionName = homeRegion.getId();
|
return this.playerId;
|
||||||
if (!regionName.startsWith("h_")) {
|
}
|
||||||
throw new IllegalArgumentException("Not a Metropolis home region: " + regionName);
|
|
||||||
}
|
public String getPlayerName() {
|
||||||
String regionOwner = regionName.substring(2);
|
return this.playerName;
|
||||||
int separator = regionOwner.indexOf('_');
|
}
|
||||||
if (separator > 0) {
|
|
||||||
try {
|
public void setPlayerName(String playerName) {
|
||||||
this.number = Integer.parseInt(regionOwner.substring(0, separator));
|
this.playerName = playerName;
|
||||||
regionOwner = regionOwner.substring(separator + 1);
|
}
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
this.number = 1;
|
private int number;
|
||||||
}
|
|
||||||
} else {
|
public PlayerHome(
|
||||||
this.number = 1;
|
UUID ownerId, String ownerName, int homeNumber, BlockVector3 min, BlockVector3 max) {
|
||||||
}
|
super(String.format("h_%d_%s", homeNumber, ownerId), min, max);
|
||||||
try {
|
this.playerId = ownerId;
|
||||||
this.playerId = UUID.fromString(regionOwner);
|
this.playerName = ownerName;
|
||||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
|
this.number = homeNumber;
|
||||||
this.playerName = offlinePlayer.getName() == null ? regionOwner : offlinePlayer.getName();
|
}
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
Set<UUID> ownerIds = homeRegion.getOwners().getUniqueIds();
|
public PlayerHome() {
|
||||||
this.playerId = ownerIds.isEmpty()
|
this.playerId = null;
|
||||||
? Bukkit.getOfflinePlayer(regionOwner).getUniqueId()
|
this.playerName = "";
|
||||||
: ownerIds.iterator().next();
|
}
|
||||||
this.playerName = regionOwner;
|
|
||||||
}
|
public PlayerHome(ProtectedRegion homeRegion) {
|
||||||
setCuboid(new Cuboid(homeRegion.getMinimumPoint(), homeRegion.getMaximumPoint()));
|
String regionName = homeRegion.getId();
|
||||||
}
|
if (!regionName.startsWith("h_")) {
|
||||||
|
throw new IllegalArgumentException("Not a Metropolis home region: " + regionName);
|
||||||
@Override
|
}
|
||||||
public boolean equals(Object other) {
|
String regionOwner = regionName.substring(2);
|
||||||
if(!(other instanceof PlayerHome)){
|
int separator = regionOwner.indexOf('_');
|
||||||
return super.equals(other);
|
if (separator > 0) {
|
||||||
}
|
try {
|
||||||
|
this.number = Integer.parseInt(regionOwner.substring(0, separator));
|
||||||
PlayerHome otherPlayerHome = (PlayerHome)other;
|
regionOwner = regionOwner.substring(separator + 1);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
if(!java.util.Objects.equals(this.playerId, otherPlayerHome.playerId)){
|
this.number = 1;
|
||||||
return false;
|
}
|
||||||
}
|
} else {
|
||||||
|
this.number = 1;
|
||||||
if(!getCuboid().equals(otherPlayerHome.getCuboid())){
|
}
|
||||||
return false;
|
try {
|
||||||
}
|
this.playerId = UUID.fromString(regionOwner);
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
|
||||||
return true;
|
this.playerName = offlinePlayer.getName() == null ? regionOwner : offlinePlayer.getName();
|
||||||
}
|
} catch (IllegalArgumentException ex) {
|
||||||
|
Set<UUID> ownerIds = homeRegion.getOwners().getUniqueIds();
|
||||||
public String toFriendlyString() {
|
this.playerId =
|
||||||
StringBuilder sb = new StringBuilder();
|
ownerIds.isEmpty()
|
||||||
|
? Bukkit.getOfflinePlayer(regionOwner).getUniqueId()
|
||||||
sb.append(String.format("Metropolis Home {Owner: %s min: (%d, %d, %d) max: (%d, %d, %d)}", getPlayerName(), getCuboid().getMinX(), getCuboid().getMinY(), getCuboid().getMinZ(), getCuboid().getMaxX(), getCuboid().getMaxY(), getCuboid().getMaxZ()));
|
: ownerIds.iterator().next();
|
||||||
|
this.playerName = regionOwner;
|
||||||
return sb.toString();
|
}
|
||||||
}
|
setCuboid(new Cuboid(homeRegion.getMinimumPoint(), homeRegion.getMaximumPoint()));
|
||||||
|
}
|
||||||
public static PlayerHome get(ProtectedRegion homeRegion){
|
|
||||||
if(homeRegion instanceof ProtectedCuboidRegion){
|
@Override
|
||||||
return new PlayerHome((ProtectedCuboidRegion) homeRegion);
|
public boolean equals(Object other) {
|
||||||
}else{
|
if (!(other instanceof PlayerHome)) {
|
||||||
return null;
|
return super.equals(other);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
PlayerHome otherPlayerHome = (PlayerHome) other;
|
||||||
public Integer getNumber() {
|
|
||||||
return number;
|
if (!java.util.Objects.equals(this.playerId, otherPlayerHome.playerId)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
public void setNumber(int number){
|
|
||||||
this.number = number;
|
if (!getCuboid().equals(otherPlayerHome.getCuboid())) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toFriendlyString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(
|
||||||
|
String.format(
|
||||||
|
"Metropolis Home {Owner: %s min: (%d, %d, %d) max: (%d, %d, %d)}",
|
||||||
|
getPlayerName(),
|
||||||
|
getCuboid().getMinX(),
|
||||||
|
getCuboid().getMinY(),
|
||||||
|
getCuboid().getMinZ(),
|
||||||
|
getCuboid().getMaxX(),
|
||||||
|
getCuboid().getMaxY(),
|
||||||
|
getCuboid().getMaxZ()));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlayerHome get(ProtectedRegion homeRegion) {
|
||||||
|
if (homeRegion instanceof ProtectedCuboidRegion) {
|
||||||
|
return new PlayerHome((ProtectedCuboidRegion) homeRegion);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumber(int number) {
|
||||||
|
this.number = number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,36 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
final class PlayerLookup {
|
final class PlayerLookup {
|
||||||
private PlayerLookup() {}
|
private PlayerLookup() {}
|
||||||
|
|
||||||
static Player findOnline(String requestedName, Iterable<? extends Player> onlinePlayers) {
|
static Player findOnline(String requestedName, Iterable<? extends Player> onlinePlayers) {
|
||||||
if (requestedName == null) {
|
if (requestedName == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (Player player : onlinePlayers) {
|
for (Player player : onlinePlayers) {
|
||||||
if (requestedName.equalsIgnoreCase(player.getName())) {
|
if (requestedName.equalsIgnoreCase(player.getName())) {
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +1,164 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.Material;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.World;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
import org.bukkit.block.Block;
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
public class Plot implements Comparable<Plot>{
|
GNU Affero General Public License for more details.
|
||||||
private int _id;
|
|
||||||
public int getId(){return _id;}
|
You should have received a copy of the GNU Affero General Public License
|
||||||
public void setId(int id){_id = id;}
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
private Cuboid _cuboid;
|
|
||||||
public Cuboid getCuboid(){return _cuboid;}
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
public void setCuboid(Cuboid cuboid){_cuboid = cuboid;}
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
private String _regionName;
|
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||||
public String getRegionName(){return _regionName;}
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
public void setRegionName(String regionName){_regionName = regionName;}
|
|
||||||
|
import org.bukkit.Location;
|
||||||
public Plot(String regionName, BlockVector3 min, BlockVector3 max){
|
import org.bukkit.Material;
|
||||||
_cuboid = new Cuboid(min, max);
|
import org.bukkit.World;
|
||||||
_regionName = regionName;
|
import org.bukkit.block.Block;
|
||||||
}
|
|
||||||
|
public class Plot implements Comparable<Plot> {
|
||||||
public Plot(ProtectedCuboidRegion cuboid){
|
private int _id;
|
||||||
_cuboid = new Cuboid(cuboid.getMinimumPoint(), cuboid.getMaximumPoint());
|
|
||||||
_regionName = cuboid.getId();
|
public int getId() {
|
||||||
}
|
return _id;
|
||||||
|
}
|
||||||
public Plot(){
|
|
||||||
_cuboid = new Cuboid();
|
public void setId(int id) {
|
||||||
_regionName = "";
|
_id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector3 getPlotMin(int roadWidth) {
|
private Cuboid _cuboid;
|
||||||
return BlockVector3.at(_cuboid.minX - roadWidth/2, _cuboid.minY, _cuboid.minZ - roadWidth/2);
|
|
||||||
}
|
public Cuboid getCuboid() {
|
||||||
|
return _cuboid;
|
||||||
public int getRow(int roadWidth, int plotSizeZ){
|
}
|
||||||
BlockVector3 min = getPlotMin(roadWidth);
|
|
||||||
return min.z() / plotSizeZ;
|
public void setCuboid(Cuboid cuboid) {
|
||||||
}
|
_cuboid = cuboid;
|
||||||
|
}
|
||||||
public int getCol(int roadWidth, int plotSizeX){
|
|
||||||
return getPlotMin(roadWidth).x() / plotSizeX;
|
private String _regionName;
|
||||||
}
|
|
||||||
|
public String getRegionName() {
|
||||||
@Override
|
return _regionName;
|
||||||
public boolean equals(Object other) {
|
}
|
||||||
if(!(other instanceof Plot)){
|
|
||||||
return super.equals(other);
|
public void setRegionName(String regionName) {
|
||||||
}
|
_regionName = regionName;
|
||||||
|
}
|
||||||
Plot otherPlayerHome = (Plot)other;
|
|
||||||
|
public Plot(String regionName, BlockVector3 min, BlockVector3 max) {
|
||||||
if(!getCuboid().equals(otherPlayerHome.getCuboid())){
|
_cuboid = new Cuboid(min, max);
|
||||||
return false;
|
_regionName = regionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
public Plot(ProtectedCuboidRegion cuboid) {
|
||||||
}
|
_cuboid = new Cuboid(cuboid.getMinimumPoint(), cuboid.getMaximumPoint());
|
||||||
|
_regionName = cuboid.getId();
|
||||||
@Override
|
}
|
||||||
public int compareTo(Plot another) {
|
|
||||||
return getCuboid().compareTo(another.getCuboid());
|
public Plot() {
|
||||||
}
|
_cuboid = new Cuboid();
|
||||||
|
_regionName = "";
|
||||||
@Override
|
}
|
||||||
public String toString(){
|
|
||||||
StringBuilder sb = new StringBuilder();
|
public BlockVector3 getPlotMin(int roadWidth) {
|
||||||
|
return BlockVector3.at(
|
||||||
sb.append(String.format("{PlayerHome }"));
|
_cuboid.minX - roadWidth / 2, _cuboid.minY, _cuboid.minZ - roadWidth / 2);
|
||||||
|
}
|
||||||
return sb.toString();
|
|
||||||
}
|
public int getRow(int roadWidth, int plotSizeZ) {
|
||||||
|
BlockVector3 min = getPlotMin(roadWidth);
|
||||||
public static Plot get(ProtectedRegion region){
|
return min.z() / plotSizeZ;
|
||||||
if(region instanceof ProtectedCuboidRegion){
|
}
|
||||||
return new Plot((ProtectedCuboidRegion) region);
|
|
||||||
}else{
|
public int getCol(int roadWidth, int plotSizeX) {
|
||||||
return null;
|
return getPlotMin(roadWidth).x() / plotSizeX;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
public String toFriendlyString() {
|
public boolean equals(Object other) {
|
||||||
StringBuilder sb = new StringBuilder();
|
if (!(other instanceof Plot)) {
|
||||||
|
return super.equals(other);
|
||||||
sb.append(String.format("Metropolis Reserved Plot {min: (%d, %d, %d) max: (%d, %d, %d)}", getCuboid().getMinX(), getCuboid().getMinY(), getCuboid().getMinZ(), getCuboid().getMaxX(), getCuboid().getMaxY(), getCuboid().getMaxZ()));
|
}
|
||||||
|
|
||||||
return sb.toString();
|
Plot otherPlayerHome = (Plot) other;
|
||||||
}
|
|
||||||
|
if (!getCuboid().equals(otherPlayerHome.getCuboid())) {
|
||||||
public boolean contains(Location bedSpawn) {
|
return false;
|
||||||
return _cuboid.contains(bedSpawn);
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
public Location getViableSpawnLocation(World world) {
|
}
|
||||||
Cuboid cuboid = getCuboid();
|
|
||||||
for(int y=cuboid.maxY-1; y>= cuboid.minY; y--){
|
@Override
|
||||||
for(int x=cuboid.minX; x<= cuboid.maxX; x++){
|
public int compareTo(Plot another) {
|
||||||
for(int z=cuboid.minZ; z<= cuboid.maxZ; z++){
|
return getCuboid().compareTo(another.getCuboid());
|
||||||
Block block = world.getBlockAt(x, y, z);
|
}
|
||||||
Block blockAbove = world.getBlockAt(x, y+1, z);
|
|
||||||
Block blockUnder = world.getBlockAt(x, y-1, z);
|
@Override
|
||||||
if(block.getType() == Material.AIR & blockAbove.getType() == Material.AIR && blockUnder.getType() != Material.AIR){
|
public String toString() {
|
||||||
return new Location(world, x, y, z);
|
StringBuilder sb = new StringBuilder();
|
||||||
}
|
|
||||||
}
|
sb.append(String.format("{PlayerHome }"));
|
||||||
}
|
|
||||||
}
|
return sb.toString();
|
||||||
|
}
|
||||||
return null;
|
|
||||||
}
|
public static Plot get(ProtectedRegion region) {
|
||||||
}
|
if (region instanceof ProtectedCuboidRegion) {
|
||||||
|
return new Plot((ProtectedCuboidRegion) region);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toFriendlyString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(
|
||||||
|
String.format(
|
||||||
|
"Metropolis Reserved Plot {min: (%d, %d, %d) max: (%d, %d, %d)}",
|
||||||
|
getCuboid().getMinX(),
|
||||||
|
getCuboid().getMinY(),
|
||||||
|
getCuboid().getMinZ(),
|
||||||
|
getCuboid().getMaxX(),
|
||||||
|
getCuboid().getMaxY(),
|
||||||
|
getCuboid().getMaxZ()));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(Location bedSpawn) {
|
||||||
|
return _cuboid.contains(bedSpawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location getViableSpawnLocation(World world) {
|
||||||
|
Cuboid cuboid = getCuboid();
|
||||||
|
for (int y = cuboid.maxY - 1; y >= cuboid.minY; y--) {
|
||||||
|
for (int x = cuboid.minX; x <= cuboid.maxX; x++) {
|
||||||
|
for (int z = cuboid.minZ; z <= cuboid.maxZ; z++) {
|
||||||
|
Block block = world.getBlockAt(x, y, z);
|
||||||
|
Block blockAbove = world.getBlockAt(x, y + 1, z);
|
||||||
|
Block blockUnder = world.getBlockAt(x, y - 1, z);
|
||||||
|
if (block.getType() == Material.AIR & blockAbove.getType() == Material.AIR
|
||||||
|
&& blockUnder.getType() != Material.AIR) {
|
||||||
|
return new Location(world, x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
public final class PlotGridLayout {
|
||||||
|
private final int plotSizeX;
|
||||||
|
private final int plotSizeY;
|
||||||
|
private final int plotSizeZ;
|
||||||
|
private final int streetWidth;
|
||||||
|
private final int avenueWidth;
|
||||||
|
private final int offsetX;
|
||||||
|
private final int offsetY;
|
||||||
|
private final int offsetZ;
|
||||||
|
private final int roadLevel;
|
||||||
|
private final int gridSizeX;
|
||||||
|
private final int gridSizeY;
|
||||||
|
private final int gridSizeZ;
|
||||||
|
|
||||||
|
public PlotGridLayout(
|
||||||
|
int plotSizeX,
|
||||||
|
int plotSizeY,
|
||||||
|
int plotSizeZ,
|
||||||
|
int streetWidth,
|
||||||
|
int avenueWidth,
|
||||||
|
int offsetX,
|
||||||
|
int offsetY,
|
||||||
|
int offsetZ,
|
||||||
|
int roadLevel) {
|
||||||
|
if (plotSizeX < 1 || plotSizeY < 1 || plotSizeZ < 1 || streetWidth < 0 || avenueWidth < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Plot dimensions must be positive and road widths nonnegative");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.plotSizeX = plotSizeX;
|
||||||
|
this.plotSizeY = plotSizeY;
|
||||||
|
this.plotSizeZ = plotSizeZ;
|
||||||
|
this.streetWidth = streetWidth;
|
||||||
|
this.avenueWidth = avenueWidth;
|
||||||
|
this.offsetX = offsetX;
|
||||||
|
this.offsetY = offsetY;
|
||||||
|
this.offsetZ = offsetZ;
|
||||||
|
this.roadLevel = roadLevel;
|
||||||
|
gridSizeX = Math.addExact(plotSizeX, avenueWidth);
|
||||||
|
gridSizeY = Math.addExact(plotSizeY, Math.max(streetWidth, avenueWidth));
|
||||||
|
gridSizeZ = Math.addExact(plotSizeZ, streetWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockVector3 gridMin(int row, int col, int level) {
|
||||||
|
return BlockVector3.at(
|
||||||
|
offsetX + col * gridSizeX, levelStartY(level), offsetZ + row * gridSizeZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockVector3 gridMax(int row, int col, int multiplierX, int multiplierZ, int level) {
|
||||||
|
return BlockVector3.at(
|
||||||
|
offsetX + (col + multiplierX) * gridSizeX - 1,
|
||||||
|
levelStartY(level) + plotSizeY - 1,
|
||||||
|
offsetZ + (row + multiplierZ) * gridSizeZ - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockVector3 plotMin(int row, int col, int level) {
|
||||||
|
BlockVector3 gridMin = gridMin(row, col, level);
|
||||||
|
return BlockVector3.at(
|
||||||
|
gridMin.x() + avenueWidth / 2, gridMin.y(), gridMin.z() + streetWidth / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockVector3 plotMax(int row, int col, int multiplierX, int multiplierZ, int level) {
|
||||||
|
BlockVector3 gridMax = gridMax(row, col, multiplierX, multiplierZ, level);
|
||||||
|
return BlockVector3.at(
|
||||||
|
gridMax.x() - (avenueWidth - avenueWidth / 2),
|
||||||
|
gridMax.y(),
|
||||||
|
gridMax.z() - (streetWidth - streetWidth / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int plotXIndexFromMin(Cuboid plot) {
|
||||||
|
return Math.floorDiv(plot.minX - offsetX - avenueWidth / 2, gridSizeX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int plotZIndexFromMin(Cuboid plot) {
|
||||||
|
return Math.floorDiv(plot.minZ - offsetZ - streetWidth / 2, gridSizeZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int levelStartY(int level) {
|
||||||
|
return roadLevel + offsetY + level * gridSizeY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int gridSizeX() {
|
||||||
|
return gridSizeX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int gridSizeY() {
|
||||||
|
return gridSizeY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int gridSizeZ() {
|
||||||
|
return gridSizeZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
public final class PlotLevelSupport {
|
||||||
|
private PlotLevelSupport() {}
|
||||||
|
|
||||||
|
public static boolean hasCompleteSupport(
|
||||||
|
int row, int col, int multiplier, int targetLevel, GeneratedCellLookup generatedCellLookup) {
|
||||||
|
if (multiplier < 1 || targetLevel < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int supportLevel = 0; supportLevel < targetLevel; supportLevel++) {
|
||||||
|
for (int rowOffset = 0; rowOffset < multiplier; rowOffset++) {
|
||||||
|
for (int colOffset = 0; colOffset < multiplier; colOffset++) {
|
||||||
|
if (!generatedCellLookup.isGenerated(row + rowOffset, col + colOffset, supportLevel)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface GeneratedCellLookup {
|
||||||
|
boolean isGenerated(int row, int col, int level);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.sk89q.worldguard.protection.flags.Flags;
|
||||||
|
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
|
||||||
|
public final class PlotReservationLookup {
|
||||||
|
private PlotReservationLookup() {}
|
||||||
|
|
||||||
|
public static List<Plot> forPlayer(Iterable<ProtectedRegion> regions, UUID playerId) {
|
||||||
|
List<Plot> reservations = new ArrayList<>();
|
||||||
|
for (ProtectedRegion region : regions) {
|
||||||
|
if (!(region instanceof ProtectedCuboidRegion) || !isReservation(region)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (region.getOwners().getUniqueIds().contains(playerId)
|
||||||
|
|| region.getMembers().getUniqueIds().contains(playerId)) {
|
||||||
|
reservations.add(Plot.get(region));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reservations.sort(Comparator.comparing(Plot::getRegionName));
|
||||||
|
return List.copyOf(reservations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Plot findByName(List<Plot> reservations, String requestedName) {
|
||||||
|
String prefixedName = requestedName.startsWith("r_") ? requestedName : "r_" + requestedName;
|
||||||
|
for (Plot reservation : reservations) {
|
||||||
|
if (reservation.getRegionName().equals(requestedName)
|
||||||
|
|| reservation.getRegionName().equals(prefixedName)) {
|
||||||
|
return reservation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isReservation(ProtectedRegion region) {
|
||||||
|
String regionId = region.getId();
|
||||||
|
if (regionId.startsWith("r_")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (regionId.startsWith("h_")
|
||||||
|
|| regionId.equalsIgnoreCase("City")
|
||||||
|
|| regionId.equalsIgnoreCase("Spawn")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return region.getFlag(Flags.LAVA_FLOW) == StateFlag.State.DENY
|
||||||
|
&& region.getFlag(Flags.SNOW_FALL) == StateFlag.State.DENY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
public final class PlotWorldBorderBounds {
|
||||||
|
private PlotWorldBorderBounds() {}
|
||||||
|
|
||||||
|
public static int maximumSearchRing(
|
||||||
|
double borderSize,
|
||||||
|
double borderCenterX,
|
||||||
|
double borderCenterZ,
|
||||||
|
int offsetX,
|
||||||
|
int offsetZ,
|
||||||
|
int gridSizeX,
|
||||||
|
int gridSizeZ,
|
||||||
|
int plotMultiplier) {
|
||||||
|
if (borderSize <= 0 || gridSizeX <= 0 || gridSizeZ <= 0 || plotMultiplier < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double halfBorder = borderSize / 2;
|
||||||
|
double farthestX = halfBorder + Math.abs(borderCenterX - offsetX);
|
||||||
|
double farthestZ = halfBorder + Math.abs(borderCenterZ - offsetZ);
|
||||||
|
double cellRadius = Math.max(farthestX / gridSizeX, farthestZ / gridSizeZ);
|
||||||
|
long bound = (long) Math.ceil(cellRadius) + plotMultiplier + 1L;
|
||||||
|
return (int) Math.min(bound, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean contains(Cuboid bounds, double borderSize, double centerX, double centerZ) {
|
||||||
|
if (borderSize <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double halfBorder = borderSize / 2;
|
||||||
|
double borderMinX = centerX - halfBorder;
|
||||||
|
double borderMaxX = centerX + halfBorder;
|
||||||
|
double borderMinZ = centerZ - halfBorder;
|
||||||
|
double borderMaxZ = centerZ + halfBorder;
|
||||||
|
return bounds.minX + 0.5 >= borderMinX
|
||||||
|
&& bounds.maxX + 0.5 <= borderMaxX
|
||||||
|
&& bounds.minZ + 0.5 >= borderMinZ
|
||||||
|
&& bounds.maxZ + 0.5 <= borderMaxZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.data.type.Stairs;
|
||||||
|
|
||||||
|
public final class RoadBlockWriter {
|
||||||
|
private RoadBlockWriter() {}
|
||||||
|
|
||||||
|
public static void build(
|
||||||
|
World world,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int z,
|
||||||
|
Material roadMaterial,
|
||||||
|
Material supportMaterial,
|
||||||
|
boolean generateSupports,
|
||||||
|
int clearSpaceAbove) {
|
||||||
|
Block roadBlock = world.getBlockAt(x, y, z);
|
||||||
|
if (roadBlock.getType() == roadMaterial || roadBlock.getBlockData() instanceof Stairs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
roadBlock.setType(roadMaterial);
|
||||||
|
if (generateSupports && (roadMaterial == Material.GRAVEL || roadMaterial == Material.SAND)) {
|
||||||
|
Block blockUnder = world.getBlockAt(x, y - 1, z);
|
||||||
|
if (!isSolid(blockUnder.getType())) {
|
||||||
|
blockUnder.setType(supportMaterial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int clearance = 1; clearance <= clearSpaceAbove; clearance++) {
|
||||||
|
Block blockAbove = world.getBlockAt(x, y + clearance, z);
|
||||||
|
if (blockAbove.getType() != Material.AIR) {
|
||||||
|
blockAbove.setType(Material.AIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSolid(Material material) {
|
||||||
|
return material.isBlock()
|
||||||
|
&& material != Material.AIR
|
||||||
|
&& material != Material.WATER
|
||||||
|
&& material != Material.LAVA
|
||||||
|
&& material != Material.TORCH
|
||||||
|
&& material != Material.REDSTONE_TORCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class RoadLayout {
|
||||||
|
private RoadLayout() {}
|
||||||
|
|
||||||
|
public static List<BlockPosition> surroundingRoad(Cuboid plot, int streetWidth, int avenueWidth) {
|
||||||
|
if (streetWidth < 0 || avenueWidth < 0 || streetWidth + avenueWidth == 0) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BlockPosition> blocks = new ArrayList<>();
|
||||||
|
addRectangle(
|
||||||
|
blocks, plot.minX, plot.maxX, plot.minZ - streetWidth, plot.minZ - 1, RoadType.STREET);
|
||||||
|
addRectangle(
|
||||||
|
blocks, plot.minX, plot.maxX, plot.maxZ + 1, plot.maxZ + streetWidth, RoadType.STREET);
|
||||||
|
addRectangle(
|
||||||
|
blocks, plot.minX - avenueWidth, plot.minX - 1, plot.minZ, plot.maxZ, RoadType.AVENUE);
|
||||||
|
addRectangle(
|
||||||
|
blocks, plot.maxX + 1, plot.maxX + avenueWidth, plot.minZ, plot.maxZ, RoadType.AVENUE);
|
||||||
|
if (streetWidth > 0 && avenueWidth > 0) {
|
||||||
|
addRectangle(
|
||||||
|
blocks,
|
||||||
|
plot.minX - avenueWidth,
|
||||||
|
plot.minX - 1,
|
||||||
|
plot.minZ - streetWidth,
|
||||||
|
plot.minZ - 1,
|
||||||
|
RoadType.INTERSECTION);
|
||||||
|
addRectangle(
|
||||||
|
blocks,
|
||||||
|
plot.maxX + 1,
|
||||||
|
plot.maxX + avenueWidth,
|
||||||
|
plot.minZ - streetWidth,
|
||||||
|
plot.minZ - 1,
|
||||||
|
RoadType.INTERSECTION);
|
||||||
|
addRectangle(
|
||||||
|
blocks,
|
||||||
|
plot.minX - avenueWidth,
|
||||||
|
plot.minX - 1,
|
||||||
|
plot.maxZ + 1,
|
||||||
|
plot.maxZ + streetWidth,
|
||||||
|
RoadType.INTERSECTION);
|
||||||
|
addRectangle(
|
||||||
|
blocks,
|
||||||
|
plot.maxX + 1,
|
||||||
|
plot.maxX + avenueWidth,
|
||||||
|
plot.maxZ + 1,
|
||||||
|
plot.maxZ + streetWidth,
|
||||||
|
RoadType.INTERSECTION);
|
||||||
|
}
|
||||||
|
return List.copyOf(blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addRectangle(
|
||||||
|
List<BlockPosition> blocks, int minX, int maxX, int minZ, int maxZ, RoadType roadType) {
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
blocks.add(new BlockPosition(x, z, roadType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RoadType {
|
||||||
|
STREET,
|
||||||
|
AVENUE,
|
||||||
|
INTERSECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
public record BlockPosition(int x, int z, RoadType roadType) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
public final class SpawnLayout {
|
||||||
|
private SpawnLayout() {}
|
||||||
|
|
||||||
|
public static Cuboid bounds(
|
||||||
|
int sizeX,
|
||||||
|
int sizeY,
|
||||||
|
int sizeZ,
|
||||||
|
int plotSizeX,
|
||||||
|
int plotSizeY,
|
||||||
|
int plotSizeZ,
|
||||||
|
int avenueWidth,
|
||||||
|
int streetWidth,
|
||||||
|
int roadLevel,
|
||||||
|
int offsetX,
|
||||||
|
int offsetY,
|
||||||
|
int offsetZ) {
|
||||||
|
if (sizeX < 1 || sizeY < 1 || sizeZ < 1 || avenueWidth < 0 || streetWidth < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Spawn multipliers must be positive and road widths nonnegative");
|
||||||
|
}
|
||||||
|
|
||||||
|
int gridSizeX = Math.addExact(plotSizeX, avenueWidth);
|
||||||
|
int gridSizeZ = Math.addExact(plotSizeZ, streetWidth);
|
||||||
|
int minX = avenueWidth / 2;
|
||||||
|
int minZ = streetWidth / 2;
|
||||||
|
int maxInsetX = avenueWidth - minX;
|
||||||
|
int maxInsetZ = streetWidth - minZ;
|
||||||
|
int maxX = Math.subtractExact(Math.multiplyExact(sizeX, gridSizeX) - 1, maxInsetX);
|
||||||
|
int baseY = Math.addExact(roadLevel, offsetY);
|
||||||
|
int maxY = Math.addExact(baseY, Math.multiplyExact(sizeY, plotSizeY) - 1);
|
||||||
|
int maxZ = Math.subtractExact(Math.multiplyExact(sizeZ, gridSizeZ) - 1, maxInsetZ);
|
||||||
|
|
||||||
|
return new Cuboid(offsetX + minX, baseY, offsetZ + minZ, offsetX + maxX, maxY, offsetZ + maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean withinBuildHeight(Cuboid cuboid, int minHeight, int maxHeight) {
|
||||||
|
return cuboid.minY >= minHeight && cuboid.maxY < maxHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,42 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
public class UserOverride {
|
|
||||||
private String _username;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
private int _plotMultiplier;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
private int _maxPlots;
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
public String getUsername(){return _username;}
|
|
||||||
public int getPlotMultiplier(){return _plotMultiplier;}
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
public int getMaxPlots(){return _maxPlots;}
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
public UserOverride(String username, int plotMultiplier, int maxPlots){
|
GNU Affero General Public License for more details.
|
||||||
_username = username;
|
|
||||||
_plotMultiplier = plotMultiplier;
|
You should have received a copy of the GNU Affero General Public License
|
||||||
_maxPlots = maxPlots;
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
}
|
*/
|
||||||
}
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
public class UserOverride {
|
||||||
|
private String _username;
|
||||||
|
private int _plotMultiplier;
|
||||||
|
private int _maxPlots;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return _username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlotMultiplier() {
|
||||||
|
return _plotMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxPlots() {
|
||||||
|
return _maxPlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserOverride(String username, int plotMultiplier, int maxPlots) {
|
||||||
|
_username = username;
|
||||||
|
_plotMultiplier = plotMultiplier;
|
||||||
|
_maxPlots = maxPlots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
|
||||||
|
public final class WorldGuardRegionOccupancy {
|
||||||
|
private WorldGuardRegionOccupancy() {}
|
||||||
|
|
||||||
|
public static List<Cuboid> boundsToAvoid(Collection<ProtectedRegion> regions) {
|
||||||
|
List<Cuboid> bounds = new ArrayList<>();
|
||||||
|
for (ProtectedRegion region : regions) {
|
||||||
|
String id = region.getId();
|
||||||
|
if (id.equalsIgnoreCase("city") || id.equalsIgnoreCase("__global__")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bounds.add(new Cuboid(region.getMinimumPoint(), region.getMaximumPoint()));
|
||||||
|
}
|
||||||
|
return List.copyOf(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean overlapsAny(Cuboid candidate, Collection<Cuboid> occupiedBounds) {
|
||||||
|
return occupiedBounds.stream().anyMatch(candidate::intersects);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,40 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.command.CommandSender;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
(at your option) any later version.
|
||||||
|
|
||||||
public class MetropolisCommand implements CommandExecutor {
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
private MetropolisPlugin _plugin;
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
public MetropolisCommand(MetropolisPlugin plugin){
|
GNU Affero General Public License for more details.
|
||||||
_plugin = plugin;
|
|
||||||
}
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
@Override
|
*/
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
sender.sendMessage("Metropolis: version "+ _plugin.pdf.getVersion());
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
return true;
|
|
||||||
}
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
}
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
public class MetropolisCommand implements CommandExecutor {
|
||||||
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
|
public MetropolisCommand(MetropolisPlugin plugin) {
|
||||||
|
_plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
|
||||||
|
sender.sendMessage("Metropolis: version " + _plugin.pdf.getVersion());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +1,141 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.command.CommandSender;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
(at your option) any later version.
|
||||||
|
|
||||||
public class MetropolisDebugGenerateTestHomesCommand implements CommandExecutor {
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
private MetropolisPlugin _plugin;
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
public MetropolisDebugGenerateTestHomesCommand(MetropolisPlugin plugin) {
|
|
||||||
_plugin = plugin;
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
}
|
*/
|
||||||
|
|
||||||
@Override
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
|
||||||
try{
|
import java.util.logging.Level;
|
||||||
int numHomes = Integer.parseInt(args[0]);
|
|
||||||
for(int i=1; i<= numHomes; i++){
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
_plugin.generateHome(String.format("test%d", i));
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
}
|
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
return true;
|
import org.bukkit.command.Command;
|
||||||
}catch(NumberFormatException ex){
|
import org.bukkit.command.CommandExecutor;
|
||||||
return false;
|
import org.bukkit.command.CommandSender;
|
||||||
}
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
}
|
|
||||||
|
public class MetropolisDebugGenerateTestHomesCommand implements CommandExecutor {
|
||||||
}
|
private static final long TICKS_BETWEEN_HOMES = 2L;
|
||||||
|
|
||||||
|
private final MetropolisPlugin _plugin;
|
||||||
|
private BukkitTask _activeTask;
|
||||||
|
|
||||||
|
public MetropolisDebugGenerateTestHomesCommand(MetropolisPlugin plugin) {
|
||||||
|
_plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
if (args.length != 1) {
|
||||||
|
sender.sendMessage("Usage: /metropolis-debug-generatetesthomes <count>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numHomes;
|
||||||
|
try {
|
||||||
|
numHomes = Integer.parseInt(args[0]);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
sender.sendMessage("Usage: /metropolis-debug-generatetesthomes <count>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numHomes < 1) {
|
||||||
|
sender.sendMessage("Usage: /metropolis-debug-generatetesthomes <count>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeTask != null && !_activeTask.isCancelled()) {
|
||||||
|
sender.sendMessage("[Metropolis] Test-home generation is already in progress.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestHomeBatch batch = new TestHomeBatch(sender, numHomes);
|
||||||
|
_activeTask =
|
||||||
|
_plugin.getServer().getScheduler().runTaskTimer(_plugin, batch, 1L, TICKS_BETWEEN_HOMES);
|
||||||
|
sender.sendMessage(
|
||||||
|
"[Metropolis] Queued "
|
||||||
|
+ numHomes
|
||||||
|
+ " test homes; generating one every "
|
||||||
|
+ TICKS_BETWEEN_HOMES
|
||||||
|
+ " ticks.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class TestHomeBatch implements Runnable {
|
||||||
|
private final CommandSender sender;
|
||||||
|
private final int requestedHomes;
|
||||||
|
private int generatedHomes;
|
||||||
|
private int nextTestNumber = 1;
|
||||||
|
private boolean finished;
|
||||||
|
|
||||||
|
private TestHomeBatch(CommandSender sender, int requestedHomes) {
|
||||||
|
this.sender = sender;
|
||||||
|
this.requestedHomes = requestedHomes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (finished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int testNumber = nextTestNumber++;
|
||||||
|
String testName = "Test" + testNumber;
|
||||||
|
try {
|
||||||
|
OfflinePlayer testPlayer = _plugin.getServer().getOfflinePlayer(testName);
|
||||||
|
if (_plugin.getNumPlots(testPlayer.getUniqueId()) > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerHome home = _plugin.generateHome(testPlayer, 1);
|
||||||
|
if (home == null) {
|
||||||
|
sender.sendMessage(
|
||||||
|
"[Metropolis] Could not generate "
|
||||||
|
+ testName
|
||||||
|
+ "; completed "
|
||||||
|
+ generatedHomes
|
||||||
|
+ " of "
|
||||||
|
+ requestedHomes
|
||||||
|
+ " test homes.");
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedHomes++;
|
||||||
|
if (generatedHomes == requestedHomes) {
|
||||||
|
sender.sendMessage("[Metropolis] Generated " + generatedHomes + " test homes.");
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
_plugin
|
||||||
|
.getLogger()
|
||||||
|
.log(Level.SEVERE, "Failed to generate test home " + testName, exception);
|
||||||
|
sender.sendMessage(
|
||||||
|
"[Metropolis] Test-home generation failed after " + generatedHomes + " homes.");
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finish() {
|
||||||
|
finished = true;
|
||||||
|
if (_activeTask != null) {
|
||||||
|
_activeTask.cancel();
|
||||||
|
_activeTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,53 +1,65 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.command.CommandSender;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
(at your option) any later version.
|
||||||
import com.sk89q.worldguard.protection.flags.Flags;
|
|
||||||
import com.sk89q.worldguard.protection.flags.StateFlag;
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
public class MetropolisFlagResetCommand implements CommandExecutor {
|
GNU Affero General Public License for more details.
|
||||||
private MetropolisPlugin _plugin;
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
public MetropolisFlagResetCommand(MetropolisPlugin plugin){
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
_plugin = plugin;
|
*/
|
||||||
}
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
@Override
|
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
ProtectedRegion cityRegion = _plugin.regionManager.getRegion("City");
|
import com.sk89q.worldguard.protection.flags.Flags;
|
||||||
cityRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||||
cityRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
cityRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
|
||||||
cityRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
import org.bukkit.command.Command;
|
||||||
cityRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
import org.bukkit.command.CommandExecutor;
|
||||||
cityRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
import org.bukkit.command.CommandSender;
|
||||||
cityRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
|
||||||
cityRegion.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
public class MetropolisFlagResetCommand implements CommandExecutor {
|
||||||
cityRegion.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
for(ProtectedRegion homeRegion: _plugin.regionManager.getRegions().values()){
|
public MetropolisFlagResetCommand(MetropolisPlugin plugin) {
|
||||||
if(homeRegion.getId().startsWith("h_")){
|
_plugin = plugin;
|
||||||
homeRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
}
|
||||||
homeRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
|
||||||
homeRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
@Override
|
||||||
homeRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
homeRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
ProtectedRegion cityRegion = _plugin.regionManager.getRegion("City");
|
||||||
homeRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||||
homeRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||||
|
cityRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||||
}
|
cityRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||||
}
|
cityRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||||
|
cityRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||||
sender.sendMessage("Metropolis: flags have been reset");
|
cityRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||||
|
cityRegion.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
||||||
return true;
|
cityRegion.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
||||||
|
|
||||||
|
for (ProtectedRegion homeRegion : _plugin.regionManager.getRegions().values()) {
|
||||||
|
if (homeRegion.getId().startsWith("h_")) {
|
||||||
}
|
homeRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||||
|
homeRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||||
}
|
homeRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||||
|
homeRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||||
|
homeRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||||
|
homeRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||||
|
homeRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage("Metropolis: flags have been reset");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,50 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
public class MetropolisHomeAcquire implements CommandExecutor {
|
public class MetropolisHomeAcquire implements CommandExecutor {
|
||||||
MetropolisPlugin _plugin;
|
MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisHomeAcquire(MetropolisPlugin plugin){
|
|
||||||
_plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public MetropolisHomeAcquire(MetropolisPlugin plugin) {
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
_plugin = plugin;
|
||||||
if(!(sender instanceof Player)){
|
}
|
||||||
sender.sendMessage("This command can only be used by players");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player player = (Player) sender;
|
|
||||||
|
|
||||||
if(_plugin.getNumPlots(player.getUniqueId()) >= _plugin.getMaxPlots(player.getUniqueId())){
|
|
||||||
sender.sendMessage("You cannot have any more plots");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_plugin.assignPlot(player);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage("This command can only be used by players");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
if (_plugin.getNumPlots(player.getUniqueId()) >= _plugin.getMaxPlots(player)) {
|
||||||
|
sender.sendMessage("You cannot have any more plots");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _plugin.acquireHome(player) != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,79 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
|
||||||
|
|
||||||
public class MetropolisHomeEvictCommand implements CommandExecutor {
|
public class MetropolisHomeEvictCommand implements CommandExecutor {
|
||||||
MetropolisPlugin _plugin = null;
|
MetropolisPlugin _plugin = null;
|
||||||
|
|
||||||
public MetropolisHomeEvictCommand(MetropolisPlugin plugin){
|
public MetropolisHomeEvictCommand(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
|
||||||
//get the player and region
|
|
||||||
String playerName = "";
|
|
||||||
|
|
||||||
if(args.length == 0){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(args.length >= 1){
|
|
||||||
playerName = args[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
OfflinePlayer player = _plugin.getOfflinePlayer(playerName);
|
|
||||||
if(player == null){
|
|
||||||
sender.sendMessage(String.format("The requested player {%s}does not appear to exist.", playerName));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProtectedRegion region = _plugin.getRegion(_plugin.getCurrentHomeRegionName(player.getUniqueId()));
|
|
||||||
if(region == null){
|
|
||||||
sender.sendMessage(String.format("The player {%s} has no home to be evicted from."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//remove the player as owner and/or member of the region
|
|
||||||
region.getMembers().removePlayer(player.getUniqueId());
|
|
||||||
region.getOwners().removePlayer(player.getUniqueId());
|
|
||||||
|
|
||||||
//if the region has no owners delete the region
|
|
||||||
if(region.getMembers().size() == 0 && region.getOwners().size() == 0){
|
|
||||||
_plugin.removeRegion(region.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
_plugin.saveRegions();
|
|
||||||
|
|
||||||
//?optionally regen the region
|
|
||||||
//_plugin.worldEdit.getCommand("regen").execute(_plugin.getServer().getConsoleSender(), "regen", new String[]{});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
// get the player and region
|
||||||
|
String playerName = "";
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length >= 1) {
|
||||||
|
playerName = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer player = _plugin.getOfflinePlayer(playerName);
|
||||||
|
if (player == null) {
|
||||||
|
sender.sendMessage(
|
||||||
|
String.format("The requested player {%s}does not appear to exist.", playerName));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtectedRegion region =
|
||||||
|
_plugin.getRegion(_plugin.getCurrentHomeRegionName(player.getUniqueId()));
|
||||||
|
if (region == null) {
|
||||||
|
sender.sendMessage(String.format("The player {%s} has no home to be evicted from."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the player as owner and/or member of the region
|
||||||
|
region.getMembers().removePlayer(player.getUniqueId());
|
||||||
|
region.getOwners().removePlayer(player.getUniqueId());
|
||||||
|
|
||||||
|
// if the region has no owners delete the region
|
||||||
|
if (region.getMembers().size() == 0 && region.getOwners().size() == 0) {
|
||||||
|
_plugin.removeRegion(region.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
_plugin.saveRegions();
|
||||||
|
|
||||||
|
// ?optionally regen the region
|
||||||
|
// _plugin.worldEdit.getCommand("regen").execute(_plugin.getServer().getConsoleSender(),
|
||||||
|
// "regen", new String[]{});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,52 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.command.CommandSender;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
import org.bukkit.OfflinePlayer;
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
public class MetropolisHomeGenerateCommand implements CommandExecutor {
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
private MetropolisPlugin _plugin;
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
public MetropolisHomeGenerateCommand(MetropolisPlugin plugin){
|
You should have received a copy of the GNU Affero General Public License
|
||||||
_plugin = plugin;
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
}
|
*/
|
||||||
|
|
||||||
@Override
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
|
||||||
if(args.length < 1){
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
return false;
|
|
||||||
}
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
OfflinePlayer player = _plugin.getOfflinePlayer(args[0]);
|
import org.bukkit.command.CommandExecutor;
|
||||||
if (player == null) {
|
import org.bukkit.command.CommandSender;
|
||||||
sender.sendMessage("Unable to find player " + args[0]);
|
|
||||||
return false;
|
public class MetropolisHomeGenerateCommand implements CommandExecutor {
|
||||||
}
|
|
||||||
_plugin.generateHome(player);
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
sender.sendMessage("[Metropolis] Home generated for " + args[0]);
|
public MetropolisHomeGenerateCommand(MetropolisPlugin plugin) {
|
||||||
|
_plugin = plugin;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
}
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (args.length < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer player = _plugin.getOfflinePlayer(args[0]);
|
||||||
|
if (player == null) {
|
||||||
|
sender.sendMessage("Unable to find player " + args[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_plugin.generateHome(player);
|
||||||
|
|
||||||
|
sender.sendMessage("[Metropolis] Home generated for " + args[0]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,76 +1,94 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
|
||||||
|
|
||||||
public class MetropolisHomeGoCommand implements CommandExecutor {
|
public class MetropolisHomeGoCommand implements CommandExecutor {
|
||||||
MetropolisPlugin _plugin = null;
|
MetropolisPlugin _plugin = null;
|
||||||
|
|
||||||
public MetropolisHomeGoCommand(MetropolisPlugin plugin){
|
|
||||||
_plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
|
||||||
Player player = null;
|
|
||||||
if(sender instanceof Player){
|
|
||||||
player = (Player)sender;
|
|
||||||
|
|
||||||
if(args.length >= 1){
|
public MetropolisHomeGoCommand(MetropolisPlugin plugin) {
|
||||||
if(player.hasPermission("")){
|
_plugin = plugin;
|
||||||
player = _plugin.getPlayer(args[0]);
|
}
|
||||||
|
|
||||||
if(player == null){
|
|
||||||
sender.sendMessage(String.format("Unable to find player %s", args[0]));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
sender.sendMessage("Permission denied");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if(args.length >= 1){
|
|
||||||
player = _plugin.getPlayer(args[0]);
|
|
||||||
|
|
||||||
if(player == null){
|
@Override
|
||||||
sender.sendMessage(String.format("Unable to find player %s", args[0]));
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
return false;
|
Player player = null;
|
||||||
}
|
if (sender instanceof Player) {
|
||||||
}else{
|
player = (Player) sender;
|
||||||
sender.sendMessage("You must be a player");
|
|
||||||
return false;
|
if (args.length >= 1) {
|
||||||
}
|
if (player.hasPermission("")) {
|
||||||
}
|
player = _plugin.getPlayer(args[0]);
|
||||||
|
|
||||||
PlayerHome home = _plugin.getPlayerHome(player);
|
if (player == null) {
|
||||||
|
sender.sendMessage(String.format("Unable to find player %s", args[0]));
|
||||||
Location loc = null;
|
return false;
|
||||||
Location bedSpawn = player.getBedSpawnLocation();
|
}
|
||||||
|
} else {
|
||||||
if(home.contains(bedSpawn)){
|
sender.sendMessage("Permission denied");
|
||||||
loc = bedSpawn;
|
return false;
|
||||||
}else {
|
}
|
||||||
loc = home.getViableSpawnLocation(_plugin.getWorld());
|
}
|
||||||
}
|
} else {
|
||||||
|
if (args.length >= 1) {
|
||||||
if(loc != null){
|
player = _plugin.getPlayer(args[0]);
|
||||||
player.teleport(loc);
|
|
||||||
}else{
|
if (player == null) {
|
||||||
if(sender == player){
|
sender.sendMessage(String.format("Unable to find player %s", args[0]));
|
||||||
sender.sendMessage("There is no valid spawn location in your home");
|
return false;
|
||||||
}else{
|
}
|
||||||
sender.sendMessage(String.format("There is no valid spawn location in %s's home", player.getName()));
|
} else {
|
||||||
}
|
sender.sendMessage("You must be a player");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
PlayerHome home = _plugin.getPlayerHome(player);
|
||||||
|
|
||||||
|
Location loc = null;
|
||||||
|
Location bedSpawn = player.getBedSpawnLocation();
|
||||||
|
|
||||||
|
if (home.contains(bedSpawn)) {
|
||||||
|
loc = bedSpawn;
|
||||||
|
} else {
|
||||||
|
loc = home.getViableSpawnLocation(_plugin.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loc != null) {
|
||||||
|
player.teleport(loc);
|
||||||
|
} else {
|
||||||
|
if (sender == player) {
|
||||||
|
sender.sendMessage("There is no valid spawn location in your home");
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(
|
||||||
|
String.format("There is no valid spawn location in %s's home", player.getName()));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,41 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.command.CommandSender;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
(at your option) any later version.
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.Plot;
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
public class MetropolisHomeListCommand implements CommandExecutor {
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
private MetropolisPlugin _plugin;
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
public MetropolisHomeListCommand(MetropolisPlugin plugin){
|
|
||||||
_plugin = plugin;
|
You should have received a copy of the GNU Affero General Public License
|
||||||
}
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
@Override
|
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
for(Plot cityBlock : _plugin.getCityBlocks()){
|
|
||||||
sender.sendMessage(String.format("%s", cityBlock.toFriendlyString()));
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
}
|
import com.majinnaibu.bukkitplugins.metropolis.Plot;
|
||||||
return true;
|
|
||||||
}
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
}
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
public class MetropolisHomeListCommand implements CommandExecutor {
|
||||||
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
|
public MetropolisHomeListCommand(MetropolisPlugin plugin) {
|
||||||
|
_plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
for (Plot cityBlock : _plugin.getCityBlocks()) {
|
||||||
|
sender.sendMessage(String.format("%s", cityBlock.toFriendlyString()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,53 +1,86 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
public class MetropolisHomeMoveCommand implements CommandExecutor {
|
public class MetropolisHomeMoveCommand implements CommandExecutor {
|
||||||
MetropolisPlugin _plugin;
|
MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisHomeMoveCommand(MetropolisPlugin plugin) {
|
public MetropolisHomeMoveCommand(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
OfflinePlayer player = null;
|
if (args.length < 1 || args.length > 2) {
|
||||||
int newHomeNumber = 0;
|
sender.sendMessage("Usage: /metropolis-home-move <homeNumber> [playerName]");
|
||||||
|
return false;
|
||||||
if(sender instanceof OfflinePlayer){
|
}
|
||||||
player = (OfflinePlayer)sender;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(args.length == 1){
|
|
||||||
try{
|
|
||||||
newHomeNumber = Integer.parseInt(args[0]);
|
|
||||||
}catch(NumberFormatException ex){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}else if(args.length >= 2){
|
|
||||||
try{
|
|
||||||
newHomeNumber = Integer.parseInt(args[0]);
|
|
||||||
}catch(NumberFormatException ex){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
player = _plugin.getOfflinePlayer(args[1]);
|
|
||||||
}else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(player == null || !_plugin.homeExists(player.getUniqueId(), newHomeNumber)){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_plugin.setHome(player.getUniqueId(), newHomeNumber);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
int homeNumber;
|
||||||
|
try {
|
||||||
|
homeNumber = Integer.parseInt(args[0]);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
sender.sendMessage("Home number must be a positive integer.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (homeNumber < 1) {
|
||||||
|
sender.sendMessage("Home number must be a positive integer.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer target;
|
||||||
|
if (args.length == 1) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Console must specify a player name.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
target = player;
|
||||||
|
} else {
|
||||||
|
target = _plugin.getOfflinePlayer(args[1]);
|
||||||
|
if (target == null) {
|
||||||
|
sender.sendMessage("No known player named " + args[1] + ".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_plugin.homeExists(target.getUniqueId(), homeNumber)) {
|
||||||
|
String targetName =
|
||||||
|
target.getName() == null ? target.getUniqueId().toString() : target.getName();
|
||||||
|
sender.sendMessage(targetName + " does not have home " + homeNumber + ".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_plugin.setHome(target.getUniqueId(), homeNumber);
|
||||||
|
String targetName =
|
||||||
|
target.getName() == null ? target.getUniqueId().toString() : target.getName();
|
||||||
|
sender.sendMessage("Home " + homeNumber + " is now active for " + targetName + ".");
|
||||||
|
if (target instanceof Player targetPlayer && targetPlayer != sender) {
|
||||||
|
targetPlayer.sendMessage("Your active Metropolis home is now home " + homeNumber + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,133 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.Plot;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.PlotReservationLookup;
|
||||||
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.Plot;
|
|
||||||
|
|
||||||
public class MetropolisPlotGoCommand implements CommandExecutor {
|
public class MetropolisPlotGoCommand implements CommandExecutor {
|
||||||
MetropolisPlugin _plugin;
|
MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisPlotGoCommand(MetropolisPlugin plugin) {
|
public MetropolisPlotGoCommand(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
Player player = null;
|
if (args.length > 2) {
|
||||||
Plot plot = null;
|
sender.sendMessage(
|
||||||
|
"Usage: /metropolis-plot-go [playerName] or /metropolis-plot-go <reservationName> [playerName]");
|
||||||
if(sender instanceof Player){
|
return false;
|
||||||
player = (Player) sender;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(args.length == 1 && player != null){
|
|
||||||
plot = _plugin.getPlot(args[0]);
|
|
||||||
}else if(args.length >= 2){
|
|
||||||
player = _plugin.getPlayer(args[1]);
|
|
||||||
plot = _plugin.getPlot(args[0]);
|
|
||||||
}else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(plot == null || player == null){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String errorMessage = _plugin.teleportPlayerToPlot(player, plot);
|
|
||||||
if(errorMessage != null){
|
|
||||||
sender.sendMessage(errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Player player;
|
||||||
|
String requestedReservation = null;
|
||||||
|
if (args.length == 0) {
|
||||||
|
if (!(sender instanceof Player senderPlayer)) {
|
||||||
|
sender.sendMessage("Console must specify an online player name.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
player = senderPlayer;
|
||||||
|
} else if (args.length == 1) {
|
||||||
|
Player namedPlayer = _plugin.getPlayer(args[0]);
|
||||||
|
if (namedPlayer != null) {
|
||||||
|
player = namedPlayer;
|
||||||
|
} else if (sender instanceof Player senderPlayer) {
|
||||||
|
player = senderPlayer;
|
||||||
|
requestedReservation = args[0];
|
||||||
|
} else {
|
||||||
|
sender.sendMessage("No online player named " + args[0] + ".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestedReservation = args[0];
|
||||||
|
player = _plugin.getPlayer(args[1]);
|
||||||
|
if (player == null) {
|
||||||
|
sender.sendMessage("No online player named " + args[1] + ".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canManagePlots = sender.hasPermission("metropolis.plot.go");
|
||||||
|
if (player != sender && !canManagePlots) {
|
||||||
|
sender.sendMessage("You do not have permission to teleport another player.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reservations = _plugin.getReservationsForPlayer(player.getUniqueId());
|
||||||
|
Plot plot;
|
||||||
|
if (requestedReservation == null) {
|
||||||
|
if (reservations.isEmpty()) {
|
||||||
|
sender.sendMessage(
|
||||||
|
player.getName() + " is not a member or owner of a Metropolis reservation.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (reservations.size() > 1) {
|
||||||
|
String names =
|
||||||
|
reservations.stream()
|
||||||
|
.map(Plot::getRegionName)
|
||||||
|
.reduce((first, second) -> first + ", " + second)
|
||||||
|
.orElse("");
|
||||||
|
sender.sendMessage(
|
||||||
|
player.getName()
|
||||||
|
+ " belongs to multiple reservations: "
|
||||||
|
+ names
|
||||||
|
+ ". Specify one with /metropolis-plot-go <reservationName> [playerName].");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
plot = reservations.get(0);
|
||||||
|
} else {
|
||||||
|
plot = PlotReservationLookup.findByName(reservations, requestedReservation);
|
||||||
|
if (plot == null && canManagePlots) {
|
||||||
|
plot = _plugin.getPlot(requestedReservation);
|
||||||
|
}
|
||||||
|
if (plot == null) {
|
||||||
|
sender.sendMessage(
|
||||||
|
player.getName()
|
||||||
|
+ " is not a member or owner of reservation "
|
||||||
|
+ requestedReservation
|
||||||
|
+ ".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player != sender && !canManagePlots) {
|
||||||
|
sender.sendMessage("You do not have permission to teleport another player.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String errorMessage = _plugin.teleportPlayerToPlot(player, plot);
|
||||||
|
if (errorMessage != null) {
|
||||||
|
sender.sendMessage(errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String destination = "reservation " + plot.getRegionName();
|
||||||
|
sender.sendMessage("Teleported " + player.getName() + " to " + destination + ".");
|
||||||
|
if (player != sender) {
|
||||||
|
player.sendMessage("You were teleported to " + destination + ".");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +1,95 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.command.CommandSender;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
import org.bukkit.entity.Player;
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
import com.sk89q.worldedit.IncompleteRegionException;
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
import com.sk89q.worldedit.regions.Region;
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
public class MetropolisPlotReserveCommand implements CommandExecutor {
|
You should have received a copy of the GNU Affero General Public License
|
||||||
MetropolisPlugin _plugin;
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
public MetropolisPlotReserveCommand(MetropolisPlugin metropolisPlugin) {
|
|
||||||
_plugin = metropolisPlugin;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
}
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
||||||
@Override
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
import com.sk89q.worldedit.IncompleteRegionException;
|
||||||
int minX = 0;
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
int minY = 0;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
int minZ = 0;
|
|
||||||
int maxX = 0;
|
import org.bukkit.command.Command;
|
||||||
int maxY = 0;
|
import org.bukkit.command.CommandExecutor;
|
||||||
int maxZ = 0;
|
import org.bukkit.command.CommandSender;
|
||||||
Cuboid cuboid = new Cuboid();
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
if(sender instanceof Player && args.length == 1){
|
public class MetropolisPlotReserveCommand implements CommandExecutor {
|
||||||
try {
|
MetropolisPlugin _plugin;
|
||||||
Region selection = _plugin.worldEdit.getSession((Player) sender)
|
|
||||||
.getSelection(BukkitAdapter.adapt(_plugin.getWorld()));
|
public MetropolisPlotReserveCommand(MetropolisPlugin metropolisPlugin) {
|
||||||
cuboid = new Cuboid(selection);
|
_plugin = metropolisPlugin;
|
||||||
} catch (IncompleteRegionException ex) {
|
}
|
||||||
sender.sendMessage("Select a WorldEdit region first.");
|
|
||||||
return false;
|
@Override
|
||||||
}
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
}else if(args.length == 7){
|
int minX = 0;
|
||||||
try{
|
int minY = 0;
|
||||||
minX = Integer.parseInt(args[1]);
|
int minZ = 0;
|
||||||
minY = Integer.parseInt(args[2]);
|
int maxX = 0;
|
||||||
minZ = Integer.parseInt(args[3]);
|
int maxY = 0;
|
||||||
maxX = Integer.parseInt(args[4]);
|
int maxZ = 0;
|
||||||
maxY = Integer.parseInt(args[5]);
|
Cuboid cuboid = new Cuboid();
|
||||||
maxZ = Integer.parseInt(args[6]);
|
|
||||||
}catch(NumberFormatException ex){
|
if (sender instanceof Player && args.length == 1) {
|
||||||
return false;
|
try {
|
||||||
}
|
Region selection =
|
||||||
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
_plugin
|
||||||
}else if(args.length == 5){
|
.worldEdit
|
||||||
try{
|
.getSession((Player) sender)
|
||||||
minX = Integer.parseInt(args[1]);
|
.getSelection(BukkitAdapter.adapt(_plugin.getWorld()));
|
||||||
minY = 0;
|
cuboid = new Cuboid(selection);
|
||||||
minZ = Integer.parseInt(args[2]);
|
} catch (IncompleteRegionException ex) {
|
||||||
maxX = Integer.parseInt(args[3]);
|
sender.sendMessage("Select a WorldEdit region first.");
|
||||||
maxY = _plugin.getWorld().getMaxHeight();
|
return false;
|
||||||
maxZ = Integer.parseInt(args[4]);
|
}
|
||||||
}catch(NumberFormatException ex){
|
} else if (args.length == 7) {
|
||||||
return false;
|
try {
|
||||||
}
|
minX = Integer.parseInt(args[1]);
|
||||||
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
minY = Integer.parseInt(args[2]);
|
||||||
}else{
|
minZ = Integer.parseInt(args[3]);
|
||||||
return false;
|
maxX = Integer.parseInt(args[4]);
|
||||||
}
|
maxY = Integer.parseInt(args[5]);
|
||||||
|
maxZ = Integer.parseInt(args[6]);
|
||||||
String regionName = args[0];
|
} catch (NumberFormatException ex) {
|
||||||
|
return false;
|
||||||
_plugin.reserveCuboid(regionName, cuboid);
|
}
|
||||||
sender.sendMessage("Reserved plot region " + regionName + ".");
|
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
} else if (args.length == 5) {
|
||||||
return true;
|
try {
|
||||||
}
|
minX = Integer.parseInt(args[1]);
|
||||||
|
minY = 0;
|
||||||
}
|
minZ = Integer.parseInt(args[2]);
|
||||||
|
maxX = Integer.parseInt(args[3]);
|
||||||
|
maxY = _plugin.getWorld().getMaxHeight();
|
||||||
|
maxZ = Integer.parseInt(args[4]);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String regionName = args[0];
|
||||||
|
|
||||||
|
_plugin.reserveCuboid(regionName, cuboid);
|
||||||
|
sender.sendMessage("Reserved plot region " + regionName + ".");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,38 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.eventlisteners;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.EventPriority;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.event.Listener;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
public class DebugListener implements Listener {
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MetropolisPlugin _plugin;
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
public DebugListener(MetropolisPlugin plugin){
|
|
||||||
_plugin = plugin;
|
You should have received a copy of the GNU Affero General Public License
|
||||||
}
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
@EventHandler(priority=EventPriority.MONITOR)
|
|
||||||
public void onEvent(PlayerInteractEvent event){
|
package com.majinnaibu.bukkitplugins.metropolis.eventlisteners;
|
||||||
//event.
|
|
||||||
}
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
}
|
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
|
||||||
|
public class DebugListener implements Listener {
|
||||||
|
MetropolisPlugin _plugin;
|
||||||
|
|
||||||
|
public DebugListener(MetropolisPlugin plugin) {
|
||||||
|
_plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onEvent(PlayerInteractEvent event) {
|
||||||
|
// event.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,39 +1,73 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.eventlisteners;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.EventHandler;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
import org.bukkit.event.EventPriority;
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
import org.bukkit.event.Listener;
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
(at your option) any later version.
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
public class PlayerJoinListener implements Listener {
|
|
||||||
private MetropolisPlugin _plugin = null;
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
public PlayerJoinListener(MetropolisPlugin plugin){
|
*/
|
||||||
_plugin = plugin;
|
|
||||||
}
|
package com.majinnaibu.bukkitplugins.metropolis.eventlisteners;
|
||||||
|
|
||||||
@EventHandler(priority=EventPriority.MONITOR)
|
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
||||||
public void onPlayerJoin(PlayerJoinEvent event){
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
Player player = event.getPlayer();
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
if(player == null){
|
|
||||||
return;
|
import org.bukkit.entity.Player;
|
||||||
}
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
PlayerHome home = _plugin.getPlayerHome(player);
|
import org.bukkit.event.Listener;
|
||||||
if(home==null){_plugin.getLogger().info("home is null");}
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
else if(home.getCuboid() == null){_plugin.getLogger().info("home.getCuboid() is null");}
|
|
||||||
else if(home.getCuboid().getVolume() == 0){_plugin.getLogger().info("home.getCuboid().getVolume() is 0");}
|
public class PlayerJoinListener implements Listener {
|
||||||
|
private MetropolisPlugin _plugin = null;
|
||||||
if(home == null || home.getCuboid() == null || home.getCuboid().getVolume() == 0){
|
|
||||||
_plugin.getLogger().info(String.format("Metropolis: Unable to get or create home for player %s", player.getName()));
|
public PlayerJoinListener(MetropolisPlugin plugin) {
|
||||||
}else{
|
_plugin = plugin;
|
||||||
Cuboid cuboid = home.getCuboid();
|
}
|
||||||
player.sendMessage(String.format("Metropolis: Welcome %s your home is between (%d, %d, %d) and (%d, %d, %d)", player.getName(), cuboid.getMinX(), cuboid.getMinY(), cuboid.getMinZ(), cuboid.getMaxX(), cuboid.getMaxY(), cuboid.getMaxZ()));
|
|
||||||
}
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
}
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
}
|
Player player = event.getPlayer();
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerHome home = _plugin.getPlayerHome(player);
|
||||||
|
if (home == null) {
|
||||||
|
_plugin.getLogger().info("home is null");
|
||||||
|
} else if (home.getCuboid() == null) {
|
||||||
|
_plugin.getLogger().info("home.getCuboid() is null");
|
||||||
|
} else if (home.getCuboid().getVolume() == 0) {
|
||||||
|
_plugin.getLogger().info("home.getCuboid().getVolume() is 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (home == null || home.getCuboid() == null || home.getCuboid().getVolume() == 0) {
|
||||||
|
_plugin
|
||||||
|
.getLogger()
|
||||||
|
.info(
|
||||||
|
String.format(
|
||||||
|
"Metropolis: Unable to get or create home for player %s", player.getName()));
|
||||||
|
} else {
|
||||||
|
Cuboid cuboid = home.getCuboid();
|
||||||
|
player.sendMessage(
|
||||||
|
String.format(
|
||||||
|
"Metropolis: Welcome %s your home is between (%d, %d, %d) and (%d, %d, %d)",
|
||||||
|
player.getName(),
|
||||||
|
cuboid.getMinX(),
|
||||||
|
cuboid.getMinY(),
|
||||||
|
cuboid.getMinZ(),
|
||||||
|
cuboid.getMaxX(),
|
||||||
|
cuboid.getMaxY(),
|
||||||
|
cuboid.getMaxZ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.util;
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
public class Material {
|
|
||||||
public static final int signTypeId = 63;
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
}
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis.util;
|
||||||
|
|
||||||
|
public class Material {
|
||||||
|
public static final int signTypeId = 63;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
Goal: Have 1 plot size for mods and 1 plot size for normals.
|
|
||||||
Implementation:
|
|
||||||
1. Set the plotSize in the config to the larger plot size there are other possible sizes but powers of 2 work really well so lets give mods a 64x64 plot. Roads are recomended but will eat into the plot on all sides so the roadsize can be ignored for this example.
|
|
||||||
2. Start the server and run /metropolis-home-generate <username> for each mod.
|
|
||||||
3. If you have not completed a full ring then run /metropolis-home-generate <test n> where n starts at 1 and increases until you have filled a full ring. This will reserve those plots for later or anyone who happens to have that username, but that can be dealth with later.
|
|
||||||
4. Stop the server and change the plotSize to a smaller number like 32.
|
|
||||||
5. Restart the server and let your users log in.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
All users will be assigned a small plot when they first connect. When you make them a mod just delete their current region and change the name of one of the reserved home regions to h_<their username>.
|
|
||||||
/region remove h_jonnynewadmin
|
|
||||||
/region select test_1
|
|
||||||
/region remove test_1
|
|
||||||
/region define h_johnnynewadmin
|
|
||||||
JohnnyNewAdmin's old home will become available for resettlement. If you want to copy johnny's old blocks into his new home region I'd suggest WorldEdit.
|
|
||||||
This process including optionally copying the blocks will be an upcoming feature. Probably in 0.5.x and maybe in 0.6.
|
|
||||||
|
|
||||||
If you later run out of larger plots you can just set the size back to the larger number and let more generate. Moving and evictions will be coming soon and make this easier.
|
|
||||||
You can manually just create a region for a user before they login named like h_<username> if you want a specific user to have a really big space as well.
|
|
||||||
@@ -18,16 +18,38 @@ plot:
|
|||||||
sign:
|
sign:
|
||||||
generate: true
|
generate: true
|
||||||
road:
|
road:
|
||||||
width: 4
|
avenues:
|
||||||
clearSpaceAbove: 66
|
width: 4
|
||||||
level: 62
|
clearSpaceAbove: 66
|
||||||
material: COBBLESTONE
|
level: 62
|
||||||
supports:
|
material: COBBLESTONE
|
||||||
generate: true
|
supports:
|
||||||
material: STONE
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
stairs:
|
||||||
|
generate: false
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
width: 2
|
||||||
|
streets:
|
||||||
|
width: 4
|
||||||
|
clearSpaceAbove: 66
|
||||||
|
level: 62
|
||||||
|
material: COBBLESTONE
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
stairs:
|
||||||
|
generate: false
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
width: 2
|
||||||
spawn:
|
spawn:
|
||||||
generate: true
|
generate: true
|
||||||
setAsWorldSpawn: true
|
setAsWorldSpawn: true
|
||||||
|
sizeX: 1
|
||||||
|
sizeY: 1
|
||||||
|
sizeZ: 1
|
||||||
material: GRASS_BLOCK
|
material: GRASS_BLOCK
|
||||||
wall:
|
wall:
|
||||||
generate: true
|
generate: true
|
||||||
|
|||||||
66
src/main/resources/examples/multi-level.yml
Normal file
66
src/main/resources/examples/multi-level.yml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
plot:
|
||||||
|
sizeX: 32
|
||||||
|
sizeY: 10
|
||||||
|
sizeZ: 32
|
||||||
|
offsetX: 0
|
||||||
|
offsetY: 0
|
||||||
|
offsetZ: 0
|
||||||
|
multiplier: 1
|
||||||
|
maxLevels: 10
|
||||||
|
maxPerPlayer: 1
|
||||||
|
initial: 1
|
||||||
|
floor:
|
||||||
|
generate: true
|
||||||
|
clearSpaceAbove: 0
|
||||||
|
material: GRASS_BLOCK
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
sign:
|
||||||
|
generate: true
|
||||||
|
road:
|
||||||
|
avenues:
|
||||||
|
width: 4
|
||||||
|
clearSpaceAbove: 9
|
||||||
|
level: 62
|
||||||
|
material: COBBLESTONE
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
stairs:
|
||||||
|
generate: true
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
width: 2
|
||||||
|
streets:
|
||||||
|
width: 4
|
||||||
|
clearSpaceAbove: 9
|
||||||
|
level: 62
|
||||||
|
material: COBBLESTONE
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
stairs:
|
||||||
|
generate: false
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
width: 2
|
||||||
|
spawn:
|
||||||
|
generate: true
|
||||||
|
setAsWorldSpawn: true
|
||||||
|
sizeX: 1
|
||||||
|
sizeY: 1
|
||||||
|
sizeZ: 1
|
||||||
|
material: GRASS_BLOCK
|
||||||
|
wall:
|
||||||
|
generate: true
|
||||||
|
material: BEDROCK
|
||||||
|
height: 10
|
||||||
|
worldname: world
|
||||||
|
userOverrides:
|
||||||
|
- username: majinnaibu
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 2
|
||||||
|
- username: hoggrim
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 2
|
||||||
@@ -22,7 +22,6 @@ commands:
|
|||||||
permission: metropolis.home.evict
|
permission: metropolis.home.evict
|
||||||
usage: /metropolis-home-evict <playerName>
|
usage: /metropolis-home-evict <playerName>
|
||||||
metropolis-home-move:
|
metropolis-home-move:
|
||||||
description: This command swaps a player's current home with a reserved plot.
|
|
||||||
permission: metropolis.home.move
|
permission: metropolis.home.move
|
||||||
usage: /metropolis-home-move <newHomeNumber> [player]
|
usage: /metropolis-home-move <newHomeNumber> [player]
|
||||||
metropolis-home-go:
|
metropolis-home-go:
|
||||||
@@ -41,9 +40,8 @@ commands:
|
|||||||
permission: metropolis.plot.reserve
|
permission: metropolis.plot.reserve
|
||||||
usage: /metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>
|
usage: /metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>
|
||||||
metropolis-plot-go:
|
metropolis-plot-go:
|
||||||
description: This command teleports the user or another player to a plot.
|
description: Teleports the sender or an authorized target to a reservation.
|
||||||
permission: metropolis.plot.go
|
usage: /metropolis-plot-go [playerName] or /metropolis-plot-go <reservationName> [playerName]
|
||||||
usage: /metropolis-plot-go <plotName> [playerName]
|
|
||||||
metropolis-debug-generatetesthomes:
|
metropolis-debug-generatetesthomes:
|
||||||
description: This command is ignored on non-debug builds.
|
description: This command is ignored on non-debug builds.
|
||||||
permission: metropolis.debug
|
permission: metropolis.debug
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.AvenueStairwayLayout.Flight;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.AvenueStairwayLayout.Step;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.block.data.Bisected;
|
||||||
|
import org.bukkit.block.data.type.Stairs;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class AvenueStairwayBuilderTest {
|
||||||
|
@Test
|
||||||
|
void buildsStreetStairsFacingEastWithWestFacingInvertedBacking() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
Flight flight =
|
||||||
|
AvenueStairwayLayout.forUpperStreetSegments(plot, 32, 36, 0, 1, 4, 2, 62, 14, 3).getFirst();
|
||||||
|
World world = mock(World.class);
|
||||||
|
Map<Step, Block> blocks = new HashMap<>();
|
||||||
|
Map<Step, Stairs> blockData = new HashMap<>();
|
||||||
|
when(world.getBlockAt(anyInt(), anyInt(), anyInt()))
|
||||||
|
.thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
Step position =
|
||||||
|
new Step(
|
||||||
|
invocation.getArgument(0),
|
||||||
|
invocation.getArgument(1),
|
||||||
|
invocation.getArgument(2));
|
||||||
|
return blocks.computeIfAbsent(
|
||||||
|
position,
|
||||||
|
ignored -> {
|
||||||
|
Block block = mock(Block.class);
|
||||||
|
Stairs stairs = mock(Stairs.class);
|
||||||
|
when(block.getBlockData()).thenReturn(stairs);
|
||||||
|
blockData.put(position, stairs);
|
||||||
|
return block;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AvenueStairwayBuilder.build(world, flight, Material.COBBLESTONE_STAIRS);
|
||||||
|
|
||||||
|
verify(blockData.get(flight.stairBlocks().getFirst())).setFacing(BlockFace.EAST);
|
||||||
|
verify(blockData.get(flight.invertedBackingBlocks().getFirst())).setFacing(BlockFace.WEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildsAscendingAndInvertedStairsAndClearsOnlyThePlannedOpening() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
Flight flight = AvenueStairwayLayout.forUpperRoad(plot, 4, 2, 62, 14, 3);
|
||||||
|
World world = mock(World.class);
|
||||||
|
Map<Step, Block> blocks = new HashMap<>();
|
||||||
|
Map<Step, Stairs> blockData = new HashMap<>();
|
||||||
|
when(world.getBlockAt(anyInt(), anyInt(), anyInt()))
|
||||||
|
.thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
Step position =
|
||||||
|
new Step(
|
||||||
|
invocation.getArgument(0),
|
||||||
|
invocation.getArgument(1),
|
||||||
|
invocation.getArgument(2));
|
||||||
|
return blocks.computeIfAbsent(
|
||||||
|
position,
|
||||||
|
ignored -> {
|
||||||
|
Block block = mock(Block.class);
|
||||||
|
Stairs stairs = mock(Stairs.class);
|
||||||
|
when(block.getBlockData()).thenReturn(stairs);
|
||||||
|
blockData.put(position, stairs);
|
||||||
|
return block;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AvenueStairwayBuilder.build(world, flight, Material.COBBLESTONE_STAIRS);
|
||||||
|
|
||||||
|
for (Step stair : flight.stairBlocks()) {
|
||||||
|
verify(blocks.get(stair)).setType(Material.COBBLESTONE_STAIRS, false);
|
||||||
|
}
|
||||||
|
for (Step backing : flight.invertedBackingBlocks()) {
|
||||||
|
verify(blocks.get(backing)).setType(Material.COBBLESTONE_STAIRS, false);
|
||||||
|
}
|
||||||
|
for (Step opening : flight.roadOpenings()) {
|
||||||
|
verify(blocks.get(opening)).setType(Material.AIR, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Step firstStair = flight.stairBlocks().getFirst();
|
||||||
|
Stairs firstStairData = blockData.get(firstStair);
|
||||||
|
assertNotNull(firstStairData);
|
||||||
|
verify(firstStairData).setFacing(BlockFace.SOUTH);
|
||||||
|
verify(firstStairData).setHalf(Bisected.Half.BOTTOM);
|
||||||
|
|
||||||
|
Step firstInvertedBacking = flight.invertedBackingBlocks().getFirst();
|
||||||
|
Stairs invertedBackingData = blockData.get(firstInvertedBacking);
|
||||||
|
assertNotNull(invertedBackingData);
|
||||||
|
verify(invertedBackingData).setFacing(BlockFace.NORTH);
|
||||||
|
verify(invertedBackingData).setHalf(Bisected.Half.TOP);
|
||||||
|
|
||||||
|
verify(world, never()).getBlockAt(anyInt(), eq(62), anyInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class AvenueStairwayLayoutTest {
|
||||||
|
@Test
|
||||||
|
void centersStairsAcrossTheAvenueAndAlongThePlotLength() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
|
||||||
|
List<AvenueStairwayLayout.Step> steps =
|
||||||
|
AvenueStairwayLayout.centeredEastAvenue(plot, 4, 2, 62, 14);
|
||||||
|
|
||||||
|
assertEquals(28, steps.size());
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(35, 63, 12), steps.get(0));
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(36, 63, 12), steps.get(1));
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(35, 64, 13), steps.get(2));
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(36, 76, 25), steps.get(27));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void opensOnlyTheThreeUpperRoadBlocksBeforeTheTopStair() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
|
||||||
|
List<AvenueStairwayLayout.Step> openings =
|
||||||
|
AvenueStairwayLayout.upperRoadOpening(plot, 4, 2, 62, 14, 3);
|
||||||
|
|
||||||
|
assertEquals(6, openings.size());
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(35, 76, 22), openings.get(0));
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(35, 76, 23), openings.get(1));
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(35, 76, 24), openings.get(2));
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(36, 76, 24), openings.get(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void upperRoadFlightPreservesTheLowerRoadAndPlansInvertedBacking() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
|
||||||
|
AvenueStairwayLayout.Flight flight = AvenueStairwayLayout.forUpperRoad(plot, 4, 2, 62, 14, 3);
|
||||||
|
|
||||||
|
assertEquals(28, flight.stairBlocks().size());
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(35, 63, 12), flight.stairBlocks().getFirst());
|
||||||
|
assertEquals(new AvenueStairwayLayout.Step(36, 76, 25), flight.stairBlocks().getLast());
|
||||||
|
assertEquals(26, flight.invertedBackingBlocks().size());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(35, 63, 13), flight.invertedBackingBlocks().getFirst());
|
||||||
|
assertTrue(flight.stairBlocks().stream().allMatch(step -> step.y() > 62));
|
||||||
|
assertTrue(flight.invertedBackingBlocks().stream().allMatch(step -> step.y() > 62));
|
||||||
|
assertEquals(6, flight.roadOpenings().size());
|
||||||
|
assertTrue(flight.roadOpenings().stream().allMatch(step -> step.y() == 76));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsOneCenteredStairFlightPerLogicalPlotOnBothAvenueBorders() {
|
||||||
|
Cuboid twoByTwoPlot = new Cuboid(2, 62, 2, 69, 71, 69);
|
||||||
|
|
||||||
|
List<AvenueStairwayLayout.Flight> flights =
|
||||||
|
AvenueStairwayLayout.forUpperRoadSegments(twoByTwoPlot, 32, 36, 0, 1, 4, 2, 62, 14, 3);
|
||||||
|
|
||||||
|
assertEquals(4, flights.size());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(71, 63, 12), flights.get(0).stairBlocks().getFirst());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(-1, 63, 12), flights.get(1).stairBlocks().getFirst());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(71, 63, 48), flights.get(2).stairBlocks().getFirst());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(-1, 63, 48), flights.get(3).stairBlocks().getFirst());
|
||||||
|
assertTrue(flights.stream().allMatch(flight -> flight.stairBlocks().getLast().y() == 76));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void appliesStairCadenceToEachLogicalPlotSegment() {
|
||||||
|
Cuboid twoByTwoPlot = new Cuboid(2, 62, 2, 69, 71, 69);
|
||||||
|
|
||||||
|
List<AvenueStairwayLayout.Flight> flights =
|
||||||
|
AvenueStairwayLayout.forUpperRoadSegments(twoByTwoPlot, 32, 36, 0, 2, 4, 2, 62, 14, 3);
|
||||||
|
|
||||||
|
assertEquals(2, flights.size());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(71, 63, 48), flights.getFirst().stairBlocks().getFirst());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(-1, 63, 48), flights.getLast().stairBlocks().getFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsStreetFlightsAlongXOnBothNorthAndSouthBorders() {
|
||||||
|
Cuboid twoByTwoPlot = new Cuboid(2, 62, 2, 69, 71, 69);
|
||||||
|
|
||||||
|
List<AvenueStairwayLayout.Flight> flights =
|
||||||
|
AvenueStairwayLayout.forUpperStreetSegments(twoByTwoPlot, 32, 36, 0, 1, 4, 2, 62, 14, 3);
|
||||||
|
|
||||||
|
assertEquals(4, flights.size());
|
||||||
|
assertEquals(AvenueStairwayLayout.Side.NORTH, flights.getFirst().side());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(12, 63, -1), flights.getFirst().stairBlocks().getFirst());
|
||||||
|
assertEquals(AvenueStairwayLayout.Side.SOUTH, flights.get(1).side());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(12, 63, 71), flights.get(1).stairBlocks().getFirst());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(48, 63, -1), flights.get(2).stairBlocks().getFirst());
|
||||||
|
assertTrue(flights.stream().allMatch(flight -> flight.stairBlocks().getLast().y() == 76));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsAStairRunThatCannotFitWithinThePlotLength() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 13);
|
||||||
|
|
||||||
|
assertTrue(AvenueStairwayLayout.centeredEastAvenue(plot, 4, 2, 62, 14).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsStairsThatAreNotNarrowerThanTheAvenue() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
|
||||||
|
assertTrue(AvenueStairwayLayout.centeredEastAvenue(plot, 4, 4, 62, 14).isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class CityRegionBoundsTest {
|
||||||
|
@Test
|
||||||
|
void expandsExistingCityToIncludeElevatedHomesAndSpawnWithoutShrinking() {
|
||||||
|
Cuboid existingCity = new Cuboid(-20, 62, -20, 20, 71, 20);
|
||||||
|
Cuboid requestedCity = new Cuboid(-40, 62, -40, 40, 71, 40);
|
||||||
|
Cuboid elevatedHome = new Cuboid(10, 104, 10, 41, 113, 41);
|
||||||
|
Cuboid spawn = new Cuboid(2, 62, 2, 69, 81, 69);
|
||||||
|
|
||||||
|
Cuboid expanded =
|
||||||
|
CityRegionBounds.expandToContain(existingCity, requestedCity, List.of(elevatedHome, spawn));
|
||||||
|
|
||||||
|
assertEquals(-40, expanded.minX);
|
||||||
|
assertEquals(62, expanded.minY);
|
||||||
|
assertEquals(-40, expanded.minZ);
|
||||||
|
assertEquals(69, expanded.maxX);
|
||||||
|
assertEquals(113, expanded.maxY);
|
||||||
|
assertEquals(69, expanded.maxZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,53 +1,70 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
|
||||||
|
|
||||||
class CuboidTest {
|
class CuboidTest {
|
||||||
@Test
|
@Test
|
||||||
void convertsWorldEditBoundsToCuboidCoordinates() {
|
void convertsWorldEditBoundsToCuboidCoordinates() {
|
||||||
Cuboid cuboid = new Cuboid(BlockVector3.at(1, 2, 3), BlockVector3.at(4, 5, 6));
|
Cuboid cuboid = new Cuboid(BlockVector3.at(1, 2, 3), BlockVector3.at(4, 5, 6));
|
||||||
|
|
||||||
assertEquals(1, cuboid.getMinX());
|
assertEquals(1, cuboid.getMinX());
|
||||||
assertEquals(2, cuboid.getMinY());
|
assertEquals(2, cuboid.getMinY());
|
||||||
assertEquals(3, cuboid.getMinZ());
|
assertEquals(3, cuboid.getMinZ());
|
||||||
assertEquals(4, cuboid.getMaxX());
|
assertEquals(4, cuboid.getMaxX());
|
||||||
assertEquals(5, cuboid.getMaxY());
|
assertEquals(5, cuboid.getMaxY());
|
||||||
assertEquals(6, cuboid.getMaxZ());
|
assertEquals(6, cuboid.getMaxZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void comparesVectorsByXThenZThenYAndHandlesNulls() {
|
void comparesVectorsByXThenZThenYAndHandlesNulls() {
|
||||||
assertEquals(-1, Cuboid.compareBlockVectors(null, BlockVector3.ZERO));
|
assertEquals(-1, Cuboid.compareBlockVectors(null, BlockVector3.ZERO));
|
||||||
assertEquals(1, Cuboid.compareBlockVectors(BlockVector3.ZERO, null));
|
assertEquals(1, Cuboid.compareBlockVectors(BlockVector3.ZERO, null));
|
||||||
assertEquals(0, Cuboid.compareBlockVectors(null, null));
|
assertEquals(0, Cuboid.compareBlockVectors(null, null));
|
||||||
assertTrue(Cuboid.compareBlockVectors(BlockVector3.at(1, 0, 0), BlockVector3.at(2, 0, 0)) < 0);
|
assertTrue(Cuboid.compareBlockVectors(BlockVector3.at(1, 0, 0), BlockVector3.at(2, 0, 0)) < 0);
|
||||||
assertTrue(Cuboid.compareBlockVectors(BlockVector3.at(1, 0, 1), BlockVector3.at(1, 0, 2)) < 0);
|
assertTrue(Cuboid.compareBlockVectors(BlockVector3.at(1, 0, 1), BlockVector3.at(1, 0, 2)) < 0);
|
||||||
assertTrue(Cuboid.compareBlockVectors(BlockVector3.at(1, 1, 2), BlockVector3.at(1, 2, 2)) < 0);
|
assertTrue(Cuboid.compareBlockVectors(BlockVector3.at(1, 1, 2), BlockVector3.at(1, 2, 2)) < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void treatsTouchingBoundsAsIntersecting() {
|
void treatsTouchingBoundsAsIntersecting() {
|
||||||
Cuboid first = new Cuboid(0, 0, 0, 10, 10, 10);
|
Cuboid first = new Cuboid(0, 0, 0, 10, 10, 10);
|
||||||
Cuboid touching = new Cuboid(10, 10, 10, 20, 20, 20);
|
Cuboid touching = new Cuboid(10, 10, 10, 20, 20, 20);
|
||||||
Cuboid separate = new Cuboid(11, 0, 0, 20, 10, 10);
|
Cuboid separate = new Cuboid(11, 0, 0, 20, 10, 10);
|
||||||
|
|
||||||
assertTrue(first.intersects(touching));
|
assertTrue(first.intersects(touching));
|
||||||
assertFalse(first.intersects(separate));
|
assertFalse(first.intersects(separate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void containsPointsOnBoundsButRejectsPointsOutside() {
|
void containsPointsOnBoundsButRejectsPointsOutside() {
|
||||||
Cuboid cuboid = new Cuboid(1, 2, 3, 4, 5, 6);
|
Cuboid cuboid = new Cuboid(1, 2, 3, 4, 5, 6);
|
||||||
|
|
||||||
assertTrue(cuboid.contains(new Location(null, 1, 2, 3)));
|
assertTrue(cuboid.contains(new Location(null, 1, 2, 3)));
|
||||||
assertTrue(cuboid.contains(new Location(null, 4, 5, 6)));
|
assertTrue(cuboid.contains(new Location(null, 4, 5, 6)));
|
||||||
assertFalse(cuboid.contains(new Location(null, 5, 5, 6)));
|
assertFalse(cuboid.contains(new Location(null, 5, 5, 6)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -11,31 +28,34 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
class CurrentHomesStoreTest {
|
class CurrentHomesStoreTest {
|
||||||
@TempDir
|
@TempDir File temporaryDirectory;
|
||||||
File temporaryDirectory;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void roundTripsHomesByUuid() throws Exception {
|
void roundTripsHomesByUuid() throws Exception {
|
||||||
File file = new File(temporaryDirectory, "currentHomes.yml");
|
File file = new File(temporaryDirectory, "currentHomes.yml");
|
||||||
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
Map<UUID, Integer> expected = Map.of(playerId, 3);
|
Map<UUID, Integer> expected = Map.of(playerId, 3);
|
||||||
|
|
||||||
CurrentHomesStore.save(file, expected);
|
CurrentHomesStore.save(file, expected);
|
||||||
|
|
||||||
assertEquals(expected, CurrentHomesStore.load(file, ignored -> null));
|
assertEquals(expected, CurrentHomesStore.load(file, ignored -> null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void resolvesLegacyPlayerNameKeys() throws Exception {
|
void resolvesLegacyPlayerNameKeys() throws Exception {
|
||||||
File file = new File(temporaryDirectory, "currentHomes.yml");
|
File file = new File(temporaryDirectory, "currentHomes.yml");
|
||||||
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
YamlConfiguration yaml = new YamlConfiguration();
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
yaml.set("OldPlayerName", 2);
|
yaml.set("OldPlayerName", 2);
|
||||||
yaml.save(file);
|
yaml.save(file);
|
||||||
|
|
||||||
assertEquals(Map.of(playerId, 2), CurrentHomesStore.load(file, name -> {
|
assertEquals(
|
||||||
assertEquals("OldPlayerName", name);
|
Map.of(playerId, 2),
|
||||||
return playerId;
|
CurrentHomesStore.load(
|
||||||
}));
|
file,
|
||||||
}
|
name -> {
|
||||||
}
|
assertEquals("OldPlayerName", name);
|
||||||
|
return playerId;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class HomeNumberAllocatorTest {
|
||||||
|
@Test
|
||||||
|
void choosesTheFirstUnusedPositiveHomeNumber() {
|
||||||
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
PlayerHome first =
|
||||||
|
new PlayerHome(playerId, "PlayerName", 1, BlockVector3.ZERO, BlockVector3.ZERO);
|
||||||
|
PlayerHome third =
|
||||||
|
new PlayerHome(playerId, "PlayerName", 3, BlockVector3.ZERO, BlockVector3.ZERO);
|
||||||
|
|
||||||
|
assertEquals(1, HomeNumberAllocator.nextHomeNumber(List.of()));
|
||||||
|
assertEquals(2, HomeNumberAllocator.nextHomeNumber(List.of(first, third)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class HomePermissionOverrideTest {
|
||||||
|
@Test
|
||||||
|
void selectsTheHighestPriorityAndUsesLastEntryForTies() throws InvalidConfigurationException {
|
||||||
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
|
config.loadFromString(
|
||||||
|
"""
|
||||||
|
permissionOverrides:
|
||||||
|
metropolis.plots.vip:
|
||||||
|
priority: 10
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 2
|
||||||
|
metropolis.plots.elite:
|
||||||
|
priority: 20
|
||||||
|
plotMultiplier: 3
|
||||||
|
maxPlots: 5
|
||||||
|
metropolis.plots.staff:
|
||||||
|
priority: 20
|
||||||
|
plotMultiplier: 4
|
||||||
|
maxPlots: 8
|
||||||
|
""");
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
|
||||||
|
List<HomePermissionOverride> overrides =
|
||||||
|
HomePermissionOverride.load(
|
||||||
|
config.getConfigurationSection("permissionOverrides"), warnings::add);
|
||||||
|
HomePermissionOverride match =
|
||||||
|
HomePermissionOverride.findMatch(
|
||||||
|
overrides,
|
||||||
|
Set.of("metropolis.plots.vip", "metropolis.plots.elite", "metropolis.plots.staff")
|
||||||
|
::contains);
|
||||||
|
|
||||||
|
assertEquals(3, overrides.size());
|
||||||
|
assertEquals("metropolis.plots.staff", match.getPermission());
|
||||||
|
assertEquals(4, match.getPlotMultiplier());
|
||||||
|
assertEquals(8, match.getMaxPlots());
|
||||||
|
assertEquals(List.of(), warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void preservesDottedPermissionNamesAndSkipsInvalidEntries() throws InvalidConfigurationException {
|
||||||
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
|
config.loadFromString(
|
||||||
|
"""
|
||||||
|
permissionOverrides:
|
||||||
|
metropolis.plots.vip:
|
||||||
|
priority: 3
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 4
|
||||||
|
metropolis.plots.invalid:
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 4
|
||||||
|
""");
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
|
||||||
|
List<HomePermissionOverride> overrides =
|
||||||
|
HomePermissionOverride.load(
|
||||||
|
config.getConfigurationSection("permissionOverrides"), warnings::add);
|
||||||
|
HomePermissionOverride match =
|
||||||
|
HomePermissionOverride.findMatch(overrides, "metropolis.plots.vip"::equals);
|
||||||
|
|
||||||
|
assertEquals(1, overrides.size());
|
||||||
|
assertEquals("metropolis.plots.vip", overrides.get(0).getPermission());
|
||||||
|
assertSame(overrides.get(0), match);
|
||||||
|
assertNull(HomePermissionOverride.findMatch(overrides, ignored -> false));
|
||||||
|
assertEquals(1, warnings.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolvesUserOverrideBeforePermissionAndGlobalFallback()
|
||||||
|
throws InvalidConfigurationException {
|
||||||
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
|
config.set("permissionOverrides.metropolis.plots.vip.priority", 10);
|
||||||
|
config.set("permissionOverrides.metropolis.plots.vip.plotMultiplier", 2);
|
||||||
|
config.set("permissionOverrides.metropolis.plots.vip.maxPlots", 4);
|
||||||
|
List<HomePermissionOverride> overrides =
|
||||||
|
HomePermissionOverride.load(
|
||||||
|
config.getConfigurationSection("permissionOverrides"), ignored -> {});
|
||||||
|
UserOverride userOverride = new UserOverride("PlayerName", 3, 6);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
6,
|
||||||
|
HomePermissionOverride.resolveMaxPlots(
|
||||||
|
userOverride, overrides, "metropolis.plots.vip"::equals, 1));
|
||||||
|
assertEquals(
|
||||||
|
3,
|
||||||
|
HomePermissionOverride.resolvePlotMultiplier(
|
||||||
|
userOverride, overrides, "metropolis.plots.vip"::equals, 1));
|
||||||
|
assertEquals(
|
||||||
|
4,
|
||||||
|
HomePermissionOverride.resolveMaxPlots(null, overrides, "metropolis.plots.vip"::equals, 1));
|
||||||
|
assertEquals(
|
||||||
|
2,
|
||||||
|
HomePermissionOverride.resolvePlotMultiplier(
|
||||||
|
null, overrides, "metropolis.plots.vip"::equals, 1));
|
||||||
|
assertEquals(1, HomePermissionOverride.resolveMaxPlots(null, overrides, ignored -> false, 1));
|
||||||
|
assertEquals(
|
||||||
|
1, HomePermissionOverride.resolvePlotMultiplier(null, overrides, ignored -> false, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,41 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
class PlayerHomeTest {
|
import org.junit.jupiter.api.Test;
|
||||||
@Test
|
|
||||||
void buildsUuidBasedRegionIdentityAndPreservesDisplayName() {
|
|
||||||
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
|
||||||
PlayerHome home = new PlayerHome(
|
|
||||||
playerId,
|
|
||||||
"PlayerName",
|
|
||||||
2,
|
|
||||||
BlockVector3.at(1, 2, 3),
|
|
||||||
BlockVector3.at(4, 5, 6));
|
|
||||||
|
|
||||||
assertEquals(playerId, home.getPlayerId());
|
class PlayerHomeTest {
|
||||||
assertEquals("PlayerName", home.getPlayerName());
|
@Test
|
||||||
assertEquals(2, home.getNumber());
|
void buildsUuidBasedRegionIdentityAndPreservesDisplayName() {
|
||||||
assertEquals("h_2_" + playerId, home.getRegionName());
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
}
|
PlayerHome home =
|
||||||
}
|
new PlayerHome(
|
||||||
|
playerId, "PlayerName", 2, BlockVector3.at(1, 2, 3), BlockVector3.at(4, 5, 6));
|
||||||
|
|
||||||
|
assertEquals(playerId, home.getPlayerId());
|
||||||
|
assertEquals("PlayerName", home.getPlayerName());
|
||||||
|
assertEquals(2, home.getNumber());
|
||||||
|
assertEquals("h_2_" + playerId, home.getRegionName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis;
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
@@ -11,12 +28,12 @@ import org.bukkit.entity.Player;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class PlayerLookupTest {
|
class PlayerLookupTest {
|
||||||
@Test
|
@Test
|
||||||
void matchesOnlinePlayerNamesCaseInsensitivelyButNotByPrefix() {
|
void matchesOnlinePlayerNamesCaseInsensitivelyButNotByPrefix() {
|
||||||
Player player = mock(Player.class);
|
Player player = mock(Player.class);
|
||||||
when(player.getName()).thenReturn("PlayerName");
|
when(player.getName()).thenReturn("PlayerName");
|
||||||
|
|
||||||
assertSame(player, PlayerLookup.findOnline("playername", List.of(player)));
|
assertSame(player, PlayerLookup.findOnline("playername", List.of(player)));
|
||||||
assertNull(PlayerLookup.findOnline("Player", List.of(player)));
|
assertNull(PlayerLookup.findOnline("Player", List.of(player)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class PlotGridLayoutTest {
|
||||||
|
@Test
|
||||||
|
void zeroOffsetsPreserveTheExistingPlotGrid() {
|
||||||
|
PlotGridLayout grid = new PlotGridLayout(32, 10, 32, 4, 4, 0, 0, 0, 62);
|
||||||
|
|
||||||
|
assertEquals(BlockVector3.at(0, 62, 0), grid.gridMin(0, 0, 0));
|
||||||
|
assertEquals(BlockVector3.at(35, 71, 35), grid.gridMax(0, 0, 1, 1, 0));
|
||||||
|
assertEquals(BlockVector3.at(2, 62, 2), grid.plotMin(0, 0, 0));
|
||||||
|
assertEquals(BlockVector3.at(33, 71, 33), grid.plotMax(0, 0, 1, 1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void offsetsShiftGridAndPlotsOnAllAxesIncludingHigherLevels() {
|
||||||
|
PlotGridLayout grid = new PlotGridLayout(32, 10, 32, 2, 6, -7, 3, 11, 62);
|
||||||
|
|
||||||
|
assertEquals(BlockVector3.at(-45, 97, -23), grid.gridMin(-1, -1, 2));
|
||||||
|
assertEquals(BlockVector3.at(30, 106, 44), grid.gridMax(-1, -1, 2, 2, 2));
|
||||||
|
assertEquals(BlockVector3.at(-42, 97, -22), grid.plotMin(-1, -1, 2));
|
||||||
|
assertEquals(BlockVector3.at(27, 106, 43), grid.plotMax(-1, -1, 2, 2, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapsNegativePlotBoundsBackToTheirLogicalIndices() {
|
||||||
|
PlotGridLayout grid = new PlotGridLayout(32, 10, 32, 2, 6, -7, 3, 11, 62);
|
||||||
|
Cuboid plot = new Cuboid(grid.plotMin(-1, -1, 0), grid.plotMax(-1, -1, 2, 2, 0));
|
||||||
|
|
||||||
|
assertEquals(-1, grid.plotXIndexFromMin(plot));
|
||||||
|
assertEquals(-1, grid.plotZIndexFromMin(plot));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class PlotLevelSupportTest {
|
||||||
|
@Test
|
||||||
|
void rejectsUpperPlotWhenAnyLogicalCellBelowIsMissing() {
|
||||||
|
Set<String> generatedCells = Set.of("0:0:0", "0:1:0", "1:0:0");
|
||||||
|
|
||||||
|
assertFalse(
|
||||||
|
PlotLevelSupport.hasCompleteSupport(
|
||||||
|
0, 0, 2, 1, (row, col, level) -> generatedCells.contains(key(row, col, level))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requiresEveryLogicalCellAtEveryLowerLevel() {
|
||||||
|
Set<String> generatedCells =
|
||||||
|
Set.of("0:0:0", "0:1:0", "1:0:0", "1:1:0", "0:0:1", "0:1:1", "1:0:1", "1:1:1");
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
PlotLevelSupport.hasCompleteSupport(
|
||||||
|
0, 0, 2, 2, (row, col, level) -> generatedCells.contains(key(row, col, level))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void baseLevelDoesNotRequireLowerSupport() {
|
||||||
|
assertTrue(PlotLevelSupport.hasCompleteSupport(3, 4, 2, 0, (row, col, level) -> false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String key(int row, int col, int level) {
|
||||||
|
return row + ":" + col + ":" + level;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldguard.protection.flags.Flags;
|
||||||
|
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class PlotReservationLookupTest {
|
||||||
|
private static final UUID PLAYER_ID = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findsReservationsWherePlayerIsOwnerOrMemberButExcludesHomeCityAndSpawn() {
|
||||||
|
ProtectedCuboidRegion memberReservation = region("r_member");
|
||||||
|
memberReservation.getMembers().addPlayer(PLAYER_ID);
|
||||||
|
ProtectedCuboidRegion ownerReservation = region("private-yard");
|
||||||
|
ownerReservation.getOwners().addPlayer(PLAYER_ID);
|
||||||
|
ProtectedCuboidRegion home = region("h_1_" + PLAYER_ID);
|
||||||
|
home.getMembers().addPlayer(PLAYER_ID);
|
||||||
|
ProtectedCuboidRegion city = region("City");
|
||||||
|
city.getMembers().addPlayer(PLAYER_ID);
|
||||||
|
ProtectedCuboidRegion spawn = region("Spawn");
|
||||||
|
spawn.getMembers().addPlayer(PLAYER_ID);
|
||||||
|
ProtectedCuboidRegion unrelated = region("other-region", false);
|
||||||
|
unrelated.getMembers().addPlayer(PLAYER_ID);
|
||||||
|
|
||||||
|
List<Plot> reservations =
|
||||||
|
PlotReservationLookup.forPlayer(
|
||||||
|
List.of(memberReservation, ownerReservation, home, city, spawn, unrelated), PLAYER_ID);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
List.of("private-yard", "r_member"),
|
||||||
|
reservations.stream().map(Plot::getRegionName).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findsNamedReservationWithOrWithoutPrefix() {
|
||||||
|
ProtectedCuboidRegion reservation = region("r_camp");
|
||||||
|
reservation.getMembers().addPlayer(PLAYER_ID);
|
||||||
|
List<Plot> reservations = PlotReservationLookup.forPlayer(List.of(reservation), PLAYER_ID);
|
||||||
|
|
||||||
|
assertEquals("r_camp", PlotReservationLookup.findByName(reservations, "camp").getRegionName());
|
||||||
|
assertEquals(
|
||||||
|
"r_camp", PlotReservationLookup.findByName(reservations, "r_camp").getRegionName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProtectedCuboidRegion region(String id) {
|
||||||
|
return region(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProtectedCuboidRegion region(String id, boolean reservedFlags) {
|
||||||
|
ProtectedCuboidRegion region =
|
||||||
|
new ProtectedCuboidRegion(id, BlockVector3.ZERO, BlockVector3.at(5, 5, 5));
|
||||||
|
if (reservedFlags) {
|
||||||
|
region.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
||||||
|
region.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
||||||
|
}
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class PlotWorldBorderBoundsTest {
|
||||||
|
@Test
|
||||||
|
void searchBoundFollowsTheWorldBorderInsteadOfAnArbitraryRingCount() {
|
||||||
|
int maximumRing = PlotWorldBorderBounds.maximumSearchRing(60_000_000, 0, 0, 0, 0, 36, 36, 1);
|
||||||
|
|
||||||
|
assertTrue(maximumRing > 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void accountsForAnOffsetBorderCenterAndMultiplier() {
|
||||||
|
int maximumRing = PlotWorldBorderBounds.maximumSearchRing(512, 100, -100, 64, -32, 36, 34, 2);
|
||||||
|
|
||||||
|
assertTrue(maximumRing > 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requiresTheCompleteGridFootprintInsideTheBorder() {
|
||||||
|
assertTrue(PlotWorldBorderBounds.contains(new Cuboid(-9, 0, -9, 9, 10, 9), 20, 0, 0));
|
||||||
|
assertFalse(PlotWorldBorderBounds.contains(new Cuboid(-11, 0, -9, 9, 10, 9), 20, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.data.type.Stairs;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class RoadBlockWriterTest {
|
||||||
|
@Test
|
||||||
|
void doesNotRewriteAnExistingRoadBlockOrItsClearance() {
|
||||||
|
World world = mock(World.class);
|
||||||
|
Block roadBlock = mock(Block.class);
|
||||||
|
when(world.getBlockAt(4, 62, 8)).thenReturn(roadBlock);
|
||||||
|
when(roadBlock.getType()).thenReturn(Material.COBBLESTONE);
|
||||||
|
|
||||||
|
RoadBlockWriter.build(world, 4, 62, 8, Material.COBBLESTONE, Material.STONE, true, 4);
|
||||||
|
|
||||||
|
verify(roadBlock, never()).setType(any(Material.class));
|
||||||
|
verify(world, never()).getBlockAt(4, 63, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fillsMissingRoadBlocksAndPreservesExistingStairTreads() {
|
||||||
|
World world = mock(World.class);
|
||||||
|
Block missingRoadBlock = mock(Block.class);
|
||||||
|
Block stairBlock = mock(Block.class);
|
||||||
|
when(world.getBlockAt(4, 62, 8)).thenReturn(missingRoadBlock);
|
||||||
|
when(world.getBlockAt(5, 76, 12)).thenReturn(stairBlock);
|
||||||
|
when(missingRoadBlock.getType()).thenReturn(Material.AIR);
|
||||||
|
when(stairBlock.getType()).thenReturn(Material.COBBLESTONE_STAIRS);
|
||||||
|
when(stairBlock.getBlockData()).thenReturn(mock(Stairs.class));
|
||||||
|
|
||||||
|
RoadBlockWriter.build(world, 4, 62, 8, Material.COBBLESTONE, Material.STONE, false, 0);
|
||||||
|
RoadBlockWriter.build(world, 5, 76, 12, Material.COBBLESTONE, Material.STONE, false, 4);
|
||||||
|
|
||||||
|
verify(missingRoadBlock).setType(Material.COBBLESTONE);
|
||||||
|
verify(stairBlock, never()).setType(any(Material.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class RoadLayoutTest {
|
||||||
|
private static final Cuboid PLOT = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildsFullRoadWidthAndNonOverlappingCornersAtOnce() {
|
||||||
|
List<RoadLayout.BlockPosition> road = RoadLayout.surroundingRoad(PLOT, 4, 4);
|
||||||
|
HashSet<RoadLayout.BlockPosition> uniqueBlocks = new HashSet<>(road);
|
||||||
|
|
||||||
|
assertEquals(576, road.size());
|
||||||
|
assertEquals(road.size(), uniqueBlocks.size());
|
||||||
|
assertTrue(road.contains(new RoadLayout.BlockPosition(34, 2, RoadLayout.RoadType.AVENUE)));
|
||||||
|
assertTrue(road.contains(new RoadLayout.BlockPosition(2, 34, RoadLayout.RoadType.STREET)));
|
||||||
|
assertTrue(
|
||||||
|
road.contains(new RoadLayout.BlockPosition(34, 34, RoadLayout.RoadType.INTERSECTION)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsNoRoadForZeroWidth() {
|
||||||
|
assertTrue(RoadLayout.surroundingRoad(PLOT, 0, 0).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesIndependentWidthsForStreetAndAvenueBands() {
|
||||||
|
List<RoadLayout.BlockPosition> road = RoadLayout.surroundingRoad(PLOT, 2, 4);
|
||||||
|
|
||||||
|
assertEquals(416, road.size());
|
||||||
|
assertTrue(road.contains(new RoadLayout.BlockPosition(2, 0, RoadLayout.RoadType.STREET)));
|
||||||
|
assertTrue(road.contains(new RoadLayout.BlockPosition(-2, 2, RoadLayout.RoadType.AVENUE)));
|
||||||
|
assertTrue(
|
||||||
|
road.contains(new RoadLayout.BlockPosition(-2, 0, RoadLayout.RoadType.INTERSECTION)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class SpawnLayoutTest {
|
||||||
|
@Test
|
||||||
|
void defaultMultipliersPreserveTheOnePlotSpawnBounds() {
|
||||||
|
Cuboid spawn = SpawnLayout.bounds(1, 1, 1, 32, 10, 32, 4, 4, 62, 0, 0, 0);
|
||||||
|
|
||||||
|
assertEqualsBounds(spawn, 2, 62, 2, 33, 71, 33);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void appliesEachMultiplierToItsOwnAlignedGridDimension() {
|
||||||
|
Cuboid spawn = SpawnLayout.bounds(2, 3, 4, 32, 10, 32, 4, 4, 62, 0, 0, 0);
|
||||||
|
|
||||||
|
assertEqualsBounds(spawn, 2, 62, 2, 69, 91, 141);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validatesTheEntireSpawnHeightAgainstWorldBounds() {
|
||||||
|
Cuboid spawn = SpawnLayout.bounds(1, 3, 1, 32, 10, 32, 4, 4, 62, 0, 0, 0);
|
||||||
|
|
||||||
|
assertTrue(SpawnLayout.withinBuildHeight(spawn, -64, 320));
|
||||||
|
assertFalse(SpawnLayout.withinBuildHeight(spawn, -64, 91));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void alignsSpawnAxesToTheirIndependentRoadWidths() {
|
||||||
|
Cuboid spawn = SpawnLayout.bounds(2, 1, 3, 32, 10, 32, 6, 2, 62, 0, 0, 0);
|
||||||
|
|
||||||
|
assertEqualsBounds(spawn, 3, 62, 1, 72, 71, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void alignsSpawnBoundsWithAllGridOffsets() {
|
||||||
|
Cuboid spawn = SpawnLayout.bounds(2, 2, 3, 32, 10, 32, 6, 2, 62, -7, 3, 11);
|
||||||
|
|
||||||
|
assertEqualsBounds(spawn, -4, 65, 12, 65, 84, 111);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsNonPositiveSpawnMultipliers() {
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> SpawnLayout.bounds(0, 1, 1, 32, 10, 32, 4, 4, 62, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertEqualsBounds(
|
||||||
|
Cuboid cuboid, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||||
|
org.junit.jupiter.api.Assertions.assertEquals(minX, cuboid.minX);
|
||||||
|
org.junit.jupiter.api.Assertions.assertEquals(minY, cuboid.minY);
|
||||||
|
org.junit.jupiter.api.Assertions.assertEquals(minZ, cuboid.minZ);
|
||||||
|
org.junit.jupiter.api.Assertions.assertEquals(maxX, cuboid.maxX);
|
||||||
|
org.junit.jupiter.api.Assertions.assertEquals(maxY, cuboid.maxY);
|
||||||
|
org.junit.jupiter.api.Assertions.assertEquals(maxZ, cuboid.maxZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class WorldGuardRegionOccupancyTest {
|
||||||
|
@Test
|
||||||
|
void treatsArbitrarySavedRegionBoundsAsOccupiedButIgnoresCityAndGlobalRegions() {
|
||||||
|
ProtectedRegion market =
|
||||||
|
region("market", BlockVector3.at(40, 62, 40), BlockVector3.at(70, 90, 70));
|
||||||
|
ProtectedRegion city =
|
||||||
|
region("City", BlockVector3.at(-100, 0, -100), BlockVector3.at(100, 320, 100));
|
||||||
|
ProtectedRegion global =
|
||||||
|
region(
|
||||||
|
"__global__",
|
||||||
|
BlockVector3.at(-30_000_000, -64, -30_000_000),
|
||||||
|
BlockVector3.at(30_000_000, 320, 30_000_000));
|
||||||
|
|
||||||
|
List<Cuboid> occupied = WorldGuardRegionOccupancy.boundsToAvoid(List.of(market, city, global));
|
||||||
|
|
||||||
|
assertEquals(1, occupied.size());
|
||||||
|
assertTrue(WorldGuardRegionOccupancy.overlapsAny(new Cuboid(60, 70, 60, 80, 75, 80), occupied));
|
||||||
|
assertFalse(WorldGuardRegionOccupancy.overlapsAny(new Cuboid(0, 62, 0, 31, 71, 31), occupied));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProtectedRegion region(String id, BlockVector3 minimum, BlockVector3 maximum) {
|
||||||
|
ProtectedRegion region = mock(ProtectedRegion.class);
|
||||||
|
when(region.getId()).thenReturn(id);
|
||||||
|
when(region.getMinimumPoint()).thenReturn(minimum);
|
||||||
|
when(region.getMaximumPoint()).thenReturn(maximum);
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
|
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MetropolisDebugGenerateTestHomesCommandTest {
|
||||||
|
@Test
|
||||||
|
void repeatedRunsContinueWithTheNextUnusedTestName() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Server server = mock(Server.class);
|
||||||
|
BukkitScheduler scheduler = mock(BukkitScheduler.class);
|
||||||
|
CommandSender sender = mock(CommandSender.class);
|
||||||
|
Set<UUID> existingHomeOwners = new HashSet<>();
|
||||||
|
List<String> generatedNames = new ArrayList<>();
|
||||||
|
List<Runnable> scheduledBatches = new ArrayList<>();
|
||||||
|
|
||||||
|
when(plugin.getServer()).thenReturn(server);
|
||||||
|
when(server.getScheduler()).thenReturn(scheduler);
|
||||||
|
when(scheduler.runTaskTimer(eq(plugin), any(Runnable.class), eq(1L), eq(2L)))
|
||||||
|
.thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
scheduledBatches.add(invocation.getArgument(1));
|
||||||
|
return mock(BukkitTask.class);
|
||||||
|
});
|
||||||
|
when(server.getOfflinePlayer(anyString()))
|
||||||
|
.thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
String name = invocation.getArgument(0);
|
||||||
|
OfflinePlayer player = mock(OfflinePlayer.class);
|
||||||
|
UUID playerId = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
|
||||||
|
when(player.getName()).thenReturn(name);
|
||||||
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
|
return player;
|
||||||
|
});
|
||||||
|
when(plugin.getNumPlots(any(UUID.class)))
|
||||||
|
.thenAnswer(invocation -> existingHomeOwners.contains(invocation.getArgument(0)) ? 1 : 0);
|
||||||
|
when(plugin.generateHome(any(OfflinePlayer.class), eq(1)))
|
||||||
|
.thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
OfflinePlayer player = invocation.getArgument(0);
|
||||||
|
generatedNames.add(player.getName());
|
||||||
|
existingHomeOwners.add(player.getUniqueId());
|
||||||
|
return mock(PlayerHome.class);
|
||||||
|
});
|
||||||
|
|
||||||
|
MetropolisDebugGenerateTestHomesCommand executor =
|
||||||
|
new MetropolisDebugGenerateTestHomesCommand(plugin);
|
||||||
|
|
||||||
|
assertTrue(executor.onCommand(sender, mock(Command.class), "testhomes", new String[] {"10"}));
|
||||||
|
assertTrue(generatedNames.isEmpty());
|
||||||
|
assertEquals(1, scheduledBatches.size());
|
||||||
|
assertFalse(executor.onCommand(sender, mock(Command.class), "testhomes", new String[] {"10"}));
|
||||||
|
|
||||||
|
scheduledBatches.getFirst().run();
|
||||||
|
assertEquals(List.of("Test1"), generatedNames);
|
||||||
|
for (int tick = 1; tick < 10; tick++) {
|
||||||
|
scheduledBatches.getFirst().run();
|
||||||
|
}
|
||||||
|
assertEquals(10, generatedNames.size());
|
||||||
|
|
||||||
|
assertTrue(executor.onCommand(sender, mock(Command.class), "testhomes", new String[] {"10"}));
|
||||||
|
assertEquals(2, scheduledBatches.size());
|
||||||
|
for (int tick = 0; tick < 20; tick++) {
|
||||||
|
scheduledBatches.get(1).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> expectedNames = new ArrayList<>();
|
||||||
|
for (int testNumber = 1; testNumber <= 20; testNumber++) {
|
||||||
|
expectedNames.add("Test" + testNumber);
|
||||||
|
}
|
||||||
|
assertEquals(expectedNames, generatedNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsNonPositiveCountsWithoutGeneratingHomes() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
MetropolisDebugGenerateTestHomesCommand executor =
|
||||||
|
new MetropolisDebugGenerateTestHomesCommand(plugin);
|
||||||
|
|
||||||
|
assertFalse(
|
||||||
|
executor.onCommand(
|
||||||
|
mock(CommandSender.class), mock(Command.class), "testhomes", new String[] {"0"}));
|
||||||
|
|
||||||
|
verify(plugin, never()).generateHome(any(OfflinePlayer.class), eq(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,25 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -9,40 +27,61 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
class MetropolisHomeAcquireTest {
|
class MetropolisHomeAcquireTest {
|
||||||
@Test
|
@Test
|
||||||
void assignsAPlotWhenThePlayerIsBelowTheirLimit() {
|
void assignsAPlotWhenThePlayerIsBelowTheirLimit() {
|
||||||
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
Player player = mock(Player.class);
|
Player player = mock(Player.class);
|
||||||
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
when(player.getUniqueId()).thenReturn(playerId);
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
when(plugin.getNumPlots(playerId)).thenReturn(0);
|
when(plugin.getNumPlots(playerId)).thenReturn(0);
|
||||||
when(plugin.getMaxPlots(playerId)).thenReturn(1);
|
when(plugin.getMaxPlots(player)).thenReturn(1);
|
||||||
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
when(plugin.acquireHome(player)).thenReturn(mock(PlayerHome.class));
|
||||||
|
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
||||||
|
|
||||||
assertTrue(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
assertTrue(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
||||||
|
|
||||||
verify(plugin).assignPlot(player);
|
verify(plugin).acquireHome(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void doesNotAssignAPlotAfterThePlayerReachesTheirLimit() {
|
void doesNotAssignAPlotAfterThePlayerReachesTheirLimit() {
|
||||||
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
Player player = mock(Player.class);
|
Player player = mock(Player.class);
|
||||||
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
when(player.getUniqueId()).thenReturn(playerId);
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
when(plugin.getNumPlots(playerId)).thenReturn(1);
|
when(plugin.getNumPlots(playerId)).thenReturn(1);
|
||||||
when(plugin.getMaxPlots(playerId)).thenReturn(1);
|
when(plugin.getMaxPlots(player)).thenReturn(1);
|
||||||
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
||||||
|
|
||||||
assertFalse(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
assertFalse(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
||||||
|
|
||||||
verify(plugin, never()).assignPlot(player);
|
verify(plugin, never()).acquireHome(player);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Test
|
||||||
|
void supportsDisablingAutomaticInitialHomeAllocation() {
|
||||||
|
assertEquals(1, MetropolisPlugin.normalizeInitialHomeCount(1));
|
||||||
|
assertTrue(MetropolisPlugin.shouldAutoGenerateInitialHome(1));
|
||||||
|
|
||||||
|
int zero = MetropolisPlugin.normalizeInitialHomeCount(0);
|
||||||
|
assertEquals(0, zero);
|
||||||
|
assertFalse(MetropolisPlugin.shouldAutoGenerateInitialHome(zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsLevelCountsWithinTheConfiguredAndWorldBounds() {
|
||||||
|
assertEquals(1, MetropolisPlugin.normalizeMaxLevels(0));
|
||||||
|
assertEquals(4, MetropolisPlugin.normalizeMaxLevels(4));
|
||||||
|
assertEquals(3, MetropolisPlugin.clampMaxLevels(10, 3));
|
||||||
|
assertEquals(1, MetropolisPlugin.clampMaxLevels(2, 1));
|
||||||
|
assertEquals(0, MetropolisPlugin.clampMaxLevels(0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.contains;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MetropolisHomeMoveCommandTest {
|
||||||
|
private static final UUID PLAYER_ID = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectsTheSendersExistingHome() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player sender = mock(Player.class);
|
||||||
|
when(sender.getUniqueId()).thenReturn(PLAYER_ID);
|
||||||
|
when(sender.getName()).thenReturn("PlayerName");
|
||||||
|
when(plugin.homeExists(PLAYER_ID, 2)).thenReturn(true);
|
||||||
|
MetropolisHomeMoveCommand command = new MetropolisHomeMoveCommand(plugin);
|
||||||
|
|
||||||
|
assertTrue(command.onCommand(sender, mock(Command.class), "move", new String[] {"2"}));
|
||||||
|
|
||||||
|
verify(plugin).setHome(PLAYER_ID, 2);
|
||||||
|
verify(sender).sendMessage(contains("Home 2 is now active for PlayerName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectsAnOfflineTargetsExistingHome() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
CommandSender sender = mock(CommandSender.class);
|
||||||
|
OfflinePlayer target = mock(OfflinePlayer.class);
|
||||||
|
when(plugin.getOfflinePlayer("OfflineName")).thenReturn(target);
|
||||||
|
when(target.getUniqueId()).thenReturn(PLAYER_ID);
|
||||||
|
when(target.getName()).thenReturn("OfflineName");
|
||||||
|
when(plugin.homeExists(PLAYER_ID, 3)).thenReturn(true);
|
||||||
|
MetropolisHomeMoveCommand command = new MetropolisHomeMoveCommand(plugin);
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
command.onCommand(sender, mock(Command.class), "move", new String[] {"3", "OfflineName"}));
|
||||||
|
|
||||||
|
verify(plugin).setHome(PLAYER_ID, 3);
|
||||||
|
verify(sender).sendMessage(contains("Home 3 is now active for OfflineName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsInvalidNumbersMissingPlayersAndNonexistentHomes() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player sender = mock(Player.class);
|
||||||
|
when(sender.getUniqueId()).thenReturn(PLAYER_ID);
|
||||||
|
MetropolisHomeMoveCommand command = new MetropolisHomeMoveCommand(plugin);
|
||||||
|
|
||||||
|
assertFalse(command.onCommand(sender, mock(Command.class), "move", new String[] {"0"}));
|
||||||
|
assertFalse(command.onCommand(sender, mock(Command.class), "move", new String[] {"nope"}));
|
||||||
|
assertFalse(
|
||||||
|
command.onCommand(sender, mock(Command.class), "move", new String[] {"1", "unknown"}));
|
||||||
|
when(plugin.getOfflinePlayer("unknown")).thenReturn(null);
|
||||||
|
assertFalse(
|
||||||
|
command.onCommand(sender, mock(Command.class), "move", new String[] {"1", "unknown"}));
|
||||||
|
assertFalse(
|
||||||
|
command.onCommand(sender, mock(Command.class), "move", new String[] {"1", "2", "extra"}));
|
||||||
|
assertFalse(command.onCommand(sender, mock(Command.class), "move", new String[] {"1"}));
|
||||||
|
|
||||||
|
verify(plugin, never()).setHome(PLAYER_ID, 0);
|
||||||
|
verify(sender, times(2)).sendMessage(contains("positive integer"));
|
||||||
|
verify(sender).sendMessage(contains("does not have home 1"));
|
||||||
|
verify(sender).sendMessage(contains("Usage:"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.contains;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.Plot;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MetropolisPlotGoCommandTest {
|
||||||
|
private static final UUID PLAYER_ID = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void teleportsPlayerToTheirOnlyReservation() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player player = player("Member", PLAYER_ID);
|
||||||
|
Plot reservation = plot("r_camp");
|
||||||
|
when(plugin.getReservationsForPlayer(PLAYER_ID)).thenReturn(List.of(reservation));
|
||||||
|
MetropolisPlotGoCommand command = new MetropolisPlotGoCommand(plugin);
|
||||||
|
|
||||||
|
assertTrue(command.onCommand(player, mock(Command.class), "plot-go", new String[0]));
|
||||||
|
|
||||||
|
verify(plugin).teleportPlayerToPlot(player, reservation);
|
||||||
|
verify(player).sendMessage(contains("reservation r_camp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void asksForAReservationNameWhenPlayerBelongsToSeveral() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player player = player("Member", PLAYER_ID);
|
||||||
|
when(plugin.getReservationsForPlayer(PLAYER_ID))
|
||||||
|
.thenReturn(List.of(plot("r_camp"), plot("r_garden")));
|
||||||
|
MetropolisPlotGoCommand command = new MetropolisPlotGoCommand(plugin);
|
||||||
|
|
||||||
|
assertFalse(command.onCommand(player, mock(Command.class), "plot-go", new String[0]));
|
||||||
|
|
||||||
|
verify(player).sendMessage(contains("r_camp, r_garden"));
|
||||||
|
verify(plugin, never()).teleportPlayerToPlot(player, plot("r_camp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void authorizedSenderCanTargetAnOnlineMember() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
CommandSender sender = mock(CommandSender.class);
|
||||||
|
Player target = player("Member", PLAYER_ID);
|
||||||
|
Plot reservation = plot("r_camp");
|
||||||
|
when(sender.hasPermission("metropolis.plot.go")).thenReturn(true);
|
||||||
|
when(plugin.getPlayer("Member")).thenReturn(target);
|
||||||
|
when(plugin.getReservationsForPlayer(PLAYER_ID)).thenReturn(List.of(reservation));
|
||||||
|
MetropolisPlotGoCommand command = new MetropolisPlotGoCommand(plugin);
|
||||||
|
|
||||||
|
assertTrue(command.onCommand(sender, mock(Command.class), "plot-go", new String[] {"Member"}));
|
||||||
|
|
||||||
|
verify(plugin).teleportPlayerToPlot(target, reservation);
|
||||||
|
verify(target).sendMessage(contains("You were teleported"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reportsNoMembershipAndUnsafeDestinations() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player player = player("Member", PLAYER_ID);
|
||||||
|
when(plugin.getReservationsForPlayer(PLAYER_ID)).thenReturn(List.of());
|
||||||
|
MetropolisPlotGoCommand command = new MetropolisPlotGoCommand(plugin);
|
||||||
|
|
||||||
|
assertFalse(command.onCommand(player, mock(Command.class), "plot-go", new String[0]));
|
||||||
|
verify(player).sendMessage(contains("not a member or owner"));
|
||||||
|
|
||||||
|
Plot reservation = plot("r_camp");
|
||||||
|
when(plugin.getReservationsForPlayer(PLAYER_ID)).thenReturn(List.of(reservation));
|
||||||
|
when(plugin.teleportPlayerToPlot(player, reservation)).thenReturn("No safe teleport location");
|
||||||
|
assertFalse(command.onCommand(player, mock(Command.class), "plot-go", new String[0]));
|
||||||
|
verify(player).sendMessage(contains("No safe teleport location"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Player player(String name, UUID id) {
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
when(player.getName()).thenReturn(name);
|
||||||
|
when(player.getUniqueId()).thenReturn(id);
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Plot plot(String name) {
|
||||||
|
return new Plot(name, BlockVector3.ZERO, BlockVector3.at(5, 5, 5));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -8,42 +25,46 @@ import static org.mockito.Mockito.mock;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
class MetropolisPlotReserveCommandTest {
|
class MetropolisPlotReserveCommandTest {
|
||||||
@Test
|
@Test
|
||||||
void reservesCoordinatesFromAllSixArguments() {
|
void reservesCoordinatesFromAllSixArguments() {
|
||||||
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
CommandSender sender = mock(CommandSender.class);
|
CommandSender sender = mock(CommandSender.class);
|
||||||
Command command = mock(Command.class);
|
Command command = mock(Command.class);
|
||||||
MetropolisPlotReserveCommand executor = new MetropolisPlotReserveCommand(plugin);
|
MetropolisPlotReserveCommand executor = new MetropolisPlotReserveCommand(plugin);
|
||||||
|
|
||||||
assertTrue(executor.onCommand(sender, command, "reserve", new String[] {"north", "1", "2", "3", "4", "5", "6"}));
|
assertTrue(
|
||||||
|
executor.onCommand(
|
||||||
|
sender, command, "reserve", new String[] {"north", "1", "2", "3", "4", "5", "6"}));
|
||||||
|
|
||||||
ArgumentCaptor<Cuboid> cuboid = ArgumentCaptor.forClass(Cuboid.class);
|
ArgumentCaptor<Cuboid> cuboid = ArgumentCaptor.forClass(Cuboid.class);
|
||||||
verify(plugin).reserveCuboid(eq("north"), cuboid.capture());
|
verify(plugin).reserveCuboid(eq("north"), cuboid.capture());
|
||||||
assertEquals(1, cuboid.getValue().getMinX());
|
assertEquals(1, cuboid.getValue().getMinX());
|
||||||
assertEquals(2, cuboid.getValue().getMinY());
|
assertEquals(2, cuboid.getValue().getMinY());
|
||||||
assertEquals(3, cuboid.getValue().getMinZ());
|
assertEquals(3, cuboid.getValue().getMinZ());
|
||||||
assertEquals(4, cuboid.getValue().getMaxX());
|
assertEquals(4, cuboid.getValue().getMaxX());
|
||||||
assertEquals(5, cuboid.getValue().getMaxY());
|
assertEquals(5, cuboid.getValue().getMaxY());
|
||||||
assertEquals(6, cuboid.getValue().getMaxZ());
|
assertEquals(6, cuboid.getValue().getMaxZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsMalformedCoordinateArgumentsWithoutCallingPlugin() {
|
void rejectsMalformedCoordinateArgumentsWithoutCallingPlugin() {
|
||||||
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
CommandSender sender = mock(CommandSender.class);
|
CommandSender sender = mock(CommandSender.class);
|
||||||
Command command = mock(Command.class);
|
Command command = mock(Command.class);
|
||||||
MetropolisPlotReserveCommand executor = new MetropolisPlotReserveCommand(plugin);
|
MetropolisPlotReserveCommand executor = new MetropolisPlotReserveCommand(plugin);
|
||||||
|
|
||||||
assertFalse(executor.onCommand(sender, command, "reserve", new String[] {"north", "1", "two", "3", "4", "5", "6"}));
|
assertFalse(
|
||||||
verifyNoInteractions(plugin);
|
executor.onCommand(
|
||||||
}
|
sender, command, "reserve", new String[] {"north", "1", "two", "3", "4", "5", "6"}));
|
||||||
}
|
verifyNoInteractions(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Metropolis.
|
||||||
|
|
||||||
|
Metropolis is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Metropolis is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.eventlisteners;
|
package com.majinnaibu.bukkitplugins.metropolis.eventlisteners;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.contains;
|
import static org.mockito.ArgumentMatchers.contains;
|
||||||
@@ -7,31 +24,33 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class PlayerJoinListenerTest {
|
class PlayerJoinListenerTest {
|
||||||
@Test
|
@Test
|
||||||
void tellsJoiningPlayerTheirHomeBounds() {
|
void tellsJoiningPlayerTheirHomeBounds() {
|
||||||
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
Player player = mock(Player.class);
|
Player player = mock(Player.class);
|
||||||
PlayerJoinEvent event = mock(PlayerJoinEvent.class);
|
PlayerJoinEvent event = mock(PlayerJoinEvent.class);
|
||||||
when(event.getPlayer()).thenReturn(player);
|
when(event.getPlayer()).thenReturn(player);
|
||||||
when(player.getName()).thenReturn("PlayerName");
|
when(player.getName()).thenReturn("PlayerName");
|
||||||
when(plugin.getPlayerHome(player)).thenReturn(new PlayerHome(
|
when(plugin.getPlayerHome(player))
|
||||||
UUID.fromString("123e4567-e89b-12d3-a456-426614174000"),
|
.thenReturn(
|
||||||
"PlayerName",
|
new PlayerHome(
|
||||||
1,
|
UUID.fromString("123e4567-e89b-12d3-a456-426614174000"),
|
||||||
BlockVector3.at(1, 2, 3),
|
"PlayerName",
|
||||||
BlockVector3.at(4, 5, 6)));
|
1,
|
||||||
|
BlockVector3.at(1, 2, 3),
|
||||||
|
BlockVector3.at(4, 5, 6)));
|
||||||
|
|
||||||
new PlayerJoinListener(plugin).onPlayerJoin(event);
|
new PlayerJoinListener(plugin).onPlayerJoin(event);
|
||||||
|
|
||||||
verify(player).sendMessage(contains("home is between (1, 2, 3) and (4, 5, 6)"));
|
verify(player).sendMessage(contains("home is between (1, 2, 3) and (4, 5, 6)"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && 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() {
|
load_project_env() {
|
||||||
local env_line env_key env_value first_char last_char
|
local env_line env_key env_value first_char last_char
|
||||||
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
||||||
@@ -36,7 +32,13 @@ load_project_env() {
|
|||||||
fi
|
fi
|
||||||
done < "$PROJECT_ROOT/.env"
|
done < "$PROJECT_ROOT/.env"
|
||||||
}
|
}
|
||||||
load_project_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_TOKEN="${MODRINTH_TOKEN:-}"
|
||||||
MODRINTH_PROJECT_ID="${MODRINTH_PROJECT_ID:-metropolis-builder}"
|
MODRINTH_PROJECT_ID="${MODRINTH_PROJECT_ID:-metropolis-builder}"
|
||||||
@@ -44,12 +46,35 @@ MODRINTH_VERSION="${MODRINTH_VERSION:-${VERSION:-}}"
|
|||||||
if [[ -z "$MODRINTH_VERSION" ]]; then
|
if [[ -z "$MODRINTH_VERSION" ]]; then
|
||||||
MODRINTH_VERSION=$(cd "$PROJECT_ROOT" && ./gradlew -q properties --property version | sed -n 's/^version: //p' | head -n1)
|
MODRINTH_VERSION=$(cd "$PROJECT_ROOT" && ./gradlew -q properties --property version | sed -n 's/^version: //p' | head -n1)
|
||||||
fi
|
fi
|
||||||
MODRINTH_CHANGELOG="${MODRINTH_CHANGELOG:-Auto release via publish-modrinth.sh}"
|
|
||||||
MODRINTH_GAME_VERSIONS="${MODRINTH_GAME_VERSIONS:-26.2}"
|
generate_changelog() {
|
||||||
|
local current_tag previous_tag range commits
|
||||||
|
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"
|
||||||
|
else
|
||||||
|
previous_tag=$(git -C "$PROJECT_ROOT" describe --tags --abbrev=0 2>/dev/null || true)
|
||||||
|
range="${previous_tag:+$previous_tag..}HEAD"
|
||||||
|
fi
|
||||||
|
commits=$(git -C "$PROJECT_ROOT" log --no-merges --format='- %s (%h)' "$range" 2>/dev/null || true)
|
||||||
|
if [[ -z "$commits" ]]; then
|
||||||
|
commits='- No commit messages found for this release.'
|
||||||
|
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_LOADERS="${MODRINTH_LOADERS:-paper}"
|
||||||
MODRINTH_VERSION_TYPE="${MODRINTH_VERSION_TYPE:-}"
|
MODRINTH_VERSION_TYPE="${MODRINTH_VERSION_TYPE:-}"
|
||||||
MODRINTH_FILE="${MODRINTH_FILE:-}"
|
MODRINTH_FILE="${MODRINTH_FILE:-}"
|
||||||
MODRINTH_DEPENDENCIES="${MODRINTH_DEPENDENCIES:-}"
|
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
|
if [[ -z "$MODRINTH_VERSION" ]]; then
|
||||||
echo "Error: could not determine the project version from Gradle." >&2
|
echo "Error: could not determine the project version from Gradle." >&2
|
||||||
@@ -73,7 +98,7 @@ if [[ -n "$MODRINTH_FILE" && "$MODRINTH_FILE" != /* ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$MODRINTH_TOKEN" ]]; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -143,3 +168,26 @@ if [[ "$HTTP_STATUS" != 2?? ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
printf '%s\n' "$HTTP_BODY" | jq .
|
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