Compare commits
40 Commits
9ea0a5ec67
...
4-add-a-co
| Author | SHA1 | Date | |
|---|---|---|---|
| 87b5efed8c | |||
| de3651f22c | |||
| da46d3e4b9 | |||
| b6b30621f0 | |||
| efc39365c3 | |||
| a13f512be6 | |||
| 013dc6749c | |||
| 08d27ede40 | |||
| 8dbbdbac91 | |||
| 4ada53ba76 | |||
| 69cc0f8677 | |||
| 2a34e5deae | |||
| 8b3421e8d6 | |||
| a1f8fb3950 | |||
| 726b178a53 | |||
| f1de135dfe | |||
| bc39305e90 | |||
| 1eab80ab2b | |||
| 1721349a71 | |||
| 5656083871 | |||
| c760f5ce46 | |||
| ba96b8a73a | |||
| e97f63af1f | |||
| 5f6acb86c3 | |||
| c127e45f8d | |||
| 81b34862b4 | |||
| 509df1b452 | |||
| 6df83fbee9 | |||
| 832eb59624 | |||
| 316accff2d | |||
| 7a5764ebe0 | |||
| f6ee7ba85c | |||
| 712e7f8ba0 | |||
| 3ace91bcff | |||
| 648871e784 | |||
| 911f9161f3 | |||
| 74c36a8aa7 | |||
| 33ed09da09 | |||
| 2437d38e70 | |||
| 990ce6811c |
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<classpath>
|
|
||||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
|
||||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
|
||||||
</classpath>
|
|
||||||
25
.env.example
Normal file
25
.env.example
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Configuration for tools/*.sh. Values are read as KEY=value data, not executed.
|
||||||
|
# Unquoted values may contain spaces; matching outer quotes are removed.
|
||||||
|
|
||||||
|
# Paper server
|
||||||
|
PAPER_VERSION=26.2
|
||||||
|
PAPER_BUILD=129
|
||||||
|
PAPER_USER_AGENT="majinnaibu-plugin-dev/1.0 (https://github.com/majinnaibu)"
|
||||||
|
JAVA_OPTS="-Xms2G -Xmx2G"
|
||||||
|
STOP_TIMEOUT=60
|
||||||
|
MINECRAFT_SERVER_JAR=paper-26.2-129.jar
|
||||||
|
# Relative paths are resolved from the project root; ~/ is expanded.
|
||||||
|
MINECRAFT_SERVER_PATH=./server
|
||||||
|
MINECRAFT_BACKUP_PATH=./backups
|
||||||
|
|
||||||
|
# Modrinth publishing
|
||||||
|
# Release version is read from Gradle's Git-sensitive semantic version and checked against the jar's plugin.yml.
|
||||||
|
MODRINTH_TOKEN=
|
||||||
|
MODRINTH_PROJECT_ID=metropolis-builder
|
||||||
|
# Optional Markdown override; empty generates notes from commits since the last tag.
|
||||||
|
MODRINTH_CHANGELOG=
|
||||||
|
MODRINTH_GAME_VERSIONS=26.2
|
||||||
|
MODRINTH_LOADERS=paper
|
||||||
|
MODRINTH_VERSION_TYPE=
|
||||||
|
MODRINTH_DEPENDENCIES=
|
||||||
|
MODRINTH_FILE=
|
||||||
45
.github/workflows/gradle.yml
vendored
Normal file
45
.github/workflows/gradle.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Java CI with Gradle
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "develop" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "develop" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- 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' }}
|
||||||
|
|
||||||
|
- 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
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,5 +1,9 @@
|
|||||||
/server
|
.gradle/
|
||||||
/Plugin.jardesc
|
build/
|
||||||
/Plugin-Release.jardesc
|
target/
|
||||||
/lib
|
.worktrees/
|
||||||
/target
|
bin/
|
||||||
|
/backups/
|
||||||
|
/server/
|
||||||
|
.env
|
||||||
|
|
||||||
|
|||||||
23
.project
23
.project
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>Metropolis</name>
|
|
||||||
<comment></comment>
|
|
||||||
<projects>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
</natures>
|
|
||||||
</projectDescription>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#Sat Mar 03 20:54:28 PST 2012
|
|
||||||
eclipse.preferences.version=1
|
|
||||||
encoding//src/main/java=UTF-8
|
|
||||||
encoding//src/main/resources=UTF-8
|
|
||||||
encoding/<project>=UTF-8
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
eclipse.preferences.version=1
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
|
||||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
|
||||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
|
||||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
|
||||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
|
||||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
|
||||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
|
||||||
org.eclipse.jdt.core.compiler.source=1.7
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#Sat Feb 18 20:11:20 PST 2012
|
|
||||||
eclipse.preferences.version=1
|
|
||||||
org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#Sat Feb 18 18:33:58 PST 2012
|
|
||||||
activeProfiles=
|
|
||||||
eclipse.preferences.version=1
|
|
||||||
resolveWorkspaceProjects=true
|
|
||||||
version=1
|
|
||||||
33
CONTRIBUTING.md
Normal file
33
CONTRIBUTING.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Live Server Tests
|
||||||
|
|
||||||
|
Live tests are opt-in and run separately from `build`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
METROPOLIS_RCON_HOST=127.0.0.1 \
|
||||||
|
METROPOLIS_RCON_PORT=25575 \
|
||||||
|
METROPOLIS_RCON_PASSWORD=... \
|
||||||
|
METROPOLIS_SERVER_DIR=/path/to/server \
|
||||||
|
./gradlew integrationTest
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a disposable or backed-up Paper server with compatible WorldGuard and WorldEdit plugin jars installed. Keep RCON local, use a temporary password, and restore the server after tests that modify regions or worlds.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
- Keep changes focused and include tests for behavior changes.
|
||||||
|
- Run `./gradlew build` before submitting.
|
||||||
|
- Run `integrationTest` only when changing behavior that requires Paper or the external plugins; do not add it to the default build.
|
||||||
|
- Update `README.md` and project descriptions when user-facing setup, commands, or dependencies change.
|
||||||
|
- Keep generated build output, server data, credentials, and local `.env` files out of commits.
|
||||||
91
Human-Tests.md
Normal file
91
Human-Tests.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Metropolis Human Testing
|
||||||
|
|
||||||
|
Use this checklist for behavior that depends on a real Paper world, player interactions, or the installed WorldGuard and WorldEdit plugins. Automated tests cover some isolated logic, but they do not establish that the complete in-game workflows behave correctly.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## Human Checklist
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
- 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.
|
||||||
|
- 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.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Existing Automated Coverage
|
||||||
|
|
||||||
|
Run `./gradlew test` for the server-free JUnit suite. It has 26 tests covering:
|
||||||
|
|
||||||
|
- `CuboidTest`: WorldEdit bound conversion, vector ordering/null handling, touching intersections, and point containment.
|
||||||
|
- `CurrentHomesStoreTest`: UUID-based persistence and resolving a legacy player-name key.
|
||||||
|
- `PlayerJoinListenerTest`: the welcome bounds message when a home is supplied by a mock.
|
||||||
|
- `PlayerLookupTest`: case-insensitive full-name matching and rejection of prefix matching.
|
||||||
|
- `PlayerHomeTest`: UUID-based region identity and display-name retention.
|
||||||
|
- `HomePermissionOverrideTest`: dotted permission-node parsing, priority and tie resolution, invalid entries, and username/permission/default precedence.
|
||||||
|
- `HomeNumberAllocatorTest`: choosing the first unused positive home number.
|
||||||
|
- `MetropolisHomeMoveCommandTest`: selecting a sender's home, selecting an offline target's home, and rejecting invalid requests.
|
||||||
|
- `PlotReservationLookupTest`: owner/member matching, exclusion of homes/City/Spawn, legacy reservation markers, and region-name aliases.
|
||||||
|
- `MetropolisPlotGoCommandTest`: self and target teleportation, ambiguous memberships, absent membership, and unsafe-destination feedback.
|
||||||
|
- `MetropolisPlotReserveCommandTest`: parsing six coordinates and rejecting malformed coordinates.
|
||||||
|
- `MetropolisHomeAcquireTest`: acquisition below the limit and refusal at the limit.
|
||||||
|
|
||||||
|
The two opt-in tests in `src/integrationTest` require a running Paper server and RCON. They check that Metropolis appears in `/plugins` and that a coordinate reservation is persisted by WorldGuard. They do not currently verify restart persistence, join allocation, WorldEdit-selection reservations, permissions, teleports, generated blocks, protection behavior, or the other command workflows above. Run them with the environment variables documented in `README.md`; use a disposable server because the reservation test changes its WorldGuard data.
|
||||||
|
|
||||||
|
The integration scope described in `TODO.md` is broader than the tests currently present. Treat the scenarios in this guide as unverified until a human has run them on the target Paper and plugin versions.
|
||||||
48
Modrinth-Description.md
Normal file
48
Modrinth-Description.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
**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.
|
||||||
|
|
||||||
|
## 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
Modrinth.json
Normal file
13
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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Project-Description.json
Normal file
23
Project-Description.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"id": "metropolis",
|
||||||
|
"title": "Metropolis",
|
||||||
|
"description": "A Paper 26.2/26.3 city-planning plugin that turns player onboarding into procedural urban growth with protected UUID-owned homes, roads, reservations, and configurable infrastructure.",
|
||||||
|
"docId": "Metropolis",
|
||||||
|
"image": {
|
||||||
|
"url": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.",
|
||||||
|
"alt": "Pixel-art aerial view of a Minecraft city with player home plots and connecting roads",
|
||||||
|
"prompt": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.",
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"backgroundClassName": "bg-surface"
|
||||||
|
},
|
||||||
|
"url": "/projects/metropolis",
|
||||||
|
"tags": ["open-source"],
|
||||||
|
"source": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/headhunter45/Metropolis"
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"url": "https://github.com/headhunter45/Metropolis/issues"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
Project-Description.md
Normal file
5
Project-Description.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Metropolis
|
||||||
|
|
||||||
|
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.
|
||||||
129
README.md
129
README.md
@@ -1,47 +1,96 @@
|
|||||||
# Description
|
# Metropolis
|
||||||
Metropolis generates a home for each user connecting to your world with roads between them. As new users connect the city expands.
|
|
||||||
|
|
||||||
WorldGuard regions are used for each home and for the city as a whole.
|
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.
|
||||||
|
|
||||||
One region called "City" is created to protect the spawn and roads. As the assigned blocks expand this region is automatically updated.
|
## Features
|
||||||
|
|
||||||
Each user gets assigned a home region named "h_username" where username is replaced with their username. This region is surrounded by a road and located next to an existing region.
|
- 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.
|
||||||
|
- 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.
|
||||||
|
|
||||||
After creation these regions can be modified as normal via WorldGuard commands.
|
## Requirements
|
||||||
|
|
||||||
# Install
|
- Paper 26.2 or 26.3, with Java 25 or newer
|
||||||
To install just drop the jar into your plugins folder. All necessarry data will be created on first launch.
|
- WorldGuard 7.0.19
|
||||||
|
- WorldEdit 7.4.5
|
||||||
|
|
||||||
# Config
|
WorldGuard and WorldEdit are required plugin dependencies. Install them alongside Metropolis in the server's `plugins` directory.
|
||||||
Edit the config.yml file. You can set the height to clear above roads, the road width, the plot size (incluing half the road width on either side), the road material, and the level at which the road is generated.
|
|
||||||
|
|
||||||
# Changelog
|
## Build and Test
|
||||||
* v0.5
|
|
||||||
Added example docs
|
Build the plugin and run the server-free unit tests:
|
||||||
Added Commands to plugin.yml to move and evict homes
|
|
||||||
Added floor and road supports to config file and generation
|
```sh
|
||||||
* v0.4.6
|
./gradlew clean build
|
||||||
Added optional generation of a sign identifying owner in plots on creation
|
```
|
||||||
Added a Cuboid constructor that takes a worldedit selection
|
|
||||||
Added a debug mode that enables extra logging. MetropolisPlugin.DEBUG should be true for snapshots and false for release builds.
|
The plugin jar is written to `build/libs/`. The Gradle wrapper uses Gradle 9.8, and Git-sensitive semantic versioning derives the artifact and `plugin.yml` versions from repository tags and commits.
|
||||||
Added an occupied Plots list to MetropolisPlugin to keep track of both player homes and reserved plots.
|
|
||||||
Added the Plot class as a parent of PlayerHome and moved relevant code to it.
|
Run the opt-in live-server tests against a running Paper server with WorldGuard, WorldEdit, and RCON enabled:
|
||||||
Added a command to reserve plots that aren't tied to a player. This can be used to setup a larger protected area around spawn
|
|
||||||
* v0.4.5
|
```sh
|
||||||
* v0.4.4
|
METROPOLIS_RCON_HOST=127.0.0.1 \
|
||||||
Added a welcome message telling players where their home is.
|
METROPOLIS_RCON_PORT=25575 \
|
||||||
Switched from PlayerLoginEvent to PlayerJoinEvent
|
METROPOLIS_RCON_PASSWORD=... \
|
||||||
* v0.4.3
|
METROPOLIS_SERVER_DIR=/path/to/server \
|
||||||
Removed call to saveconfig on plugin unload.
|
./gradlew integrationTest
|
||||||
* v0.4.2
|
```
|
||||||
Made city region refresh on server restart.
|
|
||||||
* v0.4.1
|
`integrationTest` is separate from the normal `test` and `build` tasks. Keep RCON bound to a trusted interface and do not commit its password.
|
||||||
* v0.4
|
|
||||||
* v0.3
|
## Server Scripts
|
||||||
Added command to generate a home for a user that is not currently logged in.
|
|
||||||
* v0.2.1
|
Copy `.env.example` to `.env` and configure the Paper version/build and server paths. The scripts read `.env` as key/value data.
|
||||||
Made config file save on load and disable.
|
|
||||||
* v0.2
|
- `tools/build-plugin.sh` builds and tests the plugin.
|
||||||
Added configuration options.
|
- `tools/deploy-plugin.sh` copies the Gradle-built jar to the configured server.
|
||||||
* v0.1
|
- `tools/start-server.sh` downloads the configured Paper build if needed and starts it.
|
||||||
Initial Release
|
- `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-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
|
||||||
|
|
||||||
|
`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. `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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
| Command | Permission | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `/metropolis` | None | Displays the plugin version. |
|
||||||
|
| `/metropolis-home-acquire` | Player only | Acquires an available home if the player's home limit is not reached. |
|
||||||
|
| `/metropolis-home-generate <playerName>` | `metropolis.home.generate` | Generates a home for an online or cached offline player. |
|
||||||
|
| `/metropolis-home-list` | `metropolis.home.list` | Lists the occupied and reserved Metropolis plots with their bounds. |
|
||||||
|
| `/metropolis-home-go [playerName]` | `metropolis.home.go` | Teleports the sender, or a permitted target player, to their home. |
|
||||||
|
| `/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-home-evict <playerName>` | `metropolis.home.evict` | Removes the player's ownership from their selected home region. |
|
||||||
|
| `/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` | Generates test homes; intended for development servers. |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GNU General Public License v3.
|
||||||
|
|||||||
127
TODO.md
Normal file
127
TODO.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# 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. |
|
||||||
|
| 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 | Ready | 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
|
||||||
|
**Description:**
|
||||||
|
|
||||||
|
Add the possibility to create multiple plot homes in shapes
|
||||||
|
|
||||||
|
|
||||||
|
# GIT-006 - Add levels support.
|
||||||
|
**Status:** Ready
|
||||||
|
**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
|
||||||
102
build.gradle
Normal file
102
build.gradle
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.diffplug.spotless' version '8.10.3'
|
||||||
|
id 'java-library'
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'org.danilopianini.git-sensitive-semantic-versioning' version '7.0.24'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.majinnaibu.bukkitplugins'
|
||||||
|
description = 'Metropolis'
|
||||||
|
|
||||||
|
gitSemVer {
|
||||||
|
minimumVersion.set('0.5.0')
|
||||||
|
versionPrefix.set('v')
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
|
||||||
|
maven { url = 'https://maven.enginehub.org/repo/' }
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'io.papermc.paper:paper-api:26.2.build.129-stable'
|
||||||
|
compileOnly 'com.sk89q.worldedit:worldedit-bukkit:7.4.5'
|
||||||
|
compileOnly 'com.sk89q.worldguard:worldguard-bukkit:7.0.19'
|
||||||
|
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'
|
||||||
|
testImplementation 'org.mockito:mockito-junit-jupiter:5.24.0'
|
||||||
|
testImplementation 'io.papermc.paper:paper-api:26.2.build.129-stable'
|
||||||
|
testImplementation 'com.sk89q.worldedit:worldedit-bukkit:7.4.5'
|
||||||
|
testImplementation 'com.sk89q.worldguard:worldguard-bukkit:7.0.19'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.4'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
integrationTest {
|
||||||
|
compileClasspath += sourceSets.main.output
|
||||||
|
runtimeClasspath += sourceSets.main.output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
integrationTestImplementation.extendsFrom testImplementation
|
||||||
|
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('updateDaemonJvm') {
|
||||||
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
|
||||||
|
def pluginVersion = project.version.toString()
|
||||||
|
|
||||||
|
tasks.named('processResources') {
|
||||||
|
inputs.property('version', pluginVersion)
|
||||||
|
filesMatching('plugin.yml') {
|
||||||
|
expand(version: pluginVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('integrationTest', Test) {
|
||||||
|
description = 'Runs opt-in tests against a running Paper server with RCON enabled.'
|
||||||
|
group = 'verification'
|
||||||
|
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||||
|
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||||
|
useJUnitPlatform()
|
||||||
|
shouldRunAfter tasks.named('test')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('wrapper') {
|
||||||
|
gradleVersion = '9.8.0'
|
||||||
|
distributionType = Wrapper.DistributionType.BIN
|
||||||
|
distributionBase = Wrapper.PathBase.PROJECT
|
||||||
|
distributionPath = '.gradle/wrapper/dists'
|
||||||
|
archiveBase = Wrapper.PathBase.PROJECT
|
||||||
|
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>.
|
||||||
|
*/
|
||||||
|
|
||||||
5
gradle.properties
Normal file
5
gradle.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
org.gradle.configuration-cache=true
|
||||||
|
org.gradle.daemon=true
|
||||||
|
org.gradle.java.installations.auto-download=true
|
||||||
|
org.gradle.java.installations.auto-detect=false
|
||||||
|
org.gradle.java.installations.fromEnv=JAVA_HOME
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
9
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
9
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
distributionBase=PROJECT
|
||||||
|
distributionPath=.gradle/wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=PROJECT
|
||||||
|
zipStorePath=.gradle/wrapper/dists
|
||||||
248
gradlew
vendored
Executable file
248
gradlew
vendored
Executable file
@@ -0,0 +1,248 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# gradlew start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh gradlew
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
112
gradlew.bat
vendored
Normal file
112
gradlew.bat
vendored
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem gradlew startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
|
setlocal EnableExtensions
|
||||||
|
|
||||||
|
@rem Catch executions from older scripts and ensure they exit cleanly.
|
||||||
|
@rem This can be removed once we can be reasonably confident that few people
|
||||||
|
@rem will be migrating directly to this new wrapper.
|
||||||
|
goto afterSafetyNet
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
goto exitWithErrorLevel
|
||||||
|
:afterSafetyNet
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
1>&2 echo.
|
||||||
|
1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
1>&2 echo.
|
||||||
|
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
1>&2 echo location of your Java installation.
|
||||||
|
|
||||||
|
"%COMSPEC%" /c exit 1
|
||||||
|
goto exitWithErrorLevel
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
1>&2 echo.
|
||||||
|
1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
1>&2 echo.
|
||||||
|
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
1>&2 echo location of your Java installation.
|
||||||
|
|
||||||
|
"%COMSPEC%" /c exit 1
|
||||||
|
goto exitWithErrorLevel
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute gradlew
|
||||||
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
|
||||||
|
|
||||||
|
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
|
||||||
|
:exitWithErrorLevel
|
||||||
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
108
pom.xml
108
pom.xml
@@ -1,108 +0,0 @@
|
|||||||
<!--
|
|
||||||
Maven build file for Metropolis
|
|
||||||
Copyright (c) 2012 headhunter45 <http://majinnaibu.com>
|
|
||||||
Metropolis is available under the GNU General Public License v3
|
|
||||||
-->
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.majinnaibu.bukkitplugins</groupId>
|
|
||||||
<artifactId>Metropolis</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<name>Metropolis</name>
|
|
||||||
<description>Metropolis generates homes and streets for players as they connect to your server.</description>
|
|
||||||
<url>http://www.github.com/headhunter45/Metropolis</url>
|
|
||||||
<properties>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<scm>
|
|
||||||
<connection>scm:git:git://github.com/headhunter45/Metropolis.git</connection>
|
|
||||||
<url>https://github.com/headhunter45/Metropolis</url>
|
|
||||||
<developerConnection>scm:git:git@github.com:headhunter45/Metropolis.git</developerConnection>
|
|
||||||
</scm>
|
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<!--
|
|
||||||
<repository>
|
|
||||||
<id>wg</id>
|
|
||||||
<url>http://files.zachsthings.com/repo</url>
|
|
||||||
</repository>
|
|
||||||
-->
|
|
||||||
<!-- WorldEdit/WorldGuard/CommandBook -->
|
|
||||||
<repository>
|
|
||||||
<id>sk89q</id>
|
|
||||||
<url>http://mvn2.sk89q.com/repo</url>
|
|
||||||
</repository>
|
|
||||||
|
|
||||||
<!-- Bukkit -->
|
|
||||||
<repository>
|
|
||||||
<id>bukkit-repo</id>
|
|
||||||
<url>http://repo.bukkit.org/content/groups/public</url>
|
|
||||||
</repository>
|
|
||||||
|
|
||||||
<!-- Spout -->
|
|
||||||
<repository>
|
|
||||||
<id>sonatype-nexus-releases</id>
|
|
||||||
<url>https://oss.sonatype.org/content/repositories/releases</url>
|
|
||||||
</repository>
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>sonatype-nexus-snapshots</id>
|
|
||||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>true</enabled>
|
|
||||||
</snapshots>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- WorldEdit -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.sk89q</groupId>
|
|
||||||
<artifactId>worldedit</artifactId>
|
|
||||||
<version>5.6.3</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Bukkit -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bukkit</groupId>
|
|
||||||
<artifactId>bukkit</artifactId>
|
|
||||||
<version>1.7.9-R0.2</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- WorldGuard -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.sk89q</groupId>
|
|
||||||
<artifactId>worldguard</artifactId>
|
|
||||||
<version>5.9</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<distributionManagement>
|
|
||||||
<repository>
|
|
||||||
<id>majinnaibu-repository</id>
|
|
||||||
<url>ftp://repo.majinnaibu.com</url>
|
|
||||||
</repository>
|
|
||||||
</distributionManagement>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<extensions>
|
|
||||||
<!-- Enabling the use of FTP -->
|
|
||||||
<extension>
|
|
||||||
<groupId>org.apache.maven.wagon</groupId>
|
|
||||||
<artifactId>wagon-ftp</artifactId>
|
|
||||||
<version>1.0-beta-6</version>
|
|
||||||
</extension>
|
|
||||||
</extensions>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<source>1.7</source>
|
|
||||||
<target>1.7</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
5
settings.gradle
Normal file
5
settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = 'Metropolis'
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
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.assertTrue;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MetropolisServerIntegrationTest {
|
||||||
|
@Test
|
||||||
|
void runningPaperLoadsMetropolis() throws IOException {
|
||||||
|
String installedPlugins;
|
||||||
|
try (RconClient rcon = connectToConfiguredServer()) {
|
||||||
|
installedPlugins = rcon.execute("plugins");
|
||||||
|
}
|
||||||
|
assertTrue(installedPlugins.contains("Metropolis"), installedPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void plotReservationIsPersistedByTheLoadedWorldGuardPlugin() throws IOException {
|
||||||
|
String regionId = "metropolis-it-" + UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
try (RconClient rcon = connectToConfiguredServer()) {
|
||||||
|
String 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"));
|
||||||
|
String worldName = System.getenv().getOrDefault("METROPOLIS_WORLD", "world");
|
||||||
|
Path regionsFile =
|
||||||
|
serverDirectory
|
||||||
|
.resolve("plugins/WorldGuard/worlds")
|
||||||
|
.resolve(worldName)
|
||||||
|
.resolve("regions.yml");
|
||||||
|
YamlConfiguration regions = YamlConfiguration.loadConfiguration(regionsFile.toFile());
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
regions.contains("regions." + regionId), "WorldGuard did not persist region " + regionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RconClient connectToConfiguredServer() throws IOException {
|
||||||
|
String host = requiredEnvironment("METROPOLIS_RCON_HOST");
|
||||||
|
int port = Integer.parseInt(requiredEnvironment("METROPOLIS_RCON_PORT"));
|
||||||
|
String password = requiredEnvironment("METROPOLIS_RCON_PASSWORD");
|
||||||
|
return new RconClient(host, port, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requiredEnvironment(String name) {
|
||||||
|
String value = System.getenv(name);
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
throw new IllegalStateException("Set " + name + " to run integrationTest.");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RconClient implements AutoCloseable {
|
||||||
|
private final Socket socket = new Socket();
|
||||||
|
private final DataInputStream input;
|
||||||
|
private final DataOutputStream output;
|
||||||
|
private int requestId = 1;
|
||||||
|
|
||||||
|
private RconClient(String host, int port, String password) throws IOException {
|
||||||
|
socket.connect(new InetSocketAddress(host, port), 5000);
|
||||||
|
socket.setSoTimeout(5000);
|
||||||
|
input = new DataInputStream(socket.getInputStream());
|
||||||
|
output = new DataOutputStream(socket.getOutputStream());
|
||||||
|
int authId = requestId++;
|
||||||
|
Packet response = send(authId, 3, password);
|
||||||
|
if (response.id != authId) {
|
||||||
|
throw new IOException("RCON authentication failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String execute(String command) throws IOException {
|
||||||
|
return send(requestId++, 2, command).payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Packet send(int id, int type, String payload) throws IOException {
|
||||||
|
byte[] data = payload.getBytes(StandardCharsets.UTF_8);
|
||||||
|
writeLittleEndianInt(data.length + 10);
|
||||||
|
writeLittleEndianInt(id);
|
||||||
|
writeLittleEndianInt(type);
|
||||||
|
output.write(data);
|
||||||
|
output.writeByte(0);
|
||||||
|
output.writeByte(0);
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
int length = readLittleEndianInt();
|
||||||
|
byte[] packet = input.readNBytes(length);
|
||||||
|
if (packet.length != length || length < 10) {
|
||||||
|
throw new IOException("Received an invalid RCON packet.");
|
||||||
|
}
|
||||||
|
int responseId = littleEndianInt(packet, 0);
|
||||||
|
String response = new String(packet, 8, length - 10, StandardCharsets.UTF_8);
|
||||||
|
return new Packet(responseId, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLittleEndianInt(int value) throws IOException {
|
||||||
|
output.writeInt(Integer.reverseBytes(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readLittleEndianInt() throws IOException {
|
||||||
|
return Integer.reverseBytes(input.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int littleEndianInt(byte[] bytes, int offset) {
|
||||||
|
return (bytes[offset] & 0xff)
|
||||||
|
| ((bytes[offset + 1] & 0xff) << 8)
|
||||||
|
| ((bytes[offset + 2] & 0xff) << 16)
|
||||||
|
| (bytes[offset + 3] << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Packet(int id, String payload) {}
|
||||||
|
}
|
||||||
@@ -1,231 +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 java.util.logging.Logger;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import javax.persistence.Embeddable;
|
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
|
||||||
|
|
||||||
import com.sk89q.worldedit.BlockVector;
|
|
||||||
import com.sk89q.worldedit.bukkit.selections.Selection;
|
|
||||||
|
|
||||||
@Embeddable
|
|
||||||
public class Cuboid implements Comparable<Cuboid> {
|
public class Cuboid implements Comparable<Cuboid> {
|
||||||
private int id;
|
private int id;
|
||||||
|
|
||||||
public int minX;
|
public int minX;
|
||||||
public int minY;
|
public int minY;
|
||||||
public int minZ;
|
public int minZ;
|
||||||
|
|
||||||
public int maxX;
|
public int maxX;
|
||||||
public int maxY;
|
public int maxY;
|
||||||
public int maxZ;
|
public int maxZ;
|
||||||
|
|
||||||
public int getId(){
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(int id){
|
public void setId(int id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid(int minX, int minY, int minZ, int maxX, int maxY, int maxZ){
|
public Cuboid(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||||
this.minX = minX;
|
this.minX = minX;
|
||||||
this.minY = minY;
|
this.minY = minY;
|
||||||
this.minZ = minZ;
|
this.minZ = minZ;
|
||||||
this.maxX = maxX;
|
this.maxX = maxX;
|
||||||
this.maxY = maxY;
|
this.maxY = maxY;
|
||||||
this.maxZ = maxZ;
|
this.maxZ = maxZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid(BlockVector min, BlockVector max) {
|
public Cuboid(BlockVector3 min, BlockVector3 max) {
|
||||||
this.minX = min.getBlockX();
|
this.minX = min.x();
|
||||||
this.minY = min.getBlockY();
|
this.minY = min.y();
|
||||||
this.minZ = min.getBlockZ();
|
this.minZ = min.z();
|
||||||
this.maxX = max.getBlockX();
|
this.maxX = max.x();
|
||||||
this.maxY = max.getBlockY();
|
this.maxY = max.y();
|
||||||
this.maxZ = max.getBlockZ();
|
this.maxZ = max.z();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid() {
|
public Cuboid() {
|
||||||
this.minX = 0;
|
this.minX = 0;
|
||||||
this.minY = 0;
|
this.minY = 0;
|
||||||
this.minZ = 0;
|
this.minZ = 0;
|
||||||
this.maxX = 0;
|
this.maxX = 0;
|
||||||
this.maxY = 0;
|
this.maxY = 0;
|
||||||
this.maxZ = 0;
|
this.maxZ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid(Selection selection) {
|
public Cuboid(Region selection) {
|
||||||
this.minX = selection.getMinimumPoint().getBlockX();
|
this.minX = selection.getMinimumPoint().x();
|
||||||
this.minY = selection.getMinimumPoint().getBlockY();
|
this.minY = selection.getMinimumPoint().y();
|
||||||
this.minZ = selection.getMinimumPoint().getBlockZ();
|
this.minZ = selection.getMinimumPoint().z();
|
||||||
this.maxX = selection.getMaximumPoint().getBlockX();
|
this.maxX = selection.getMaximumPoint().x();
|
||||||
this.maxY = selection.getMaximumPoint().getBlockY();
|
this.maxY = selection.getMaximumPoint().y();
|
||||||
this.maxZ = selection.getMaximumPoint().getBlockZ();
|
this.maxZ = selection.getMaximumPoint().z();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector getMin(){
|
public BlockVector3 getMin() {
|
||||||
return new BlockVector(minX, minY, minZ);
|
return BlockVector3.at(minX, minY, minZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector getMax(){
|
public BlockVector3 getMax() {
|
||||||
return new BlockVector(maxX, maxY, maxZ);
|
return BlockVector3.at(maxX, maxY, maxZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Cuboid o) {
|
public int compareTo(Cuboid o) {
|
||||||
BlockVector min = getMin();
|
BlockVector3 min = getMin();
|
||||||
BlockVector otherMin = o.getMin();
|
BlockVector3 otherMin = o.getMin();
|
||||||
|
|
||||||
if(min.getBlockX() < otherMin.getBlockX()){
|
if (min.x() < otherMin.x()) {
|
||||||
return -1;
|
return -1;
|
||||||
}else if(min.getBlockX() > otherMin.getBlockX()){
|
} else if (min.x() > otherMin.x()) {
|
||||||
return 1;
|
return 1;
|
||||||
}else if(min.getBlockZ() < otherMin.getBlockZ()){
|
} else if (min.z() < otherMin.z()) {
|
||||||
return -1;
|
return -1;
|
||||||
}else if(min.getBlockZ() > otherMin.getBlockZ()){
|
} else if (min.z() > otherMin.z()) {
|
||||||
return 1;
|
return 1;
|
||||||
}else if(min.getBlockY() < otherMin.getBlockY()){
|
} else if (min.y() < otherMin.y()) {
|
||||||
return -1;
|
return -1;
|
||||||
}else if(min.getBlockY() > otherMin.getBlockY()){
|
} else if (min.y() > otherMin.y()) {
|
||||||
return 1;
|
return 1;
|
||||||
}else{
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int compareBlockVectors(BlockVector v1, BlockVector v2){
|
public static int compareBlockVectors(BlockVector3 v1, BlockVector3 v2) {
|
||||||
Logger log = Logger.getLogger("Minecraft");
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(v1 == null){
|
public static boolean isBlockLessThan(BlockVector3 v1, BlockVector3 v2) {
|
||||||
if(v2 == null){
|
return compareBlockVectors(v1, v2) < 0;
|
||||||
log.info("in Cuboid.compareBlockVectors v1 and v2 are null");
|
}
|
||||||
return 0;
|
|
||||||
}else{
|
|
||||||
log.info("in Cubiod.compareBlockVectors v1 is null");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}else if(v2 == null){
|
|
||||||
log.info("in Cubiod.compareBlockVectors v2 is null");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
log.info(String.format("v1.x: %d, v1.y: %d, v1.z: %d, v2.x: %d, v2.y: %d, v2.z: %d", v1.getBlockX(), v1.getBlockY(), v1.getBlockZ(), v2.getBlockX(), v2.getBlockY(), v2.getBlockZ()));
|
|
||||||
if(v1.getBlockX() < v2.getBlockX()){
|
|
||||||
return -1;
|
|
||||||
}else if(v1.getBlockX() > v2.getBlockX()){
|
|
||||||
return 1;
|
|
||||||
}else if(v1.getBlockZ() < v2.getBlockZ()){
|
|
||||||
return -1;
|
|
||||||
}else if(v1.getBlockZ() > v2.getBlockZ()){
|
|
||||||
return 1;
|
|
||||||
}else if(v1.getBlockY() < v2.getBlockY()){
|
|
||||||
return -1;
|
|
||||||
}else if(v1.getBlockY() > v2.getBlockY()){
|
|
||||||
return 1;
|
|
||||||
}else{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isBlockLessThan(BlockVector v1, BlockVector v2) {
|
public Cuboid inset(int amount) {
|
||||||
return compareBlockVectors(v1, v2) < 0;
|
return inset(amount, amount, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid inset(int amount){
|
public Cuboid inset(int x, int z) {
|
||||||
return inset(amount, amount, amount);
|
return inset(x, 0, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid inset(int x, int z){
|
public Cuboid inset(int x, int y, int z) {
|
||||||
return inset(x, 0, z);
|
return new Cuboid(
|
||||||
}
|
this.minX + x, this.minY, this.minZ + z, this.maxX - x, this.maxY, this.maxZ - z);
|
||||||
|
}
|
||||||
|
|
||||||
public Cuboid inset(int x, int y, int z){
|
public int getVolume() {
|
||||||
return new Cuboid(this.minX + x, this.minY, this.minZ + z, this.maxX - x, this.maxY, this.maxZ - z);
|
return (this.maxX - this.minX) * (this.maxY - this.minY) * (this.maxZ - this.minZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVolume() {
|
public int getMinX() {
|
||||||
return (this.maxX - this.minX) * (this.maxY - this.minY) * (this.maxZ - this.minZ);
|
return minX;
|
||||||
}
|
}
|
||||||
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) {
|
public void setMinX(int minX) {
|
||||||
return outset(amount, amount, amount);
|
this.minX = minX;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid outset(int x, int z){
|
public int getMinY() {
|
||||||
return outset(x, 0, z);
|
return minY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cuboid outset(int x, int y, int z) {
|
public void setMinY(int minY) {
|
||||||
return new Cuboid(this.minX - x, this.minY - y, this.minZ - z, this.maxX + x, this.maxY + y, this.maxZ + z);
|
this.minY = minY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCenterX(){
|
public int getMinZ() {
|
||||||
return (this.minX + this.maxX) /2;
|
return minZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCenterY(){
|
public void setMinZ(int minZ) {
|
||||||
return (this.minY + this.maxY)/2;
|
this.minZ = minZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCenterZ(){
|
public int getMaxX() {
|
||||||
return (this.minZ + this.maxZ)/2;
|
return maxX;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean intersects(Cuboid other) {
|
public void setMaxX(int maxX) {
|
||||||
if (this.maxX >= other.minX && this.minX <= other.maxX)
|
this.maxX = maxX;
|
||||||
{
|
}
|
||||||
if (this.maxZ >= other.minZ && this.minZ <= other.maxZ)
|
|
||||||
{
|
public int getMaxY() {
|
||||||
if (this.maxY >= other.minY && this.minY <= other.maxY)
|
return maxY;
|
||||||
{
|
}
|
||||||
return true;
|
|
||||||
}
|
public void setMaxY(int maxY) {
|
||||||
else
|
this.maxY = maxY;
|
||||||
{
|
}
|
||||||
return false;
|
|
||||||
}
|
public int getMaxZ() {
|
||||||
}
|
return maxZ;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
return false;
|
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
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
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.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
final class CurrentHomesStore {
|
||||||
|
private CurrentHomesStore() {}
|
||||||
|
|
||||||
|
static Map<UUID, Integer> load(File file, Function<String, UUID> legacyOwnerResolver)
|
||||||
|
throws IOException, InvalidConfigurationException {
|
||||||
|
Map<UUID, Integer> homesByOwner = new HashMap<>();
|
||||||
|
if (!file.exists()) {
|
||||||
|
return homesByOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
yaml.load(file);
|
||||||
|
for (String ownerKey : yaml.getKeys(false)) {
|
||||||
|
UUID ownerId;
|
||||||
|
try {
|
||||||
|
ownerId = UUID.fromString(ownerKey);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
ownerId = legacyOwnerResolver.apply(ownerKey);
|
||||||
|
}
|
||||||
|
int homeNumber = yaml.getInt(ownerKey, 0);
|
||||||
|
if (ownerId != null && homeNumber > 0) {
|
||||||
|
homesByOwner.put(ownerId, homeNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return homesByOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void save(File file, Map<UUID, Integer> homesByOwner) throws IOException {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
if (parent != null) {
|
||||||
|
parent.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
homesByOwner.forEach((ownerId, homeNumber) -> yaml.set(ownerId.toString(), homeNumber));
|
||||||
|
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,111 +1,144 @@
|
|||||||
|
/*
|
||||||
|
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.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
import org.bukkit.OfflinePlayer;
|
|
||||||
|
|
||||||
import com.avaje.ebean.validation.NotNull;
|
|
||||||
import com.sk89q.worldedit.BlockVector;
|
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
|
||||||
@Entity()
|
import org.bukkit.Bukkit;
|
||||||
@Table(name="Metropolis_PlayerHome")
|
import org.bukkit.OfflinePlayer;
|
||||||
public class PlayerHome extends Plot{
|
|
||||||
@NotNull
|
|
||||||
private UUID playerUUID = null;
|
|
||||||
public UUID getPlayerUUID(){return this.playerUUID;}
|
|
||||||
public void setPlayerUUID(UUID playerUUID){this.playerUUID = playerUUID;}
|
|
||||||
|
|
||||||
private int number;
|
public class PlayerHome extends Plot {
|
||||||
|
private UUID playerId;
|
||||||
|
private String playerName;
|
||||||
|
|
||||||
public PlayerHome(UUID owner, BlockVector min, BlockVector max) {
|
public UUID getPlayerId() {
|
||||||
super("h_1_" + owner, min, max);
|
return this.playerId;
|
||||||
this.playerUUID = owner;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerHome(OfflinePlayer owner, BlockVector min, BlockVector max) {
|
public String getPlayerName() {
|
||||||
super("h_1_" + owner.getUniqueId(), min, max);
|
return this.playerName;
|
||||||
this.playerUUID = owner.getUniqueId();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerHome() {
|
public void setPlayerName(String playerName) {
|
||||||
this.playerUUID = null;
|
this.playerName = playerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerHome(ProtectedRegion homeRegion){
|
private int number;
|
||||||
this.playerUUID = null;
|
|
||||||
this.number = 0;
|
|
||||||
|
|
||||||
try{
|
public PlayerHome(
|
||||||
String rname = homeRegion.getId();
|
UUID ownerId, String ownerName, int homeNumber, BlockVector3 min, BlockVector3 max) {
|
||||||
|
super(String.format("h_%d_%s", homeNumber, ownerId), min, max);
|
||||||
|
this.playerId = ownerId;
|
||||||
|
this.playerName = ownerName;
|
||||||
|
this.number = homeNumber;
|
||||||
|
}
|
||||||
|
|
||||||
if(rname.startsWith("h_")){
|
public PlayerHome() {
|
||||||
int secondUnderscore = rname.indexOf('_', 2);
|
this.playerId = null;
|
||||||
if(secondUnderscore > 2){
|
this.playerName = "";
|
||||||
try{
|
}
|
||||||
this.number = Integer.parseInt(rname.substring(2, secondUnderscore));
|
|
||||||
this.playerUUID = UUID.fromString(rname.substring(secondUnderscore+1));
|
|
||||||
}catch(Exception ex){
|
|
||||||
this.number = 0;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
this.number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCuboid(new Cuboid(homeRegion.getMinimumPoint(), homeRegion.getMaximumPoint()));
|
public PlayerHome(ProtectedRegion homeRegion) {
|
||||||
}
|
String regionName = homeRegion.getId();
|
||||||
else{
|
if (!regionName.startsWith("h_")) {
|
||||||
throw new RuntimeException("Method not implemented.");
|
throw new IllegalArgumentException("Not a Metropolis home region: " + regionName);
|
||||||
}
|
}
|
||||||
}catch(Exception ex){
|
String regionOwner = regionName.substring(2);
|
||||||
throw new RuntimeException("Method not implemented.", ex);
|
int separator = regionOwner.indexOf('_');
|
||||||
}
|
if (separator > 0) {
|
||||||
}
|
try {
|
||||||
|
this.number = Integer.parseInt(regionOwner.substring(0, separator));
|
||||||
|
regionOwner = regionOwner.substring(separator + 1);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
this.number = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.number = 1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.playerId = UUID.fromString(regionOwner);
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
|
||||||
|
this.playerName = offlinePlayer.getName() == null ? regionOwner : offlinePlayer.getName();
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
Set<UUID> ownerIds = homeRegion.getOwners().getUniqueIds();
|
||||||
|
this.playerId =
|
||||||
|
ownerIds.isEmpty()
|
||||||
|
? Bukkit.getOfflinePlayer(regionOwner).getUniqueId()
|
||||||
|
: ownerIds.iterator().next();
|
||||||
|
this.playerName = regionOwner;
|
||||||
|
}
|
||||||
|
setCuboid(new Cuboid(homeRegion.getMinimumPoint(), homeRegion.getMaximumPoint()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
if(!(other instanceof PlayerHome)){
|
if (!(other instanceof PlayerHome)) {
|
||||||
return super.equals(other);
|
return super.equals(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerHome otherPlayerHome = (PlayerHome)other;
|
PlayerHome otherPlayerHome = (PlayerHome) other;
|
||||||
|
|
||||||
if(!this.playerUUID.equals(otherPlayerHome.playerUUID)){
|
if (!java.util.Objects.equals(this.playerId, otherPlayerHome.playerId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!getCuboid().equals(otherPlayerHome.getCuboid())){
|
if (!getCuboid().equals(otherPlayerHome.getCuboid())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toFriendlyString() {
|
public String toFriendlyString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
sb.append(String.format("Metropolis Home {Owner: %s min: (%d, %d, %d) max: (%d, %d, %d)}", getPlayerUUID(), getCuboid().getMinX(), getCuboid().getMinY(), getCuboid().getMinZ(), getCuboid().getMaxX(), getCuboid().getMaxY(), getCuboid().getMaxZ()));
|
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();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PlayerHome get(ProtectedRegion homeRegion){
|
public static PlayerHome get(ProtectedRegion homeRegion) {
|
||||||
if(homeRegion instanceof ProtectedCuboidRegion){
|
if (homeRegion instanceof ProtectedCuboidRegion) {
|
||||||
return new PlayerHome((ProtectedCuboidRegion) homeRegion);
|
return new PlayerHome((ProtectedCuboidRegion) homeRegion);
|
||||||
}else{
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getNumber() {
|
public Integer getNumber() {
|
||||||
return number;
|
return number;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNumber(int number){
|
public void setNumber(int number) {
|
||||||
this.number = number;
|
this.number = number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +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;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
final class PlayerLookup {
|
||||||
|
private PlayerLookup() {}
|
||||||
|
|
||||||
|
static Player findOnline(String requestedName, Iterable<? extends Player> onlinePlayers) {
|
||||||
|
if (requestedName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (Player player : onlinePlayers) {
|
||||||
|
if (requestedName.equalsIgnoreCase(player.getName())) {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,130 +1,164 @@
|
|||||||
|
/*
|
||||||
|
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 javax.persistence.Entity;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import javax.persistence.Id;
|
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||||
import javax.persistence.Table;
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
|
|
||||||
import com.avaje.ebean.validation.NotNull;
|
public class Plot implements Comparable<Plot> {
|
||||||
import com.sk89q.worldedit.BlockVector;
|
private int _id;
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
|
||||||
|
|
||||||
@Entity()
|
public int getId() {
|
||||||
@Table(name="Metropolis_Plot")
|
return _id;
|
||||||
public class Plot implements Comparable<Plot>{
|
}
|
||||||
@Id
|
|
||||||
private int _id;
|
|
||||||
public int getId(){return _id;}
|
|
||||||
public void setId(int id){_id = id;}
|
|
||||||
|
|
||||||
@NotNull
|
public void setId(int id) {
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
private Cuboid _cuboid;
|
private Cuboid _cuboid;
|
||||||
public Cuboid getCuboid(){return _cuboid;}
|
|
||||||
public void setCuboid(Cuboid cuboid){_cuboid = cuboid;}
|
|
||||||
|
|
||||||
@NotNull
|
public Cuboid getCuboid() {
|
||||||
private String _regionName;
|
return _cuboid;
|
||||||
public String getRegionName(){return _regionName;}
|
}
|
||||||
public void setRegionName(String regionName){_regionName = regionName;}
|
|
||||||
|
|
||||||
public Plot(String regionName, BlockVector min, BlockVector max){
|
public void setCuboid(Cuboid cuboid) {
|
||||||
_cuboid = new Cuboid(min, max);
|
_cuboid = cuboid;
|
||||||
_regionName = regionName;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Plot(ProtectedCuboidRegion cuboid){
|
private String _regionName;
|
||||||
_cuboid = new Cuboid(cuboid.getMinimumPoint(), cuboid.getMaximumPoint());
|
|
||||||
_regionName = cuboid.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plot(){
|
public String getRegionName() {
|
||||||
_cuboid = new Cuboid();
|
return _regionName;
|
||||||
_regionName = "";
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public BlockVector getPlotMin(int roadWidth) {
|
public void setRegionName(String regionName) {
|
||||||
return new BlockVector(_cuboid.minX - roadWidth/2, _cuboid.minY, _cuboid.minZ - roadWidth/2);
|
_regionName = regionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRow(int roadWidth, int plotSizeZ){
|
public Plot(String regionName, BlockVector3 min, BlockVector3 max) {
|
||||||
BlockVector min = getPlotMin(roadWidth);
|
_cuboid = new Cuboid(min, max);
|
||||||
return min.getBlockZ() / plotSizeZ;
|
_regionName = regionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCol(int roadWidth, int plotSizeX){
|
public Plot(ProtectedCuboidRegion cuboid) {
|
||||||
return getPlotMin(roadWidth).getBlockX() / plotSizeX;
|
_cuboid = new Cuboid(cuboid.getMinimumPoint(), cuboid.getMaximumPoint());
|
||||||
}
|
_regionName = cuboid.getId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public Plot() {
|
||||||
public boolean equals(Object other) {
|
_cuboid = new Cuboid();
|
||||||
if(!(other instanceof Plot)){
|
_regionName = "";
|
||||||
return super.equals(other);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Plot otherPlayerHome = (Plot)other;
|
public BlockVector3 getPlotMin(int roadWidth) {
|
||||||
|
return BlockVector3.at(
|
||||||
|
_cuboid.minX - roadWidth / 2, _cuboid.minY, _cuboid.minZ - roadWidth / 2);
|
||||||
|
}
|
||||||
|
|
||||||
if(!getCuboid().equals(otherPlayerHome.getCuboid())){
|
public int getRow(int roadWidth, int plotSizeZ) {
|
||||||
return false;
|
BlockVector3 min = getPlotMin(roadWidth);
|
||||||
}
|
return min.z() / plotSizeZ;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
public int getCol(int roadWidth, int plotSizeX) {
|
||||||
}
|
return getPlotMin(roadWidth).x() / plotSizeX;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Plot another) {
|
public boolean equals(Object other) {
|
||||||
return getCuboid().compareTo(another.getCuboid());
|
if (!(other instanceof Plot)) {
|
||||||
}
|
return super.equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
Plot otherPlayerHome = (Plot) other;
|
||||||
public String toString(){
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append(String.format("{PlayerHome }"));
|
if (!getCuboid().equals(otherPlayerHome.getCuboid())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Plot get(ProtectedRegion region){
|
@Override
|
||||||
if(region instanceof ProtectedCuboidRegion){
|
public int compareTo(Plot another) {
|
||||||
return new Plot((ProtectedCuboidRegion) region);
|
return getCuboid().compareTo(another.getCuboid());
|
||||||
}else{
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toFriendlyString() {
|
@Override
|
||||||
StringBuilder sb = new StringBuilder();
|
public String toString() {
|
||||||
|
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()));
|
sb.append(String.format("{PlayerHome }"));
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean contains(Location bedSpawn) {
|
public static Plot get(ProtectedRegion region) {
|
||||||
return _cuboid.contains(bedSpawn);
|
if (region instanceof ProtectedCuboidRegion) {
|
||||||
}
|
return new Plot((ProtectedCuboidRegion) region);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Location getViableSpawnLocation(World world) {
|
public String toFriendlyString() {
|
||||||
Cuboid cuboid = getCuboid();
|
StringBuilder sb = new StringBuilder();
|
||||||
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;
|
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,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,42 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
||||||
public class UserOverride {
|
public class UserOverride {
|
||||||
private String _username;
|
private String _username;
|
||||||
private int _plotMultiplier;
|
private int _plotMultiplier;
|
||||||
private int _maxPlots;
|
private int _maxPlots;
|
||||||
|
|
||||||
public String getUsername(){return _username;}
|
public String getUsername() {
|
||||||
public int getPlotMultiplier(){return _plotMultiplier;}
|
return _username;
|
||||||
public int getMaxPlots(){return _maxPlots;}
|
}
|
||||||
|
|
||||||
public UserOverride(String username, int plotMultiplier, int maxPlots){
|
public int getPlotMultiplier() {
|
||||||
_username = username;
|
return _plotMultiplier;
|
||||||
_plotMultiplier = plotMultiplier;
|
}
|
||||||
_maxPlots = maxPlots;
|
|
||||||
}
|
public int getMaxPlots() {
|
||||||
|
return _maxPlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserOverride(String username, int plotMultiplier, int maxPlots) {
|
||||||
|
_username = username;
|
||||||
|
_plotMultiplier = plotMultiplier;
|
||||||
|
_maxPlots = maxPlots;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,40 @@
|
|||||||
|
/*
|
||||||
|
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 com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
public class MetropolisCommand implements CommandExecutor {
|
public class MetropolisCommand implements CommandExecutor {
|
||||||
private MetropolisPlugin _plugin;
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisCommand(MetropolisPlugin plugin){
|
public MetropolisCommand(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) {
|
||||||
|
|
||||||
sender.sendMessage("Metropolis: version "+ _plugin.pdf.getVersion());
|
sender.sendMessage("Metropolis: version " + _plugin.pdf.getVersion());
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,47 @@
|
|||||||
|
/*
|
||||||
|
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 org.bukkit.Bukkit;
|
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 com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
public class MetropolisDebugGenerateTestHomesCommand implements CommandExecutor {
|
public class MetropolisDebugGenerateTestHomesCommand implements CommandExecutor {
|
||||||
|
|
||||||
private MetropolisPlugin _plugin;
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisDebugGenerateTestHomesCommand(MetropolisPlugin plugin) {
|
public MetropolisDebugGenerateTestHomesCommand(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
@Override
|
try {
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
int numHomes = Integer.parseInt(args[0]);
|
||||||
try{
|
for (int i = 1; i <= numHomes; i++) {
|
||||||
int numHomes = Integer.parseInt(args[0]);
|
_plugin.generateHome(String.format("test%d", i));
|
||||||
for(int i=1; i<= numHomes; i++){
|
}
|
||||||
_plugin.generateHome(Bukkit.getOfflinePlayer(String.format("test%d", i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}catch(NumberFormatException ex){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
public class MetropolisDebugGetMaterialCommand implements CommandExecutor {
|
|
||||||
|
|
||||||
private MetropolisPlugin _plugin;
|
|
||||||
|
|
||||||
public MetropolisDebugGetMaterialCommand(MetropolisPlugin plugin) {
|
|
||||||
_plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
|
||||||
try {
|
|
||||||
sender.sendMessage(String.format("Material.getMaterial(\"%s\") returns \"%s\"", args[0], Material.getMaterial(args[0])));
|
|
||||||
return true;
|
|
||||||
} catch(Exception ex) {
|
|
||||||
_plugin.getLogger().log(Level.ALL, "Error", ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
public class MetropolisDebugMatchMaterialCommand implements CommandExecutor {
|
|
||||||
|
|
||||||
private MetropolisPlugin _plugin;
|
|
||||||
|
|
||||||
public MetropolisDebugMatchMaterialCommand(MetropolisPlugin plugin) {
|
|
||||||
_plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
|
||||||
try {
|
|
||||||
sender.sendMessage(String.format("Material.getMaterial(\"%s\") returns \"%s\"", String.join(" ", args), Material.matchMaterial(args[0])));
|
|
||||||
return true;
|
|
||||||
} catch(Exception ex) {
|
|
||||||
_plugin.getLogger().log(Level.ALL, "Error", ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +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.commands;
|
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.sk89q.worldguard.protection.flags.Flags;
|
||||||
|
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
|
||||||
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.flags.DefaultFlag;
|
|
||||||
import com.sk89q.worldguard.protection.flags.StateFlag;
|
|
||||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
|
||||||
|
|
||||||
public class MetropolisFlagResetCommand implements CommandExecutor {
|
public class MetropolisFlagResetCommand implements CommandExecutor {
|
||||||
private MetropolisPlugin _plugin;
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisFlagResetCommand(MetropolisPlugin plugin){
|
public MetropolisFlagResetCommand(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
ProtectedRegion cityRegion = _plugin.regionManager.getRegion("City");
|
ProtectedRegion cityRegion = _plugin.regionManager.getRegion("City");
|
||||||
cityRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.LAVA_FLOW, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
||||||
cityRegion.setFlag(DefaultFlag.SNOW_FALL, StateFlag.State.DENY);
|
cityRegion.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
||||||
|
|
||||||
for(ProtectedRegion homeRegion: _plugin.regionManager.getRegions().values()){
|
for (ProtectedRegion homeRegion : _plugin.regionManager.getRegions().values()) {
|
||||||
if(homeRegion.getId().startsWith("h_")){
|
if (homeRegion.getId().startsWith("h_")) {
|
||||||
homeRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
homeRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||||
homeRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
homeRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||||
homeRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
homeRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||||
homeRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
homeRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||||
homeRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
homeRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||||
homeRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
homeRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||||
homeRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
homeRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
sender.sendMessage("Metropolis: flags have been reset");
|
||||||
}
|
|
||||||
|
|
||||||
sender.sendMessage("Metropolis: flags have been reset");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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){
|
public MetropolisHomeAcquire(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) {
|
||||||
if(!(sender instanceof Player)){
|
if (!(sender instanceof Player)) {
|
||||||
sender.sendMessage("This command can only be used by players");
|
sender.sendMessage("This command can only be used by players");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player player = (Player) sender;
|
Player player = (Player) sender;
|
||||||
|
|
||||||
if(_plugin.getNumPlots(player.getName()) >= _plugin.getMaxPlots(player.getName())){
|
if (_plugin.getNumPlots(player.getUniqueId()) >= _plugin.getMaxPlots(player)) {
|
||||||
sender.sendMessage("You cannot have any more plots");
|
sender.sendMessage("You cannot have any more plots");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_plugin.assignPlot(player);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return _plugin.acquireHome(player) != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +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
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
//get the player and region
|
// get the player and region
|
||||||
String targetUUIDString = null;
|
String playerName = "";
|
||||||
|
|
||||||
OfflinePlayer targetPlayer = null;
|
if (args.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if(args.length == 0){
|
if (args.length >= 1) {
|
||||||
return false;
|
playerName = args[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(args.length >= 1){
|
OfflinePlayer player = _plugin.getOfflinePlayer(playerName);
|
||||||
targetUUIDString = args[0];
|
if (player == null) {
|
||||||
targetPlayer = _plugin.getOfflinePlayer(targetUUIDString);
|
sender.sendMessage(
|
||||||
}
|
String.format("The requested player {%s}does not appear to exist.", playerName));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if(targetPlayer == null){
|
ProtectedRegion region =
|
||||||
sender.sendMessage(String.format("The requested player {%s}does not appear to exist.", targetUUIDString));
|
_plugin.getRegion(_plugin.getCurrentHomeRegionName(player.getUniqueId()));
|
||||||
return false;
|
if (region == null) {
|
||||||
}
|
sender.sendMessage(String.format("The player {%s} has no home to be evicted from."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ProtectedRegion region = _plugin.getRegion(String.format("h_%s", targetPlayer.getUniqueId().toString()));
|
// remove the player as owner and/or member of the region
|
||||||
if(region == null){
|
region.getMembers().removePlayer(player.getUniqueId());
|
||||||
sender.sendMessage(String.format("The player {%s} has no home to be evicted from."));
|
region.getOwners().removePlayer(player.getUniqueId());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//remove the player as owner and/or member of the region
|
// if the region has no owners delete the region
|
||||||
region.getMembers().removePlayer(targetPlayer.getName());//playerName);
|
if (region.getMembers().size() == 0 && region.getOwners().size() == 0) {
|
||||||
region.getOwners().removePlayer(targetPlayer.getName());
|
_plugin.removeRegion(region.getId());
|
||||||
|
}
|
||||||
|
|
||||||
//if the region has no owners delete the region
|
_plugin.saveRegions();
|
||||||
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(),
|
||||||
//?optionally regen the region
|
// "regen", new String[]{});
|
||||||
//_plugin.worldEdit.getCommand("regen").execute(_plugin.getServer().getConsoleSender(), "regen", new String[]{});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,52 @@
|
|||||||
|
/*
|
||||||
|
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 org.bukkit.Bukkit;
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
|
||||||
|
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;
|
|
||||||
|
|
||||||
public class MetropolisHomeGenerateCommand implements CommandExecutor {
|
public class MetropolisHomeGenerateCommand implements CommandExecutor {
|
||||||
|
|
||||||
private MetropolisPlugin _plugin;
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisHomeGenerateCommand(MetropolisPlugin plugin){
|
public MetropolisHomeGenerateCommand(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@Override
|
||||||
@Override
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
if (args.length < 1) {
|
||||||
if(args.length < 1){
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_plugin.generateHome(Bukkit.getOfflinePlayer(args[0]));
|
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]);
|
sender.sendMessage("[Metropolis] Home generated for " + args[0]);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +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){
|
public MetropolisHomeGoCommand(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;
|
Player player = null;
|
||||||
if(sender instanceof Player){
|
if (sender instanceof Player) {
|
||||||
player = (Player)sender;
|
player = (Player) sender;
|
||||||
}else{
|
|
||||||
sender.sendMessage("You must be a player");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerHome home = _plugin.getPlayerHome(player);
|
if (args.length >= 1) {
|
||||||
|
if (player.hasPermission("")) {
|
||||||
|
player = _plugin.getPlayer(args[0]);
|
||||||
|
|
||||||
Location loc = null;
|
if (player == null) {
|
||||||
Location bedSpawn = player.getBedSpawnLocation();
|
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(home.contains(bedSpawn)){
|
if (player == null) {
|
||||||
loc = bedSpawn;
|
sender.sendMessage(String.format("Unable to find player %s", args[0]));
|
||||||
}else {
|
return false;
|
||||||
loc = home.getViableSpawnLocation(_plugin.getWorld());
|
}
|
||||||
}
|
} else {
|
||||||
|
sender.sendMessage("You must be a player");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(loc != null){
|
PlayerHome home = _plugin.getPlayerHome(player);
|
||||||
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;
|
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 @@
|
|||||||
|
/*
|
||||||
|
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 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.majinnaibu.bukkitplugins.metropolis.Plot;
|
|
||||||
|
|
||||||
public class MetropolisHomeListCommand implements CommandExecutor {
|
public class MetropolisHomeListCommand implements CommandExecutor {
|
||||||
private MetropolisPlugin _plugin;
|
private MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisHomeListCommand(MetropolisPlugin plugin){
|
public MetropolisHomeListCommand(MetropolisPlugin plugin) {
|
||||||
_plugin = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@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){
|
int homeNumber;
|
||||||
player = (OfflinePlayer)sender;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if(args.length == 1){
|
OfflinePlayer target;
|
||||||
try{
|
if (args.length == 1) {
|
||||||
newHomeNumber = Integer.parseInt(args[0]);
|
if (!(sender instanceof Player player)) {
|
||||||
}catch(NumberFormatException ex){
|
sender.sendMessage("Console must specify a player name.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}else if(args.length >= 2){
|
target = player;
|
||||||
try{
|
} else {
|
||||||
newHomeNumber = Integer.parseInt(args[0]);
|
target = _plugin.getOfflinePlayer(args[1]);
|
||||||
}catch(NumberFormatException ex){
|
if (target == null) {
|
||||||
return false;
|
sender.sendMessage("No known player named " + args[1] + ".");
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player = _plugin.getOfflinePlayer(args[1]);
|
if (!_plugin.homeExists(target.getUniqueId(), homeNumber)) {
|
||||||
}else{
|
String targetName =
|
||||||
return false;
|
target.getName() == null ? target.getUniqueId().toString() : target.getName();
|
||||||
}
|
sender.sendMessage(targetName + " does not have home " + homeNumber + ".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if(player == null || !_plugin.homeExists(player.getUniqueId(), newHomeNumber)){
|
_plugin.setHome(target.getUniqueId(), homeNumber);
|
||||||
return false;
|
String targetName =
|
||||||
}
|
target.getName() == null ? target.getUniqueId().toString() : target.getName();
|
||||||
|
sender.sendMessage("Home " + homeNumber + " is now active for " + targetName + ".");
|
||||||
_plugin.setHome(player.getUniqueId(), newHomeNumber);
|
if (target instanceof Player targetPlayer && targetPlayer != sender) {
|
||||||
|
targetPlayer.sendMessage("Your active Metropolis home is now home " + homeNumber + ".");
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
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]");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if(sender instanceof Player){
|
Player player;
|
||||||
player = (Player) sender;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(args.length == 1 && player != null){
|
boolean canManagePlots = sender.hasPermission("metropolis.plot.go");
|
||||||
plot = _plugin.getPlot(args[0]);
|
if (player != sender && !canManagePlots) {
|
||||||
}else if(args.length >= 2){
|
sender.sendMessage("You do not have permission to teleport another player.");
|
||||||
player = _plugin.getPlayer(args[1]);
|
return false;
|
||||||
plot = _plugin.getPlot(args[0]);
|
}
|
||||||
}else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(plot == null || player == null){
|
var reservations = _plugin.getReservationsForPlayer(player.getUniqueId());
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String errorMessage = _plugin.teleportPlayerToPlot(player, plot);
|
if (player != sender && !canManagePlots) {
|
||||||
if(errorMessage != null){
|
sender.sendMessage("You do not have permission to teleport another player.");
|
||||||
sender.sendMessage(errorMessage);
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
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,67 +1,95 @@
|
|||||||
|
/*
|
||||||
|
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.Cuboid;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.sk89q.worldedit.IncompleteRegionException;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
|
||||||
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.Cuboid;
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
import com.sk89q.worldedit.bukkit.selections.Selection;
|
|
||||||
|
|
||||||
public class MetropolisPlotReserveCommand implements CommandExecutor {
|
public class MetropolisPlotReserveCommand implements CommandExecutor {
|
||||||
MetropolisPlugin _plugin;
|
MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public MetropolisPlotReserveCommand(MetropolisPlugin metropolisPlugin) {
|
public MetropolisPlotReserveCommand(MetropolisPlugin metropolisPlugin) {
|
||||||
_plugin = metropolisPlugin;
|
_plugin = metropolisPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
int minX = 0;
|
int minX = 0;
|
||||||
int minY = 0;
|
int minY = 0;
|
||||||
int minZ = 0;
|
int minZ = 0;
|
||||||
int maxX = 0;
|
int maxX = 0;
|
||||||
int maxY = 0;
|
int maxY = 0;
|
||||||
int maxZ = 0;
|
int maxZ = 0;
|
||||||
Cuboid cuboid = new Cuboid();
|
Cuboid cuboid = new Cuboid();
|
||||||
|
|
||||||
if(sender instanceof Player && args.length == 1){
|
if (sender instanceof Player && args.length == 1) {
|
||||||
Selection selection = _plugin.worldEdit.getSelection((Player) sender);
|
try {
|
||||||
cuboid = new Cuboid(selection);
|
Region selection =
|
||||||
}else if(args.length == 6){
|
_plugin
|
||||||
try{
|
.worldEdit
|
||||||
minX = Integer.parseInt(args[1]);
|
.getSession((Player) sender)
|
||||||
minY = Integer.parseInt(args[2]);
|
.getSelection(BukkitAdapter.adapt(_plugin.getWorld()));
|
||||||
minZ = Integer.parseInt(args[3]);
|
cuboid = new Cuboid(selection);
|
||||||
maxX = Integer.parseInt(args[4]);
|
} catch (IncompleteRegionException ex) {
|
||||||
maxY = Integer.parseInt(args[5]);
|
sender.sendMessage("Select a WorldEdit region first.");
|
||||||
maxZ = Integer.parseInt(args[6]);
|
return false;
|
||||||
}catch(NumberFormatException ex){
|
}
|
||||||
return false;
|
} else if (args.length == 7) {
|
||||||
}
|
try {
|
||||||
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
minX = Integer.parseInt(args[1]);
|
||||||
}else if(args.length == 4){
|
minY = Integer.parseInt(args[2]);
|
||||||
try{
|
minZ = Integer.parseInt(args[3]);
|
||||||
minX = Integer.parseInt(args[1]);
|
maxX = Integer.parseInt(args[4]);
|
||||||
minY = 0;
|
maxY = Integer.parseInt(args[5]);
|
||||||
minZ = Integer.parseInt(args[2]);
|
maxZ = Integer.parseInt(args[6]);
|
||||||
maxX = Integer.parseInt(args[3]);
|
} catch (NumberFormatException ex) {
|
||||||
maxY = _plugin.getWorld().getMaxHeight();
|
return false;
|
||||||
maxZ = Integer.parseInt(args[4]);
|
}
|
||||||
}catch(NumberFormatException ex){
|
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
return false;
|
} else if (args.length == 5) {
|
||||||
}
|
try {
|
||||||
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
minX = Integer.parseInt(args[1]);
|
||||||
}else{
|
minY = 0;
|
||||||
return false;
|
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];
|
String regionName = args[0];
|
||||||
|
|
||||||
_plugin.reserveCuboid(regionName, cuboid);
|
_plugin.reserveCuboid(regionName, cuboid);
|
||||||
|
sender.sendMessage("Reserved plot region " + regionName + ".");
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,38 @@
|
|||||||
|
/*
|
||||||
|
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 com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
|
|
||||||
public class DebugListener implements Listener {
|
public class DebugListener implements Listener {
|
||||||
MetropolisPlugin _plugin;
|
MetropolisPlugin _plugin;
|
||||||
|
|
||||||
public DebugListener(MetropolisPlugin plugin){
|
public DebugListener(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority=EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onEvent(PlayerInteractEvent event){
|
public void onEvent(PlayerInteractEvent event) {
|
||||||
//event.
|
// event.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,73 @@
|
|||||||
|
/*
|
||||||
|
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 com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
|
||||||
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
|
||||||
|
|
||||||
public class PlayerJoinListener implements Listener {
|
public class PlayerJoinListener implements Listener {
|
||||||
private MetropolisPlugin _plugin = null;
|
private MetropolisPlugin _plugin = null;
|
||||||
|
|
||||||
public PlayerJoinListener(MetropolisPlugin plugin){
|
public PlayerJoinListener(MetropolisPlugin plugin) {
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority=EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onPlayerJoin(PlayerJoinEvent event){
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
if(player == null){
|
if (player == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerHome home = _plugin.getPlayerHome(player);
|
PlayerHome home = _plugin.getPlayerHome(player);
|
||||||
if(home==null){MetropolisPlugin.log.info("home is null");}
|
if (home == null) {
|
||||||
else if(home.getCuboid() == null){MetropolisPlugin.log.info("home.getCuboid() is null");}
|
_plugin.getLogger().info("home is null");
|
||||||
else if(home.getCuboid().getVolume() == 0){MetropolisPlugin.log.info("home.getCuboid().getVolume() is 0");}
|
} 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){
|
if (home == null || home.getCuboid() == null || home.getCuboid().getVolume() == 0) {
|
||||||
MetropolisPlugin.log.info(String.format("Metropolis: Unable to get or create home for player %s", player.getName()));
|
_plugin
|
||||||
}else{
|
.getLogger()
|
||||||
Cuboid cuboid = home.getCuboid();
|
.info(
|
||||||
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()));
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
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.util;
|
||||||
|
|
||||||
|
public class Material {
|
||||||
|
public static final int signTypeId = 63;
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
//From evilmidget38 at https://gist.github.com/evilmidget38/a5c971d2f2b2c3b3fb37
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.util;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
public class NameFetcher implements Callable<Map<UUID, String>> {
|
|
||||||
private static final String PROFILE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
|
||||||
private final JSONParser jsonParser = new JSONParser();
|
|
||||||
private final List<UUID> uuids;
|
|
||||||
public NameFetcher(List<UUID> uuids) {
|
|
||||||
this.uuids = ImmutableList.copyOf(uuids);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<UUID, String> call() throws Exception {
|
|
||||||
Map<UUID, String> uuidStringMap = new HashMap<UUID, String>();
|
|
||||||
for (UUID uuid: uuids) {
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(PROFILE_URL+uuid.toString().replace("-", "")).openConnection();
|
|
||||||
JSONObject response = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
|
|
||||||
String name = (String) response.get("name");
|
|
||||||
if (name == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String cause = (String) response.get("cause");
|
|
||||||
String errorMessage = (String) response.get("errorMessage");
|
|
||||||
if (cause != null && cause.length() > 0) {
|
|
||||||
throw new IllegalStateException(errorMessage);
|
|
||||||
}
|
|
||||||
uuidStringMap.put(uuid, name);
|
|
||||||
}
|
|
||||||
return uuidStringMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
//From evilmidget38 at https://gist.github.com/evilmidget38/26d70114b834f71fb3b4
|
|
||||||
package com.majinnaibu.bukkitplugins.metropolis.util;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.json.simple.JSONArray;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
public class UUIDFetcher implements Callable<Map<String, UUID>> {
|
|
||||||
private static final double PROFILES_PER_REQUEST = 100;
|
|
||||||
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
|
|
||||||
private final JSONParser jsonParser = new JSONParser();
|
|
||||||
private final List<String> names;
|
|
||||||
private final boolean rateLimiting;
|
|
||||||
|
|
||||||
public UUIDFetcher(List<String> names, boolean rateLimiting) {
|
|
||||||
this.names = ImmutableList.copyOf(names);
|
|
||||||
this.rateLimiting = rateLimiting;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUIDFetcher(List<String> names) {
|
|
||||||
this(names, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, UUID> call() throws Exception {
|
|
||||||
Map<String, UUID> uuidMap = new HashMap<String, UUID>();
|
|
||||||
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
|
|
||||||
for (int i = 0; i < requests; i++) {
|
|
||||||
HttpURLConnection connection = createConnection();
|
|
||||||
String body = JSONArray.toJSONString(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
|
|
||||||
writeBody(connection, body);
|
|
||||||
JSONArray array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
|
|
||||||
for (Object profile : array) {
|
|
||||||
JSONObject jsonProfile = (JSONObject) profile;
|
|
||||||
String id = (String) jsonProfile.get("id");
|
|
||||||
String name = (String) jsonProfile.get("name");
|
|
||||||
UUID uuid = UUIDFetcher.getUUID(id);
|
|
||||||
uuidMap.put(name, uuid);
|
|
||||||
}
|
|
||||||
if (rateLimiting && i != requests - 1) {
|
|
||||||
Thread.sleep(100L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uuidMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writeBody(HttpURLConnection connection, String body) throws Exception {
|
|
||||||
OutputStream stream = connection.getOutputStream();
|
|
||||||
stream.write(body.getBytes());
|
|
||||||
stream.flush();
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpURLConnection createConnection() throws Exception {
|
|
||||||
URL url = new URL(PROFILE_URL);
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
connection.setRequestMethod("POST");
|
|
||||||
connection.setRequestProperty("Content-Type", "application/json");
|
|
||||||
connection.setUseCaches(false);
|
|
||||||
connection.setDoInput(true);
|
|
||||||
connection.setDoOutput(true);
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UUID getUUID(String id) {
|
|
||||||
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +id.substring(20, 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] toBytes(UUID uuid) {
|
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
|
|
||||||
byteBuffer.putLong(uuid.getMostSignificantBits());
|
|
||||||
byteBuffer.putLong(uuid.getLeastSignificantBits());
|
|
||||||
return byteBuffer.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UUID fromBytes(byte[] array) {
|
|
||||||
if (array.length != 16) {
|
|
||||||
throw new IllegalArgumentException("Illegal byte array length: " + array.length);
|
|
||||||
}
|
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
|
|
||||||
long mostSignificant = byteBuffer.getLong();
|
|
||||||
long leastSignificant = byteBuffer.getLong();
|
|
||||||
return new UUID(mostSignificant, leastSignificant);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UUID getUUIDOf(String name) throws Exception {
|
|
||||||
return new UUIDFetcher(Arrays.asList(name)).call().get(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
plot:
|
plot:
|
||||||
sizeX: 32
|
sizeX: 32
|
||||||
sizeY: 256
|
sizeY: 256
|
||||||
|
sizeZ: 32
|
||||||
|
offsetX: 0
|
||||||
|
offsetY: 0
|
||||||
|
offsetZ: 0
|
||||||
|
multiplier: 1
|
||||||
|
maxPerPlayer: 1
|
||||||
|
initial: 1
|
||||||
floor:
|
floor:
|
||||||
generate: true
|
generate: true
|
||||||
clearSpaceAbove: 66
|
clearSpaceAbove: 66
|
||||||
offsetZ: 0
|
material: GRASS_BLOCK
|
||||||
supports:
|
supports:
|
||||||
generate: true
|
generate: true
|
||||||
material: STONE
|
material: STONE
|
||||||
@@ -21,7 +28,7 @@ road:
|
|||||||
spawn:
|
spawn:
|
||||||
generate: true
|
generate: true
|
||||||
setAsWorldSpawn: true
|
setAsWorldSpawn: true
|
||||||
material: COBBLESTONE
|
material: GRASS_BLOCK
|
||||||
wall:
|
wall:
|
||||||
generate: true
|
generate: true
|
||||||
material: BEDROCK
|
material: BEDROCK
|
||||||
|
|||||||
@@ -1,19 +1,48 @@
|
|||||||
name: Metropolis
|
name: Metropolis
|
||||||
main: com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin
|
main: com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin
|
||||||
version: 1.0
|
api-version: '26.2'
|
||||||
depend: [WorldGuard]
|
depend: [WorldGuard, WorldEdit]
|
||||||
softdepend: [WorldEdit,CommandBook]
|
softdepend: [CommandBook]
|
||||||
|
version: '${version}'
|
||||||
database: false
|
database: false
|
||||||
commands:
|
commands:
|
||||||
metropolis:
|
metropolis:
|
||||||
description: This command displays the current metropolis version.
|
description: This command displays the current metropolis version.
|
||||||
usage: /metropolis
|
usage: /metropolis
|
||||||
metropolis-home-generate:
|
metropolis-home-generate:
|
||||||
description: This command generates a home for a user as if they'd just logged in.
|
description: This command generates a home for a user as if they'd just logged in.
|
||||||
|
permission: metropolis.home.generate
|
||||||
|
usage: /metropolis-home-generate <playername>
|
||||||
|
metropolis-home-list:
|
||||||
|
description: This lists the regions managed by Metropolis
|
||||||
|
permission: metropolis.home.list
|
||||||
|
usage: /metropolis-home-list
|
||||||
|
metropolis-home-evict:
|
||||||
|
description: This unassigns a player's current home.
|
||||||
|
permission: metropolis.home.evict
|
||||||
|
usage: /metropolis-home-evict <playerName>
|
||||||
|
metropolis-home-move:
|
||||||
|
permission: metropolis.home.move
|
||||||
|
usage: /metropolis-home-move <newHomeNumber> [player]
|
||||||
|
metropolis-home-go:
|
||||||
|
description: Teleports the user or another player to his home.
|
||||||
|
permission: metropolis.home.go
|
||||||
|
usage: /metropolis-home-go
|
||||||
|
metropolis-home-acquire:
|
||||||
|
description: Acquires an available home plot.
|
||||||
|
usage: /metropolis-home-acquire
|
||||||
|
metropolis-flag-reset:
|
||||||
|
description: This command resets the WorldGuard flags for all managed regions (city and h_*).
|
||||||
|
permission: metropolis.flag.reset
|
||||||
|
usage: /metropolis-flag-reset
|
||||||
|
metropolis-plot-reserve:
|
||||||
|
description: This command reserves a plot so it won't be assigned as a home.
|
||||||
|
permission: metropolis.plot.reserve
|
||||||
|
usage: /metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>
|
||||||
|
metropolis-plot-go:
|
||||||
|
description: Teleports the sender or an authorized target to a reservation.
|
||||||
|
usage: /metropolis-plot-go [playerName] or /metropolis-plot-go <reservationName> [playerName]
|
||||||
|
metropolis-debug-generatetesthomes:
|
||||||
|
description: This command is ignored on non-debug builds.
|
||||||
permission: metropolis.debug
|
permission: metropolis.debug
|
||||||
usage: /metropolis-home-generate <playername>
|
usage: /metropolis-debug-gentesthomes
|
||||||
metropolis-home-list:
|
|
||||||
description: This lists the regions managed by Metropolis
|
|
||||||
permission: metropolis.home.list
|
|
||||||
usage: /metropolis-home-list
|
|
||||||
metropolis-home-evict:
|
|
||||||
@@ -0,0 +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;
|
||||||
|
|
||||||
|
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 com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class CuboidTest {
|
||||||
|
@Test
|
||||||
|
void convertsWorldEditBoundsToCuboidCoordinates() {
|
||||||
|
Cuboid cuboid = new Cuboid(BlockVector3.at(1, 2, 3), BlockVector3.at(4, 5, 6));
|
||||||
|
|
||||||
|
assertEquals(1, cuboid.getMinX());
|
||||||
|
assertEquals(2, cuboid.getMinY());
|
||||||
|
assertEquals(3, cuboid.getMinZ());
|
||||||
|
assertEquals(4, cuboid.getMaxX());
|
||||||
|
assertEquals(5, cuboid.getMaxY());
|
||||||
|
assertEquals(6, cuboid.getMaxZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void comparesVectorsByXThenZThenYAndHandlesNulls() {
|
||||||
|
assertEquals(-1, Cuboid.compareBlockVectors(null, BlockVector3.ZERO));
|
||||||
|
assertEquals(1, Cuboid.compareBlockVectors(BlockVector3.ZERO, 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, 1), BlockVector3.at(1, 0, 2)) < 0);
|
||||||
|
assertTrue(Cuboid.compareBlockVectors(BlockVector3.at(1, 1, 2), BlockVector3.at(1, 2, 2)) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void treatsTouchingBoundsAsIntersecting() {
|
||||||
|
Cuboid first = new Cuboid(0, 0, 0, 10, 10, 10);
|
||||||
|
Cuboid touching = new Cuboid(10, 10, 10, 20, 20, 20);
|
||||||
|
Cuboid separate = new Cuboid(11, 0, 0, 20, 10, 10);
|
||||||
|
|
||||||
|
assertTrue(first.intersects(touching));
|
||||||
|
assertFalse(first.intersects(separate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void containsPointsOnBoundsButRejectsPointsOutside() {
|
||||||
|
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, 4, 5, 6)));
|
||||||
|
assertFalse(cuboid.contains(new Location(null, 5, 5, 6)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
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.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
class CurrentHomesStoreTest {
|
||||||
|
@TempDir File temporaryDirectory;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void roundTripsHomesByUuid() throws Exception {
|
||||||
|
File file = new File(temporaryDirectory, "currentHomes.yml");
|
||||||
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
Map<UUID, Integer> expected = Map.of(playerId, 3);
|
||||||
|
|
||||||
|
CurrentHomesStore.save(file, expected);
|
||||||
|
|
||||||
|
assertEquals(expected, CurrentHomesStore.load(file, ignored -> null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolvesLegacyPlayerNameKeys() throws Exception {
|
||||||
|
File file = new File(temporaryDirectory, "currentHomes.yml");
|
||||||
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
yaml.set("OldPlayerName", 2);
|
||||||
|
yaml.save(file);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Map.of(playerId, 2),
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.UUID;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class PlayerHomeTest {
|
||||||
|
@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());
|
||||||
|
assertEquals("PlayerName", home.getPlayerName());
|
||||||
|
assertEquals(2, home.getNumber());
|
||||||
|
assertEquals("h_2_" + playerId, home.getRegionName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
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.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class PlayerLookupTest {
|
||||||
|
@Test
|
||||||
|
void matchesOnlinePlayerNamesCaseInsensitivelyButNotByPrefix() {
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
when(player.getName()).thenReturn("PlayerName");
|
||||||
|
|
||||||
|
assertSame(player, PlayerLookup.findOnline("playername", List.of(player)));
|
||||||
|
assertNull(PlayerLookup.findOnline("Player", List.of(player)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,78 @@
|
|||||||
|
/*
|
||||||
|
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.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
|
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MetropolisHomeAcquireTest {
|
||||||
|
@Test
|
||||||
|
void assignsAPlotWhenThePlayerIsBelowTheirLimit() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
|
when(plugin.getNumPlots(playerId)).thenReturn(0);
|
||||||
|
when(plugin.getMaxPlots(player)).thenReturn(1);
|
||||||
|
when(plugin.acquireHome(player)).thenReturn(mock(PlayerHome.class));
|
||||||
|
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
||||||
|
|
||||||
|
assertTrue(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
||||||
|
|
||||||
|
verify(plugin).acquireHome(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doesNotAssignAPlotAfterThePlayerReachesTheirLimit() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
UUID playerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
|
||||||
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
|
when(plugin.getNumPlots(playerId)).thenReturn(1);
|
||||||
|
when(plugin.getMaxPlots(player)).thenReturn(1);
|
||||||
|
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
||||||
|
|
||||||
|
assertFalse(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +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.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.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
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.CommandSender;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
class MetropolisPlotReserveCommandTest {
|
||||||
|
@Test
|
||||||
|
void reservesCoordinatesFromAllSixArguments() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
CommandSender sender = mock(CommandSender.class);
|
||||||
|
Command command = mock(Command.class);
|
||||||
|
MetropolisPlotReserveCommand executor = new MetropolisPlotReserveCommand(plugin);
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
executor.onCommand(
|
||||||
|
sender, command, "reserve", new String[] {"north", "1", "2", "3", "4", "5", "6"}));
|
||||||
|
|
||||||
|
ArgumentCaptor<Cuboid> cuboid = ArgumentCaptor.forClass(Cuboid.class);
|
||||||
|
verify(plugin).reserveCuboid(eq("north"), cuboid.capture());
|
||||||
|
assertEquals(1, cuboid.getValue().getMinX());
|
||||||
|
assertEquals(2, cuboid.getValue().getMinY());
|
||||||
|
assertEquals(3, cuboid.getValue().getMinZ());
|
||||||
|
assertEquals(4, cuboid.getValue().getMaxX());
|
||||||
|
assertEquals(5, cuboid.getValue().getMaxY());
|
||||||
|
assertEquals(6, cuboid.getValue().getMaxZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsMalformedCoordinateArgumentsWithoutCallingPlugin() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
CommandSender sender = mock(CommandSender.class);
|
||||||
|
Command command = mock(Command.class);
|
||||||
|
MetropolisPlotReserveCommand executor = new MetropolisPlotReserveCommand(plugin);
|
||||||
|
|
||||||
|
assertFalse(
|
||||||
|
executor.onCommand(
|
||||||
|
sender, command, "reserve", new String[] {"north", "1", "two", "3", "4", "5", "6"}));
|
||||||
|
verifyNoInteractions(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.contains;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||||
|
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||||
|
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 {
|
||||||
|
@Test
|
||||||
|
void tellsJoiningPlayerTheirHomeBounds() {
|
||||||
|
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
PlayerJoinEvent event = mock(PlayerJoinEvent.class);
|
||||||
|
when(event.getPlayer()).thenReturn(player);
|
||||||
|
when(player.getName()).thenReturn("PlayerName");
|
||||||
|
when(plugin.getPlayerHome(player))
|
||||||
|
.thenReturn(
|
||||||
|
new PlayerHome(
|
||||||
|
UUID.fromString("123e4567-e89b-12d3-a456-426614174000"),
|
||||||
|
"PlayerName",
|
||||||
|
1,
|
||||||
|
BlockVector3.at(1, 2, 3),
|
||||||
|
BlockVector3.at(4, 5, 6)));
|
||||||
|
|
||||||
|
new PlayerJoinListener(plugin).onPlayerJoin(event);
|
||||||
|
|
||||||
|
verify(player).sendMessage(contains("home is between (1, 2, 3) and (4, 5, 6)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
103
tools/backup-server.sh
Executable file
103
tools/backup-server.sh
Executable file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Backs up plugins, config, worlds, and settings from MINECRAFT_SERVER_PATH
|
||||||
|
# to MINECRAFT_BACKUP_PATH/backup-<timestamp>[-label].tar.bz2.
|
||||||
|
#
|
||||||
|
# Usage: backup-server.sh [-f|--force] [label]
|
||||||
|
# -f Back up even if the server is running (worlds may be inconsistent).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$TOOLS_DIR/.." && pwd)"
|
||||||
|
if [[ ! -r "$PROJECT_ROOT/.env" ]]; then
|
||||||
|
echo "Error: $PROJECT_ROOT/.env is missing or unreadable. Copy .env.example to .env and set its values." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
load_project_env() {
|
||||||
|
local env_line env_key env_value first_char last_char
|
||||||
|
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
||||||
|
env_line="${env_line%$'\r'}"
|
||||||
|
[[ "$env_line" =~ ^[[:space:]]*(#|$) ]] && continue
|
||||||
|
[[ "$env_line" == *=* ]] || continue
|
||||||
|
|
||||||
|
env_key="${env_line%%=*}"
|
||||||
|
env_value="${env_line#*=}"
|
||||||
|
env_key="${env_key#"${env_key%%[![:space:]]*}"}"
|
||||||
|
env_key="${env_key%"${env_key##*[![:space:]]}"}"
|
||||||
|
[[ "$env_key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||||
|
env_value="${env_value#"${env_value%%[![:space:]]*}"}"
|
||||||
|
env_value="${env_value%"${env_value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
if (( ${#env_value} >= 2 )); then
|
||||||
|
first_char="${env_value:0:1}"
|
||||||
|
last_char="${env_value:$((${#env_value} - 1)):1}"
|
||||||
|
if [[ "$first_char" == "$last_char" && ( "$first_char" == '"' || "$first_char" == "'" ) ]]; then
|
||||||
|
env_value="${env_value:1:$((${#env_value} - 2))}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${!env_key:-}" ]]; then
|
||||||
|
export "$env_key=$env_value"
|
||||||
|
fi
|
||||||
|
done < "$PROJECT_ROOT/.env"
|
||||||
|
}
|
||||||
|
load_project_env
|
||||||
|
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH:-MCServer}"
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH/#\~/$HOME}"
|
||||||
|
[[ "$MINECRAFT_SERVER_PATH" == /* ]] || MINECRAFT_SERVER_PATH="$PROJECT_ROOT/$MINECRAFT_SERVER_PATH"
|
||||||
|
MINECRAFT_BACKUP_PATH="${MINECRAFT_BACKUP_PATH:-backups}"
|
||||||
|
MINECRAFT_BACKUP_PATH="${MINECRAFT_BACKUP_PATH/#\~/$HOME}"
|
||||||
|
[[ "$MINECRAFT_BACKUP_PATH" == /* ]] || MINECRAFT_BACKUP_PATH="$PROJECT_ROOT/$MINECRAFT_BACKUP_PATH"
|
||||||
|
|
||||||
|
FORCE=false
|
||||||
|
LABEL=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-f|--force) FORCE=true ;;
|
||||||
|
-h|--help) sed -n '2,7p' "$0"; exit 0 ;;
|
||||||
|
-*) echo "Unknown option: $arg" >&2; exit 1 ;;
|
||||||
|
*) LABEL="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -d "$MINECRAFT_SERVER_PATH" ]] || { echo "Error: $MINECRAFT_SERVER_PATH does not exist." >&2; exit 1; }
|
||||||
|
|
||||||
|
if pgrep -f "java.*paper-.*\.jar" >/dev/null && ! $FORCE; then
|
||||||
|
echo "Error: the server is running. Stop it first (tools/stop-server.sh) or pass --force." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$MINECRAFT_SERVER_PATH"
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
ITEMS=()
|
||||||
|
[[ -d plugins ]] && ITEMS+=(plugins)
|
||||||
|
[[ -d config ]] && ITEMS+=(config)
|
||||||
|
for dir in */; do
|
||||||
|
dir="${dir%/}"
|
||||||
|
[[ -f "$dir/level.dat" ]] && ITEMS+=("$dir")
|
||||||
|
done
|
||||||
|
for f in *.yml *.yaml *.json *.properties eula.txt; do
|
||||||
|
ITEMS+=("$f")
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#ITEMS[@]} == 0 )); then
|
||||||
|
echo "Error: nothing to back up in $MINECRAFT_SERVER_PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Keep labels filename-safe.
|
||||||
|
LABEL="${LABEL//[^A-Za-z0-9._-]/_}"
|
||||||
|
NAME="backup-$(date +%Y%m%d-%H%M%S)${LABEL:+-$LABEL}.tar.bz2"
|
||||||
|
mkdir -p "$MINECRAFT_BACKUP_PATH"
|
||||||
|
ARCHIVE="$MINECRAFT_BACKUP_PATH/$NAME"
|
||||||
|
|
||||||
|
echo "Backing up: ${ITEMS[*]}"
|
||||||
|
trap 'rm -f "$ARCHIVE.part"' EXIT
|
||||||
|
tar --exclude 'plugins/.paper-remapped' --exclude '*/session.lock' \
|
||||||
|
-cjf "$ARCHIVE.part" "${ITEMS[@]}"
|
||||||
|
mv "$ARCHIVE.part" "$ARCHIVE"
|
||||||
|
trap - EXIT
|
||||||
|
|
||||||
|
echo "Created $ARCHIVE ($(du -h "$ARCHIVE" | awk '{print $1}'))"
|
||||||
10
tools/build-plugin.sh
Executable file
10
tools/build-plugin.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Find project root (one directory above this script)
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
exec ./gradlew build "$@"
|
||||||
62
tools/deploy-plugin.sh
Executable file
62
tools/deploy-plugin.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
if [[ ! -r "$PROJECT_ROOT/.env" ]]; then
|
||||||
|
echo "Error: $PROJECT_ROOT/.env is missing or unreadable. Copy .env.example to .env and set its values." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
load_project_env() {
|
||||||
|
local env_line env_key env_value first_char last_char
|
||||||
|
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
||||||
|
env_line="${env_line%$'\r'}"
|
||||||
|
[[ "$env_line" =~ ^[[:space:]]*(#|$) ]] && continue
|
||||||
|
[[ "$env_line" == *=* ]] || continue
|
||||||
|
|
||||||
|
env_key="${env_line%%=*}"
|
||||||
|
env_value="${env_line#*=}"
|
||||||
|
env_key="${env_key#"${env_key%%[![:space:]]*}"}"
|
||||||
|
env_key="${env_key%"${env_key##*[![:space:]]}"}"
|
||||||
|
[[ "$env_key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||||
|
env_value="${env_value#"${env_value%%[![:space:]]*}"}"
|
||||||
|
env_value="${env_value%"${env_value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
if (( ${#env_value} >= 2 )); then
|
||||||
|
first_char="${env_value:0:1}"
|
||||||
|
last_char="${env_value:$((${#env_value} - 1)):1}"
|
||||||
|
if [[ "$first_char" == "$last_char" && ( "$first_char" == '"' || "$first_char" == "'" ) ]]; then
|
||||||
|
env_value="${env_value:1:$((${#env_value} - 2))}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${!env_key:-}" ]]; then
|
||||||
|
export "$env_key=$env_value"
|
||||||
|
fi
|
||||||
|
done < "$PROJECT_ROOT/.env"
|
||||||
|
}
|
||||||
|
load_project_env
|
||||||
|
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH:-MCServer}"
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH/#\~/$HOME}"
|
||||||
|
[[ "$MINECRAFT_SERVER_PATH" == /* ]] || MINECRAFT_SERVER_PATH="$PROJECT_ROOT/$MINECRAFT_SERVER_PATH"
|
||||||
|
|
||||||
|
if [[ -z "$MINECRAFT_SERVER_PATH" ]]; then
|
||||||
|
echo "Error: MINECRAFT_SERVER_PATH environment variable is not set."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLUGIN_JAR=$(ls -t "$PROJECT_ROOT"/build/libs/*.jar 2>/dev/null | head -n1 || true)
|
||||||
|
if [[ ! -f "$PLUGIN_JAR" ]]; then
|
||||||
|
echo "Error: No plugin jar found in $PROJECT_ROOT/build/libs. Build the plugin first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if find "$PROJECT_ROOT/src/main/java" "$PROJECT_ROOT/src/main/resources" -type f -newer "$PLUGIN_JAR" | grep -q .; then
|
||||||
|
echo "Warning: The built plugin jar is older than some source files. Consider rebuilding."
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$MINECRAFT_SERVER_PATH/plugins"
|
||||||
|
cp "$PLUGIN_JAR" "$MINECRAFT_SERVER_PATH/plugins/"
|
||||||
|
echo "Deployed $PLUGIN_JAR to $MINECRAFT_SERVER_PATH/plugins/"
|
||||||
45
tools/publish-local.sh
Executable file
45
tools/publish-local.sh
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
# Use the same local JDK bootstrap pattern as the Gradle projects.
|
||||||
|
export GRADLE_USER_HOME="${GRADLE_USER_HOME:-$PROJECT_ROOT/.gradle/user-home}"
|
||||||
|
mkdir -p "$GRADLE_USER_HOME/jdks"
|
||||||
|
|
||||||
|
ensure_java_25() {
|
||||||
|
local version="25"
|
||||||
|
local os arch archive java_bin actual_home
|
||||||
|
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) os="mac" ;;
|
||||||
|
Linux) os="linux" ;;
|
||||||
|
*) echo "Unsupported OS: $(uname -s)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$(uname -m)" in
|
||||||
|
arm64|aarch64) arch="aarch64" ;;
|
||||||
|
x86_64|amd64) arch="x64" ;;
|
||||||
|
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
java_bin=$(find "$GRADLE_USER_HOME/jdks" -path '*/bin/java' -type f 2>/dev/null | head -n 1 || true)
|
||||||
|
if [[ -z "$java_bin" ]]; then
|
||||||
|
echo "Downloading Temurin JDK $version into $GRADLE_USER_HOME/jdks..."
|
||||||
|
archive="$GRADLE_USER_HOME/jdks/temurin-$version.tar.gz"
|
||||||
|
curl -fsSL "https://api.adoptium.net/v3/binary/latest/$version/ga/${os}/${arch}/jdk/hotspot/normal/eclipse" -o "$archive"
|
||||||
|
tar -xzf "$archive" -C "$GRADLE_USER_HOME/jdks"
|
||||||
|
rm -f "$archive"
|
||||||
|
java_bin=$(find "$GRADLE_USER_HOME/jdks" -path '*/bin/java' -type f 2>/dev/null | head -n 1 || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$java_bin" ]] || { echo "Error: could not locate installed JDK 25." >&2; exit 1; }
|
||||||
|
actual_home="$(dirname "$(dirname "$java_bin")")"
|
||||||
|
export JAVA_HOME="$actual_home"
|
||||||
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_java_25
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
exec ./gradlew publishToMavenLocal "$@"
|
||||||
161
tools/publish-modrinth.sh
Executable file
161
tools/publish-modrinth.sh
Executable file
@@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
if [[ ! -r "$PROJECT_ROOT/.env" ]]; then
|
||||||
|
echo "Error: $PROJECT_ROOT/.env is missing or unreadable. Copy .env.example to .env and set its values." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
load_project_env() {
|
||||||
|
local env_line env_key env_value first_char last_char
|
||||||
|
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
||||||
|
env_line="${env_line%$'\r'}"
|
||||||
|
[[ "$env_line" =~ ^[[:space:]]*(#|$) ]] && continue
|
||||||
|
[[ "$env_line" == *=* ]] || continue
|
||||||
|
|
||||||
|
env_key="${env_line%%=*}"
|
||||||
|
env_value="${env_line#*=}"
|
||||||
|
env_key="${env_key#"${env_key%%[![:space:]]*}"}"
|
||||||
|
env_key="${env_key%"${env_key##*[![:space:]]}"}"
|
||||||
|
[[ "$env_key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||||
|
env_value="${env_value#"${env_value%%[![:space:]]*}"}"
|
||||||
|
env_value="${env_value%"${env_value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
if (( ${#env_value} >= 2 )); then
|
||||||
|
first_char="${env_value:0:1}"
|
||||||
|
last_char="${env_value:$((${#env_value} - 1)):1}"
|
||||||
|
if [[ "$first_char" == "$last_char" && ( "$first_char" == '"' || "$first_char" == "'" ) ]]; then
|
||||||
|
env_value="${env_value:1:$((${#env_value} - 2))}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${!env_key:-}" ]]; then
|
||||||
|
export "$env_key=$env_value"
|
||||||
|
fi
|
||||||
|
done < "$PROJECT_ROOT/.env"
|
||||||
|
}
|
||||||
|
load_project_env
|
||||||
|
|
||||||
|
MODRINTH_TOKEN="${MODRINTH_TOKEN:-}"
|
||||||
|
MODRINTH_PROJECT_ID="${MODRINTH_PROJECT_ID:-metropolis-builder}"
|
||||||
|
MODRINTH_VERSION="${MODRINTH_VERSION:-${VERSION:-}}"
|
||||||
|
if [[ -z "$MODRINTH_VERSION" ]]; then
|
||||||
|
MODRINTH_VERSION=$(cd "$PROJECT_ROOT" && ./gradlew -q properties --property version | sed -n 's/^version: //p' | head -n1)
|
||||||
|
fi
|
||||||
|
generate_changelog() {
|
||||||
|
local current_tag previous_tag range commits
|
||||||
|
current_tag=$(git -C "$PROJECT_ROOT" describe --tags --exact-match HEAD 2>/dev/null || true)
|
||||||
|
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_VERSION_TYPE="${MODRINTH_VERSION_TYPE:-}"
|
||||||
|
MODRINTH_FILE="${MODRINTH_FILE:-}"
|
||||||
|
MODRINTH_DEPENDENCIES="${MODRINTH_DEPENDENCIES:-}"
|
||||||
|
|
||||||
|
if [[ -z "$MODRINTH_VERSION" ]]; then
|
||||||
|
echo "Error: could not determine the project version from Gradle." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -z "$MODRINTH_VERSION_TYPE" ]]; then
|
||||||
|
if [[ "$MODRINTH_VERSION" == *-* ]]; then
|
||||||
|
MODRINTH_VERSION_TYPE="beta"
|
||||||
|
else
|
||||||
|
MODRINTH_VERSION_TYPE="release"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
case "$MODRINTH_VERSION_TYPE" in
|
||||||
|
release|beta|alpha) ;;
|
||||||
|
*) echo "Error: MODRINTH_VERSION_TYPE must be release, beta, or alpha." >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
MODRINTH_VERSION_NAME="Metropolis $MODRINTH_VERSION"
|
||||||
|
|
||||||
|
if [[ -n "$MODRINTH_FILE" && "$MODRINTH_FILE" != /* ]]; then
|
||||||
|
MODRINTH_FILE="$PROJECT_ROOT/$MODRINTH_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$MODRINTH_TOKEN" ]]; then
|
||||||
|
echo "Error: MODRINTH_TOKEN is not set in $PROJECT_ROOT/.env" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$MODRINTH_FILE" ]]; then
|
||||||
|
MODRINTH_FILE="$PROJECT_ROOT/build/libs/Metropolis-$MODRINTH_VERSION.jar"
|
||||||
|
fi
|
||||||
|
if [[ -z "$MODRINTH_FILE" || ! -f "$MODRINTH_FILE" ]]; then
|
||||||
|
echo "Error: no jar found for Modrinth upload. Build the project first or set MODRINTH_FILE." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v unzip >/dev/null; then
|
||||||
|
echo "Error: 'unzip' is required to verify the plugin version in the jar." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
JAR_VERSION=$(unzip -p "$MODRINTH_FILE" plugin.yml 2>/dev/null | sed -n 's/^version: //p' | tr -d "'\"" | head -n1 || true)
|
||||||
|
if [[ -z "$JAR_VERSION" ]]; then
|
||||||
|
echo "Error: could not read the plugin version from $MODRINTH_FILE." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$JAR_VERSION" != "$MODRINTH_VERSION" ]]; then
|
||||||
|
echo "Error: jar version $JAR_VERSION does not match Gradle version $MODRINTH_VERSION. Rebuild the plugin and retry." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
csv_to_json_array() {
|
||||||
|
jq -cn --arg csv "$1" '$csv | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0))'
|
||||||
|
}
|
||||||
|
MODRINTH_GAME_VERSIONS_JSON=$(csv_to_json_array "$MODRINTH_GAME_VERSIONS")
|
||||||
|
MODRINTH_LOADERS_JSON=$(csv_to_json_array "$MODRINTH_LOADERS")
|
||||||
|
|
||||||
|
if [[ -n "$MODRINTH_DEPENDENCIES" ]]; then
|
||||||
|
if ! MODRINTH_DEPENDENCIES_JSON=$(jq -ce 'if type == "array" then . else error("dependencies must be a JSON array") end' <<<"$MODRINTH_DEPENDENCIES"); then
|
||||||
|
echo "Error: MODRINTH_DEPENDENCIES must be a JSON array." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
MODRINTH_DEPENDENCIES_JSON='[]'
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODRINTH_DATA=$(jq -cn \
|
||||||
|
--arg project_id "$MODRINTH_PROJECT_ID" \
|
||||||
|
--arg name "$MODRINTH_VERSION_NAME" \
|
||||||
|
--arg version_number "$MODRINTH_VERSION" \
|
||||||
|
--arg version_type "$MODRINTH_VERSION_TYPE" \
|
||||||
|
--arg changelog "$MODRINTH_CHANGELOG" \
|
||||||
|
--argjson featured false \
|
||||||
|
--argjson dependencies "$MODRINTH_DEPENDENCIES_JSON" \
|
||||||
|
--argjson game_versions "$MODRINTH_GAME_VERSIONS_JSON" \
|
||||||
|
--argjson loaders "$MODRINTH_LOADERS_JSON" \
|
||||||
|
'{project_id: $project_id, name: $name, version_number: $version_number, version_type: $version_type, featured: $featured, changelog: $changelog, dependencies: $dependencies, game_versions: $game_versions, loaders: $loaders, file_parts: ["file"], primary_file: "file"}')
|
||||||
|
|
||||||
|
CURL_ARGS=(
|
||||||
|
-X POST "https://api.modrinth.com/v2/version"
|
||||||
|
-H "Authorization: $MODRINTH_TOKEN"
|
||||||
|
-F "data=$MODRINTH_DATA;type=application/json"
|
||||||
|
-F "file=@$MODRINTH_FILE"
|
||||||
|
)
|
||||||
|
|
||||||
|
HTTP_RESPONSE=$(curl -sS -w $'\n%{http_code}' "${CURL_ARGS[@]}")
|
||||||
|
HTTP_STATUS="${HTTP_RESPONSE##*$'\n'}"
|
||||||
|
HTTP_BODY="${HTTP_RESPONSE%$'\n'*}"
|
||||||
|
if [[ "$HTTP_STATUS" != 2?? ]]; then
|
||||||
|
echo "Error: Modrinth API returned HTTP $HTTP_STATUS:" >&2
|
||||||
|
if ! printf '%s\n' "$HTTP_BODY" | jq -r '.description // .error // .' >&2; then
|
||||||
|
printf '%s\n' "$HTTP_BODY" >&2
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$HTTP_BODY" | jq .
|
||||||
107
tools/restore-server.sh
Executable file
107
tools/restore-server.sh
Executable file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Restores a backup made by backup-server.sh into MINECRAFT_SERVER_PATH.
|
||||||
|
# Each top-level item in the archive (plugins, config, worlds, settings) is
|
||||||
|
# replaced; a "pre-restore" backup of the current state is taken first.
|
||||||
|
#
|
||||||
|
# Usage: restore-server.sh [-y|--yes] [--no-backup] [backup-file|name]
|
||||||
|
# Defaults to the newest backup in MINECRAFT_BACKUP_PATH.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$TOOLS_DIR/.." && pwd)"
|
||||||
|
if [[ ! -r "$PROJECT_ROOT/.env" ]]; then
|
||||||
|
echo "Error: $PROJECT_ROOT/.env is missing or unreadable. Copy .env.example to .env and set its values." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
load_project_env() {
|
||||||
|
local env_line env_key env_value first_char last_char
|
||||||
|
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
||||||
|
env_line="${env_line%$'\r'}"
|
||||||
|
[[ "$env_line" =~ ^[[:space:]]*(#|$) ]] && continue
|
||||||
|
[[ "$env_line" == *=* ]] || continue
|
||||||
|
|
||||||
|
env_key="${env_line%%=*}"
|
||||||
|
env_value="${env_line#*=}"
|
||||||
|
env_key="${env_key#"${env_key%%[![:space:]]*}"}"
|
||||||
|
env_key="${env_key%"${env_key##*[![:space:]]}"}"
|
||||||
|
[[ "$env_key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||||
|
env_value="${env_value#"${env_value%%[![:space:]]*}"}"
|
||||||
|
env_value="${env_value%"${env_value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
if (( ${#env_value} >= 2 )); then
|
||||||
|
first_char="${env_value:0:1}"
|
||||||
|
last_char="${env_value:$((${#env_value} - 1)):1}"
|
||||||
|
if [[ "$first_char" == "$last_char" && ( "$first_char" == '"' || "$first_char" == "'" ) ]]; then
|
||||||
|
env_value="${env_value:1:$((${#env_value} - 2))}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${!env_key:-}" ]]; then
|
||||||
|
export "$env_key=$env_value"
|
||||||
|
fi
|
||||||
|
done < "$PROJECT_ROOT/.env"
|
||||||
|
}
|
||||||
|
load_project_env
|
||||||
|
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH:-MCServer}"
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH/#\~/$HOME}"
|
||||||
|
[[ "$MINECRAFT_SERVER_PATH" == /* ]] || MINECRAFT_SERVER_PATH="$PROJECT_ROOT/$MINECRAFT_SERVER_PATH"
|
||||||
|
MINECRAFT_BACKUP_PATH="${MINECRAFT_BACKUP_PATH:-backups}"
|
||||||
|
MINECRAFT_BACKUP_PATH="${MINECRAFT_BACKUP_PATH/#\~/$HOME}"
|
||||||
|
[[ "$MINECRAFT_BACKUP_PATH" == /* ]] || MINECRAFT_BACKUP_PATH="$PROJECT_ROOT/$MINECRAFT_BACKUP_PATH"
|
||||||
|
|
||||||
|
ASSUME_YES=false
|
||||||
|
SAFETY_BACKUP=true
|
||||||
|
ARCHIVE=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-y|--yes) ASSUME_YES=true ;;
|
||||||
|
--no-backup) SAFETY_BACKUP=false ;;
|
||||||
|
-h|--help) sed -n '2,8p' "$0"; exit 0 ;;
|
||||||
|
-*) echo "Unknown option: $arg" >&2; exit 1 ;;
|
||||||
|
*) ARCHIVE="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$ARCHIVE" ]]; then
|
||||||
|
ARCHIVE=$(ls -t "$MINECRAFT_BACKUP_PATH"/backup-*.tar.bz2 2>/dev/null | head -n1 || true)
|
||||||
|
[[ -n "$ARCHIVE" ]] || { echo "Error: no backups found in $MINECRAFT_BACKUP_PATH." >&2; exit 1; }
|
||||||
|
elif [[ ! -f "$ARCHIVE" && -f "$MINECRAFT_BACKUP_PATH/$ARCHIVE" ]]; then
|
||||||
|
ARCHIVE="$MINECRAFT_BACKUP_PATH/$ARCHIVE"
|
||||||
|
fi
|
||||||
|
[[ -f "$ARCHIVE" ]] || { echo "Error: backup not found: $ARCHIVE" >&2; exit 1; }
|
||||||
|
ARCHIVE="$(cd "$(dirname "$ARCHIVE")" && pwd)/$(basename "$ARCHIVE")"
|
||||||
|
|
||||||
|
if pgrep -f "java.*paper-.*\.jar" >/dev/null; then
|
||||||
|
echo "Error: the server is running. Stop it first (tools/stop-server.sh)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENTRIES=$(tar -tjf "$ARCHIVE")
|
||||||
|
if grep -qE '^/|(^|/)\.\.(/|$)' <<<"$ENTRIES"; then
|
||||||
|
echo "Error: archive contains absolute or '..' paths; refusing to restore." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TOP_LEVEL=$(sed -E 's#^\./##; s#/.*##' <<<"$ENTRIES" | { grep -vE '^\.?$' || true; } | sort -u)
|
||||||
|
[[ -n "$TOP_LEVEL" ]] || { echo "Error: backup is empty." >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "Restore $(basename "$ARCHIVE") into $MINECRAFT_SERVER_PATH"
|
||||||
|
echo "These items will be replaced:"
|
||||||
|
sed 's/^/ /' <<<"$TOP_LEVEL"
|
||||||
|
if ! $ASSUME_YES; then
|
||||||
|
read -r -p "Continue? [y/N] " reply
|
||||||
|
[[ "$reply" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$MINECRAFT_SERVER_PATH"
|
||||||
|
if $SAFETY_BACKUP && [[ -n "$(ls -A "$MINECRAFT_SERVER_PATH")" ]]; then
|
||||||
|
"$TOOLS_DIR/backup-server.sh" pre-restore
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r item; do
|
||||||
|
rm -rf "${MINECRAFT_SERVER_PATH:?}/$item"
|
||||||
|
done <<<"$TOP_LEVEL"
|
||||||
|
|
||||||
|
tar -xjf "$ARCHIVE" -C "$MINECRAFT_SERVER_PATH"
|
||||||
|
echo "Restored $(basename "$ARCHIVE")."
|
||||||
131
tools/start-server.sh
Executable file
131
tools/start-server.sh
Executable file
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Downloads (if missing) and runs a Paper server via the Fill v3 API.
|
||||||
|
# Server settings come from the project .env file.
|
||||||
|
#
|
||||||
|
# Env vars:
|
||||||
|
# JAVA_OPTS Extra JVM args (default: -Xms2G -Xmx2G)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$TOOLS_DIR/.." && pwd)"
|
||||||
|
if [[ ! -r "$PROJECT_ROOT/.env" ]]; then
|
||||||
|
echo "Error: $PROJECT_ROOT/.env is missing or unreadable. Copy .env.example to .env and set its values." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
load_project_env() {
|
||||||
|
local env_line env_key env_value first_char last_char
|
||||||
|
while IFS= read -r env_line || [[ -n "$env_line" ]]; do
|
||||||
|
env_line="${env_line%$'\r'}"
|
||||||
|
[[ "$env_line" =~ ^[[:space:]]*(#|$) ]] && continue
|
||||||
|
[[ "$env_line" == *=* ]] || continue
|
||||||
|
|
||||||
|
env_key="${env_line%%=*}"
|
||||||
|
env_value="${env_line#*=}"
|
||||||
|
env_key="${env_key#"${env_key%%[![:space:]]*}"}"
|
||||||
|
env_key="${env_key%"${env_key##*[![:space:]]}"}"
|
||||||
|
[[ "$env_key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||||
|
env_value="${env_value#"${env_value%%[![:space:]]*}"}"
|
||||||
|
env_value="${env_value%"${env_value##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
if (( ${#env_value} >= 2 )); then
|
||||||
|
first_char="${env_value:0:1}"
|
||||||
|
last_char="${env_value:$((${#env_value} - 1)):1}"
|
||||||
|
if [[ "$first_char" == "$last_char" && ( "$first_char" == '"' || "$first_char" == "'" ) ]]; then
|
||||||
|
env_value="${env_value:1:$((${#env_value} - 2))}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${!env_key:-}" ]]; then
|
||||||
|
export "$env_key=$env_value"
|
||||||
|
fi
|
||||||
|
done < "$PROJECT_ROOT/.env"
|
||||||
|
}
|
||||||
|
load_project_env
|
||||||
|
|
||||||
|
PAPER_VERSION="${PAPER_VERSION:-}"
|
||||||
|
PAPER_BUILD="${PAPER_BUILD:-}"
|
||||||
|
MINECRAFT_SERVER_JAR="${MINECRAFT_SERVER_JAR:-}"
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH:-MCServer}"
|
||||||
|
MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH/#\~/$HOME}"
|
||||||
|
[[ "$MINECRAFT_SERVER_PATH" == /* ]] || MINECRAFT_SERVER_PATH="$PROJECT_ROOT/$MINECRAFT_SERVER_PATH"
|
||||||
|
|
||||||
|
[[ -n "$PAPER_VERSION" ]] || { echo "Error: PAPER_VERSION is not set (see .env)." >&2; exit 1; }
|
||||||
|
|
||||||
|
PAPER_PROJECT="paper"
|
||||||
|
JAVA_OPTS="${JAVA_OPTS:--Xms2G -Xmx2G}"
|
||||||
|
FILL_API="https://fill.papermc.io/v3/projects/${PAPER_PROJECT}/versions/${PAPER_VERSION}"
|
||||||
|
# Fill requires a non-generic User-Agent with contact info.
|
||||||
|
USER_AGENT="${PAPER_USER_AGENT:-majinnaibu-plugin-dev/1.0 (https://github.com/majinnaibu)}"
|
||||||
|
|
||||||
|
for cmd in curl jq java shasum; do
|
||||||
|
command -v "$cmd" >/dev/null || { echo "Error: '$cmd' is required." >&2; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
api_get() {
|
||||||
|
curl -fsSL -H "User-Agent: $USER_AGENT" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$MINECRAFT_SERVER_PATH"
|
||||||
|
cd "$MINECRAFT_SERVER_PATH"
|
||||||
|
|
||||||
|
JAR_BUILD="${MINECRAFT_SERVER_JAR:-}"
|
||||||
|
JAR_BUILD="${JAR_BUILD#paper-"$PAPER_VERSION"-}"; JAR_BUILD="${JAR_BUILD%.jar}"
|
||||||
|
if [[ -z "$PAPER_BUILD" && "$JAR_BUILD" =~ ^[0-9]+$ ]]; then
|
||||||
|
PAPER_BUILD="$JAR_BUILD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$PAPER_BUILD" ]]; then
|
||||||
|
echo "Resolving latest stable build for Paper $PAPER_VERSION..."
|
||||||
|
PAPER_BUILD=$(api_get "$FILL_API/builds" \
|
||||||
|
| jq -r 'map(select(.channel == "STABLE")) | max_by(.id) | .id // empty') || true
|
||||||
|
if [[ -z "$PAPER_BUILD" ]]; then
|
||||||
|
# Offline or no stable build: fall back to the newest local jar for this version.
|
||||||
|
for f in paper-"$PAPER_VERSION"-*.jar; do
|
||||||
|
b="${f#paper-"$PAPER_VERSION"-}"; b="${b%.jar}"
|
||||||
|
[[ "$b" =~ ^[0-9]+$ ]] && (( b > ${PAPER_BUILD:-0} )) && PAPER_BUILD="$b"
|
||||||
|
done
|
||||||
|
[[ -n "$PAPER_BUILD" ]] || { echo "Error: no stable build found for $PAPER_VERSION." >&2; exit 1; }
|
||||||
|
echo "Could not resolve from API; using local build $PAPER_BUILD."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXPECTED_JAR="paper-${PAPER_VERSION}-${PAPER_BUILD}.jar"
|
||||||
|
if [[ -n "${MINECRAFT_SERVER_JAR:-}" && "$MINECRAFT_SERVER_JAR" != "$EXPECTED_JAR" ]]; then
|
||||||
|
echo "Error: MINECRAFT_SERVER_JAR=$MINECRAFT_SERVER_JAR does not match PAPER_VERSION/PAPER_BUILD ($EXPECTED_JAR)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
MINECRAFT_SERVER_JAR="$EXPECTED_JAR"
|
||||||
|
|
||||||
|
if [[ ! -f "$MINECRAFT_SERVER_JAR" ]]; then
|
||||||
|
echo "Downloading $MINECRAFT_SERVER_JAR..."
|
||||||
|
BUILD_JSON=$(api_get "$FILL_API/builds/$PAPER_BUILD")
|
||||||
|
DOWNLOAD_URL=$(jq -r '.downloads."server:default".url // empty' <<<"$BUILD_JSON")
|
||||||
|
EXPECTED_SHA=$(jq -r '.downloads."server:default".checksums.sha256 // empty' <<<"$BUILD_JSON")
|
||||||
|
[[ -n "$DOWNLOAD_URL" ]] || { echo "Error: no download for $PAPER_VERSION build $PAPER_BUILD." >&2; exit 1; }
|
||||||
|
|
||||||
|
TMP_JAR="$MINECRAFT_SERVER_JAR.part"
|
||||||
|
trap 'rm -f "$TMP_JAR"' EXIT
|
||||||
|
curl -fL --progress-bar -H "User-Agent: $USER_AGENT" -o "$TMP_JAR" "$DOWNLOAD_URL"
|
||||||
|
|
||||||
|
ACTUAL_SHA=$(shasum -a 256 "$TMP_JAR" | awk '{print $1}')
|
||||||
|
if [[ -n "$EXPECTED_SHA" && "$ACTUAL_SHA" != "$EXPECTED_SHA" ]]; then
|
||||||
|
echo "Error: checksum mismatch (expected $EXPECTED_SHA, got $ACTUAL_SHA)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mv "$TMP_JAR" "$MINECRAFT_SERVER_JAR"
|
||||||
|
trap - EXIT
|
||||||
|
fi
|
||||||
|
|
||||||
|
export MINECRAFT_SERVER_PATH MINECRAFT_SERVER_JAR
|
||||||
|
|
||||||
|
"$TOOLS_DIR/stop-server.sh"
|
||||||
|
|
||||||
|
if ! grep -qs '^eula=true' eula.txt; then
|
||||||
|
echo "Note: accept the EULA by setting eula=true in $MINECRAFT_SERVER_PATH/eula.txt (server will exit until then)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting $MINECRAFT_SERVER_JAR in $MINECRAFT_SERVER_PATH..."
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
exec java $JAVA_OPTS -jar "$MINECRAFT_SERVER_JAR" --nogui
|
||||||
|
|
||||||
34
tools/stop-server.sh
Executable file
34
tools/stop-server.sh
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Stops any running Paper server (java process running a paper-*.jar).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STOP_TIMEOUT="${STOP_TIMEOUT:-60}"
|
||||||
|
|
||||||
|
SERVER_PIDS=$(pgrep -f "java.*paper-.*\.jar" || true)
|
||||||
|
|
||||||
|
if [[ -z "$SERVER_PIDS" ]]; then
|
||||||
|
echo "Minecraft server is not running."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for pid in $SERVER_PIDS; do
|
||||||
|
echo "Stopping Minecraft server (PID: $pid)..."
|
||||||
|
# SIGTERM lets Paper run its shutdown hook and save worlds.
|
||||||
|
kill "$pid"
|
||||||
|
done
|
||||||
|
|
||||||
|
for ((i = 0; i < STOP_TIMEOUT; i++)); do
|
||||||
|
remaining=""
|
||||||
|
for pid in $SERVER_PIDS; do
|
||||||
|
kill -0 "$pid" 2>/dev/null && remaining="$remaining $pid"
|
||||||
|
done
|
||||||
|
if [[ -z "$remaining" ]]; then
|
||||||
|
echo "Minecraft server stopped."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Server did not stop within ${STOP_TIMEOUT}s; forcing:$remaining"
|
||||||
|
kill -9 $remaining
|
||||||
Reference in New Issue
Block a user