Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
24
.env.example
Normal file
24
.env.example
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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
|
||||
MODRINTH_CHANGELOG=
|
||||
MODRINTH_GAME_VERSIONS=26.2
|
||||
MODRINTH_LOADERS=paper
|
||||
MODRINTH_VERSION_TYPE=
|
||||
MODRINTH_DEPENDENCIES=
|
||||
MODRINTH_FILE=
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
/server
|
||||
/Plugin.jardesc
|
||||
/Plugin-Release.jardesc
|
||||
/lib
|
||||
/target
|
||||
.gradle/
|
||||
build/
|
||||
target/
|
||||
.worktrees/
|
||||
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 @@
|
||||
#Sat Mar 03 21:02:20 PST 2012
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
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.6
|
||||
@@ -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.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "metropolis",
|
||||
"title": "Metropolis",
|
||||
"description": "Legacy Bukkit plugin that automatically creates protected home plots and roads as players join a Minecraft server.",
|
||||
"description": "Paper plugin that automatically creates protected home plots and connecting roads as players join a Minecraft server.",
|
||||
"docId": "Metropolis",
|
||||
"image": {
|
||||
"url": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.",
|
||||
@@ -12,9 +12,7 @@
|
||||
"backgroundClassName": "bg-surface"
|
||||
},
|
||||
"url": "/projects/metropolis",
|
||||
"tags": [
|
||||
"open-source"
|
||||
],
|
||||
"tags": ["open-source"],
|
||||
"source": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/headhunter45/Metropolis"
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
# Metropolis
|
||||
|
||||
Metropolis is a legacy Bukkit plugin that automatically expands a protected Minecraft city as players join the server. Each player receives a home plot surrounded by roads and positioned beside the existing city, with WorldGuard regions protecting the city and individual homes.
|
||||
Metropolis is a Paper plugin that expands a protected Minecraft city as players join. It generates home plots and connecting roads, uses WorldGuard to protect the city and homes, and supports reserved plots and home teleportation.
|
||||
|
||||
## Details
|
||||
## Platform
|
||||
|
||||
- Platform: Bukkit/Minecraft server plugin
|
||||
- Language: Java
|
||||
- Bukkit API: 1.7.9-R0.2
|
||||
- Dependencies: WorldGuard 5.9 and WorldEdit 5.6.3
|
||||
- Features: automatic home plots, road and plot generation, WorldGuard region management, reserved plots, home teleportation, and configurable plot and road materials
|
||||
- Paper 26.2, Java 25+
|
||||
- WorldGuard 7.0.19 and WorldEdit 7.4.5
|
||||
- Built with Gradle 9.8 and Git-sensitive semantic versioning
|
||||
- Configuration: `plugins/Metropolis/config.yml`
|
||||
- Source: https://github.com/headhunter45/Metropolis
|
||||
- Unit tests run without a server; live integration tests use the separate Gradle `integrationTest` task.
|
||||
- License: GNU General Public License v3
|
||||
|
||||
## Image 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.
|
||||
98
README.md
98
README.md
@@ -1,47 +1,67 @@
|
||||
Metropolis generates a home for each user connecting to your world with roads between them. As new users connect the city expands.
|
||||
# Metropolis
|
||||
|
||||
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.
|
||||
## Requirements
|
||||
|
||||
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.
|
||||
- Paper 26.2, with Java 25 or newer
|
||||
- WorldGuard 7.0.19
|
||||
- WorldEdit 7.4.5
|
||||
|
||||
After creation these regions can be modified as normal via WorldGuard commands.
|
||||
WorldGuard and WorldEdit are required plugin dependencies. Install them alongside Metropolis in the server's `plugins` directory.
|
||||
|
||||
INSTALL:
|
||||
To install just drop the jar into your plugins folder. All necessarry data will be created on first launch.
|
||||
## Build and Test
|
||||
|
||||
CONFIG:
|
||||
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.
|
||||
Build the plugin and run the server-free unit tests:
|
||||
|
||||
Changelog:
|
||||
v0.5
|
||||
Added example docs
|
||||
Added Commands to plugin.yml to move and evict homes
|
||||
Added floor and road supports to config file and generation
|
||||
v0.4.6
|
||||
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.
|
||||
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.
|
||||
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
|
||||
v0.4.4
|
||||
Added a welcome message telling players where their home is.
|
||||
Switched from PlayerLoginEvent to PlayerJoinEvent
|
||||
v0.4.3
|
||||
Removed call to saveconfig on plugin unload.
|
||||
v0.4.2
|
||||
Made city region refresh on server restart.
|
||||
v0.4.1
|
||||
v0.4
|
||||
v0.3
|
||||
Added command to generate a home for a user that is not currently logged in.
|
||||
v0.2.1
|
||||
Made config file save on load and disable.
|
||||
v0.2
|
||||
Added configuration options.
|
||||
```sh
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
v0.1
|
||||
Initial Release
|
||||
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.
|
||||
|
||||
Run the opt-in live-server tests against a running Paper server with WorldGuard, WorldEdit, and RCON enabled:
|
||||
|
||||
```sh
|
||||
METROPOLIS_RCON_HOST=127.0.0.1 \
|
||||
METROPOLIS_RCON_PORT=25575 \
|
||||
METROPOLIS_RCON_PASSWORD=... \
|
||||
METROPOLIS_SERVER_DIR=/path/to/server \
|
||||
./gradlew integrationTest
|
||||
```
|
||||
|
||||
`integrationTest` is separate from the normal `test` and `build` tasks. Keep RCON bound to a trusted interface and do not commit its password.
|
||||
|
||||
## Server Scripts
|
||||
|
||||
Copy `.env.example` to `.env` and configure the Paper version/build and server paths. The scripts read `.env` as key/value data.
|
||||
|
||||
- `tools/build-plugin.sh` builds and tests the plugin.
|
||||
- `tools/deploy-plugin.sh` copies the Gradle-built jar to the configured server.
|
||||
- `tools/start-server.sh` downloads the configured Paper build if needed and starts it.
|
||||
- `tools/backup-server.sh` and `tools/restore-server.sh` back up or restore the configured server.
|
||||
- `tools/publish-local.sh` publishes the artifact to the local Maven repository.
|
||||
- `tools/publish-modrinth.sh` uploads a built jar after checking its version.
|
||||
|
||||
## Configuration
|
||||
|
||||
The default configuration is in `src/main/resources/config.yml` and is copied to `plugins/Metropolis/config.yml` on first startup. Materials use Bukkit names; the previous numeric IDs for stone, grass, cobblestone, and bedrock are still accepted when loading a configuration.
|
||||
|
||||
Player home ownership and selected-home data use UUIDs. Existing name-keyed `currentHomes.yml` entries and name-based WorldGuard home regions are recognized during migration.
|
||||
|
||||
## Commands
|
||||
|
||||
- `/metropolis` displays the plugin version.
|
||||
- `/metropolis-home-acquire` acquires an available home plot.
|
||||
- `/metropolis-home-generate <player>` generates a home for a known online or cached offline player.
|
||||
- `/metropolis-home-list` lists Metropolis plots.
|
||||
- `/metropolis-home-go [player]` teleports to a home.
|
||||
- `/metropolis-home-move <home-number> [player]` changes a player's selected home.
|
||||
- `/metropolis-home-evict <player>` removes a player's home ownership.
|
||||
- `/metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>` reserves coordinates; a player may instead provide a WorldEdit selection.
|
||||
- `/metropolis-plot-go <plot-name> [player]` teleports to a plot.
|
||||
- `/metropolis-flag-reset` reapplies Metropolis protection flags.
|
||||
|
||||
## License
|
||||
|
||||
GNU General Public License v3.
|
||||
|
||||
119
TODO.md
Normal file
119
TODO.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Modernize Metropolis Plugin: Implementation Tasks
|
||||
|
||||
| ID | Status | Title |
|
||||
|:-------:|:--------:|:------|
|
||||
| MET-001 | Done | Initialize Gradle in the project root, add the Gradle wrapper, and create the project build file. |
|
||||
| MET-002 | Done | Set Gradle project metadata and configure a Java toolchain compatible with the target modern Paper version. |
|
||||
| MET-003 | Done | Configure repositories and compatible PaperMC, WorldEdit, WorldGuard, and other required or optional dependencies; add build plugins such as Shadow only if needed. |
|
||||
| MET-004 | Done | Move or verify plugin.yml under src/main/resources, configure Gradle resource processing, and update its Paper api-version, dependencies, commands, permissions, and required fields. |
|
||||
| MET-005 | Done | Add JUnit 5 test infrastructure and keep the default Gradle `test` task server-free. Use an in-memory Paper harness such as MockBukkit for Bukkit/Paper behavior and mocks or test doubles for WorldEdit, WorldGuard, and narrow API boundaries where appropriate. |
|
||||
| MET-006 | Done | Build the initial Gradle project and run its tests; resolve build setup issues before removing the existing Maven build. |
|
||||
| MET-007 | Done | Update .gitignore for Gradle and remove obsolete Maven and Eclipse project files after the Gradle build succeeds. |
|
||||
| MET-008 | Done | Analyze Java sources for deprecated or removed Bukkit/Paper APIs and legacy patterns, including logging, event registration, configuration, player lookup, and player-keyed storage. |
|
||||
| MET-009 | Done | Review custom scripts and resources for required updates to the Gradle build and modern Paper server workflow. |
|
||||
| MET-010 | Done | Refactor plugin logging to use JavaPlugin.getLogger(). |
|
||||
| MET-011 | Done | Refactor event listeners and registration to use Listener, @EventHandler, and the modern plugin manager API. |
|
||||
| MET-012 | Done | Replace deprecated configuration APIs with the modern Bukkit/Paper configuration API, including load, save, and reload behavior. |
|
||||
| MET-013 | Done | Replace Player-keyed storage with UUID-based storage wherever player identity is persisted or tracked. |
|
||||
| MET-014 | Done | Review player lookup logic and use exact or otherwise explicit name-matching behavior where appropriate. |
|
||||
| MET-015 | Done | Refactor remaining code that uses deprecated or removed Bukkit, Spigot, or Paper APIs so it is compatible with the target Paper API. |
|
||||
| MET-016 | Done | Add fast unit tests for plot and home allocation, reservation validation, player lookup and UUID-based ownership, configuration persistence, command validation, and event behavior. Keep domain rules independent of a running Minecraft server and mock external plugin boundaries as needed. |
|
||||
| MET-017 | Done | Add a separate Gradle `integrationTest` source set/task for tests requiring an actual Paper server and compatible WorldGuard/WorldEdit plugins. Verify plugin loading, command registration and dispatch, region-backed reservations and home allocation, and persistence across a server restart. Run it only when explicitly requested; keep it out of the normal `test` and `build` tasks. |
|
||||
| MET-018 | Done | Build the plugin with Gradle and use the provided server scripts to smoke-test it on modern Paper with its required plugins installed. |
|
||||
| MET-019 | Done | Address bugs and incompatibilities found by unit tests, integration tests, or modern Paper server testing. |
|
||||
| MET-020 | Done | Adopt the git-sensitive semantic versioning Gradle plugin used by MobScores and ScoreKeeper, using the existing 0.5-SNAPSHOT version as the migration baseline; verify version/tag behavior and enable the Gradle configuration cache when compatible. |
|
||||
| MET-021 | Done | Update README.md, CONTRIBUTING.md, other documentation, and project scripts with the Gradle build, test, versioning, and modern Paper server workflow. |
|
||||
| MET-022 | Planning | (Optional) Add new features, quality-of-life improvements, or CI configuration after the modernization and documentation tasks are complete. |
|
||||
| GIT-009 | Planning | i have three reserve plots and one just got overwritten when a new player joined. |
|
||||
| GIT-008 | Ready | Implement multiple plot sizes by granting multiple plots around each other.
|
||||
| GIT-007 | Ready | Allow buying and selling of homes via an ecnoomy plugin. |
|
||||
| GIT-006 | Ready | Add levels support. |
|
||||
| GIT-005 | Planning | Add the possibility to create multiple plot homes in shapes. |
|
||||
| GIT-004 | Ready | Add a command to request home allocation. |
|
||||
| GIT-003 | Ready | Add support for multiple homes. |
|
||||
| GIT-002 | Ready | Add a command to teleport to a reservation you are part of. |
|
||||
| GIT-001 | Ready | Add a command to move to a different home. |
|
||||
|
||||
# GIT-001 - Add a command to move to a different home.
|
||||
**Status:** Ready
|
||||
**Descriptioon:**
|
||||
|
||||
/metropolis-home-move [playerName]
|
||||
The target player will be the callee or if specified [playerName].
|
||||
This command should set the target player's home to the plot defined by .
|
||||
|
||||
# GIT-002 - Add a command to teleport to a reservation you are part of.
|
||||
**Status:** Ready
|
||||
**Description:**
|
||||
|
||||
/metropolis-plot-go [playerName]
|
||||
The target player should be [playerName] if specified and otherwise should be the callee.
|
||||
The reservation to teleport to is
|
||||
|
||||
### Comments
|
||||
|
||||
Or better yet just use uhome or create your own, integrated that records the home location at the height set in the configuration file, but on the road next to the plot. Using existing plugins to insert into SQL say uhome, would allow privatization and minimal work on your end.
|
||||
|
||||
# GIT-003 - Add support for multiple homes
|
||||
|
||||
**Status:** Ready
|
||||
**Description:**
|
||||
|
||||
The max number of homes per player should be defined by a permission such as
|
||||
metropolis.maxhomes: 2
|
||||
|
||||
A config option should be added to determine when the homes are added with at least the following options
|
||||
|
||||
Allocate all homes that aren't allocated on login
|
||||
Allocate at most 1 home per login
|
||||
Only allocate the first home on login, and require a command to allocate additional homes.
|
||||
|
||||
# GIT-004 - Add a command to request home allocation.
|
||||
**Status:** Ready
|
||||
**Description:**
|
||||
|
||||
Add a command that users can type to get their first or subsequent homes.
|
||||
Add a config option to not generate any homes automatically, but to require this command to generate a user's first home.
|
||||
|
||||
This could be used so that homes won't be generated until a user has read the server rules which would include telling them to type the command towards the end.
|
||||
|
||||
# GIT-005 - Add the possibility to create multiple plot homes in shapes.
|
||||
**Status:** Planning
|
||||
**Description:**
|
||||
|
||||
Add the possibility to create multiple plot homes in shapes
|
||||
|
||||
|
||||
# GIT-006 - Add levels support.
|
||||
**Status:** Ready
|
||||
**Description:**
|
||||
|
||||
Allow generating multilevel cities with stairs or interchanges to go between levels (streets are level but avenues are ramps.
|
||||
|
||||
# GIT-007 - Allow buying and selling of homes via an ecnoomy plugin.
|
||||
**Status:** Ready
|
||||
**Description:**
|
||||
|
||||
Allow buying and selling of homes via an ecnoomy plugin. Include decent real estate support including wtb and wts listings as well as an auction facility.
|
||||
|
||||
This should also support not giving free plots to users with none. This way one would have to go out and mine to earn money which could be spent to purchase a plot.
|
||||
|
||||
# GIT-008 - Implement multiple plot sizes by granting multiple plots around each other.
|
||||
**Status:** Ready
|
||||
**Description:**
|
||||
|
||||
a= user a
|
||||
x= empty plot
|
||||
s= spawn
|
||||
|
||||
user a get's a small plot which is defined as 1 plot
|
||||
a x x
|
||||
x s x
|
||||
x x x
|
||||
|
||||
user b gets a large plot which is defined as 4 plots
|
||||
x x b b x
|
||||
x a b b x
|
||||
x x s x x
|
||||
x x x x x
|
||||
x x x x x
|
||||
90
build.gradle
Normal file
90
build.gradle
Normal file
@@ -0,0 +1,90 @@
|
||||
plugins {
|
||||
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('')
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
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%
|
||||
94
pom.xml
94
pom.xml
@@ -1,94 +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>0.5-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>
|
||||
-->
|
||||
<repository>
|
||||
<id>sk89q</id>
|
||||
<url>http://mvn2.sk89q.com/repo</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>bukkit-repo</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public</url>
|
||||
</repository>
|
||||
|
||||
<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>
|
||||
</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,124 @@
|
||||
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,16 +1,11 @@
|
||||
package com.majinnaibu.bukkitplugins.metropolis;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import com.sk89q.worldedit.bukkit.selections.Selection;
|
||||
|
||||
@Embeddable
|
||||
public class Cuboid implements Comparable<Cuboid> {
|
||||
private int id;
|
||||
|
||||
@@ -39,13 +34,13 @@ public class Cuboid implements Comparable<Cuboid> {
|
||||
this.maxZ = maxZ;
|
||||
}
|
||||
|
||||
public Cuboid(BlockVector min, BlockVector max) {
|
||||
this.minX = min.getBlockX();
|
||||
this.minY = min.getBlockY();
|
||||
this.minZ = min.getBlockZ();
|
||||
this.maxX = max.getBlockX();
|
||||
this.maxY = max.getBlockY();
|
||||
this.maxZ = max.getBlockZ();
|
||||
public Cuboid(BlockVector3 min, BlockVector3 max) {
|
||||
this.minX = min.x();
|
||||
this.minY = min.y();
|
||||
this.minZ = min.z();
|
||||
this.maxX = max.x();
|
||||
this.maxY = max.y();
|
||||
this.maxZ = max.z();
|
||||
}
|
||||
|
||||
public Cuboid() {
|
||||
@@ -57,79 +52,73 @@ public class Cuboid implements Comparable<Cuboid> {
|
||||
this.maxZ = 0;
|
||||
}
|
||||
|
||||
public Cuboid(Selection selection) {
|
||||
this.minX = selection.getMinimumPoint().getBlockX();
|
||||
this.minY = selection.getMinimumPoint().getBlockY();
|
||||
this.minZ = selection.getMinimumPoint().getBlockZ();
|
||||
this.maxX = selection.getMaximumPoint().getBlockX();
|
||||
this.maxY = selection.getMaximumPoint().getBlockY();
|
||||
this.maxZ = selection.getMaximumPoint().getBlockZ();
|
||||
public Cuboid(Region selection) {
|
||||
this.minX = selection.getMinimumPoint().x();
|
||||
this.minY = selection.getMinimumPoint().y();
|
||||
this.minZ = selection.getMinimumPoint().z();
|
||||
this.maxX = selection.getMaximumPoint().x();
|
||||
this.maxY = selection.getMaximumPoint().y();
|
||||
this.maxZ = selection.getMaximumPoint().z();
|
||||
}
|
||||
|
||||
public BlockVector getMin(){
|
||||
return new BlockVector(minX, minY, minZ);
|
||||
public BlockVector3 getMin(){
|
||||
return BlockVector3.at(minX, minY, minZ);
|
||||
}
|
||||
|
||||
public BlockVector getMax(){
|
||||
return new BlockVector(maxX, maxY, maxZ);
|
||||
public BlockVector3 getMax(){
|
||||
return BlockVector3.at(maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Cuboid o) {
|
||||
BlockVector min = getMin();
|
||||
BlockVector otherMin = o.getMin();
|
||||
BlockVector3 min = getMin();
|
||||
BlockVector3 otherMin = o.getMin();
|
||||
|
||||
if(min.getBlockX() < otherMin.getBlockX()){
|
||||
if(min.x() < otherMin.x()){
|
||||
return -1;
|
||||
}else if(min.getBlockX() > otherMin.getBlockX()){
|
||||
}else if(min.x() > otherMin.x()){
|
||||
return 1;
|
||||
}else if(min.getBlockZ() < otherMin.getBlockZ()){
|
||||
}else if(min.z() < otherMin.z()){
|
||||
return -1;
|
||||
}else if(min.getBlockZ() > otherMin.getBlockZ()){
|
||||
}else if(min.z() > otherMin.z()){
|
||||
return 1;
|
||||
}else if(min.getBlockY() < otherMin.getBlockY()){
|
||||
}else if(min.y() < otherMin.y()){
|
||||
return -1;
|
||||
}else if(min.getBlockY() > otherMin.getBlockY()){
|
||||
}else if(min.y() > otherMin.y()){
|
||||
return 1;
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static int compareBlockVectors(BlockVector v1, BlockVector v2){
|
||||
Logger log = Logger.getLogger("Minecraft");
|
||||
|
||||
public static int compareBlockVectors(BlockVector3 v1, BlockVector3 v2){
|
||||
if(v1 == null){
|
||||
if(v2 == null){
|
||||
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()){
|
||||
if(v1.x() < v2.x()){
|
||||
return -1;
|
||||
}else if(v1.getBlockX() > v2.getBlockX()){
|
||||
}else if(v1.x() > v2.x()){
|
||||
return 1;
|
||||
}else if(v1.getBlockZ() < v2.getBlockZ()){
|
||||
}else if(v1.z() < v2.z()){
|
||||
return -1;
|
||||
}else if(v1.getBlockZ() > v2.getBlockZ()){
|
||||
}else if(v1.z() > v2.z()){
|
||||
return 1;
|
||||
}else if(v1.getBlockY() < v2.getBlockY()){
|
||||
}else if(v1.y() < v2.y()){
|
||||
return -1;
|
||||
}else if(v1.getBlockY() > v2.getBlockY()){
|
||||
}else if(v1.y() > v2.y()){
|
||||
return 1;
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isBlockLessThan(BlockVector v1, BlockVector v2) {
|
||||
public static boolean isBlockLessThan(BlockVector3 v1, BlockVector3 v2) {
|
||||
return compareBlockVectors(v1, v2) < 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
@@ -31,6 +31,7 @@ import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisDebugGenerateTestHomesCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisFlagResetCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisHomeEvictCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisHomeAcquire;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisHomeGenerateCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisHomeGoCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisHomeListCommand;
|
||||
@@ -38,12 +39,13 @@ import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisHomeMoveComman
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisPlotGoCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.commands.MetropolisPlotReserveCommand;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.eventlisteners.PlayerJoinListener;
|
||||
import com.sk89q.util.yaml.YAMLProcessor;
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||
import com.sk89q.worldguard.WorldGuard;
|
||||
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
|
||||
import com.sk89q.worldguard.domains.DefaultDomain;
|
||||
import com.sk89q.worldguard.protection.flags.DefaultFlag;
|
||||
import com.sk89q.worldguard.protection.flags.Flags;
|
||||
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||
import com.sk89q.worldguard.protection.managers.RegionManager;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||
@@ -51,7 +53,6 @@ import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
|
||||
public class MetropolisPlugin extends JavaPlugin {
|
||||
public static final boolean DEBUG = false;
|
||||
public static final Logger log=Logger.getLogger("Minecraft");
|
||||
private static final int version = 1;
|
||||
|
||||
public static final int ROAD_NORTH=1;
|
||||
@@ -66,9 +67,9 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
public RegionManager regionManager = null;
|
||||
|
||||
private List<Plot> _occupiedPlots;
|
||||
private HashMap<String, List<Plot>> _ownedPlots;
|
||||
private HashMap<String, UserOverride> _userOverrides;
|
||||
private HashMap<String, Integer> _currentHomes;
|
||||
private HashMap<UUID, List<Plot>> _ownedPlots;
|
||||
private HashMap<UUID, UserOverride> _userOverrides;
|
||||
private HashMap<UUID, Integer> _currentHomes;
|
||||
|
||||
private PlayerJoinListener _playerJoinListener = null;
|
||||
|
||||
@@ -88,7 +89,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
private Material roadSupportMaterial = Material.STONE;
|
||||
private String worldName = "world";
|
||||
private boolean generateFloor = false;
|
||||
private Material floorMaterial = Material.GRASS;
|
||||
private Material floorMaterial = Material.GRASS_BLOCK;
|
||||
private int spaceAboveFloor = 2;
|
||||
private boolean generateSign = false;
|
||||
private boolean generateSpawn = true;
|
||||
@@ -110,33 +111,33 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
log.info(String.format("%s disabled", pdf.getFullName()));
|
||||
getLogger().info(String.format("%s disabled", pdf.getFullName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
pdf = getDescription();
|
||||
|
||||
_ownedPlots = new HashMap<String, List<Plot>>();
|
||||
_userOverrides = new HashMap<String, UserOverride>();
|
||||
_currentHomes = new HashMap<String, Integer>();
|
||||
_ownedPlots = new HashMap<UUID, List<Plot>>();
|
||||
_userOverrides = new HashMap<UUID, UserOverride>();
|
||||
_currentHomes = new HashMap<UUID, Integer>();
|
||||
loadCurrentHomes();
|
||||
|
||||
if(DEBUG){log.info("Checking config");}
|
||||
if(DEBUG){getLogger().info("Checking config");}
|
||||
Configuration config = getConfig();
|
||||
if(!config.contains("version")){
|
||||
//new
|
||||
if(DEBUG){log.info("No config exists. Assuming new installation.");}
|
||||
if(DEBUG){getLogger().info("No config exists. Assuming new installation.");}
|
||||
}else{
|
||||
int configVersion = safeGetIntFromConfig(config, "version");
|
||||
if(configVersion < version){
|
||||
if(DEBUG){log.info(String.format("Updating config from version v%s to v%s.", configVersion, version));}
|
||||
if(DEBUG){getLogger().info(String.format("Updating config from version v%s to v%s.", configVersion, version));}
|
||||
if(configVersion != version){
|
||||
//upgrade config
|
||||
config.set("version", version);
|
||||
}
|
||||
saveConfig();
|
||||
if(DEBUG){log.info("Config updated");}
|
||||
if(DEBUG){getLogger().info("Config updated");}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +146,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
|
||||
config.options().copyDefaults(true);
|
||||
|
||||
if(DEBUG){log.info("Reading configuration from file.");}
|
||||
if(DEBUG){getLogger().info("Reading configuration from file.");}
|
||||
plotSizeX = safeGetIntFromConfig(config, "plot.sizeX");
|
||||
plotSizeZ = safeGetIntFromConfig(config, "plot.sizeZ");
|
||||
generateFloor = safeGetBooleanFromConfig(config, "plot.floor.generate");
|
||||
@@ -165,7 +166,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
spawnFloorMaterial = safeGetMaterialFromConfig(config, "spawn.material");
|
||||
generateWall = safeGetBooleanFromConfig(config, "wall.generate");
|
||||
wallMaterial = safeGetMaterialFromConfig(config, "wall.material");
|
||||
wallHeight = safeGetIntFromConfig(config, "wall.material");
|
||||
wallHeight = safeGetIntFromConfig(config, "wall.height");
|
||||
worldName = safeGetStringFromConfig(config, "worldname");
|
||||
_maxPlots = safeGetIntFromConfig(config, "plot.multiplier");
|
||||
_plotMultiplier = safeGetIntFromConfig(config, "plot.maxPerPlayer");
|
||||
@@ -173,9 +174,9 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
buildUserOverrides();
|
||||
|
||||
saveConfig();
|
||||
if(DEBUG){log.info("Done reading config.");}
|
||||
if(DEBUG){getLogger().info("Done reading config.");}
|
||||
|
||||
log.info(String.format("Metropolis: world name is %s", worldName));
|
||||
getLogger().info(String.format("Metropolis: world name is %s", worldName));
|
||||
|
||||
Server server = getServer();
|
||||
if(server == null){
|
||||
@@ -208,7 +209,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
gridSizeY = world.getMaxHeight();
|
||||
gridSizeZ = plotSizeZ + roadWidth;
|
||||
|
||||
regionManager = worldGuard.getRegionManager(world);
|
||||
regionManager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(world));
|
||||
if(regionManager == null){
|
||||
throw new RuntimeException("WorldGuard regions don't seem to be enabled.");
|
||||
}
|
||||
@@ -217,15 +218,15 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
if(_cityRegion == null){
|
||||
_cityRegion = new ProtectedCuboidRegion("City", getPlotMin(0, 0, 1), this.getPlotMax(0, 0, 1));
|
||||
_cityRegion.setPriority(0);
|
||||
_cityRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.LAVA_FLOW, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(DefaultFlag.SNOW_FALL, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
||||
_cityRegion.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
||||
regionManager.addRegion(_cityRegion);
|
||||
}
|
||||
|
||||
@@ -235,15 +236,15 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
if(_spawnRegion == null){
|
||||
_spawnRegion = new ProtectedCuboidRegion("Spawn", getPlotMin(0, 0, 1), getPlotMax(0, 0, 1));
|
||||
_spawnRegion.setPriority(1);
|
||||
_spawnRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.LAVA_FLOW, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(DefaultFlag.SNOW_FALL, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
||||
_spawnRegion.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
||||
regionManager.addRegion(_spawnRegion);
|
||||
|
||||
_spawnCuboid = new Cuboid(_spawnRegion.getMinimumPoint(), _spawnRegion.getMaximumPoint());
|
||||
@@ -257,29 +258,28 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
|
||||
if(DEBUG){
|
||||
/*
|
||||
log.info("Metropolis: first 25 plots");
|
||||
getLogger().info("Metropolis: first 25 plots");
|
||||
|
||||
int n = 5;
|
||||
|
||||
for (int ix=-n; ix<=n; ix++){
|
||||
for (int iz=-n; iz<=n; iz++){
|
||||
log.info(getCuboid(iz, ix).toString());
|
||||
getLogger().info(getCuboid(iz, ix).toString());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
log.info(String.format("roadWidth = %d", roadWidth));
|
||||
getLogger().info(String.format("roadWidth = %d", roadWidth));
|
||||
}
|
||||
|
||||
_occupiedPlots = new ArrayList<Plot>();
|
||||
fillOccupiedPlots();
|
||||
resizeCityRegion();
|
||||
|
||||
if(_playerJoinListener == null){
|
||||
_playerJoinListener = new PlayerJoinListener(this);
|
||||
}
|
||||
_playerJoinListener = new PlayerJoinListener(this);
|
||||
getServer().getPluginManager().registerEvents(_playerJoinListener, this);
|
||||
|
||||
log.info(String.format("%s enabled", pdf.getFullName()));
|
||||
getLogger().info(String.format("%s enabled", pdf.getFullName()));
|
||||
|
||||
RegisterCommandHandler("metropolis", new MetropolisCommand(this));
|
||||
|
||||
@@ -288,6 +288,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
RegisterCommandHandler("metropolis-flag-reset", new MetropolisFlagResetCommand(this));
|
||||
|
||||
RegisterCommandHandler("metropolis-home-evict", new MetropolisHomeEvictCommand(this));
|
||||
RegisterCommandHandler("metropolis-home-acquire", new MetropolisHomeAcquire(this));
|
||||
RegisterCommandHandler("metropolis-home-generate", new MetropolisHomeGenerateCommand(this));
|
||||
RegisterCommandHandler("metropolis-home-go", new MetropolisHomeGoCommand(this));
|
||||
RegisterCommandHandler("metropolis-home-list", new MetropolisHomeListCommand(this));
|
||||
@@ -298,19 +299,14 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void loadCurrentHomes() {
|
||||
YAMLProcessor processor = new YAMLProcessor(new File(getDataFolder(), "currentHomes.yml"), true);
|
||||
File homesFile = new File(getDataFolder(), "currentHomes.yml");
|
||||
try {
|
||||
processor.load();
|
||||
} catch (IOException e) {
|
||||
log.info(e.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> keys = processor.getMap().keySet();
|
||||
|
||||
_currentHomes.clear();
|
||||
for(String username : keys){
|
||||
_currentHomes.put(username, processor.getInt(username, 0));
|
||||
_currentHomes.putAll(CurrentHomesStore.load(
|
||||
homesFile,
|
||||
ownerName -> getServer().getOfflinePlayer(ownerName).getUniqueId()));
|
||||
saveCurrentHomes();
|
||||
} catch (IOException | org.bukkit.configuration.InvalidConfigurationException e) {
|
||||
getLogger().log(java.util.logging.Level.SEVERE, "Unable to load currentHomes.yml", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,8 +341,9 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
UUID playerId = getServer().getOfflinePlayer(username).getUniqueId();
|
||||
UserOverride override = new UserOverride(username, plotMultiplier, maxPlots);
|
||||
_userOverrides.put(username, override);
|
||||
_userOverrides.put(playerId, override);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,8 +351,8 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
|
||||
private Cuboid getCuboid(int row, int col) {
|
||||
//This is only used for debug info
|
||||
BlockVector min = getPlotMin(row, col, 1);
|
||||
BlockVector max = getPlotMax(row, col, 1);
|
||||
BlockVector3 min = getPlotMin(row, col, 1);
|
||||
BlockVector3 max = getPlotMax(row, col, 1);
|
||||
return new Cuboid(min, max);
|
||||
}
|
||||
|
||||
@@ -398,24 +395,30 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
private Material safeGetMaterialFromConfig(Configuration config, String name){
|
||||
Material material = null;
|
||||
if(config.isInt(name)){
|
||||
material = Material.getMaterial(config.getInt(name));
|
||||
material = switch (config.getInt(name)) {
|
||||
case 1 -> Material.STONE;
|
||||
case 2 -> Material.GRASS_BLOCK;
|
||||
case 4 -> Material.COBBLESTONE;
|
||||
case 7 -> Material.BEDROCK;
|
||||
default -> null;
|
||||
};
|
||||
}else if(config.isString(name)){
|
||||
material = Material.getMaterial(config.getString(name));
|
||||
if(material== null){
|
||||
material = Material.matchMaterial(config.getString(name));
|
||||
}
|
||||
material = Material.matchMaterial(config.getString(name));
|
||||
}
|
||||
if (material == null) {
|
||||
getLogger().severe("Invalid material configured at " + name);
|
||||
throwInvalidConfigException();
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
private void throwInvalidConfigException() {
|
||||
log.info("Metropolis: ERROR config file is invalid. Please correct Metropolis/config.yml and restart the server.");
|
||||
getLogger().info("Metropolis: ERROR config file is invalid. Please correct Metropolis/config.yml and restart the server.");
|
||||
throw new RuntimeException("Config file is invalid.");
|
||||
}
|
||||
|
||||
private void setupSpawn() {
|
||||
log.info("Metropolis: Spawn Cuboid is " + _spawnCuboid.toString());
|
||||
getLogger().info("Metropolis: Spawn Cuboid is " + _spawnCuboid.toString());
|
||||
|
||||
if(generateSpawn){
|
||||
int x= 0;
|
||||
@@ -454,12 +457,12 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
ProtectedCuboidRegion cuboidRegion = (ProtectedCuboidRegion) region;
|
||||
if(cuboidRegion.getId().startsWith("h_")){
|
||||
PlayerHome home = PlayerHome.get(region);
|
||||
if(!_currentHomes.containsKey(home.getPlayerName()))
|
||||
if(!_currentHomes.containsKey(home.getPlayerId()))
|
||||
{
|
||||
_currentHomes.put(home.getPlayerName(), home.getNumber());
|
||||
_currentHomes.put(home.getPlayerId(), home.getNumber());
|
||||
}
|
||||
_occupiedPlots.add(home);
|
||||
addOwnedPlot(home.getPlayerName(), home);
|
||||
addOwnedPlot(home.getPlayerId(), home);
|
||||
}else if(cuboidRegion.getId().startsWith("r_")){
|
||||
_occupiedPlots.add(Plot.get(cuboidRegion));
|
||||
}
|
||||
@@ -469,14 +472,14 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
size=calculateCitySize();
|
||||
}
|
||||
|
||||
private void addOwnedPlot(String substring, Plot plot) {
|
||||
if(_ownedPlots.containsKey(substring)){
|
||||
List<Plot> plots = _ownedPlots.get(substring);
|
||||
private void addOwnedPlot(UUID playerId, Plot plot) {
|
||||
if(_ownedPlots.containsKey(playerId)){
|
||||
List<Plot> plots = _ownedPlots.get(playerId);
|
||||
plots.add(plot);
|
||||
}else{
|
||||
List<Plot> plots = new ArrayList<Plot>();
|
||||
plots.add(plot);
|
||||
_ownedPlots.put(substring, plots);
|
||||
_ownedPlots.put(playerId, plots);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,16 +491,23 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
public PlayerHome getPlayerHome(Player player) {
|
||||
PlayerHome home = null;
|
||||
|
||||
String regionName = "h_" + player.getName();
|
||||
int homeNumber = _currentHomes.getOrDefault(player.getUniqueId(), 1);
|
||||
String regionName = String.format("h_%d_%s", homeNumber, player.getUniqueId());
|
||||
ProtectedRegion homeRegion = regionManager.getRegion(regionName);
|
||||
|
||||
if(homeRegion == null){
|
||||
if(DEBUG){
|
||||
log.info(String.format("Creating home for player %s", player.getName()));
|
||||
PlayerHome existingHome = getOwnedHome(player.getUniqueId(), homeNumber);
|
||||
if (existingHome != null) {
|
||||
existingHome.setPlayerName(player.getName());
|
||||
return existingHome;
|
||||
}
|
||||
home = generateHome(player.getName());
|
||||
if(DEBUG){
|
||||
getLogger().info(String.format("Creating home for player %s", player.getName()));
|
||||
}
|
||||
home = generateHome(player);
|
||||
}else{
|
||||
home = new PlayerHome(homeRegion);
|
||||
home.setPlayerName(player.getName());
|
||||
}
|
||||
|
||||
return home;
|
||||
@@ -527,7 +537,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void setFloor(int x, int y, int z) {
|
||||
//if(DEBUG){log.info(String.format("setting road at (%d, %d, %d)", x, y, z));}
|
||||
//if(DEBUG){getLogger().info(String.format("setting road at (%d, %d, %d)", x, y, z));}
|
||||
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
|
||||
@@ -551,7 +561,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
|
||||
if(plotCuboid == null){
|
||||
if(DEBUG){
|
||||
log.warning("plotCuboid is null");
|
||||
getLogger().warning("plotCuboid is null");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -638,7 +648,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void setRoad(int x, int y, int z) {
|
||||
//if(DEBUG){log.info(String.format("setting road at (%d, %d, %d)", x, y, z));}
|
||||
//if(DEBUG){getLogger().info(String.format("setting road at (%d, %d, %d)", x, y, z));}
|
||||
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
//Set the road block
|
||||
@@ -665,8 +675,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
material != Material.WATER &&
|
||||
material != Material.LAVA &&
|
||||
material != Material.TORCH &&
|
||||
material != Material.REDSTONE_TORCH_OFF &&
|
||||
material != Material.REDSTONE_TORCH_ON;
|
||||
material != Material.REDSTONE_TORCH;
|
||||
}
|
||||
|
||||
private boolean isPhysicsMaterial(Material material) {
|
||||
@@ -716,7 +725,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
//Top
|
||||
for(col = min; col <= max; col++){
|
||||
if(!areBlocksOccupied(row, col, plotMultiplier)){
|
||||
if(DEBUG){log.info(String.format("row: %d, col: %d", row, col));}
|
||||
if(DEBUG){getLogger().info(String.format("row: %d, col: %d", row, col));}
|
||||
return new Cuboid(getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
||||
}
|
||||
}
|
||||
@@ -725,7 +734,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
col = max;
|
||||
for(row=min + 1; row < max; row++){
|
||||
if(!areBlocksOccupied(row, col, plotMultiplier)){
|
||||
if(DEBUG){log.info(String.format("row: %d, col: %d", row, col));}
|
||||
if(DEBUG){getLogger().info(String.format("row: %d, col: %d", row, col));}
|
||||
return new Cuboid(getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
||||
}
|
||||
}
|
||||
@@ -734,7 +743,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
row = max;
|
||||
for(col = max; col >= min; col--){
|
||||
if(!areBlocksOccupied(row, col, plotMultiplier)){
|
||||
if(DEBUG){log.info(String.format("row: %d, col: %d", row, col));}
|
||||
if(DEBUG){getLogger().info(String.format("row: %d, col: %d", row, col));}
|
||||
return new Cuboid(getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
||||
}
|
||||
}
|
||||
@@ -744,7 +753,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
for(row = max; row > min; row--){
|
||||
if(!areBlocksOccupied(row, col, plotMultiplier)){
|
||||
if(row != 0 || col != 0){
|
||||
if(DEBUG){log.info(String.format("row: %d, col: %d", row, col));}
|
||||
if(DEBUG){getLogger().info(String.format("row: %d, col: %d", row, col));}
|
||||
return new Cuboid(getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
||||
}
|
||||
}
|
||||
@@ -755,7 +764,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
max = ring - (plotMultiplier-1);
|
||||
}
|
||||
|
||||
if(DEBUG){log.info(String.format("row: %d, col: %d", row, col));}
|
||||
if(DEBUG){getLogger().info(String.format("row: %d, col: %d", row, col));}
|
||||
return new Cuboid(getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
||||
}
|
||||
|
||||
@@ -765,14 +774,19 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
if(cityRegion instanceof ProtectedCuboidRegion){
|
||||
ProtectedCuboidRegion region = (ProtectedCuboidRegion)cityRegion;
|
||||
|
||||
BlockVector min;
|
||||
BlockVector max;
|
||||
BlockVector3 min;
|
||||
BlockVector3 max;
|
||||
|
||||
min = getPlotMin(-size/2, -size/2, 1);
|
||||
max = getPlotMax(size/2, size/2, 1);
|
||||
|
||||
region.setMinimumPoint(min);
|
||||
region.setMaximumPoint(max);
|
||||
ProtectedCuboidRegion resizedRegion = new ProtectedCuboidRegion(region.getId(), min, max);
|
||||
resizedRegion.copyFrom(region);
|
||||
regionManager.removeRegion(region.getId());
|
||||
regionManager.addRegion(resizedRegion);
|
||||
_cityRegion = resizedRegion;
|
||||
_cityCuboid = new Cuboid(min, max);
|
||||
saveRegions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,43 +796,43 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
for(Plot home: _occupiedPlots){
|
||||
int plotCol=Math.abs(getPlotXFromMin(home.getCuboid()));
|
||||
int plotRow=Math.abs(getPlotZFromMin(home.getCuboid()));
|
||||
if(DEBUG){log.info(String.format("col: %d, row: %d, iSize: %d", plotCol, plotRow, iSize));}
|
||||
if(DEBUG){getLogger().info(String.format("col: %d, row: %d, iSize: %d", plotCol, plotRow, iSize));}
|
||||
iSize = Math.max(Math.max(plotRow*2+1, plotCol*2+1), iSize);
|
||||
}
|
||||
|
||||
if(DEBUG){log.info(String.format("City size is %d", iSize));}
|
||||
if(DEBUG){getLogger().info(String.format("City size is %d", iSize));}
|
||||
return iSize;
|
||||
}
|
||||
|
||||
public BlockVector getPlotMin(int row, int col, int plotMultiplier){
|
||||
BlockVector gridMin = getGridMin(row, col, plotMultiplier);
|
||||
public BlockVector3 getPlotMin(int row, int col, int plotMultiplier){
|
||||
BlockVector3 gridMin = getGridMin(row, col, plotMultiplier);
|
||||
|
||||
BlockVector bv = new BlockVector(gridMin.getBlockX() + roadWidth/2, gridMin.getBlockY(), gridMin.getBlockZ() + roadWidth/2);
|
||||
log.info(String.format("getPlotMin (%d, %d, %d)", bv.getBlockX(), bv.getBlockY(), bv.getBlockZ()));
|
||||
BlockVector3 bv = BlockVector3.at(gridMin.x() + roadWidth/2, gridMin.y(), gridMin.z() + roadWidth/2);
|
||||
getLogger().info(String.format("getPlotMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||
return bv;
|
||||
}
|
||||
|
||||
public BlockVector getPlotMax(int row, int col, int plotMultiplier){
|
||||
BlockVector gridMax = getGridMax(row, col, plotMultiplier);
|
||||
public BlockVector3 getPlotMax(int row, int col, int plotMultiplier){
|
||||
BlockVector3 gridMax = getGridMax(row, col, plotMultiplier);
|
||||
|
||||
BlockVector bv = new BlockVector(gridMax.getBlockX() - (roadWidth - roadWidth/2), gridMax.getBlockY(), gridMax.getBlockZ() - (roadWidth-roadWidth/2));
|
||||
log.info(String.format("getPlotMax (%d, %d, %d)", bv.getBlockX(), bv.getBlockY(), bv.getBlockZ()));
|
||||
BlockVector3 bv = BlockVector3.at(gridMax.x() - (roadWidth - roadWidth/2), gridMax.y(), gridMax.z() - (roadWidth-roadWidth/2));
|
||||
getLogger().info(String.format("getPlotMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||
return bv;
|
||||
}
|
||||
|
||||
public BlockVector getGridMin(int row, int col, int plotMultiplier){
|
||||
public BlockVector3 getGridMin(int row, int col, int plotMultiplier){
|
||||
int level = 0;
|
||||
|
||||
BlockVector bv = new BlockVector(col * gridSizeX, level * gridSizeY, row * gridSizeZ);
|
||||
log.info(String.format("getGridMin (%d, %d, %d)", bv.getBlockX(), bv.getBlockY(), bv.getBlockZ()));
|
||||
BlockVector3 bv = BlockVector3.at(col * gridSizeX, level * gridSizeY, row * gridSizeZ);
|
||||
getLogger().info(String.format("getGridMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||
return bv;
|
||||
}
|
||||
|
||||
public BlockVector getGridMax(int row, int col, int plotMultiplier){
|
||||
public BlockVector3 getGridMax(int row, int col, int plotMultiplier){
|
||||
int level = 0;
|
||||
|
||||
BlockVector bv = new BlockVector((col+plotMultiplier) * gridSizeX*plotMultiplier-1, (level+1/*plotMultiplier*/) * gridSizeY-1, (row+plotMultiplier) * gridSizeZ-1);
|
||||
log.info(String.format("getGridMax (%d, %d, %d)", bv.getBlockX(), bv.getBlockY(), bv.getBlockZ()));
|
||||
BlockVector3 bv = BlockVector3.at((col+plotMultiplier) * gridSizeX*plotMultiplier-1, (level+1/*plotMultiplier*/) * gridSizeY-1, (row+plotMultiplier) * gridSizeZ-1);
|
||||
getLogger().info(String.format("getGridMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||
return bv;
|
||||
}
|
||||
|
||||
@@ -830,68 +844,81 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
return (cuboid.minZ - roadWidth/2)/gridSizeZ;
|
||||
}
|
||||
|
||||
private void setHomeOccupied(String owner, BlockVector minimumPoint, BlockVector maximumPoint) {
|
||||
|
||||
PlayerHome home = new PlayerHome(owner, minimumPoint, maximumPoint);
|
||||
private void setHomeOccupied(UUID ownerId, String ownerName, int homeNumber, BlockVector3 minimumPoint, BlockVector3 maximumPoint) {
|
||||
PlayerHome home = new PlayerHome(ownerId, ownerName, homeNumber, minimumPoint, maximumPoint);
|
||||
if(!_occupiedPlots.contains(home)){
|
||||
_occupiedPlots.add(home);
|
||||
addOwnedPlot(ownerId, home);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerHome generateHome(String playerName) {
|
||||
int multiplier = getPlotMultiplier(playerName);
|
||||
return generateHome(getServer().getOfflinePlayer(playerName));
|
||||
}
|
||||
|
||||
if(DEBUG){log.info(String.format("Generating home for %s", playerName));}
|
||||
public PlayerHome generateHome(OfflinePlayer player) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
String playerName = player.getName() == null ? playerId.toString() : player.getName();
|
||||
int homeNumber = _currentHomes.getOrDefault(playerId, 1);
|
||||
int multiplier = getPlotMultiplier(playerId);
|
||||
|
||||
if(DEBUG){getLogger().info(String.format("Generating home for %s", playerName));}
|
||||
Cuboid homeCuboid = null;
|
||||
ProtectedRegion phomeRegion = null;
|
||||
String regionName = "h_1_" + playerName;
|
||||
String regionName = getHomeRegionName(playerId, homeNumber);
|
||||
phomeRegion = regionManager.getRegion(regionName);
|
||||
if(phomeRegion != null){
|
||||
return PlayerHome.get(phomeRegion);
|
||||
}
|
||||
PlayerHome existingHome = getOwnedHome(playerId, homeNumber);
|
||||
if (existingHome != null) {
|
||||
return existingHome;
|
||||
}
|
||||
|
||||
homeCuboid = findNextUnownedHomeRegion(multiplier);
|
||||
|
||||
log.info("Metropolis Generating home in " + homeCuboid.toString());
|
||||
getLogger().info("Metropolis Generating home in " + homeCuboid.toString());
|
||||
|
||||
ProtectedCuboidRegion newHomeRegion = new ProtectedCuboidRegion(regionName, homeCuboid.getMin(), homeCuboid.getMax());
|
||||
newHomeRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
newHomeRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||
|
||||
DefaultDomain d = newHomeRegion.getOwners();
|
||||
d.addPlayer(playerName);
|
||||
d.addPlayer(playerId);
|
||||
newHomeRegion.setPriority(1);
|
||||
|
||||
regionManager.addRegion(newHomeRegion);
|
||||
try {
|
||||
regionManager.save();
|
||||
} catch (Exception e) {
|
||||
log.info("Metropolis: ERROR Problem saving region");
|
||||
getLogger().info("Metropolis: ERROR Problem saving region");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
regionManager.save();
|
||||
} catch (Exception e) {
|
||||
log.info("Metropolis: ERROR Problem saving region");
|
||||
getLogger().info("Metropolis: ERROR Problem saving region");
|
||||
e.printStackTrace();
|
||||
}
|
||||
log.info(String.format(
|
||||
getLogger().info(String.format(
|
||||
"New home region (%d, %d, %d) (%d, %d, %d)",
|
||||
newHomeRegion.getMinimumPoint().getBlockX(),
|
||||
newHomeRegion.getMinimumPoint().getBlockY(),
|
||||
newHomeRegion.getMinimumPoint().getBlockZ(),
|
||||
newHomeRegion.getMaximumPoint().getBlockX(),
|
||||
newHomeRegion.getMaximumPoint().getBlockY(),
|
||||
newHomeRegion.getMaximumPoint().getBlockZ()
|
||||
newHomeRegion.getMinimumPoint().x(),
|
||||
newHomeRegion.getMinimumPoint().y(),
|
||||
newHomeRegion.getMinimumPoint().z(),
|
||||
newHomeRegion.getMaximumPoint().x(),
|
||||
newHomeRegion.getMaximumPoint().y(),
|
||||
newHomeRegion.getMaximumPoint().z()
|
||||
));
|
||||
|
||||
setHomeOccupied(playerName, newHomeRegion.getMinimumPoint(), newHomeRegion.getMaximumPoint());
|
||||
setHomeOccupied(playerId, playerName, homeNumber, newHomeRegion.getMinimumPoint(), newHomeRegion.getMaximumPoint());
|
||||
_currentHomes.putIfAbsent(playerId, homeNumber);
|
||||
saveCurrentHomes();
|
||||
|
||||
createRoads(homeCuboid);
|
||||
|
||||
@@ -899,19 +926,21 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
generateFloor(homeCuboid);
|
||||
}
|
||||
|
||||
if(DEBUG){log.info(String.format("generateSign: %s", String.valueOf(generateSign)));}
|
||||
if(DEBUG){getLogger().info(String.format("generateSign: %s", String.valueOf(generateSign)));}
|
||||
if(generateSign){
|
||||
generateSign(homeCuboid, playerName);
|
||||
}
|
||||
|
||||
if(DEBUG){log.info(String.format("Done generating home for %s", playerName));}
|
||||
if(DEBUG){getLogger().info(String.format("Done generating home for %s", playerName));}
|
||||
|
||||
return new PlayerHome(newHomeRegion);
|
||||
PlayerHome home = new PlayerHome(newHomeRegion);
|
||||
home.setPlayerName(playerName);
|
||||
return home;
|
||||
}
|
||||
|
||||
private void generateSign(Cuboid plotCuboid, String playerName) {
|
||||
Block signBlock = world.getBlockAt(plotCuboid.getCenterX(), roadLevel+1, plotCuboid.getCenterZ());
|
||||
signBlock.setType(Material.SIGN_POST);
|
||||
signBlock.setType(Material.OAK_SIGN);
|
||||
Sign sign = (Sign)signBlock.getState();
|
||||
sign.setLine(0, "Home of");
|
||||
|
||||
@@ -936,18 +965,19 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
|
||||
public void reserveCuboid(String regionName, Cuboid cuboid) {
|
||||
ProtectedCuboidRegion reservedRegion = new ProtectedCuboidRegion(regionName, cuboid.getMin(), cuboid.getMax());
|
||||
reservedRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.LAVA_FLOW, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(DefaultFlag.SNOW_FALL, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
||||
reservedRegion.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
||||
regionManager.addRegion(reservedRegion);
|
||||
|
||||
_occupiedPlots.add(Plot.get(reservedRegion));
|
||||
saveRegions();
|
||||
}
|
||||
|
||||
public Cuboid getCityCuboid(){
|
||||
@@ -981,7 +1011,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
try{
|
||||
regionManager.removeRegion(regionId);
|
||||
}catch(Exception ex){
|
||||
log.info(String.format("[ERROR] Metropolis: Unable to remove region {%s}.", regionId));
|
||||
getLogger().info(String.format("[ERROR] Metropolis: Unable to remove region {%s}.", regionId));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -990,14 +1020,14 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
try {
|
||||
regionManager.save();
|
||||
} catch (Exception ex) {
|
||||
log.info(String.format("[SEVERE] Metropolis: Unable to save WorldGuard regions."));
|
||||
getLogger().info(String.format("[SEVERE] Metropolis: Unable to save WorldGuard regions."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumPlots(String name) {
|
||||
if(_ownedPlots.containsKey(name)){
|
||||
List<Plot> plots = _ownedPlots.get(name);
|
||||
public int getNumPlots(UUID playerId) {
|
||||
if(_ownedPlots.containsKey(playerId)){
|
||||
List<Plot> plots = _ownedPlots.get(playerId);
|
||||
if(plots == null){
|
||||
return 0;
|
||||
}else{
|
||||
@@ -1008,22 +1038,21 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxPlots(String name) {
|
||||
if(_userOverrides.containsKey(name)){
|
||||
return _userOverrides.get(name).getMaxPlots();
|
||||
public int getMaxPlots(UUID playerId) {
|
||||
if(_userOverrides.containsKey(playerId)){
|
||||
return _userOverrides.get(playerId).getMaxPlots();
|
||||
}else{
|
||||
return _maxPlots;
|
||||
}
|
||||
}
|
||||
|
||||
public void assignPlot(OfflinePlayer player) {
|
||||
//PlayerHome home = generateHome(player.getName());
|
||||
generateHome(player.getName());
|
||||
generateHome(player);
|
||||
}
|
||||
|
||||
private int getPlotMultiplier(String name) {
|
||||
if(_userOverrides.containsKey(name)){
|
||||
return _userOverrides.get(name).getPlotMultiplier();
|
||||
private int getPlotMultiplier(UUID playerId) {
|
||||
if(_userOverrides.containsKey(playerId)){
|
||||
return _userOverrides.get(playerId).getPlotMultiplier();
|
||||
}else{
|
||||
return _plotMultiplier;
|
||||
}
|
||||
@@ -1045,11 +1074,24 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
public Player getPlayer(String name) {
|
||||
return getServer().getPlayer(name);
|
||||
Player player = getServer().getPlayerExact(name);
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
return PlayerLookup.findOnline(name, getServer().getOnlinePlayers());
|
||||
}
|
||||
|
||||
public OfflinePlayer getOfflinePlayer(String name){
|
||||
return getServer().getOfflinePlayer(name);
|
||||
Player onlinePlayer = getPlayer(name);
|
||||
if (onlinePlayer != null) {
|
||||
return onlinePlayer;
|
||||
}
|
||||
for (OfflinePlayer offlinePlayer : getServer().getOfflinePlayers()) {
|
||||
if (name.equalsIgnoreCase(offlinePlayer.getName())) {
|
||||
return offlinePlayer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String teleportPlayerToPlot(Player player, Plot plot) {
|
||||
@@ -1062,9 +1104,9 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean homeExists(String playerName, int homeNumber) {
|
||||
public boolean homeExists(UUID playerId, int homeNumber) {
|
||||
for(Plot plot: _occupiedPlots){
|
||||
if(plot.getRegionName().equalsIgnoreCase(String.format("h_%d_%s", homeNumber, playerName))){
|
||||
if(plot instanceof PlayerHome home && home.getPlayerId().equals(playerId) && home.getNumber() == homeNumber){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1072,17 +1114,41 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setHome(String name, int newHomeNumber) {
|
||||
_currentHomes.put(name, newHomeNumber);
|
||||
public void setHome(UUID playerId, int newHomeNumber) {
|
||||
_currentHomes.put(playerId, newHomeNumber);
|
||||
saveCurrentHomes();
|
||||
}
|
||||
|
||||
public String getHomeRegionName(UUID playerId, int homeNumber) {
|
||||
PlayerHome existingHome = getOwnedHome(playerId, homeNumber);
|
||||
return existingHome == null
|
||||
? String.format("h_%d_%s", homeNumber, playerId)
|
||||
: existingHome.getRegionName();
|
||||
}
|
||||
|
||||
public String getCurrentHomeRegionName(UUID playerId) {
|
||||
return getHomeRegionName(playerId, _currentHomes.getOrDefault(playerId, 1));
|
||||
}
|
||||
|
||||
private PlayerHome getOwnedHome(UUID playerId, int homeNumber) {
|
||||
List<Plot> plots = _ownedPlots.get(playerId);
|
||||
if (plots == null) {
|
||||
return null;
|
||||
}
|
||||
for (Plot plot : plots) {
|
||||
if (plot instanceof PlayerHome home && home.getNumber() == homeNumber) {
|
||||
return home;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void saveCurrentHomes() {
|
||||
File outFile = new File(getDataFolder(), "currentHomes.yml");
|
||||
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
//YAMLProcessor processor = new YAMLProcessor(new File(getDataFolder(), "currentHomes.yml"), true);
|
||||
//processor.save();
|
||||
File homesFile = new File(getDataFolder(), "currentHomes.yml");
|
||||
try {
|
||||
CurrentHomesStore.save(homesFile, _currentHomes);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(java.util.logging.Level.SEVERE, "Unable to save currentHomes.yml", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,65 @@
|
||||
package com.majinnaibu.bukkitplugins.metropolis;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.avaje.ebean.validation.NotNull;
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
|
||||
@Entity()
|
||||
@Table(name="Metropolis_PlayerHome")
|
||||
public class PlayerHome extends Plot{
|
||||
@NotNull
|
||||
private UUID playerId;
|
||||
private String playerName;
|
||||
public UUID getPlayerId(){return this.playerId;}
|
||||
public String getPlayerName(){return this.playerName;}
|
||||
public void setPlayerName(String playerName){this.playerName = playerName;}
|
||||
|
||||
private int number;
|
||||
|
||||
public PlayerHome(String owner, BlockVector min, BlockVector max) {
|
||||
super("h_" + owner, min, max);
|
||||
this.playerName = owner;
|
||||
public PlayerHome(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;
|
||||
}
|
||||
|
||||
public PlayerHome() {
|
||||
this.playerId = null;
|
||||
this.playerName = "";
|
||||
}
|
||||
|
||||
public PlayerHome(ProtectedRegion homeRegion){
|
||||
try{
|
||||
String rname = homeRegion.getId();
|
||||
|
||||
if(rname.startsWith("h_")){
|
||||
int secondUnderscore = rname.indexOf('_', 2);
|
||||
if(secondUnderscore > 2){
|
||||
try{
|
||||
this.number = Integer.parseInt(rname.substring(2, secondUnderscore));
|
||||
this.playerName = rname.substring(secondUnderscore+1);
|
||||
}catch(Exception ex){
|
||||
this.number = 0;
|
||||
}
|
||||
}else{
|
||||
this.number = 0;
|
||||
this.playerName = rname.substring(2);
|
||||
}
|
||||
|
||||
setCuboid(new Cuboid(homeRegion.getMinimumPoint(), homeRegion.getMaximumPoint()));
|
||||
}
|
||||
else{
|
||||
throw new RuntimeException("Method not implemented.");
|
||||
}
|
||||
}catch(Exception ex){
|
||||
throw new RuntimeException("Method not implemented.", ex);
|
||||
String regionName = homeRegion.getId();
|
||||
if (!regionName.startsWith("h_")) {
|
||||
throw new IllegalArgumentException("Not a Metropolis home region: " + regionName);
|
||||
}
|
||||
String regionOwner = regionName.substring(2);
|
||||
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
|
||||
@@ -63,7 +70,7 @@ public class PlayerHome extends Plot{
|
||||
|
||||
PlayerHome otherPlayerHome = (PlayerHome)other;
|
||||
|
||||
if(!this.playerName.equals(otherPlayerHome.playerName)){
|
||||
if(!java.util.Objects.equals(this.playerId, otherPlayerHome.playerId)){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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,39 +1,28 @@
|
||||
package com.majinnaibu.bukkitplugins.metropolis;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import com.avaje.ebean.validation.NotNull;
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
|
||||
@Entity()
|
||||
@Table(name="Metropolis_Plot")
|
||||
public class Plot implements Comparable<Plot>{
|
||||
@Id
|
||||
private int _id;
|
||||
public int getId(){return _id;}
|
||||
public void setId(int id){_id = id;}
|
||||
|
||||
@NotNull
|
||||
|
||||
private Cuboid _cuboid;
|
||||
public Cuboid getCuboid(){return _cuboid;}
|
||||
public void setCuboid(Cuboid cuboid){_cuboid = cuboid;}
|
||||
|
||||
@NotNull
|
||||
private String _regionName;
|
||||
public String getRegionName(){return _regionName;}
|
||||
public void setRegionName(String regionName){_regionName = regionName;}
|
||||
|
||||
public Plot(String regionName, BlockVector min, BlockVector max){
|
||||
public Plot(String regionName, BlockVector3 min, BlockVector3 max){
|
||||
_cuboid = new Cuboid(min, max);
|
||||
_regionName = regionName;
|
||||
}
|
||||
@@ -48,17 +37,17 @@ public class Plot implements Comparable<Plot>{
|
||||
_regionName = "";
|
||||
}
|
||||
|
||||
public BlockVector getPlotMin(int roadWidth) {
|
||||
return new BlockVector(_cuboid.minX - roadWidth/2, _cuboid.minY, _cuboid.minZ - roadWidth/2);
|
||||
public BlockVector3 getPlotMin(int roadWidth) {
|
||||
return BlockVector3.at(_cuboid.minX - roadWidth/2, _cuboid.minY, _cuboid.minZ - roadWidth/2);
|
||||
}
|
||||
|
||||
public int getRow(int roadWidth, int plotSizeZ){
|
||||
BlockVector min = getPlotMin(roadWidth);
|
||||
return min.getBlockZ() / plotSizeZ;
|
||||
BlockVector3 min = getPlotMin(roadWidth);
|
||||
return min.z() / plotSizeZ;
|
||||
}
|
||||
|
||||
public int getCol(int roadWidth, int plotSizeX){
|
||||
return getPlotMin(roadWidth).getBlockX() / plotSizeX;
|
||||
return getPlotMin(roadWidth).x() / plotSizeX;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||
import com.sk89q.worldguard.protection.flags.DefaultFlag;
|
||||
import com.sk89q.worldguard.protection.flags.Flags;
|
||||
import com.sk89q.worldguard.protection.flags.StateFlag;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
|
||||
@@ -19,25 +19,25 @@ public class MetropolisFlagResetCommand implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
ProtectedRegion cityRegion = _plugin.regionManager.getRegion("City");
|
||||
cityRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.LAVA_FLOW, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(DefaultFlag.SNOW_FALL, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.LAVA_FLOW, StateFlag.State.DENY);
|
||||
cityRegion.setFlag(Flags.SNOW_FALL, StateFlag.State.DENY);
|
||||
|
||||
for(ProtectedRegion homeRegion: _plugin.regionManager.getRegions().values()){
|
||||
if(homeRegion.getId().startsWith("h_")){
|
||||
homeRegion.setFlag(DefaultFlag.PVP, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(DefaultFlag.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(DefaultFlag.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(DefaultFlag.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(DefaultFlag.ENDER_BUILD, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(DefaultFlag.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(DefaultFlag.TNT, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(Flags.PVP, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(Flags.MOB_DAMAGE, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(Flags.ENDER_BUILD, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(Flags.GHAST_FIREBALL, StateFlag.State.DENY);
|
||||
homeRegion.setFlag(Flags.TNT, StateFlag.State.DENY);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class MetropolisHomeAcquire implements CommandExecutor {
|
||||
|
||||
Player player = (Player) sender;
|
||||
|
||||
if(_plugin.getNumPlots(player.getName()) >= _plugin.getMaxPlots(player.getName())){
|
||||
if(_plugin.getNumPlots(player.getUniqueId()) >= _plugin.getMaxPlots(player.getUniqueId())){
|
||||
sender.sendMessage("You cannot have any more plots");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -28,21 +28,21 @@ public class MetropolisHomeEvictCommand implements CommandExecutor {
|
||||
playerName = args[0];
|
||||
}
|
||||
|
||||
OfflinePlayer player = _plugin.getServer().getOfflinePlayer(playerName);
|
||||
OfflinePlayer player = _plugin.getOfflinePlayer(playerName);
|
||||
if(player == null){
|
||||
sender.sendMessage(String.format("The requested player {%s}does not appear to exist.", playerName));
|
||||
return false;
|
||||
}
|
||||
|
||||
ProtectedRegion region = _plugin.getRegion(String.format("h_%s", player.getName()));
|
||||
ProtectedRegion region = _plugin.getRegion(_plugin.getCurrentHomeRegionName(player.getUniqueId()));
|
||||
if(region == null){
|
||||
sender.sendMessage(String.format("The player {%s} has no home to be evicted from."));
|
||||
return false;
|
||||
}
|
||||
|
||||
//remove the player as owner and/or member of the region
|
||||
region.getMembers().removePlayer(playerName);
|
||||
region.getOwners().removePlayer(playerName);
|
||||
region.getMembers().removePlayer(player.getUniqueId());
|
||||
region.getOwners().removePlayer(player.getUniqueId());
|
||||
|
||||
//if the region has no owners delete the region
|
||||
if(region.getMembers().size() == 0 && region.getOwners().size() == 0){
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||
|
||||
@@ -20,7 +21,12 @@ public class MetropolisHomeGenerateCommand implements CommandExecutor {
|
||||
return false;
|
||||
}
|
||||
|
||||
_plugin.generateHome(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]);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public class MetropolisHomeGoCommand implements CommandExecutor {
|
||||
|
||||
if(args.length >= 1){
|
||||
if(player.hasPermission("")){
|
||||
player = _plugin.getServer().getPlayer(args[0]);
|
||||
player = _plugin.getPlayer(args[0]);
|
||||
|
||||
if(player == null){
|
||||
sender.sendMessage(String.format("Unable to find player %s", args[0]));
|
||||
@@ -37,7 +37,7 @@ public class MetropolisHomeGoCommand implements CommandExecutor {
|
||||
}
|
||||
}else{
|
||||
if(args.length >= 1){
|
||||
player = _plugin.getServer().getPlayer(args[0]);
|
||||
player = _plugin.getPlayer(args[0]);
|
||||
|
||||
if(player == null){
|
||||
sender.sendMessage(String.format("Unable to find player %s", args[0]));
|
||||
|
||||
@@ -41,11 +41,11 @@ public class MetropolisHomeMoveCommand implements CommandExecutor {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(player == null || !_plugin.homeExists(player.getName(), newHomeNumber)){
|
||||
if(player == null || !_plugin.homeExists(player.getUniqueId(), newHomeNumber)){
|
||||
return false;
|
||||
}
|
||||
|
||||
_plugin.setHome(player.getName(), newHomeNumber);
|
||||
_plugin.setHome(player.getUniqueId(), newHomeNumber);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||
import com.sk89q.worldedit.bukkit.selections.Selection;
|
||||
import com.sk89q.worldedit.IncompleteRegionException;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
|
||||
public class MetropolisPlotReserveCommand implements CommandExecutor {
|
||||
MetropolisPlugin _plugin;
|
||||
@@ -27,9 +29,15 @@ public class MetropolisPlotReserveCommand implements CommandExecutor {
|
||||
Cuboid cuboid = new Cuboid();
|
||||
|
||||
if(sender instanceof Player && args.length == 1){
|
||||
Selection selection = _plugin.worldEdit.getSelection((Player) sender);
|
||||
cuboid = new Cuboid(selection);
|
||||
}else if(args.length == 6){
|
||||
try {
|
||||
Region selection = _plugin.worldEdit.getSession((Player) sender)
|
||||
.getSelection(BukkitAdapter.adapt(_plugin.getWorld()));
|
||||
cuboid = new Cuboid(selection);
|
||||
} catch (IncompleteRegionException ex) {
|
||||
sender.sendMessage("Select a WorldEdit region first.");
|
||||
return false;
|
||||
}
|
||||
}else if(args.length == 7){
|
||||
try{
|
||||
minX = Integer.parseInt(args[1]);
|
||||
minY = Integer.parseInt(args[2]);
|
||||
@@ -41,7 +49,7 @@ public class MetropolisPlotReserveCommand implements CommandExecutor {
|
||||
return false;
|
||||
}
|
||||
cuboid = new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}else if(args.length == 4){
|
||||
}else if(args.length == 5){
|
||||
try{
|
||||
minX = Integer.parseInt(args[1]);
|
||||
minY = 0;
|
||||
@@ -60,6 +68,7 @@ public class MetropolisPlotReserveCommand implements CommandExecutor {
|
||||
String regionName = args[0];
|
||||
|
||||
_plugin.reserveCuboid(regionName, cuboid);
|
||||
sender.sendMessage("Reserved plot region " + regionName + ".");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ public class PlayerJoinListener implements Listener {
|
||||
|
||||
public PlayerJoinListener(MetropolisPlugin plugin){
|
||||
_plugin = plugin;
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.MONITOR)
|
||||
@@ -26,12 +25,12 @@ public class PlayerJoinListener implements Listener {
|
||||
}
|
||||
|
||||
PlayerHome home = _plugin.getPlayerHome(player);
|
||||
if(home==null){MetropolisPlugin.log.info("home is null");}
|
||||
else if(home.getCuboid() == null){MetropolisPlugin.log.info("home.getCuboid() is null");}
|
||||
else if(home.getCuboid().getVolume() == 0){MetropolisPlugin.log.info("home.getCuboid().getVolume() is 0");}
|
||||
if(home==null){_plugin.getLogger().info("home is null");}
|
||||
else if(home.getCuboid() == null){_plugin.getLogger().info("home.getCuboid() is null");}
|
||||
else if(home.getCuboid().getVolume() == 0){_plugin.getLogger().info("home.getCuboid().getVolume() is 0");}
|
||||
|
||||
if(home == null || home.getCuboid() == null || home.getCuboid().getVolume() == 0){
|
||||
MetropolisPlugin.log.info(String.format("Metropolis: Unable to get or create home for player %s", player.getName()));
|
||||
_plugin.getLogger().info(String.format("Metropolis: Unable to get or create home for player %s", player.getName()));
|
||||
}else{
|
||||
Cuboid cuboid = home.getCuboid();
|
||||
player.sendMessage(String.format("Metropolis: Welcome %s your home is between (%d, %d, %d) and (%d, %d, %d)", player.getName(), cuboid.getMinX(), cuboid.getMinY(), cuboid.getMinZ(), cuboid.getMaxX(), cuboid.getMaxY(), cuboid.getMaxZ()));
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
plot:
|
||||
sizeX: 32
|
||||
sizeY: 256
|
||||
sizeX: 32
|
||||
sizeY: 256
|
||||
sizeZ: 32
|
||||
offsetX: 0
|
||||
offsetY: 0
|
||||
offsetZ: 0
|
||||
multiplier: 1
|
||||
maxPerPlayer: 1
|
||||
initial: 1
|
||||
floor:
|
||||
generate: true
|
||||
clearSpaceAbove: 66
|
||||
offsetZ: 0
|
||||
material: GRASS_BLOCK
|
||||
supports:
|
||||
generate: true
|
||||
initial: 1
|
||||
material: STONE
|
||||
sign:
|
||||
generate: true
|
||||
generate: true
|
||||
road:
|
||||
width: 4
|
||||
clearSpaceAbove: 66
|
||||
level: 62
|
||||
generate: true
|
||||
material: COBBLESTONE
|
||||
supports:
|
||||
generate: true
|
||||
generate: true
|
||||
material: STONE
|
||||
spawn:
|
||||
generate: true
|
||||
setAsWorldSpawn: true
|
||||
level: 62
|
||||
material: GRASS_BLOCK
|
||||
wall:
|
||||
generate: true
|
||||
generate: true
|
||||
material: BEDROCK
|
||||
height: 66
|
||||
worldname: world
|
||||
userOverrides:
|
||||
|
||||
@@ -1,10 +1,50 @@
|
||||
name: Metropolis
|
||||
main: com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin
|
||||
depend: [WorldGuard]
|
||||
softdepend: [WorldEdit,CommandBook]
|
||||
version: 0.5
|
||||
api-version: '26.2'
|
||||
depend: [WorldGuard, WorldEdit]
|
||||
softdepend: [CommandBook]
|
||||
version: '${version}'
|
||||
database: false
|
||||
commands:
|
||||
metropolis:
|
||||
description: This command displays the current metropolis version.
|
||||
usage: /metropolis
|
||||
usage: /metropolis
|
||||
metropolis-home-generate:
|
||||
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:
|
||||
description: This command swaps a player's current home with a reserved plot.
|
||||
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: This command teleports the user or another player to a plot.
|
||||
permission: metropolis.plot.go
|
||||
usage: /metropolis-plot-go <plotName> [playerName]
|
||||
metropolis-debug-generatetesthomes:
|
||||
description: This command is ignored on non-debug builds.
|
||||
permission: metropolis.debug
|
||||
usage: /metropolis-debug-gentesthomes
|
||||
@@ -0,0 +1,53 @@
|
||||
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 org.bukkit.Location;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
|
||||
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,41 @@
|
||||
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,27 @@
|
||||
package com.majinnaibu.bukkitplugins.metropolis;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
|
||||
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,22 @@
|
||||
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,48 @@
|
||||
package com.majinnaibu.bukkitplugins.metropolis.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
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 org.bukkit.command.Command;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||
|
||||
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(playerId)).thenReturn(1);
|
||||
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
||||
|
||||
assertTrue(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
||||
|
||||
verify(plugin).assignPlot(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(playerId)).thenReturn(1);
|
||||
MetropolisHomeAcquire executor = new MetropolisHomeAcquire(plugin);
|
||||
|
||||
assertFalse(executor.onCommand(player, mock(Command.class), "acquire", new String[0]));
|
||||
|
||||
verify(plugin, never()).assignPlot(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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 org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import com.majinnaibu.bukkitplugins.metropolis.Cuboid;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||
|
||||
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,37 @@
|
||||
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 org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
|
||||
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
|
||||
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 "$@"
|
||||
145
tools/publish-modrinth.sh
Executable file
145
tools/publish-modrinth.sh
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/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
|
||||
MODRINTH_CHANGELOG="${MODRINTH_CHANGELOG:-Auto release via publish-modrinth.sh}"
|
||||
MODRINTH_GAME_VERSIONS="${MODRINTH_GAME_VERSIONS:-26.2}"
|
||||
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