mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 08:42:11 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
608cb8611c | ||
|
|
a965b9ce56 | ||
|
|
981d368058 | ||
|
|
7507099a4e | ||
|
|
29b58b1655 | ||
|
|
a1081c1b6b | ||
|
|
3152b62668 | ||
|
|
0082cc9852 | ||
|
|
f60b656bed | ||
|
|
da5f5de5cd | ||
|
|
e9aacecc2f | ||
|
|
fa3adbe605 | ||
|
|
e33b2f7025 | ||
|
|
a9cc577754 | ||
|
|
02344a31cf | ||
|
|
2f4871ef71 | ||
|
|
9750fad4b7 | ||
|
|
df77372d96 | ||
|
|
039af3c219 | ||
|
|
09ec4f87ef | ||
|
|
f996f22f4b | ||
|
|
b230c5ae9c | ||
|
|
2561107697 | ||
|
|
70babc8656 | ||
|
|
bd09d6c950 | ||
|
|
b94de763ca | ||
|
|
22ca57543f | ||
|
|
25c70170d9 | ||
|
|
9926041c2f | ||
|
|
1c99898d0d | ||
|
|
41e0261eee | ||
|
|
642f3ab1b8 | ||
|
|
57d1844756 | ||
|
|
73e1650fc3 | ||
|
|
800709ca95 | ||
|
|
935cbad21c | ||
|
|
0e2a27b298 | ||
|
|
baaa175423 | ||
|
|
e2b7a31191 | ||
|
|
afa2825bc7 | ||
|
|
21b4f6b087 | ||
|
|
a771652c8f | ||
|
|
e89b4f19ee | ||
|
|
eb8ef32a94 | ||
|
|
f9d783bc5c | ||
|
|
87dd4168cd | ||
|
|
2da6ece606 | ||
|
|
cbb3a7a518 | ||
|
|
57796d295b | ||
|
|
09314e8bce | ||
|
|
907a6dd5c0 | ||
|
|
a50bd807b6 | ||
|
|
02081b4654 | ||
|
|
156ec7911d | ||
|
|
2b95c62a84 | ||
|
|
539d5c43d1 | ||
|
|
1664690472 | ||
|
|
455396713e | ||
|
|
8bc3a593d0 | ||
|
|
5177f290ae | ||
|
|
5118864bb8 | ||
|
|
654da03add | ||
|
|
61229356ad | ||
|
|
fa5aa41547 | ||
|
|
04f07657eb | ||
|
|
eea2af169f | ||
|
|
6d393723ff | ||
|
|
9137ef04d2 | ||
|
|
878c4bea3d | ||
|
|
021d86e21c | ||
|
|
3e9425c8f1 | ||
|
|
cc60b9c938 | ||
|
|
d3cc950a97 | ||
|
|
5e97b1169b | ||
|
|
c3f4f117e5 | ||
|
|
814901b1c1 | ||
|
|
7c6b1d07b0 | ||
|
|
c389a41cdc | ||
|
|
2db9ae7c66 | ||
|
|
b80aea4c38 | ||
|
|
35b129701f | ||
|
|
d92fe17c4f | ||
|
|
488e258e3f | ||
|
|
d096d1a941 | ||
|
|
3ef4271e63 | ||
|
|
0a33d35b2b | ||
|
|
b4fedea1db | ||
|
|
4d92964dda | ||
|
|
3443b175ff | ||
|
|
ad3fe9e114 | ||
|
|
d1660d7e81 | ||
|
|
7750b61103 | ||
|
|
eeb1019bbb | ||
|
|
6bbaad8bdb | ||
|
|
1ef4470979 | ||
|
|
3110f462ac | ||
|
|
170e1ac347 | ||
|
|
86b95034cc | ||
|
|
8f6d4d5960 | ||
|
|
a34d2f810b | ||
|
|
db002ab2b1 | ||
|
|
c9c8a1a9c6 | ||
|
|
cdc22bc533 | ||
|
|
1efe98ab46 | ||
|
|
989242810d | ||
|
|
29d3c0c8c1 | ||
|
|
97cc7e53f0 | ||
|
|
4c4544340a | ||
|
|
755fab05ba | ||
|
|
5f295fd57b | ||
|
|
86b888a68e | ||
|
|
4fd8350a96 | ||
|
|
5702426ddd | ||
|
|
9c562d09ea | ||
|
|
59d1c44c23 | ||
|
|
1082f73be7 | ||
|
|
1372626b50 | ||
|
|
c09dacb8ec | ||
|
|
518d64882c | ||
|
|
4f08b9a446 | ||
|
|
a3b7dd542d | ||
|
|
c68936f06d | ||
|
|
b91214f958 | ||
|
|
ff26ffd9c5 | ||
|
|
058a7c97cb | ||
|
|
a50965509d | ||
|
|
ce263b5b1f | ||
|
|
4ddcb3f0bf | ||
|
|
d76a6a6ca6 | ||
|
|
e9a20254b0 | ||
|
|
42a8d33824 | ||
|
|
d89f00a607 | ||
|
|
41c8c17bc9 | ||
|
|
a3a621945a | ||
|
|
71eedd392d | ||
|
|
ba78b7da3a | ||
|
|
38231ef0d4 | ||
|
|
232d18f8da | ||
|
|
530a8a9c2c | ||
|
|
8a96c7b610 | ||
|
|
bb2da157da | ||
|
|
1c521e0f79 | ||
|
|
aab13c1ec6 | ||
|
|
ae4d33d7b1 | ||
|
|
d5b2cc884b | ||
|
|
50f6152a71 | ||
|
|
c84ae0493c |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
github: Multiverse
|
||||
open_collective: multiverse-plugins
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
files: '["${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar"]'
|
||||
name: ${{ steps.release-info.outputs.tag_name }}
|
||||
changelog: ${{ steps.release-artifact.outputs.body }}
|
||||
game_versions: 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
game_versions: 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
version_type: ${{ steps.parse-release-type.outputs.release_type }}
|
||||
loaders: bukkit, spigot, paper
|
||||
dependencies: ${{ inputs.modrinth_dependencies }}
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
changelog: ${{ steps.release-artifact.outputs.body }}
|
||||
changelog_type: markdown
|
||||
display_name: ${{ steps.release-info.outputs.tag_name }}
|
||||
game_versions: 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
game_versions: 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
release_type: ${{ steps.parse-release-type.outputs.release_type }}
|
||||
project_relations: ${{ inputs.dbo_project_relations }}
|
||||
file_path: ${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar
|
||||
@@ -136,5 +136,5 @@ jobs:
|
||||
channel: ${{ steps.parse-release-type.outputs.release_type }}
|
||||
files: '[{"path": "${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar", "platforms": ["PAPER"]}]'
|
||||
description: ${{ steps.release-artifact.outputs.body }}
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-1.21.5"]}'
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-1.21.8"]}'
|
||||
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
|
||||
@@ -9,10 +9,9 @@
|
||||
|
||||
[](https://github.com/Multiverse/Multiverse-Core/releases/latest)
|
||||
[](https://github.com/Multiverse/Multiverse-Core/releases)
|
||||
[](https://discord.gg/NZtfKky)
|
||||
[](https://patreon.com/dumptruckman)
|
||||
[](LICENSE.md)
|
||||
|
||||
[](https://discord.gg/NZtfKky)
|
||||
[](https://github.com/sponsors/Multiverse)
|
||||
[](https://opencollective.com/multiverse-plugins)
|
||||
|
||||
# About
|
||||
|
||||
@@ -44,7 +43,7 @@ Simply build the source with Gradle:
|
||||
* Want others to love Multiverse too? You can join the [Multiverse Discord community](https://discord.gg/NZtfKky) and help others with issues and setup!
|
||||
* A Multiverse guru? You can update our [Wiki](https://github.com/Multiverse/multiverse-web) with your latest tip, tricks and guides! The wiki open for all to edit and improve.
|
||||
* Love coding? You could look at ["State: Open to PR"](https://github.com/Multiverse/Multiverse-Core/labels/State%3A%20Open%20to%20PR) and ["Resolution: Accepted"](https://github.com/Multiverse/Multiverse-Core/labels/Resolution%3A%20Accepted) issues. We're always happy to receive bug fixes and feature additions as [pull requests](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3/).
|
||||
* If you'd like to make a financial contribution to the project, do consider joining our [Patreon](https://www.patreon.com/dumptruckman) or make a one-time donation [here](https://paypal.me/dumptruckman)!
|
||||
* If you'd like to make a financial contribution to the project, do consider donating to our [Github Sponsors](https://github.com/sponsors/Multiverse) or [Open Collective](https://opencollective.com/multiverse-plugins)!
|
||||
|
||||
Additionally, we would like to give a big thanks to everyone that has supported Multiverse over the years, as well as those in the years to come. Thank you!
|
||||
|
||||
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.0'
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.1'
|
||||
}
|
||||
|
||||
group = 'org.mvplugins.multiverse.core'
|
||||
@@ -25,9 +25,9 @@ repositories {
|
||||
}
|
||||
|
||||
configure(apiDependencies) {
|
||||
serverApiVersion = '1.19.4-R0.1-SNAPSHOT'
|
||||
serverApiVersion = '1.21.8-R0.1-SNAPSHOT'
|
||||
mockBukkitServerApiVersion = '1.21'
|
||||
mockBukkitVersion = '4.31.1'
|
||||
mockBukkitVersion = '4.69.2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -48,9 +48,9 @@ dependencies {
|
||||
}
|
||||
|
||||
// Utils
|
||||
shadowed 'io.vavr:vavr:0.10.4'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.0.3'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.0.3') {
|
||||
shadowed 'io.vavr:vavr:0.10.7'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.1.1'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.1.1') {
|
||||
exclude group: 'org.apache.maven', module: 'maven-core'
|
||||
}
|
||||
shadowed('com.dumptruckman.minecraft:Logging:1.1.1') {
|
||||
@@ -60,8 +60,8 @@ dependencies {
|
||||
shadowed('org.bstats:bstats-bukkit:3.1.0') {
|
||||
exclude group: 'org.bukkit', module: 'bukkit'
|
||||
}
|
||||
shadowed 'net.minidev:json-smart:2.4.9'
|
||||
shadowed 'org.jetbrains:annotations:22.0.0'
|
||||
shadowed 'net.minidev:json-smart:2.5.2'
|
||||
shadowed 'org.jetbrains:annotations:26.0.2'
|
||||
shadowed 'io.papermc:paperlib:1.0.8'
|
||||
|
||||
// Tests
|
||||
|
||||
@@ -15,6 +15,7 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -229,8 +230,7 @@ public class MultiverseCore extends MultiverseModule {
|
||||
this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(getDescription().getAuthors()));
|
||||
|
||||
if (configProvider.get().isShowingDonateMessage()) {
|
||||
Logging.config("\u001B[32mHelp dumptruckman keep this project alive. Become a patron! https://www.patreon.com/dumptruckman\u001B[39m");
|
||||
Logging.config("\u001B[32mOne time donations are also appreciated: https://www.paypal.me/dumptruckman\u001B[39m");
|
||||
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a small donation: https://github.com/sponsors/Multiverse\u001B[39m");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,11 @@ public class MultiverseCore extends MultiverseModule {
|
||||
* Gets the MultiverseCoreApi
|
||||
*
|
||||
* @return The MultiverseCoreApi
|
||||
*
|
||||
* @deprecated Use {@link MultiverseCoreApi#get()} directly.
|
||||
*/
|
||||
@Deprecated(since = "5.1", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public MultiverseCoreApi getApi() {
|
||||
return MultiverseCoreApi.get();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
@@ -15,7 +16,10 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Provides access to the MultiverseCore API.
|
||||
@@ -23,6 +27,7 @@ import java.util.Objects;
|
||||
public class MultiverseCoreApi {
|
||||
|
||||
private static MultiverseCoreApi instance;
|
||||
private static final List<Consumer<MultiverseCoreApi>> whenLoadedCallbacks = new ArrayList<>();
|
||||
|
||||
static void init(@NotNull MultiverseCore multiverseCore) {
|
||||
if (instance != null) {
|
||||
@@ -30,6 +35,9 @@ public class MultiverseCoreApi {
|
||||
}
|
||||
instance = new MultiverseCoreApi(multiverseCore.getServiceLocator());
|
||||
Bukkit.getServicesManager().register(MultiverseCoreApi.class, instance, multiverseCore, ServicePriority.Normal);
|
||||
|
||||
whenLoadedCallbacks.forEach(c -> c.accept(instance));
|
||||
whenLoadedCallbacks.clear();
|
||||
}
|
||||
|
||||
static void shutdown() {
|
||||
@@ -37,10 +45,49 @@ public class MultiverseCoreApi {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook your plugin into the MultiverseCoreApi here to ensure you only start using the API after it has been initialized.
|
||||
* Use this if you know your plugin may load before Multiverse-Core is fully initialized.
|
||||
* <br/>
|
||||
* This handy method removes the need for you to check with plugin manager or listen to plugin enable event.
|
||||
* <br/>
|
||||
* Callback will be called immediately if the MultiverseCoreApi has already been initialized.
|
||||
*
|
||||
* @param consumer The callback to execute when the MultiverseCoreApi has been initialized.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static void whenLoaded(@NotNull Consumer<MultiverseCoreApi> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance);
|
||||
} else {
|
||||
whenLoadedCallbacks.add(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the MultiverseCoreApi has been initialized.
|
||||
*
|
||||
* @return True if the MultiverseCoreApi has been initialized, false otherwise
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static boolean isLoaded() {
|
||||
return instance != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MultiverseCoreApi. This will throw an exception if the Multiverse-Core has not been initialized.
|
||||
* <br/>
|
||||
* You can check if the MultiverseCoreApi has been initialized with {@link #isLoaded()} before using this method.
|
||||
* <br/>
|
||||
* Alternatively, you can use {@link #whenLoaded(Consumer)} to hook into the MultiverseCoreApi if your plugin may
|
||||
* load before Multiverse-Core is fully initialized.
|
||||
*
|
||||
* @return The MultiverseCoreApi
|
||||
* @throws IllegalStateException if the MultiverseCoreApi has not been initialized
|
||||
*/
|
||||
public static @NotNull MultiverseCoreApi get() {
|
||||
if (instance == null) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.mvplugins.multiverse.core;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -13,11 +14,16 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.utils.MinecraftTimeFormatter;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
|
||||
@@ -68,32 +74,54 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
@Override
|
||||
public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) {
|
||||
// Split string in to an Array with underscores
|
||||
String[] paramsArray = REPatterns.UNDERSCORE.split(params, 2);
|
||||
List<String> paramsArray = Lists.newArrayList(REPatterns.UNDERSCORE.split(params));
|
||||
|
||||
// No placeholder defined
|
||||
if (paramsArray.length < 1) {
|
||||
warning("No placeholder defined");
|
||||
if (paramsArray.isEmpty()) {
|
||||
warning("No placeholder key defined");
|
||||
return null;
|
||||
}
|
||||
|
||||
final var placeholder = paramsArray[0];
|
||||
Option<LoadedMultiverseWorld> targetWorld;
|
||||
final var placeholder = paramsArray.removeFirst();
|
||||
|
||||
// If no world is defined, use the player's world
|
||||
if (paramsArray.length == 1) {
|
||||
if (!offlinePlayer.isOnline()) {
|
||||
return null;
|
||||
}
|
||||
targetWorld = worldManager.getLoadedWorld(((Player)offlinePlayer).getWorld());
|
||||
} else {
|
||||
targetWorld = worldManager.getLoadedWorld(paramsArray[1]);
|
||||
}
|
||||
String worldName = parseWorldName(offlinePlayer, paramsArray);
|
||||
if (worldName == null) return null;
|
||||
|
||||
// Fail if world is null
|
||||
return targetWorld.map(world -> getWorldPlaceHolderValue(placeholder, world)).getOrNull();
|
||||
return worldManager.getLoadedWorld(worldName)
|
||||
.onEmpty(() -> warning("Multiverse World not found: " + worldName))
|
||||
.map(world -> getWorldPlaceHolderValue(placeholder, paramsArray, world))
|
||||
.getOrNull();
|
||||
}
|
||||
|
||||
private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder, @NotNull LoadedMultiverseWorld world) {
|
||||
private @Nullable String parseWorldName(OfflinePlayer offlinePlayer, List<String> paramsArray) {
|
||||
// No world defined, get from player
|
||||
if (paramsArray.isEmpty()) {
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
return player.getWorld().getName();
|
||||
} else {
|
||||
warning("You must specify a world name for non-player placeholders");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Try get from params
|
||||
String paramWorldName = paramsArray.getLast();
|
||||
if (worldManager.isLoadedWorld(paramWorldName)) {
|
||||
paramsArray.removeLast();
|
||||
return paramWorldName;
|
||||
}
|
||||
|
||||
// Param not a world, fallback to player
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
return player.getWorld().getName();
|
||||
}
|
||||
warning("Multiverse World not found: " + paramWorldName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
// Switch to find what specific placeholder we want
|
||||
switch (placeholder.toLowerCase()) {
|
||||
case "alias" -> {
|
||||
@@ -151,7 +179,22 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return String.valueOf(world.getSeed());
|
||||
}
|
||||
case "time" -> {
|
||||
return String.valueOf(world.getBukkitWorld().map(World::getTime).getOrElse(0L));
|
||||
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.getFirst() : "";
|
||||
long time = world.getBukkitWorld().map(World::getTime).getOrElse(0L);
|
||||
switch (timeFormat) {
|
||||
case "" -> {
|
||||
return String.valueOf(time);
|
||||
}
|
||||
case "12h" -> {
|
||||
return MinecraftTimeFormatter.format12h(time);
|
||||
}
|
||||
case "24h" -> {
|
||||
return MinecraftTimeFormatter.format24h(time);
|
||||
}
|
||||
default -> {
|
||||
return MinecraftTimeFormatter.formatTime(time, timeFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
case "type" -> {
|
||||
return world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
|
||||
|
||||
@@ -33,12 +33,15 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.anchor.MultiverseAnchor;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.IssuerAwareValue;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.MultiverseWorldValue;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
|
||||
import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
@@ -84,7 +87,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
registerAsyncCompletion("commands", this::suggestCommands);
|
||||
registerAsyncCompletion("destinations", this::suggestDestinations);
|
||||
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
|
||||
registerStaticCompletion("environments", suggestEnums(World.Environment.class));
|
||||
registerStaticCompletion("environments", List.of("normal", "nether", "the_end")); // Don't tab complete the "custom" environment
|
||||
registerAsyncCompletion("flags", this::suggestFlags);
|
||||
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules);
|
||||
@@ -129,6 +132,22 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
if (context.hasConfig("playerOnly") && !context.getIssuer().isPlayer()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (context.hasConfig("byIssuerForArg")) {
|
||||
Boolean byIssuerForArg = Try.of(() -> context.getContextValueByName(IssuerAwareValue.class, context.getConfig("byIssuerForArg")))
|
||||
.map(IssuerAwareValue::isByIssuer)
|
||||
.getOrElse(false);
|
||||
if (!byIssuerForArg) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
if (context.hasConfig("notByIssuerForArg")) {
|
||||
Boolean byIssuerForArg = Try.of(() -> context.getContextValueByName(IssuerAwareValue.class, context.getConfig("notByIssuerForArg")))
|
||||
.map(IssuerAwareValue::isByIssuer)
|
||||
.getOrElse(false);
|
||||
if (byIssuerForArg) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
if (context.hasConfig("resolveUntil")) {
|
||||
if (!Try.run(() -> context.getContextValueByName(Object.class, context.getConfig("resolveUntil"))).isSuccess()) {
|
||||
return Collections.emptyList();
|
||||
@@ -195,7 +214,10 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
}
|
||||
|
||||
private Collection<String> suggestDestinations(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(Player[].class))
|
||||
return Try.of(() -> context.getContextValue(PlayerArrayValue.class))
|
||||
.map(PlayerArrayValue::value)
|
||||
.recover(IllegalStateException.class, e -> context.getContextValue(Player[].class))
|
||||
.recover(IllegalStateException.class, e -> new Player[]{context.getContextValue(Player.class)})
|
||||
.map(players -> {
|
||||
if (players.length == 0) {
|
||||
// Most likely console did not specify a player
|
||||
@@ -213,9 +235,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
return destinationsProvider.suggestDestinations(teleporter, deststring).stream()
|
||||
.filter(packet -> corePermissionsChecker
|
||||
.checkDestinationPacketPermission(teleporter, Arrays.asList(players), packet))
|
||||
.map(packet -> packet.destination() instanceof WorldDestination
|
||||
? packet.destinationString()
|
||||
: packet.destination().getIdentifier() + ":" + packet.destinationString())
|
||||
.map(DestinationSuggestionPacket::parsableString)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -248,7 +268,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
private Collection<String> suggestMVConfigValues(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(String.class))
|
||||
.map(propertyName -> config.getStringPropertyHandle()
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET))
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET, context.getSender()))
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@@ -283,7 +303,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
|
||||
private Collection<String> suggestMVWorldPropsName(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> {
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
|
||||
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
|
||||
return world.getStringPropertyHandle().getModifiablePropertyNames(action);
|
||||
}).getOrElse(Collections.emptyList());
|
||||
@@ -291,10 +311,10 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
|
||||
private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> {
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
|
||||
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
|
||||
String propertyName = context.getContextValue(String.class);
|
||||
return world.getStringPropertyHandle().getSuggestedPropertyValue(propertyName, context.getInput(), action);
|
||||
return world.getStringPropertyHandle().getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
|
||||
}).getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@@ -335,7 +355,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
return world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(spawnCategory)
|
||||
.getStringPropertyHandle()
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), action);
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
|
||||
}).getOrElse(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,18 @@ import co.aikar.commands.ACFUtil;
|
||||
import co.aikar.commands.BukkitCommandExecutionContext;
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import co.aikar.commands.MinecraftMessageKeys;
|
||||
import co.aikar.commands.PaperCommandContexts;
|
||||
import co.aikar.commands.contexts.ContextResolver;
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -20,6 +27,10 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.anchor.MultiverseAnchor;
|
||||
import org.mvplugins.multiverse.core.command.context.GameRuleValue;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.IssuerAwareContextBuilder;
|
||||
import org.mvplugins.multiverse.core.command.context.PlayerLocation;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.MultiverseWorldValue;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
@@ -27,6 +38,7 @@ import org.mvplugins.multiverse.core.display.filters.ContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
|
||||
import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
@@ -69,13 +81,16 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
registerContext(GameRule.class, this::parseGameRule);
|
||||
registerContext(GameRuleValue.class, this::parseGameRuleValue);
|
||||
registerContext(GeneratorPlugin.class, this::parseGeneratorPlugin);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld.class, this::parseLoadedMultiverseWorld);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedMultiverseWorldArray);
|
||||
registerIssuerAwareContext(MultiverseWorld.class, this::parseMultiverseWorld);
|
||||
registerIssuerAwareContext(MultiverseWorld[].class, this::parseMultiverseWorldArray);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld.class, loadedMultiverseWorldContextBuilder().generateContext());
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld[].class, loadedMultiverseWorldArrayContextBuilder().generateContext());
|
||||
registerIssuerAwareContext(MultiverseWorld.class, multiverseWorldContextBuilder().generateContext());
|
||||
registerIssuerAwareContext(MultiverseWorldValue.class, multiverseWorldContextBuilder().generateContext(MultiverseWorldValue::new));
|
||||
registerIssuerAwareContext(MultiverseWorld[].class, multiverseWorldArrayContextBuilder().generateContext());
|
||||
registerContext(MultiverseAnchor.class, this::parseMultiverseAnchor);
|
||||
registerIssuerAwareContext(Player.class, this::parsePlayer);
|
||||
registerIssuerAwareContext(Player[].class, this::parsePlayerArray);
|
||||
registerIssuerAwareContext(Player.class, playerContextBuilder().generateContext());
|
||||
registerIssuerAwareContext(Player[].class, playerArrayContextBuilder().generateContext());
|
||||
registerIssuerAwareContext(PlayerArrayValue.class, playerArrayContextBuilder().generateContext(PlayerArrayValue::new));
|
||||
registerIssuerAwareContext(PlayerLocation.class, this::parsePlayerLocation);
|
||||
registerContext(SpawnCategory[].class, this::parseSpawnCategories);
|
||||
}
|
||||
|
||||
@@ -100,8 +115,9 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
throw new InvalidCommandArgument("No destination specified.");
|
||||
}
|
||||
|
||||
return destinationsProvider.parseDestination(destination)
|
||||
.getOrThrow(failure -> MVInvalidCommandArgument.of(failure.getFailureMessage()));
|
||||
return destinationsProvider.parseDestination(context.getSender(), destination)
|
||||
.getOrThrow(failure ->
|
||||
new InvalidCommandArgument(failure.getFailureMessage().formatted(context.getIssuer())));
|
||||
}
|
||||
|
||||
private GameRule<?> parseGameRule(BukkitCommandExecutionContext context) {
|
||||
@@ -149,107 +165,42 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
return generatorPlugin;
|
||||
}
|
||||
|
||||
private LoadedMultiverseWorld parseLoadedMultiverseWorld(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldName = context.getFirstArg();
|
||||
LoadedMultiverseWorld world = getLoadedMultiverseWorld(worldName);
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getLoadedWorld(context.getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World '" + worldName + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
private IssuerAwareContextBuilder<LoadedMultiverseWorld> loadedMultiverseWorldContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<LoadedMultiverseWorld>()
|
||||
.fromPlayer((context, player) -> worldManager.getLoadedWorld(player.getWorld()).getOrNull())
|
||||
.fromInput((context, input) -> getLoadedMultiverseWorld(input))
|
||||
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a loaded Multiverse World."))
|
||||
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a loaded multiverse world. Either specify a multiverse world name or use this command in a loaded multiverse world."))
|
||||
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console."))
|
||||
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a loaded multiverse world."));
|
||||
}
|
||||
|
||||
private LoadedMultiverseWorld[] parseLoadedMultiverseWorldArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
LoadedMultiverseWorld playerWorld = worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new LoadedMultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldStrings = context.getFirstArg();
|
||||
String[] worldNames = worldStrings == null ? new String[0] : REPatterns.COMMA.split(worldStrings);
|
||||
Set<LoadedMultiverseWorld> worlds = new HashSet<>(worldNames.length);
|
||||
for (String worldName : worldNames) {
|
||||
if ("*".equals(worldName)) {
|
||||
worlds.addAll(worldManager.getLoadedWorlds());
|
||||
break;
|
||||
}
|
||||
LoadedMultiverseWorld world = getLoadedMultiverseWorld(worldName);
|
||||
if (world == null) {
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
}
|
||||
worlds.add(world);
|
||||
}
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new LoadedMultiverseWorld[0]);
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
LoadedMultiverseWorld playerWorld = worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new LoadedMultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Worlds '" + worldStrings + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new LoadedMultiverseWorld[0]);
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldStrings + " is not a loaded multiverse world.");
|
||||
private IssuerAwareContextBuilder<LoadedMultiverseWorld[]> loadedMultiverseWorldArrayContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<LoadedMultiverseWorld[]>()
|
||||
.fromPlayer((context, player) -> worldManager.getLoadedWorld(player.getWorld())
|
||||
.map(world -> new LoadedMultiverseWorld[]{world})
|
||||
.getOrNull())
|
||||
.fromInput((context, input) -> {
|
||||
String[] worldNames = input == null ? new String[0] : REPatterns.COMMA.split(input);
|
||||
Set<LoadedMultiverseWorld> worlds = new HashSet<>(worldNames.length);
|
||||
for (String worldName : worldNames) {
|
||||
if ("*".equals(worldName)) {
|
||||
worlds.addAll(worldManager.getLoadedWorlds());
|
||||
break;
|
||||
}
|
||||
LoadedMultiverseWorld world = getLoadedMultiverseWorld(worldName);
|
||||
if (world == null) {
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
}
|
||||
worlds.add(world);
|
||||
}
|
||||
return worlds.isEmpty() ? null : worlds.toArray(new LoadedMultiverseWorld[0]);
|
||||
})
|
||||
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a loaded Multiverse World."))
|
||||
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a loaded multiverse world. Either specify a multiverse world name or use this command in a loaded multiverse world."))
|
||||
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console."))
|
||||
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a loaded multiverse world."));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -259,107 +210,41 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
: worldManager.getLoadedWorld(worldName).getOrNull();
|
||||
}
|
||||
|
||||
private MultiverseWorld parseMultiverseWorld(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldName = context.getFirstArg();
|
||||
MultiverseWorld world = getMultiverseWorld(worldName);
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getWorld(context.getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World '" + worldName + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
private IssuerAwareContextBuilder<MultiverseWorld> multiverseWorldContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<MultiverseWorld>()
|
||||
.fromPlayer((context, player) -> worldManager.getWorld(player.getWorld()).getOrNull())
|
||||
.fromInput((context, input) -> getMultiverseWorld(input))
|
||||
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a Multiverse World."))
|
||||
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."))
|
||||
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a multiverse world. Remember to specify the world name when using this command in console."))
|
||||
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a multiverse world."));
|
||||
}
|
||||
|
||||
private MultiverseWorld[] parseMultiverseWorldArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
MultiverseWorld playerWorld = worldManager.getWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new MultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldStrings = context.getFirstArg();
|
||||
String[] worldNames = worldStrings == null ? new String[0] : REPatterns.COMMA.split(worldStrings);
|
||||
Set<MultiverseWorld> worlds = new HashSet<>(worldNames.length);
|
||||
for (String worldName : worldNames) {
|
||||
if ("*".equals(worldName)) {
|
||||
worlds.addAll(worldManager.getWorlds());
|
||||
break;
|
||||
}
|
||||
MultiverseWorld world = getMultiverseWorld(worldName);
|
||||
if (world == null) {
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
}
|
||||
worlds.add(world);
|
||||
}
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new MultiverseWorld[0]);
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
MultiverseWorld playerWorld = worldManager.getWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new MultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Worlds '" + worldStrings + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new MultiverseWorld[0]);
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldStrings + " is not a loaded multiverse world.");
|
||||
private IssuerAwareContextBuilder<MultiverseWorld[]> multiverseWorldArrayContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<MultiverseWorld[]>()
|
||||
.fromPlayer((context, player) -> worldManager.getWorld(player.getWorld())
|
||||
.map(world -> new MultiverseWorld[]{world})
|
||||
.getOrNull())
|
||||
.fromInput((context, input) -> {
|
||||
String[] worldNames = input == null ? new String[0] : REPatterns.COMMA.split(input);
|
||||
Set<MultiverseWorld> worlds = new HashSet<>(worldNames.length);
|
||||
for (String worldName : worldNames) {
|
||||
if ("*".equals(worldName)) {
|
||||
worlds.addAll(worldManager.getWorlds());
|
||||
break;
|
||||
}
|
||||
MultiverseWorld world = getMultiverseWorld(worldName);
|
||||
if (world == null) {
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a multiverse world.");
|
||||
}
|
||||
worlds.add(world);
|
||||
}
|
||||
return worlds.isEmpty() ? null : worlds.toArray(new MultiverseWorld[0]);
|
||||
})
|
||||
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a Multiverse World."))
|
||||
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."))
|
||||
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a multiverse world. Remember to specify the world name when using this command in console."))
|
||||
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a multiverse world."));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -375,92 +260,110 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("The anchor '" +anchorName + "' does not exist."));
|
||||
}
|
||||
|
||||
private Player parsePlayer(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get player based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return context.getIssuer().getPlayer();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player.");
|
||||
}
|
||||
|
||||
String playerIdentifier = context.getFirstArg();
|
||||
Player player = PlayerFinder.get(playerIdentifier, context.getSender());
|
||||
|
||||
// Get player based on input, fallback to sender if input is not a player
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (player != null) {
|
||||
context.popFirstArg();
|
||||
return player;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return context.getIssuer().getPlayer();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Invalid player: " + playerIdentifier
|
||||
+ ". Either specify an online player or use this command as a player.");
|
||||
}
|
||||
|
||||
// Get player based on input only
|
||||
if (player != null) {
|
||||
context.popFirstArg();
|
||||
return player;
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Player " + playerIdentifier + " not found.");
|
||||
private IssuerAwareContextBuilder<Player> playerContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<Player>()
|
||||
.fromPlayer((context, player) -> player)
|
||||
.fromInput((context, input) -> PlayerFinder.get(input, context.getSender()))
|
||||
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player."))
|
||||
.issuerAwareInputFailMessage((context, input) -> Message.of("Invalid player: " + input + ". Either specify an online player or use this command as a player."))
|
||||
.inputOnlyFailMessage((context, input) -> Message.of("Player " + input + " not found."));
|
||||
}
|
||||
|
||||
private Player[] parsePlayerArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
private IssuerAwareContextBuilder<Player[]> playerArrayContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<Player[]>()
|
||||
.fromPlayer((context, player) -> new Player[]{player})
|
||||
.fromInput((context, input) -> {
|
||||
Player[] players = PlayerFinder.getMulti(input, context.getSender()).toArray(new Player[0]);
|
||||
return (players.length == 0) ? null : players;
|
||||
})
|
||||
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player."))
|
||||
.issuerAwareInputFailMessage((context, input) -> Message.of("Invalid player: " + input + ". Either specify an online player or use this command as a player."))
|
||||
.inputOnlyFailMessage((context, input) -> Message.of("Player " + input + " not found."));
|
||||
}
|
||||
|
||||
// Get player based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return new Player[]{context.getIssuer().getPlayer()};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player.");
|
||||
}
|
||||
private PlayerLocation parsePlayerLocation(BukkitCommandExecutionContext context) {
|
||||
Try<Location> location = Try.of(() -> parseLocationFromInput(context.getFirstArg(), context.getSender()));
|
||||
|
||||
String playerIdentifier = context.getFirstArg();
|
||||
Player[] players = PlayerFinder.getMulti(playerIdentifier, context.getSender()).toArray(new Player[0]);
|
||||
|
||||
// Get player based on input, fallback to sender if input is not a player
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (players.length > 0) {
|
||||
context.popFirstArg();
|
||||
return players;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return new Player[]{context.getIssuer().getPlayer()};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Invalid player: " + playerIdentifier
|
||||
+ ". Either specify an online player or use this command as a player.");
|
||||
}
|
||||
|
||||
// Get player based on input only
|
||||
if (players.length > 0) {
|
||||
if (location.isSuccess()) {
|
||||
context.popFirstArg();
|
||||
return players;
|
||||
return new PlayerLocation(location.get());
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
if (context.getPlayer() != null) {
|
||||
return new PlayerLocation(context.getPlayer().getLocation());
|
||||
}
|
||||
if (context.getFirstArg() == null) {
|
||||
throw new InvalidCommandArgument("You must specify a world location when using this command in console.");
|
||||
}
|
||||
if (location.getCause() instanceof InvalidCommandArgument) {
|
||||
throw (InvalidCommandArgument)location.getCause();
|
||||
}
|
||||
throw new RuntimeException(location.getCause());
|
||||
}
|
||||
|
||||
// copied from ACF
|
||||
private Location parseLocationFromInput(String input, CommandSender sender) {
|
||||
String[] split = REPatterns.COLON.split(input, 2);
|
||||
if (split.length == 0) {
|
||||
throw new InvalidCommandArgument(true);
|
||||
} else if (split.length < 2 && !(sender instanceof Player) && !(sender instanceof BlockCommandSender)) {
|
||||
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_WORLD, new String[0]);
|
||||
} else {
|
||||
Location sourceLoc = null;
|
||||
String world;
|
||||
String rest;
|
||||
if (split.length == 2) {
|
||||
world = split[0];
|
||||
rest = split[1];
|
||||
} else if (sender instanceof Player) {
|
||||
sourceLoc = ((Player)sender).getLocation();
|
||||
world = sourceLoc.getWorld().getName();
|
||||
rest = split[0];
|
||||
} else {
|
||||
if (!(sender instanceof BlockCommandSender)) {
|
||||
throw new InvalidCommandArgument(true);
|
||||
}
|
||||
|
||||
sourceLoc = ((BlockCommandSender)sender).getBlock().getLocation();
|
||||
world = sourceLoc.getWorld().getName();
|
||||
rest = split[0];
|
||||
}
|
||||
|
||||
boolean rel = rest.startsWith("~");
|
||||
split = REPatterns.COMMA.split(rel ? rest.substring(1) : rest);
|
||||
if (split.length < 3) {
|
||||
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ, new String[0]);
|
||||
} else {
|
||||
Double x = ACFUtil.parseDouble(split[0], rel ? (double)0.0F : null);
|
||||
Double y = ACFUtil.parseDouble(split[1], rel ? (double)0.0F : null);
|
||||
Double z = ACFUtil.parseDouble(split[2], rel ? (double)0.0F : null);
|
||||
if (sourceLoc != null && rel) {
|
||||
x = x + sourceLoc.getX();
|
||||
y = y + sourceLoc.getY();
|
||||
z = z + sourceLoc.getZ();
|
||||
} else if (rel) {
|
||||
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_CONSOLE_NOT_RELATIVE, new String[0]);
|
||||
}
|
||||
|
||||
if (x != null && y != null && z != null) {
|
||||
World worldObj = Bukkit.getWorld(world);
|
||||
if (worldObj == null) {
|
||||
throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD, new String[0]);
|
||||
} else if (split.length >= 5) {
|
||||
Float yaw = ACFUtil.parseFloat(split[3]);
|
||||
Float pitch = ACFUtil.parseFloat(split[4]);
|
||||
if (pitch != null && yaw != null) {
|
||||
return new Location(worldObj, x, y, z, yaw, pitch);
|
||||
} else {
|
||||
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ, new String[0]);
|
||||
}
|
||||
} else {
|
||||
return new Location(worldObj, x, y, z);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ, new String[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new InvalidCommandArgument("Player " + playerIdentifier + " not found.");
|
||||
}
|
||||
|
||||
private SpawnCategory[] parseSpawnCategories(BukkitCommandExecutionContext context) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.command;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.MessageKeys;
|
||||
import co.aikar.commands.MessageType;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -9,6 +8,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
|
||||
public class MVCommandIssuer extends BukkitCommandIssuer {
|
||||
|
||||
@@ -24,8 +24,13 @@ public class MVCommandIssuer extends BukkitCommandIssuer {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageInternal(String message) {
|
||||
ChatTextFormatter.sendFormattedMessage(getIssuer(), message);
|
||||
}
|
||||
|
||||
public void sendError(String message, MessageReplacement... replacements) {
|
||||
sendMessage(MessageType.INFO, message, replacements);
|
||||
sendMessage(MessageType.ERROR, message, replacements);
|
||||
}
|
||||
|
||||
public void sendSyntax(String message, MessageReplacement... replacements) {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.mvplugins.multiverse.core.command.context;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Wrapper for context that get location from player if executed by player,
|
||||
* else requires user input of location coordinates.
|
||||
*
|
||||
* @param value The location
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public record PlayerLocation(Location value) {
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
package org.mvplugins.multiverse.core.command.context.issueraware;
|
||||
|
||||
import co.aikar.commands.BukkitCommandExecutionContext;
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import co.aikar.commands.contexts.IssuerAwareContextResolver;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Reusable logic of issuer only and issuer aware context resolvers
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public final class IssuerAwareContextBuilder<T> {
|
||||
|
||||
private BiFunction<BukkitCommandExecutionContext, Player, T> fromPlayer;
|
||||
private BiFunction<BukkitCommandExecutionContext, String, T> fromInput;
|
||||
private Function<BukkitCommandExecutionContext, Message> issuerOnlyFailMessage = context -> Message.of("This command can only be used by a player.");
|
||||
private BiFunction<BukkitCommandExecutionContext, Player, Message> issuerAwarePlayerFailMessage = (context, player) -> Message.of("Unable to resolve context for player '" + player.getName() + "'.");
|
||||
private BiFunction<BukkitCommandExecutionContext, String, Message> issuerAwareInputFailMessage = (context, input) -> Message.of("Unable to resolve context for input '" + input + "'.");
|
||||
private BiFunction<BukkitCommandExecutionContext, String, Message> inputOnlyFailMessage = (context, input) -> Message.of("Unable to resolve context for input '" + input + "'.");
|
||||
|
||||
public IssuerAwareContextBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse value from player itself.
|
||||
*
|
||||
* @param fromInput the parsing function
|
||||
* @return The same builder for chaining
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public IssuerAwareContextBuilder<T> fromPlayer(BiFunction<BukkitCommandExecutionContext, Player, T> fromInput) {
|
||||
this.fromPlayer = fromInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse value from command input string.
|
||||
*
|
||||
* @param fromInput the parsing function
|
||||
* @return The same builder for chaining
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public IssuerAwareContextBuilder<T> fromInput(BiFunction<BukkitCommandExecutionContext, String, T> fromInput) {
|
||||
this.fromInput = fromInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When getting value fails as issuer is not a player.
|
||||
*
|
||||
* @param issuerOnlyFailMessage The message
|
||||
* @return The same builder for chaining
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public IssuerAwareContextBuilder<T> issuerOnlyFailMessage(Function<BukkitCommandExecutionContext, Message> issuerOnlyFailMessage) {
|
||||
this.issuerOnlyFailMessage = issuerOnlyFailMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When getting value fails as player cannot parse the value.
|
||||
*
|
||||
* @param issuerAwarePlayerFailMessage The message
|
||||
* @return The same builder for chaining
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public IssuerAwareContextBuilder<T> issuerAwarePlayerFailMessage(BiFunction<BukkitCommandExecutionContext, Player, Message> issuerAwarePlayerFailMessage) {
|
||||
this.issuerAwarePlayerFailMessage = issuerAwarePlayerFailMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When getting value fails as input cannot parse the value.
|
||||
*
|
||||
* @param issuerAwareInputFailMessage The message
|
||||
* @return The same builder for chaining
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public IssuerAwareContextBuilder<T> issuerAwareInputFailMessage(BiFunction<BukkitCommandExecutionContext, String, Message> issuerAwareInputFailMessage) {
|
||||
this.issuerAwareInputFailMessage = issuerAwareInputFailMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When getting value fails as input cannot parse the value.
|
||||
*
|
||||
* @param inputOnlyFailMessage The message
|
||||
* @return The same builder for chaining
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public IssuerAwareContextBuilder<T> inputOnlyFailMessage(BiFunction<BukkitCommandExecutionContext, String, Message> inputOnlyFailMessage) {
|
||||
this.inputOnlyFailMessage = inputOnlyFailMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the issuer aware context resolver logic.
|
||||
*
|
||||
* @return The generated context resolver
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public IssuerAwareContextResolver<T, BukkitCommandExecutionContext> generateContext() {
|
||||
Objects.requireNonNull(fromPlayer);
|
||||
Objects.requireNonNull(fromInput);
|
||||
Objects.requireNonNull(issuerOnlyFailMessage);
|
||||
Objects.requireNonNull(issuerAwarePlayerFailMessage);
|
||||
Objects.requireNonNull(issuerAwareInputFailMessage);
|
||||
Objects.requireNonNull(inputOnlyFailMessage);
|
||||
|
||||
return context -> {
|
||||
BukkitCommandIssuer issuer = context.getIssuer();
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (issuer.isPlayer()) {
|
||||
T result = fromPlayer.apply(context, issuer.getPlayer());
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new InvalidCommandArgument(issuerOnlyFailMessage.apply(context).formatted(issuer));
|
||||
}
|
||||
|
||||
String input = context.getFirstArg();
|
||||
T result = fromInput.apply(context, input);
|
||||
if (result != null) {
|
||||
context.popFirstArg();
|
||||
return result;
|
||||
}
|
||||
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (issuer.isPlayer()) {
|
||||
Player player = issuer.getPlayer();
|
||||
result = fromPlayer.apply(context, player);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
throw new InvalidCommandArgument(issuerAwarePlayerFailMessage.apply(context, player).formatted(issuer));
|
||||
}
|
||||
throw new InvalidCommandArgument(issuerAwareInputFailMessage.apply(context, input).formatted(issuer));
|
||||
}
|
||||
|
||||
throw new InvalidCommandArgument(inputOnlyFailMessage.apply(context, input).formatted(issuer));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the issuer aware context resolver logic, with marking of whether the value was resolved from player or input.
|
||||
*
|
||||
* @param createValue The function to create the {@link IssuerAwareValue} value
|
||||
* @param <I> The type of the value
|
||||
* @return The generated context resolver
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public <I extends IssuerAwareValue> IssuerAwareContextResolver<I, BukkitCommandExecutionContext> generateContext(BiFunction<Boolean, T, I> createValue) {
|
||||
// todo: This is a copy and paste from above
|
||||
|
||||
Objects.requireNonNull(fromPlayer);
|
||||
Objects.requireNonNull(fromInput);
|
||||
Objects.requireNonNull(issuerOnlyFailMessage);
|
||||
Objects.requireNonNull(issuerAwarePlayerFailMessage);
|
||||
Objects.requireNonNull(issuerAwareInputFailMessage);
|
||||
Objects.requireNonNull(inputOnlyFailMessage);
|
||||
|
||||
return context -> {
|
||||
BukkitCommandIssuer issuer = context.getIssuer();
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (issuer.isPlayer()) {
|
||||
T result = fromPlayer.apply(context, issuer.getPlayer());
|
||||
if (result != null) {
|
||||
return createValue.apply(true, result);
|
||||
}
|
||||
}
|
||||
throw new InvalidCommandArgument(issuerOnlyFailMessage.apply(context).formatted(issuer));
|
||||
}
|
||||
|
||||
String input = context.getFirstArg();
|
||||
T result = fromInput.apply(context, input);
|
||||
if (result != null) {
|
||||
context.popFirstArg();
|
||||
return createValue.apply(false, result);
|
||||
}
|
||||
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (issuer.isPlayer()) {
|
||||
Player player = issuer.getPlayer();
|
||||
result = fromPlayer.apply(context, player);
|
||||
if (result != null) {
|
||||
return createValue.apply(true, result);
|
||||
}
|
||||
throw new InvalidCommandArgument(issuerAwarePlayerFailMessage.apply(context, player).formatted(issuer));
|
||||
}
|
||||
throw new InvalidCommandArgument(issuerAwareInputFailMessage.apply(context, input).formatted(issuer));
|
||||
}
|
||||
|
||||
throw new InvalidCommandArgument(inputOnlyFailMessage.apply(context, input).formatted(issuer));
|
||||
};
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.mvplugins.multiverse.core.command.context.issueraware;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Base class for issuer aware values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public abstract class IssuerAwareValue {
|
||||
protected final boolean byIssuer;
|
||||
|
||||
/**
|
||||
* Constructor to create an issuer aware value
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
protected IssuerAwareValue(boolean byIssuer) {
|
||||
this.byIssuer = byIssuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the value is by issuer or input
|
||||
*
|
||||
* @return true if by issuer, false if by input
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public boolean isByIssuer() {
|
||||
return byIssuer;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package org.mvplugins.multiverse.core.command.context.issueraware;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Issuer aware value wrapper for {@link MultiverseWorld}
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public final class MultiverseWorldValue extends IssuerAwareValue {
|
||||
|
||||
private final MultiverseWorld world;
|
||||
|
||||
/**
|
||||
* Constructor for issuer aware value
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public MultiverseWorldValue(boolean byIssuer, MultiverseWorld world) {
|
||||
super(byIssuer);
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
/**
|
||||
* The containing world wrapped
|
||||
*
|
||||
* @return wrapped world
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public MultiverseWorld value() {
|
||||
return world;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package org.mvplugins.multiverse.core.command.context.issueraware;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Issuer aware value wrapper for {@link Player} array
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public final class PlayerArrayValue extends IssuerAwareValue {
|
||||
|
||||
private final Player[] players;
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public PlayerArrayValue(boolean byIssuer, Player[] players) {
|
||||
super(byIssuer);
|
||||
this.players = players;
|
||||
}
|
||||
|
||||
/**
|
||||
* The containing player array wrapped
|
||||
*
|
||||
* @return wrapped player array
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Player[] value() {
|
||||
return players;
|
||||
}
|
||||
}
|
||||
@@ -65,12 +65,13 @@ class ConfigCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
private void updateConfigValue(MVCommandIssuer issuer, String name, String value) {
|
||||
config.getStringPropertyHandle().setPropertyString(name, value)
|
||||
var stringPropertyHandle = config.getStringPropertyHandle();
|
||||
stringPropertyHandle.setPropertyString(issuer.getIssuer(), name, value)
|
||||
.onSuccess(ignore -> {
|
||||
config.save();
|
||||
issuer.sendMessage(MVCorei18n.CONFIG_SET_SUCCESS,
|
||||
Replace.NAME.with(name),
|
||||
Replace.VALUE.with(value));
|
||||
Replace.VALUE.with(stringPropertyHandle.getProperty(name).getOrNull()));
|
||||
})
|
||||
.onFailure(e -> issuer.sendMessage(MVCorei18n.CONFIG_SET_ERROR,
|
||||
Replace.NAME.with(name),
|
||||
|
||||
@@ -57,7 +57,7 @@ class DeleteCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("delete")
|
||||
@CommandPermission("multiverse.core.delete")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.delete.description}")
|
||||
void onDeleteCommand(
|
||||
|
||||
@@ -42,7 +42,6 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
@Subcommand("gamerule|rule")
|
||||
class GameruleCommand extends CoreCommand {
|
||||
|
||||
private final PageFilterFlags flags;
|
||||
@@ -52,7 +51,7 @@ class GameruleCommand extends CoreCommand {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Subcommand("set")
|
||||
@Subcommand("gamerule|rule set")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules @gamerulesvalues @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> <Gamerule value> [World or *]")
|
||||
@@ -103,7 +102,7 @@ class GameruleCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes,unchecked")
|
||||
@Subcommand("reset")
|
||||
@Subcommand("gamerule|rule reset")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> [World or *]")
|
||||
@@ -145,9 +144,9 @@ class GameruleCommand extends CoreCommand {
|
||||
}
|
||||
}
|
||||
|
||||
@Subcommand("list")
|
||||
@Subcommand("gamerule|rule list")
|
||||
@CommandPermission("multiverse.core.gamerule.list")
|
||||
@CommandCompletion("@mvworlds|@flags:groupName=mvgamerulecommand @flags:groupName=" + PageFilterFlags.NAME)
|
||||
@CommandCompletion("@mvworlds|@flags:groupName=" + PageFilterFlags.NAME + " @flags:groupName=" + PageFilterFlags.NAME)
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.gamerule.list.description}")
|
||||
void onGameruleListCommand(
|
||||
@@ -220,6 +219,11 @@ class GameruleCommand extends CoreCommand {
|
||||
super.onGameruleSetCommand(issuer, gamerule, gameRuleValue, worlds);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onGameruleResetCommand(MVCommandIssuer issuer, GameRule gamerule, LoadedMultiverseWorld[] worlds) {
|
||||
super.onGameruleResetCommand(issuer, gamerule, worlds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvrules|mvgamerules")
|
||||
void onGameruleListCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String[] flags) {
|
||||
|
||||
@@ -68,7 +68,8 @@ class ImportCommand extends CoreCommand {
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.generator(parsedFlags.flagValue(flags.generator, String.class))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn)))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
|
||||
.onSuccess(newWorld -> {
|
||||
Logging.fine("World import success: " + newWorld);
|
||||
issuer.sendInfo(MVCorei18n.IMPORT_SUCCESS, Replace.WORLD.with(newWorld.getName()));
|
||||
@@ -105,6 +106,10 @@ class ImportCommand extends CoreCommand {
|
||||
private final CommandValueFlag<String> biome = flag(CommandValueFlag.builder("--biome", String.class)
|
||||
.addAlias("-b")
|
||||
.build());
|
||||
|
||||
private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check")
|
||||
.addAlias("-f")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -3,9 +3,8 @@ package org.mvplugins.multiverse.core.commands;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Conditions;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Single;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -15,19 +14,26 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
|
||||
@Service
|
||||
class LoadCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final LoadCommand.Flags flags;
|
||||
|
||||
@Inject
|
||||
LoadCommand(@NotNull WorldManager worldManager) {
|
||||
LoadCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) {
|
||||
this.worldManager = worldManager;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Subcommand("load")
|
||||
@@ -38,13 +44,19 @@ class LoadCommand extends CoreCommand {
|
||||
void onLoadCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Single
|
||||
@Conditions("worldname:scope=unloaded")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.load.world.description}")
|
||||
String worldName) {
|
||||
issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(worldName));
|
||||
worldManager.loadWorld(worldName)
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(world.getName()));
|
||||
worldManager.loadWorld(LoadWorldOptions.world(world)
|
||||
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
|
||||
.onSuccess(newWorld -> {
|
||||
Logging.fine("World load success: " + newWorld);
|
||||
issuer.sendInfo(MVCorei18n.LOAD_SUCCESS, Replace.WORLD.with(newWorld.getName()));
|
||||
@@ -54,17 +66,32 @@ class LoadCommand extends CoreCommand {
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvunload";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check")
|
||||
.addAlias("-f")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class LegacyAlias extends LoadCommand implements LegacyAliasCommand {
|
||||
@Inject
|
||||
LegacyAlias(@NotNull WorldManager worldManager) {
|
||||
super(worldManager);
|
||||
LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) {
|
||||
super(worldManager, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvload")
|
||||
void onLoadCommand(MVCommandIssuer issuer, String worldName) {
|
||||
super.onLoadCommand(issuer, worldName);
|
||||
void onLoadCommand(MVCommandIssuer issuer, MultiverseWorld world, String[] flagArray) {
|
||||
super.onLoadCommand(issuer, world, flagArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,27 +10,32 @@ import co.aikar.commands.annotation.Single;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.MultiverseWorldValue;
|
||||
import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.*;
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
class ModifyCommand extends CoreCommand {
|
||||
|
||||
private static final Map<String, String> REMOVED_PROPERTIES = Map.of(
|
||||
"animals", "/mv entity-spawn-config modify [world] animal set spawn <true|false>",
|
||||
"monsters", "/mv entity-spawn-config modify [world] monster set spawn <true|false>"
|
||||
);
|
||||
|
||||
private final WorldManager worldManager;
|
||||
|
||||
@Inject
|
||||
@@ -40,7 +45,10 @@ class ModifyCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("modify")
|
||||
@CommandPermission("multiverse.core.modify")
|
||||
@CommandCompletion("@mvworlds:scope=both @propsmodifyaction @mvworldpropsname @mvworldpropsvalue")
|
||||
@CommandCompletion("@mvworlds:scope=both|@propsmodifyaction:byIssuerForArg=arg1 " +
|
||||
"@propsmodifyaction:notByIssuerForArg=arg1|@mvworldpropsname:byIssuerForArg=arg1 " +
|
||||
"@mvworldpropsname:notByIssuerForArg=arg1|@mvworldpropsvalue:byIssuerForArg=arg1 " +
|
||||
"@mvworldpropsvalue:notByIssuerForArg=arg1")
|
||||
@Syntax("[world] <set|add|remove|reset> <property> <value>")
|
||||
@Description("{@@mv-core.modify.description}")
|
||||
void onModifyCommand(// SUPPRESS CHECKSTYLE: ParameterNumber
|
||||
@@ -49,7 +57,7 @@ class ModifyCommand extends CoreCommand {
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.modify.world.description}")
|
||||
@NotNull MultiverseWorld world,
|
||||
@NotNull MultiverseWorldValue worldValue,
|
||||
|
||||
@Syntax("<set|add|remove|reset>")
|
||||
@Description("")
|
||||
@@ -64,6 +72,15 @@ class ModifyCommand extends CoreCommand {
|
||||
@Syntax("[value]")
|
||||
@Description("{@@mv-core.modify.value.description}")
|
||||
@Nullable String propertyValue) {
|
||||
if (REMOVED_PROPERTIES.containsKey(propertyName)) {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_PROPERTYREMOVED,
|
||||
replace("{property}").with(propertyName),
|
||||
replace("{replacement}").with(REMOVED_PROPERTIES.get(propertyName)));
|
||||
return;
|
||||
}
|
||||
|
||||
MultiverseWorld world = worldValue.value();
|
||||
|
||||
if (action.isRequireValue() && propertyValue == null) {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_SPECIFYVALUE,
|
||||
replace("{action}").with(action.name().toLowerCase()),
|
||||
@@ -77,7 +94,7 @@ class ModifyCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
StringPropertyHandle worldPropertyHandle = world.getStringPropertyHandle();
|
||||
worldPropertyHandle.modifyPropertyString(propertyName, propertyValue, action).onSuccess(ignore -> {
|
||||
worldPropertyHandle.modifyPropertyString(issuer.getIssuer(), propertyName, propertyValue, action).onSuccess(ignore -> {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_SUCCESS,
|
||||
replace("{action}").with(action.name().toLowerCase()),
|
||||
replace("{property}").with(propertyName),
|
||||
@@ -111,7 +128,7 @@ class ModifyCommand extends CoreCommand {
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvmodify|mvm")
|
||||
void onModifyCommand(MVCommandIssuer issuer, @NotNull MultiverseWorld world, @NotNull PropertyModifyAction action, @NotNull String propertyName, @Nullable String propertyValue) {
|
||||
void onModifyCommand(MVCommandIssuer issuer, @NotNull MultiverseWorldValue world, @NotNull PropertyModifyAction action, @NotNull String propertyName, @Nullable String propertyValue) {
|
||||
super.onModifyCommand(issuer, world, action, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
@@ -23,19 +25,20 @@ import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
|
||||
|
||||
@Service
|
||||
class RemoveCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
private final RemovePlayerFlags flags;
|
||||
private final Flags flags;
|
||||
|
||||
@Inject
|
||||
RemoveCommand(
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
@NotNull RemovePlayerFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
this.worldManager = worldManager;
|
||||
this.playerWorldTeleporter = playerWorldTeleporter;
|
||||
@@ -44,7 +47,7 @@ class RemoveCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("remove")
|
||||
@CommandPermission("multiverse.core.remove")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.remove.description}")
|
||||
void onRemoveCommand(
|
||||
@@ -64,12 +67,14 @@ class RemoveCommand extends CoreCommand {
|
||||
? worldManager.getLoadedWorld(world).map(playerWorldTeleporter::removeFromWorld).getOrElse(AsyncAttemptsAggregate::emptySuccess)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> doWorldRemoving(issuer, world))
|
||||
future.onSuccess(() -> doWorldRemoving(issuer, world, parsedFlags))
|
||||
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
|
||||
}
|
||||
|
||||
private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world) {
|
||||
worldManager.removeWorld(world)
|
||||
private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
worldManager.removeWorld(RemoveWorldOptions.world(world)
|
||||
.saveBukkitWorld(!parsedFlags.hasFlag(flags.noSave))
|
||||
.unloadBukkitWorld(!parsedFlags.hasFlag(flags.noUnloadBukkitWorld)))
|
||||
.onSuccess(removedWorldName -> {
|
||||
Logging.fine("World remove success: " + removedWorldName);
|
||||
issuer.sendInfo(MVCorei18n.REMOVE_SUCCESS, Replace.WORLD.with(removedWorldName));
|
||||
@@ -79,13 +84,32 @@ class RemoveCommand extends CoreCommand {
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvremove";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world")
|
||||
.addAlias("-b")
|
||||
.build());
|
||||
|
||||
private final CommandFlag noSave = flag(CommandFlag.builder("--no-save")
|
||||
.addAlias("-n")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class LegacyAlias extends RemoveCommand implements LegacyAliasCommand {
|
||||
@Inject
|
||||
LegacyAlias(
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
RemovePlayerFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
super(worldManager, playerWorldTeleporter, flags);
|
||||
}
|
||||
|
||||
@@ -1,76 +1,95 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.context.PlayerLocation;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.UnsafeFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@Service
|
||||
class SetSpawnCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final BlockSafety blockSafety;
|
||||
private final UnsafeFlags flags;
|
||||
|
||||
@Inject
|
||||
SetSpawnCommand(@NotNull WorldManager worldManager) {
|
||||
SetSpawnCommand(@NotNull WorldManager worldManager, @NotNull BlockSafety blockSafety, @NotNull UnsafeFlags flags) {
|
||||
this.worldManager = worldManager;
|
||||
this.blockSafety = blockSafety;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@CommandAlias("mvsetspawn")
|
||||
@Subcommand("setspawn")
|
||||
@CommandPermission("multiverse.core.spawn.set")
|
||||
@Syntax("[x],[y],[z],[pitch],[yaw]")
|
||||
@CommandCompletion("@flags:groupName=" + UnsafeFlags.NAME + " @flags:resolveUntil=arg1,groupName=" + UnsafeFlags.NAME)
|
||||
@Syntax("[worldname:x,y,z[,pitch,yaw]] [--unsafe]")
|
||||
@Description("{@@mv-core.setspawn.description}")
|
||||
void onSetSpawnCommand(
|
||||
BukkitCommandIssuer issuer,
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("[worldname:x,y,z[,pitch,yaw]]")
|
||||
@Description("{@@mv-core.setspawn.location.description}")
|
||||
PlayerLocation playerLocation,
|
||||
|
||||
@Optional
|
||||
@Syntax("<location>")
|
||||
@Description("{@@mv-core.setspawn.location.description}")
|
||||
Location location) {
|
||||
Option.of(location).orElse(() -> {
|
||||
if (issuer.isPlayer()) {
|
||||
return Option.of(issuer.getPlayer().getLocation());
|
||||
}
|
||||
return Option.none();
|
||||
}).peek(finalLocation ->
|
||||
worldManager.getLoadedWorld(finalLocation.getWorld())
|
||||
.peek(mvWorld -> mvWorld.setSpawnLocation(finalLocation)
|
||||
.onSuccess(ignore -> issuer.sendMessage(
|
||||
"Successfully set spawn in " + mvWorld.getName() + " to "
|
||||
+ prettyLocation(mvWorld.getSpawnLocation())))
|
||||
.onFailure(e -> issuer.sendMessage(e.getLocalizedMessage())))
|
||||
.onEmpty(() -> issuer.sendMessage("That world is not loaded or does not exist!")))
|
||||
.onEmpty(() -> issuer.sendMessage("You must specify a location in the format: worldname:x,y,z"));
|
||||
@Syntax("[--unsafe]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
Location location = playerLocation.value();
|
||||
|
||||
if (!parsedFlags.hasFlag(flags.unsafe) && !blockSafety.canSpawnAtLocationSafely(location)) {
|
||||
issuer.sendMessage(MVCorei18n.SETSPAWN_UNSAFE);
|
||||
return;
|
||||
}
|
||||
|
||||
worldManager.getLoadedWorld(location.getWorld())
|
||||
.peek(mvWorld -> mvWorld.setSpawnLocation(location)
|
||||
.flatMap(ignore -> worldManager.saveWorldsConfig())
|
||||
.onSuccess(ignore -> issuer.sendMessage(MVCorei18n.SETSPAWN_SUCCESS,
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.LOCATION.with(prettyLocation(location))))
|
||||
.onFailure(e -> issuer.sendMessage(MVCorei18n.SETSPAWN_FAILED,
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.ERROR.with(e))))
|
||||
.onEmpty(() -> issuer.sendMessage(MVCorei18n.SETSPAWN_NOTMVWORLD,
|
||||
Replace.WORLD.with(location.getWorld().getName())));
|
||||
}
|
||||
|
||||
private String prettyLocation(Location location) {
|
||||
return location.getX() + ", " + location.getY() + ", " + location.getZ() + ". pitch:" + location.getPitch()
|
||||
+ ", yaw:" + location.getYaw();
|
||||
return "%.2f, %.2f, %.2f, pitch:%.2f, yaw:%.2f"
|
||||
.formatted(location.getX(), location.getY(), location.getZ(), location.getPitch(), location.getYaw());
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class LegacyAlias extends SetSpawnCommand implements LegacyAliasCommand {
|
||||
@Inject
|
||||
LegacyAlias(@NotNull WorldManager worldManager) {
|
||||
super(worldManager);
|
||||
LegacyAlias(@NotNull WorldManager worldManager, @NotNull BlockSafety blockSafety, @NotNull UnsafeFlags flags) {
|
||||
super(worldManager, blockSafety, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvss")
|
||||
void onSetSpawnCommand(BukkitCommandIssuer issuer, Location location) {
|
||||
super.onSetSpawnCommand(issuer, location);
|
||||
void onSetSpawnCommand(MVCommandIssuer issuer, PlayerLocation location, String[] flagArray) {
|
||||
super.onSetSpawnCommand(issuer, location, flagArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
@CommandAlias("mvspawn")
|
||||
@Subcommand("spawn")
|
||||
@CommandPermission("@mvspawn")
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvspawnother|@flags:groupName=" + UnsafeFlags.NAME + ",resolveUntil=arg1"
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvspawnother|@flags:resolveUntil=arg1,groupName=" + UnsafeFlags.NAME
|
||||
+ " @flags:groupName=" + UnsafeFlags.NAME)
|
||||
@Syntax("[player]")
|
||||
@Description("{@@mv-core.spawn.description}")
|
||||
@@ -108,7 +108,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
safetyTeleporter.to(mvWorld.getSpawnLocation())
|
||||
.by(issuer)
|
||||
.checkSafety(checkSafety)
|
||||
.teleport(entity)
|
||||
.teleportSingle(entity)
|
||||
.onSuccess(() -> issuer.sendInfo(MVCorei18n.SPAWN_SUCCESS,
|
||||
Replace.PLAYER.with(entity.equals(issuer.getPlayer())
|
||||
? Message.of(MVCorei18n.GENERIC_YOU)
|
||||
@@ -119,7 +119,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
? Message.of(MVCorei18n.GENERIC_YOU)
|
||||
: Message.of(entity.getName())),
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.REASON.with(failure.getFailureMessage())));
|
||||
Replace.REASON.with(failure.getFirst().getFailureMessage())));
|
||||
}
|
||||
|
||||
private void handleMultiTeleport(MVCommandIssuer issuer, LoadedMultiverseWorld mvWorld,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
@@ -20,8 +18,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.UnsafeFlags;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
@@ -31,7 +28,6 @@ import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportFailureReason;
|
||||
|
||||
@Service
|
||||
final class TeleportCommand extends CoreCommand {
|
||||
@@ -57,10 +53,9 @@ final class TeleportCommand extends CoreCommand {
|
||||
@CommandAlias("mvtp")
|
||||
@Subcommand("teleport|tp")
|
||||
@CommandPermission("@mvteleport")
|
||||
@CommandCompletion(
|
||||
"@destinations:playerOnly|@playersarray:checkPermissions=@mvteleportother "
|
||||
+ "@destinations:othersOnly|@flags:groupName=" + UnsafeFlags.NAME + ",resolveUntil=arg2 "
|
||||
+ "@flags:groupName=" + UnsafeFlags.NAME)
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvteleportother|@destinations:byIssuerForArg=arg1 "
|
||||
+ "@destinations:notByIssuerForArg=arg1|@flags:byIssuerForArg=arg1,groupName=" + UnsafeFlags.NAME + " "
|
||||
+ "@flags:notByIssuerForArg=arg1,groupName=" + UnsafeFlags.NAME)
|
||||
@Syntax("[player] <destination> [--unsafe]")
|
||||
@Description("{@@mv-core.teleport.description}")
|
||||
void onTeleportCommand(
|
||||
@@ -69,7 +64,7 @@ final class TeleportCommand extends CoreCommand {
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[player]")
|
||||
@Description("{@@mv-core.teleport.player.description}")
|
||||
Player[] players,
|
||||
PlayerArrayValue playersValue,
|
||||
|
||||
@Syntax("<destination>")
|
||||
@Description("{@@mv-core.teleport.destination.description}")
|
||||
@@ -79,6 +74,7 @@ final class TeleportCommand extends CoreCommand {
|
||||
@Syntax("[--unsafe]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
Player[] players = playersValue.value();
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
if (players.length == 1) {
|
||||
@@ -105,14 +101,21 @@ final class TeleportCommand extends CoreCommand {
|
||||
safetyTeleporter.to(destination)
|
||||
.by(issuer)
|
||||
.checkSafety(!parsedFlags.hasFlag(flags.unsafe) && destination.checkTeleportSafety())
|
||||
.teleport(player)
|
||||
.passengerMode(config.getPassengerMode())
|
||||
.teleportSingle(player)
|
||||
.onSuccess(() -> issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(getYouOrName(issuer, player)),
|
||||
Replace.DESTINATION.with(destination.toString())))
|
||||
.onFailure(failure -> issuer.sendError(MVCorei18n.TELEPORT_FAILED,
|
||||
Replace.PLAYER.with(getYouOrName(issuer, player)),
|
||||
Replace.DESTINATION.with(destination.toString()),
|
||||
Replace.REASON.with(failure.getFailureMessage())));
|
||||
.onFailureCount(reasonsCountMap -> {
|
||||
for (var entry : reasonsCountMap.entrySet()) {
|
||||
Logging.finer("Failed to teleport %s players to %s: %s",
|
||||
entry.getValue(), destination, entry.getKey());
|
||||
issuer.sendError(MVCorei18n.TELEPORT_FAILED,
|
||||
Replace.PLAYER.with(player.getName()),
|
||||
Replace.DESTINATION.with(destination.toString()),
|
||||
Replace.REASON.with(Message.of(entry.getKey())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Message getYouOrName(MVCommandIssuer issuer, Player player) {
|
||||
@@ -131,6 +134,7 @@ final class TeleportCommand extends CoreCommand {
|
||||
safetyTeleporter.to(destination)
|
||||
.by(issuer)
|
||||
.checkSafety(!parsedFlags.hasFlag(flags.unsafe) && destination.checkTeleportSafety())
|
||||
.passengerMode(config.getPassengerMode())
|
||||
.teleport(List.of(players))
|
||||
.onSuccessCount(successCount -> issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(successCount + " players"),
|
||||
|
||||
@@ -12,6 +12,7 @@ import jakarta.inject.Provider;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -28,6 +29,8 @@ import org.mvplugins.multiverse.core.config.migration.action.MoveMigratorAction;
|
||||
import org.mvplugins.multiverse.core.config.migration.VersionMigrator;
|
||||
import org.mvplugins.multiverse.core.config.migration.action.SetMigratorAction;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.teleportation.PassengerMode;
|
||||
import org.mvplugins.multiverse.core.teleportation.PassengerModes;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder.DimensionFormat;
|
||||
|
||||
@Service
|
||||
@@ -187,6 +190,26 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.enforceFlight);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnRate(boolean applyEntitySpawnRate) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnRate, applyEntitySpawnRate);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getApplyEntitySpawnRate() {
|
||||
return configHandle.get(configNodes.applyEntitySpawnRate);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnLimit(boolean applyEntitySpawnLimit) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnLimit, applyEntitySpawnLimit);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getApplyEntitySpawnLimit() {
|
||||
return configHandle.get(configNodes.applyEntitySpawnLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -231,6 +254,31 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.useFinerTeleportPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the passenger mode
|
||||
*
|
||||
* @param passengerMode The passenger mode
|
||||
* @return The set result
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> setPassengerMode(PassengerModes passengerMode) {
|
||||
return configHandle.set(configNodes.passengerMode, passengerMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the passenger mode
|
||||
*
|
||||
* @return The passenger mode
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public PassengerMode getPassengerMode() {
|
||||
return configHandle.get(configNodes.passengerMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -575,6 +623,26 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.bukkitYmlPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setServerPropertiesPath(String serverPropertiesPath) {
|
||||
return configHandle.set(configNodes.serverPropertiesPath, serverPropertiesPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public String getServerPropertiesPath() {
|
||||
return configHandle.get(configNodes.serverPropertiesPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setAutoDetectGeneratorPlugins(boolean autoDetectGeneratorPlugins) {
|
||||
return configHandle.set(configNodes.autoDetectGeneratorPlugins, autoDetectGeneratorPlugins);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getAutoDetectGeneratorPlugins() {
|
||||
return configHandle.get(configNodes.autoDetectGeneratorPlugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -632,10 +700,11 @@ public final class CoreConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying config file object
|
||||
* Gets the underlying config file object. For internal use only.
|
||||
*
|
||||
* @return The config file
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public FileConfiguration getConfig() {
|
||||
return configHandle.getConfig();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
@@ -19,12 +19,13 @@ import org.mvplugins.multiverse.core.config.node.Node;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.EventPriorityMapper;
|
||||
import org.mvplugins.multiverse.core.event.MVDebugModeEvent;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.permissions.PermissionUtils;
|
||||
import org.mvplugins.multiverse.core.teleportation.PassengerModes;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder.DimensionFormat;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -93,7 +94,6 @@ final class CoreConfigNodes {
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoImportDefaultWorlds = node(ConfigNode.builder("world.auto-import-default-worlds", Boolean.class)
|
||||
.comment("")
|
||||
.comment("When enabled, Multiverse will automatically import default worlds defined in the server.properties")
|
||||
.comment("`level-name` property when the Multiverse is enabled or reloaded. This will include the nether and ")
|
||||
.comment("end if the server created them.")
|
||||
@@ -110,6 +110,7 @@ final class CoreConfigNodes {
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> enforceAccess = node(ConfigNode.builder("world.enforce-access", Boolean.class)
|
||||
.comment("")
|
||||
.comment("This setting will prevent players from entering worlds they don't have access to.")
|
||||
.comment("If this is set to false, players will be able to enter any world they want.")
|
||||
.comment("If this is set to true, players will only be able to enter worlds they have")
|
||||
@@ -136,6 +137,24 @@ final class CoreConfigNodes {
|
||||
.name("enforce-flight")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnRate = node(ConfigNode.builder("world.apply-entity-spawn-rate", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `tick-rate` config in worlds.yml.")
|
||||
.comment("If disabled, the `tick-rate` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity spawn rate per world.")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-rate")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnLimit = node(ConfigNode.builder("world.apply-entity-spawn-limit", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `spawn-limit` config when a world is loaded.")
|
||||
.comment("If disabled, the `spawn-limit` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity limits per world.")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-limit")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoPurgeEntities = node(ConfigNode.builder("world.auto-purge-entities", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will purge entities on world load based world's entity spawn config.")
|
||||
@@ -183,6 +202,20 @@ final class CoreConfigNodes {
|
||||
.name("use-finer-teleport-permissions")
|
||||
.build());
|
||||
|
||||
final ConfigNode<PassengerModes> passengerMode = node(ConfigNode.builder("teleport.passenger-mode", PassengerModes.class)
|
||||
.comment("")
|
||||
.comment("Configures how passengers and vehicles are handled when an entity is teleported.")
|
||||
.comment(" default: Server will handle passengers and vehicles, this usually means entities will not be teleported to a different world if they have passengers.")
|
||||
.comment(" dismount_passengers: Passengers will be removed from the parent entity before the teleport.")
|
||||
.comment(" dismount_vehicle: Vehicle will be removed and from the parent entity before the teleport.")
|
||||
.comment(" dismount_all: All passengers and vehicles will be removed from the parent entity before the teleport.")
|
||||
.comment(" retain_passengers: Passengers will teleport together with the parent entity.")
|
||||
.comment(" retain_vehicle: Vehicles will teleport together with the parent entity.")
|
||||
.comment(" retain_all: All passengers and vehicles will teleport together with the parent entity.")
|
||||
.defaultValue(PassengerModes.DEFAULT)
|
||||
.name("passenger-mode")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> concurrentTeleportLimit = node(ConfigNode.builder("teleport.concurrent-teleport-limit", Integer.class)
|
||||
.comment("")
|
||||
.comment("Sets the maximum number of players allowed to be teleported at once with `/mv teleport` command")
|
||||
@@ -237,6 +270,7 @@ final class CoreConfigNodes {
|
||||
.defaultValue("")
|
||||
.name("first-spawn-location")
|
||||
.suggester(this::suggestDestinations)
|
||||
.stringParser(this::parseDestinationString)
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> enableJoinDestination = node(ConfigNode.builder("spawn.enable-join-destination", Boolean.class)
|
||||
@@ -254,6 +288,7 @@ final class CoreConfigNodes {
|
||||
.defaultValue("")
|
||||
.name("join-destination")
|
||||
.suggester(this::suggestDestinations)
|
||||
.stringParser(this::parseDestinationString)
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> defaultRespawnInOverworld = node(ConfigNode.builder("spawn.default-respawn-in-overworld", Boolean.class)
|
||||
@@ -477,6 +512,23 @@ final class CoreConfigNodes {
|
||||
.name("bukkit-yml-path")
|
||||
.build());
|
||||
|
||||
final ConfigNode<String> serverPropertiesPath = node(ConfigNode.builder("misc.server-properties-path", String.class)
|
||||
.comment("")
|
||||
.comment("Change this if you use a custom path for the server.properties file with `--config` startup flag.")
|
||||
.comment("Note: this config option needs a server restart to take effect.")
|
||||
.defaultValue("server.properties")
|
||||
.name("server-properties-path")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoDetectGeneratorPlugins = node(ConfigNode.builder("misc.auto-detect-generator-plugins", Boolean.class)
|
||||
.comment("")
|
||||
.comment("When enabled, Multiverse will attempt to automatically detect world generator plugins installed on your server.")
|
||||
.comment("This option only affects tab-completion within `/mv create` command and output of `/mv generators` command.")
|
||||
.comment("Disabling this will not have any affect on the usages of world generator plugins itself.")
|
||||
.defaultValue(true)
|
||||
.name("auto-detect-generator-plugins")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> globalDebug = node(ConfigNode.builder("misc.global-debug", Integer.class)
|
||||
.comment("")
|
||||
.comment("This is our debug flag to help identify issues with Multiverse.")
|
||||
@@ -501,6 +553,7 @@ final class CoreConfigNodes {
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> debugPermissions = node(ConfigNode.builder("misc.debug-permissions", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether console will log every permission check done by all multiverse plugins.")
|
||||
.comment("This will only work if the above 'global-debug' is set to 1 or more.")
|
||||
.defaultValue(false)
|
||||
@@ -532,15 +585,14 @@ final class CoreConfigNodes {
|
||||
.hidden()
|
||||
.build());
|
||||
|
||||
// todo: Maybe combine with the similar method in MVCommandCompletion but that has permission checking
|
||||
private Collection<String> suggestDestinations(String input) {
|
||||
return destinationsProvider.get().getDestinations().stream()
|
||||
.flatMap(destination -> destination.suggestDestinations(Bukkit.getConsoleSender(), null)
|
||||
.stream()
|
||||
.map(packet -> destination instanceof WorldDestination
|
||||
? packet.destinationString()
|
||||
: destination.getIdentifier() + ":" + packet.destinationString()))
|
||||
.toList();
|
||||
private Collection<String> suggestDestinations(CommandSender sender, String input) {
|
||||
return destinationsProvider.get().suggestDestinationStrings(sender, input);
|
||||
}
|
||||
|
||||
private Try<String> parseDestinationString(CommandSender sender, String input, Class<String> type) {
|
||||
return destinationsProvider.get().parseDestination(sender, input)
|
||||
.map(DestinationInstance::toString)
|
||||
.toTry();
|
||||
}
|
||||
|
||||
private static final class DimensionFormatNodeSerializer implements NodeSerializer<DimensionFormat> {
|
||||
|
||||
+128
-28
@@ -5,6 +5,9 @@ import java.util.Collections;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -40,6 +43,7 @@ public class StringPropertyHandle {
|
||||
|
||||
/**
|
||||
* Get property names that can be modified. ADD and REMOVE actions can only be used on list nodes.
|
||||
*
|
||||
* @param action The target action
|
||||
* @return The property names modifiable for the action.
|
||||
*/
|
||||
@@ -71,20 +75,47 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Suggests property values for command auto-complete based on the input and action type.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param input The input value to suggest based on.
|
||||
* @param action The modification action being performed.
|
||||
* @param name The name of the property.
|
||||
* @param input The input value to suggest based on.
|
||||
* @param action The modification action being performed.
|
||||
* @return A collection of suggested values.
|
||||
*/
|
||||
public Collection<String> getSuggestedPropertyValue(
|
||||
@Nullable String name, @Nullable String input, @NotNull PropertyModifyAction action) {
|
||||
@Nullable String name,
|
||||
@Nullable String input,
|
||||
@NotNull PropertyModifyAction action
|
||||
) {
|
||||
return getSuggestedPropertyValue(name, input, action, Bukkit.getConsoleSender());
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests property values for command auto-complete based on the input and action type.
|
||||
* <br />
|
||||
* Providing a sender gives contextual information such as sender name, permissions, or player location
|
||||
* for better suggestions.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param input The input value to suggest based on.
|
||||
* @param action The modification action being performed.
|
||||
* @param sender The sender context to use.
|
||||
* @return A collection of suggested values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Collection<String> getSuggestedPropertyValue(
|
||||
@Nullable String name,
|
||||
@Nullable String input,
|
||||
@NotNull PropertyModifyAction action,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
return switch (action) {
|
||||
case SET -> findNode(name, ValueNode.class)
|
||||
.map(node -> node.suggest(input))
|
||||
.map(node -> node.suggest(sender, input))
|
||||
.getOrElse(Collections.emptyList());
|
||||
|
||||
case ADD -> findNode(name, ListValueNode.class)
|
||||
.map(node -> node.suggestItem(input))
|
||||
.map(node -> node.suggestItem(sender, input))
|
||||
.getOrElse(Collections.emptyList());
|
||||
|
||||
case REMOVE -> findNode(name, ListValueNode.class)
|
||||
@@ -111,7 +142,7 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Sets the value of the specified property name.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The value to set.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
@@ -122,7 +153,7 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Adds a value to a list property name.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The value to add.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
@@ -133,7 +164,7 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Removes a value from a list property name.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The value to remove.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
@@ -154,9 +185,9 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Modifies a property name based on the given action.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param value The new value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @param name The name of the property.
|
||||
* @param value The new value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> modifyProperty(
|
||||
@@ -173,59 +204,128 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Sets the property value from a string representation.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to set.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> setPropertyString(@Nullable String name, @Nullable String value) {
|
||||
return setPropertyString(Bukkit.getConsoleSender(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the property value from a string representation with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to set.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> setPropertyString(@NotNull CommandSender sender, @Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ValueNode.class)
|
||||
.flatMap(node -> node.parseFromString(value)
|
||||
.flatMap(node -> node.parseFromString(sender, value)
|
||||
.flatMap(parsedValue -> handle.set(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to a list property using its string representation.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to add.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> addPropertyString(@Nullable String name, @Nullable String value) {
|
||||
return addPropertyString(Bukkit.getConsoleSender(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to a list property using its string representation with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to add.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> addPropertyString(@NotNull CommandSender sender, @Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ListValueNode.class)
|
||||
.flatMap(node -> node.parseItemFromString(value)
|
||||
.flatMap(node -> node.parseItemFromString(sender, value)
|
||||
.flatMap(parsedValue -> handle.add(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value from a list property using its string representation.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to remove.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> removePropertyString(@Nullable String name, @Nullable String value) {
|
||||
return removePropertyString(Bukkit.getConsoleSender(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value from a list property using its string representation with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to remove.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> removePropertyString(@NotNull CommandSender sender, @Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ListValueNode.class)
|
||||
.flatMap(node -> node.parseItemFromString(value)
|
||||
.flatMap(node -> node.parseItemFromString(sender, value)
|
||||
.flatMap(parsedValue -> handle.remove(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a property using a string value based on the given action.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param value The string value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> modifyPropertyString(
|
||||
@Nullable String name, @Nullable String value, @NotNull PropertyModifyAction action) {
|
||||
@Nullable String name,
|
||||
@Nullable String value,
|
||||
@NotNull PropertyModifyAction action
|
||||
) {
|
||||
return modifyPropertyString(Bukkit.getConsoleSender(), name, value, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a property using a string value based on the given action with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> modifyPropertyString(
|
||||
@NotNull CommandSender sender,
|
||||
@Nullable String name,
|
||||
@Nullable String value,
|
||||
@NotNull PropertyModifyAction action
|
||||
) {
|
||||
if (action.isRequireValue() && (value == null)) {
|
||||
return Try.failure(new IllegalArgumentException("Value is required for PropertyModifyAction: " + action));
|
||||
}
|
||||
return switch (action) {
|
||||
case SET -> setPropertyString(name, value);
|
||||
case ADD -> addPropertyString(name, value);
|
||||
case REMOVE -> removePropertyString(name, value);
|
||||
case SET -> setPropertyString(sender, name, value);
|
||||
case ADD -> addPropertyString(sender, name, value);
|
||||
case REMOVE -> removePropertyString(sender, name, value);
|
||||
case RESET -> resetProperty(name);
|
||||
default -> Try.failure(new IllegalArgumentException("Unknown action: " + action));
|
||||
};
|
||||
@@ -234,9 +334,9 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Finds a configuration node by name and type.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param type The expected class type of the node.
|
||||
* @param <T> The type of node.
|
||||
* @param name The name of the node.
|
||||
* @param type The expected class type of the node.
|
||||
* @param <T> The type of node.
|
||||
* @return A Try containing the found node or a failure if not found.
|
||||
*/
|
||||
private <T extends Node> Try<T> findNode(@Nullable String name, @NotNull Class<T> type) {
|
||||
|
||||
@@ -8,9 +8,14 @@ import java.util.function.Supplier;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.DefaultSerializerProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultStringParserProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
|
||||
@@ -42,6 +47,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
|
||||
protected final @Nullable String name;
|
||||
protected final @NotNull Class<T> type;
|
||||
protected final @NotNull String[] aliases;
|
||||
protected @Nullable Supplier<T> defaultValue;
|
||||
protected @Nullable NodeSuggester suggester;
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
@@ -54,6 +60,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<T> type,
|
||||
@NotNull String[] aliases,
|
||||
@Nullable Supplier<T> defaultValue,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<T> stringParser,
|
||||
@@ -63,6 +70,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
super(path, comments);
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.aliases = aliases;
|
||||
this.defaultValue = defaultValue;
|
||||
this.suggester = (suggester != null)
|
||||
? suggester
|
||||
@@ -93,6 +101,14 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String[] getAliases() {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -115,6 +131,17 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<String> suggest(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (suggester != null && suggester instanceof SenderNodeSuggester senderSuggester) {
|
||||
return senderSuggester.suggest(sender, input);
|
||||
}
|
||||
return suggest(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -126,6 +153,17 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return Try.failure(new UnsupportedOperationException("No string parser for type " + type.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<T> parseFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (stringParser != null && stringParser instanceof SenderNodeStringParser<T> senderStringParser) {
|
||||
return senderStringParser.parse(sender, input, type);
|
||||
}
|
||||
return parseFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -164,6 +202,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
|
||||
protected @Nullable String name;
|
||||
protected @NotNull final Class<T> type;
|
||||
protected @NotNull String[] aliases = Strings.EMPTY_ARRAY;
|
||||
protected @Nullable Supplier<T> defaultValue;
|
||||
protected @Nullable NodeSuggester suggester;
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
@@ -243,6 +282,20 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return name(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aliases for this node. Aliases are alternative identifiers for referencing the node.
|
||||
*
|
||||
* @param aliases The aliases to set for this node.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B aliases(@NotNull String... aliases) {
|
||||
this.aliases = aliases;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for this node.
|
||||
*
|
||||
@@ -254,6 +307,20 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for this node with sender context.
|
||||
*
|
||||
* @param suggester The suggester for this node.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B suggester(@NotNull SenderNodeSuggester suggester) {
|
||||
this.suggester = suggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for this node.
|
||||
*
|
||||
@@ -265,6 +332,20 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for this node with sender context.
|
||||
*
|
||||
* @param stringParser The string parser for this node.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B stringParser(@NotNull SenderNodeStringParser<T> stringParser) {
|
||||
this.stringParser = stringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for this node.
|
||||
*
|
||||
@@ -304,7 +385,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
@Override
|
||||
public @NotNull ConfigNode<T> build() {
|
||||
return new ConfigNode<>(path, comments.toArray(new String[0]),
|
||||
name, type, defaultValue, suggester, stringParser, serializer, validator, onSetValue);
|
||||
name, type, aliases, defaultValue, suggester, stringParser, serializer, validator, onSetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -13,6 +12,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import io.vavr.Value;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -20,6 +21,8 @@ import org.mvplugins.multiverse.core.config.node.functions.DefaultStringParserPr
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.DefaultSerializerProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
@@ -58,6 +61,7 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<List<I>> type,
|
||||
@NotNull String[] aliases,
|
||||
@Nullable Supplier<List<I>> defaultValueSupplier,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<List<I>> stringParser,
|
||||
@@ -70,7 +74,7 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
@Nullable NodeSerializer<I> itemSerializer,
|
||||
@Nullable Function<I, Try<Void>> itemValidator,
|
||||
@Nullable BiConsumer<I, I> onSetItemValue) {
|
||||
super(path, comments, name, type, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
super(path, comments, name, type, aliases, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
validator, onSetValue);
|
||||
this.itemType = itemType;
|
||||
this.itemSuggester = itemSuggester != null
|
||||
@@ -110,12 +114,12 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
}
|
||||
|
||||
private void setDefaultSuggester() {
|
||||
this.suggester = input -> {
|
||||
if (input == null) {
|
||||
return itemSuggester.suggest(null);
|
||||
}
|
||||
return StringFormatter.addonToCommaSeperated(input, itemSuggester.suggest(input));
|
||||
};
|
||||
if (itemSuggester instanceof SenderNodeSuggester senderItemSuggester) {
|
||||
this.suggester = (SenderNodeSuggester)(sender, input) ->
|
||||
StringFormatter.addonToCommaSeperated(input, senderItemSuggester.suggest(sender, input));
|
||||
} else {
|
||||
this.suggester = input -> StringFormatter.addonToCommaSeperated(input, itemSuggester.suggest(input));
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaultStringParser() {
|
||||
@@ -194,6 +198,17 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<String> suggestItem(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (itemSuggester != null && itemSuggester instanceof SenderNodeSuggester senderSuggester) {
|
||||
return senderSuggester.suggest(sender, input);
|
||||
}
|
||||
return suggestItem(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -205,6 +220,17 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return Try.failure(new UnsupportedOperationException("No item string parser for type " + itemType));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<I> parseItemFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (itemStringParser != null && itemStringParser instanceof SenderNodeStringParser<I> senderStringParser) {
|
||||
return senderStringParser.parse(sender, input, itemType);
|
||||
}
|
||||
return parseItemFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -267,6 +293,20 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for an individual item in the list with sender context.
|
||||
*
|
||||
* @param itemSuggester The suggester.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B itemSuggester(@NotNull SenderNodeSuggester itemSuggester) {
|
||||
this.itemSuggester = itemSuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for an individual item in the list.
|
||||
*
|
||||
@@ -278,6 +318,20 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for an individual item in the list with sender context.
|
||||
*
|
||||
* @param itemStringParser The string parser.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B itemStringParser(@NotNull SenderNodeStringParser<I> itemStringParser) {
|
||||
this.itemStringParser = itemStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for an individual item in the list.
|
||||
*
|
||||
@@ -321,6 +375,7 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
comments.toArray(new String[0]),
|
||||
name,
|
||||
type,
|
||||
aliases,
|
||||
defaultValue,
|
||||
suggester,
|
||||
stringParser,
|
||||
|
||||
@@ -4,6 +4,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -31,6 +33,21 @@ public interface ListValueNode<I> extends ValueNode<List<I>> {
|
||||
*/
|
||||
@NotNull Collection<String> suggestItem(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node. Use contextural information from the sender such as
|
||||
* sender name, permissions, or player location for better suggestions.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The input string.
|
||||
* @return A collection of possible string values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull Collection<String> suggestItem(@NotNull CommandSender sender, @Nullable String input) {
|
||||
return suggestItem(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link I}. Used for property set by user input.
|
||||
*
|
||||
@@ -39,6 +56,20 @@ public interface ListValueNode<I> extends ValueNode<List<I>> {
|
||||
*/
|
||||
@NotNull Try<I> parseItemFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link I}. Used for property set by user input.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull Try<I> parseItemFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
return parseItemFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.mvplugins.multiverse.core.config.node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
|
||||
@@ -16,6 +18,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public class NodeGroup implements Collection<Node> {
|
||||
private final Collection<Node> nodes;
|
||||
private final List<String> nodeNames;
|
||||
private final Map<String, Node> nodesMap;
|
||||
|
||||
/**
|
||||
@@ -23,6 +26,7 @@ public class NodeGroup implements Collection<Node> {
|
||||
*/
|
||||
public NodeGroup() {
|
||||
this.nodes = new ArrayList<>();
|
||||
this.nodeNames = new ArrayList<>();
|
||||
this.nodesMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@@ -34,18 +38,27 @@ public class NodeGroup implements Collection<Node> {
|
||||
public NodeGroup(@NotNull Collection<Node> nodes) {
|
||||
this.nodes = nodes;
|
||||
this.nodesMap = new HashMap<>(nodes.size());
|
||||
this.nodeNames = new ArrayList<>(nodes.size());
|
||||
nodes.forEach(this::addNodeIndex);
|
||||
}
|
||||
|
||||
private void addNodeIndex(@NotNull Node node) {
|
||||
if (node instanceof ValueNode) {
|
||||
((ValueNode<?>) node).getName().peek(name -> nodesMap.put(name, node));
|
||||
if (node instanceof ValueNode<?> valueNode) {
|
||||
valueNode.getName().peek(name -> {
|
||||
nodeNames.add(name);
|
||||
nodesMap.put(name, node);
|
||||
Arrays.stream(valueNode.getAliases()).forEach(alias -> nodesMap.put(alias, node));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNodeIndex(@NotNull Node node) {
|
||||
if (node instanceof ValueNode) {
|
||||
((ValueNode<?>) node).getName().peek(nodesMap::remove);
|
||||
if (node instanceof ValueNode<?> valueNode) {
|
||||
valueNode.getName().peek(name -> {
|
||||
nodeNames.remove(name);
|
||||
nodesMap.remove(name);
|
||||
Arrays.stream(valueNode.getAliases()).forEach(alias -> nodesMap.remove(alias, node));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +68,7 @@ public class NodeGroup implements Collection<Node> {
|
||||
* @return The names of all nodes in this group.
|
||||
*/
|
||||
public @NotNull Collection<String> getNames() {
|
||||
return nodesMap.keySet();
|
||||
return nodeNames;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,9 @@ import java.util.Collection;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -12,7 +15,7 @@ import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
public interface ValueNode<T> extends Node {
|
||||
|
||||
/**
|
||||
* Gets the name of this node. Used for identifying the node from user input.
|
||||
* Gets the name of this node. Used for identifying the node from user input. This must be unique within a node group.
|
||||
*
|
||||
* @return An {@link Option} containing the name of this node, or {@link Option.None} if the node has no name.
|
||||
*/
|
||||
@@ -25,6 +28,19 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
@NotNull Class<T> getType();
|
||||
|
||||
/**
|
||||
* Gets the aliases of this node. Serves as shorter or legacy alternatives the {@link #getName()} and must be
|
||||
* unique within a node group.
|
||||
*
|
||||
* @return The aliases of this node.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull String[] getAliases() {
|
||||
return Strings.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value with type {@link T} of the node.
|
||||
*
|
||||
@@ -40,6 +56,19 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
@NotNull Collection<String> suggest(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node. Use contextural information from the sender such as
|
||||
* sender name, permissions, or player location for better suggestions.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The input string.
|
||||
* @return A collection of possible string values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@NotNull Collection<String> suggest(@NotNull CommandSender sender, @Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T}. Used for property set by user input.
|
||||
*
|
||||
@@ -48,6 +77,21 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
@NotNull Try<T> parseFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T} with context from the sender.
|
||||
* Used for property set by user input.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull Try<T> parseFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
return parseFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that suggests possible values for a node value.
|
||||
* A function that suggests possible values for a node value. These suggestions must be able to be used to parse the
|
||||
* value from string with {@link NodeStringParser}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NodeSuggester {
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that parses a string into a node value object of type {@link T} with contextual information from the sender.
|
||||
*
|
||||
* @param <T> The type of the object to parse.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public interface SenderNodeStringParser<T> extends NodeStringParser<T> {
|
||||
/**
|
||||
* Parses a string into a node value object of type {@link T} with contextual information from the sender.
|
||||
* This ties in with {@link SenderNodeSuggester} that provides suggestions based on the sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param string The string to parse.
|
||||
* @param type The type of the object to parse.
|
||||
* @return The parsed object, or {@link Try.Failure} if the string could not be parsed.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@NotNull Try<T> parse(@NotNull CommandSender sender, @Nullable String string, @NotNull Class<T> type);
|
||||
|
||||
@Override
|
||||
default @NotNull Try<T> parse(@Nullable String string, @NotNull Class<T> type) {
|
||||
return parse(Bukkit.getConsoleSender(), string, type);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A function that suggests possible values for a node value with sender contextual information.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@FunctionalInterface
|
||||
public interface SenderNodeSuggester extends NodeSuggester {
|
||||
|
||||
/**
|
||||
* Suggests possible values for a node value. Generated based on the current user input and sender contextual information.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The current partial user input
|
||||
* @return The possible values.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@NotNull Collection<String> suggest(@NotNull CommandSender sender, @Nullable String input);
|
||||
|
||||
@Override
|
||||
default @NotNull Collection<String> suggest(@Nullable String input) {
|
||||
return suggest(Bukkit.getConsoleSender(), input);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package org.mvplugins.multiverse.core.destination;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
@@ -11,6 +13,9 @@ import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
/**
|
||||
* A destination is a location that can be teleported to.
|
||||
* <br />
|
||||
* Please ensure you implement at least one of {@link #getDestinationInstance(CommandSender, String)} or
|
||||
* {@link #getDestinationInstance(String)} to prevent a stack overflow.
|
||||
*
|
||||
* @param <D> The type of the destination
|
||||
* @param <T> The type of the destination instance
|
||||
@@ -18,13 +23,13 @@ import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
@Contract
|
||||
public interface Destination<D extends Destination<D, T, F>, T extends DestinationInstance<T, D>, F extends FailureReason> {
|
||||
/**
|
||||
* Returns the identifier or prefix that is required for this destination.
|
||||
* Returns the identifier or prefix required for this destination.
|
||||
*
|
||||
* <p>Portals have a prefix of "p" for example and OpenWarp (third party plugin) uses "ow". This is derived from a
|
||||
* hash and cannot have duplicate values. Read that as your plugin cannot use 'p' because it's already used.
|
||||
* hash and cannot have duplicate values. Means that your plugin cannot use 'p' because it's already used.
|
||||
* Please check the wiki when adding a custom destination!</p>
|
||||
*
|
||||
* @return The identifier or prefix that is required for this destination.
|
||||
* @return The identifier or prefix required for this destination.
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
|
||||
@@ -33,8 +38,36 @@ public interface Destination<D extends Destination<D, T, F>, T extends Destinati
|
||||
*
|
||||
* @param destinationParams The destination parameters. ex: p:MyPortal:nw
|
||||
* @return The destination instance, or null if the destination parameters are invalid.
|
||||
*
|
||||
* @deprecated Use {@link #getDestinationInstance(CommandSender, String)} instead. This method will no longer be
|
||||
* called by {@link DestinationsProvider}.
|
||||
*/
|
||||
@NotNull Attempt<T, F> getDestinationInstance(@NotNull String destinationParams);
|
||||
@Deprecated(forRemoval = true, since = "5.1")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
default @NotNull Attempt<T, F> getDestinationInstance(@NotNull String destinationParams) {
|
||||
return getDestinationInstance(Bukkit.getConsoleSender(), destinationParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination instance for the given destination parameters with sender context. This allows
|
||||
* for shorthands such as getting location or name from sender. If no sender context is available, use
|
||||
* {@link Bukkit#getConsoleSender()} will be defaulted by {@link DestinationsProvider}.
|
||||
* <br />
|
||||
* Note that the resulting {@link DestinationInstance} should be (de)serializable without the original sender context.
|
||||
* <br />
|
||||
* For example, the parsable string with sender context `e:@here` should return a {@link DestinationInstance} that
|
||||
* is serialized to `e:world:x,y,z:p:y`, which can be deserialized without the original sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param destinationParams The destination parameters. ex: p:MyPortal:nw
|
||||
* @return The destination instance, or null if the destination parameters are invalid.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default Attempt<T, F> getDestinationInstance(@NotNull CommandSender sender, @NotNull String destinationParams) {
|
||||
return getDestinationInstance(destinationParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible destinations for the given destination parameters. This packet's destination
|
||||
|
||||
+18
@@ -1,5 +1,8 @@
|
||||
package org.mvplugins.multiverse.core.destination;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
|
||||
/**
|
||||
* Data of a possible destination for tab completion and permission checking
|
||||
*
|
||||
@@ -8,4 +11,19 @@ package org.mvplugins.multiverse.core.destination;
|
||||
* @param finerPermissionSuffix The finer permission suffix
|
||||
*/
|
||||
public record DestinationSuggestionPacket(Destination<?, ?, ?> destination, String destinationString, String finerPermissionSuffix) {
|
||||
|
||||
/**
|
||||
* Gets a parsable string representation of the destination that is most likely valid for
|
||||
* {@link DestinationsProvider#parseDestination(String)}.
|
||||
*
|
||||
* @return The parsable string
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public String parsableString() {
|
||||
return destination instanceof WorldDestination
|
||||
? destinationString
|
||||
: destination.getIdentifier() + ":" + destinationString;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,9 @@ import java.util.Map;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -50,8 +52,25 @@ public final class DestinationsProvider {
|
||||
* @param destinationString The destination string.
|
||||
* @return The destination object, or null if invalid format.
|
||||
*/
|
||||
@SuppressWarnings("unchecked,rawtypes")
|
||||
public @NotNull Attempt<DestinationInstance<?, ?>, FailureReason> parseDestination(@NotNull String destinationString) {
|
||||
return this.parseDestination(Bukkit.getConsoleSender(), destinationString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a destination string to a destination object with sender context.
|
||||
*
|
||||
* @param sender The target sender context.
|
||||
* @param destinationString The destination string.
|
||||
* @return The destination object, or null if invalid format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@SuppressWarnings("unchecked,rawtypes")
|
||||
public @NotNull Attempt<DestinationInstance<?, ?>, FailureReason> parseDestination(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationString
|
||||
) {
|
||||
String[] items = destinationString.split(SEPARATOR, 2);
|
||||
|
||||
String idString = items[0];
|
||||
@@ -73,7 +92,7 @@ public final class DestinationsProvider {
|
||||
replace("{ids}").with(String.join(", ", this.destinationMap.keySet())));
|
||||
}
|
||||
|
||||
return destination.getDestinationInstance(destinationParams);
|
||||
return destination.getDestinationInstance(sender, destinationParams);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,12 +114,36 @@ public final class DestinationsProvider {
|
||||
return this.destinationMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets suggestions for possible parsable destinations.
|
||||
*
|
||||
* @param sender The target sender context.
|
||||
* @param destinationParams The current user input.
|
||||
* @return A collection of destination suggestions.
|
||||
*/
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return this.getDestinations().stream()
|
||||
.flatMap(destination -> destination.suggestDestinations(sender, destinationParams).stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets suggestions for possible parsable destinations.
|
||||
*
|
||||
* @param sender The target sender context.
|
||||
* @param destinationParams The current user input.
|
||||
* @return A collection of destination suggestions in parsable string format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull Collection<String> suggestDestinationStrings(@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return suggestDestinations(sender, destinationParams)
|
||||
.stream()
|
||||
.map(DestinationSuggestionPacket::parsableString)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public enum ParseFailureReason implements FailureReason {
|
||||
INVALID_DESTINATION_ID(MVCorei18n.DESTINATION_PARSE_FAILUREREASON_INVALIDDESTINATIONID),
|
||||
;
|
||||
|
||||
@@ -45,7 +45,10 @@ public final class AnchorDestination implements Destination<AnchorDestination, A
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<AnchorDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<AnchorDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
return this.anchorManager.getAnchor(destinationParams)
|
||||
.fold(
|
||||
() -> Attempt.failure(InstanceFailureReason.ANCHOR_NOT_FOUND, replace("{anchor}").with(destinationParams)),
|
||||
|
||||
@@ -45,7 +45,10 @@ public final class BedDestination implements Destination<BedDestination, BedDest
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<BedDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<BedDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
Player player = PlayerFinder.get(destinationParams);
|
||||
if (player == null && !OWN_BED_STRING.equals(destinationParams)) {
|
||||
return Attempt.failure(InstanceFailureReason.PLAYER_NOT_FOUND, Replace.PLAYER.with(destinationParams));
|
||||
|
||||
@@ -51,7 +51,10 @@ public final class CannonDestination implements Destination<CannonDestination, C
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<CannonDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<CannonDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
String[] params = REPatterns.COLON.split(destinationParams);
|
||||
if (params.length != 5) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_FORMAT);
|
||||
|
||||
+78
-32
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.destination.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
@@ -8,7 +9,9 @@ import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -16,9 +19,12 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.utils.position.PositionNumber;
|
||||
import org.mvplugins.multiverse.core.utils.position.VectorPosition;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
@@ -60,55 +66,53 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
* @return A new {@link ExactDestinationInstance}
|
||||
*/
|
||||
public @NotNull ExactDestinationInstance fromLocation(@NotNull Location location) {
|
||||
return new ExactDestinationInstance(this, new UnloadedWorldLocation(location));
|
||||
return new ExactDestinationInstance(
|
||||
this,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<ExactDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
String[] items = REPatterns.COLON.split(destinationParams);
|
||||
public @NotNull Attempt<ExactDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
String[] items = REPatterns.COLON.split(destinationParams, 2);
|
||||
if (items.length < 2) {
|
||||
if (items[0].equals("@here")) {
|
||||
return getLocationFromSender(sender)
|
||||
.map(location -> Attempt.<ExactDestinationInstance, InstanceFailureReason>success(
|
||||
new ExactDestinationInstance(
|
||||
this,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
)
|
||||
))
|
||||
.getOrElse(() -> Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT)); // todo: specific failure reason for this case
|
||||
}
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_FORMAT);
|
||||
}
|
||||
|
||||
String worldName = items[0];
|
||||
String coordinates = items[1];
|
||||
String[] coordinatesParams = REPatterns.COMMA.split(coordinates);
|
||||
if (coordinatesParams.length != 3) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT);
|
||||
}
|
||||
String positionStr = items[1];
|
||||
|
||||
World world = getLoadedMultiverseWorld(worldName).flatMap(LoadedMultiverseWorld::getBukkitWorld).getOrNull();
|
||||
if (world == null) {
|
||||
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
UnloadedWorldLocation location;
|
||||
EntityPosition position;
|
||||
try {
|
||||
location = new UnloadedWorldLocation(
|
||||
world,
|
||||
Double.parseDouble(coordinatesParams[0]),
|
||||
Double.parseDouble(coordinatesParams[1]),
|
||||
Double.parseDouble(coordinatesParams[2])
|
||||
);
|
||||
} catch (NumberFormatException e) {
|
||||
position = EntityPosition.fromString(positionStr);
|
||||
} catch (PositionParseException e) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
|
||||
if (items.length == 4) {
|
||||
String pitch = items[2];
|
||||
String yaw = items[3];
|
||||
try {
|
||||
location.setPitch(Float.parseFloat(pitch));
|
||||
location.setYaw(Float.parseFloat(yaw));
|
||||
} catch (NumberFormatException e) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt.success(new ExactDestinationInstance(this, location));
|
||||
return Attempt.success(new ExactDestinationInstance(this, worldName, position));
|
||||
}
|
||||
|
||||
//TODO: Extract to a world finder class
|
||||
@@ -118,19 +122,61 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
: worldManager.getLoadedWorld(worldName);
|
||||
}
|
||||
|
||||
private Option<Location> getLocationFromSender(CommandSender sender) {
|
||||
if (sender instanceof Entity entity) {
|
||||
return Option.of(entity.getLocation());
|
||||
}
|
||||
if (sender instanceof BlockCommandSender blockSender) {
|
||||
return Option.of(blockSender.getBlock().getLocation());
|
||||
}
|
||||
return Option.none();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(
|
||||
@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return worldManager.getLoadedWorlds().stream()
|
||||
Stream<DestinationSuggestionPacket> stream = worldManager.getLoadedWorlds().stream()
|
||||
.filter(world -> worldEntryCheckerProvider.forSender(sender)
|
||||
.canAccessWorld(world)
|
||||
.isSuccess())
|
||||
.map(world ->
|
||||
new DestinationSuggestionPacket(this, world.getTabCompleteName() + ":", world.getName()))
|
||||
.toList();
|
||||
new DestinationSuggestionPacket(this, world.getTabCompleteName() + ":", world.getName()));
|
||||
|
||||
Location location = getLocationFromSender(sender).getOrNull();
|
||||
if (location != null) {
|
||||
var herePacket = new DestinationSuggestionPacket(
|
||||
this,
|
||||
"@here",
|
||||
location.getWorld().getName()
|
||||
);
|
||||
var locationPacket = new DestinationSuggestionPacket(
|
||||
this,
|
||||
"%s:%.2f,%.2f,%.2f".formatted(
|
||||
location.getWorld().getName(),
|
||||
location.getX(),
|
||||
location.getY(),
|
||||
location.getZ()
|
||||
),
|
||||
location.getWorld().getName()
|
||||
);
|
||||
var locationPacketPW = new DestinationSuggestionPacket(
|
||||
this,
|
||||
"%s:%.2f,%.2f,%.2f:%.2f:%.2f".formatted(
|
||||
location.getWorld().getName(),
|
||||
location.getX(),
|
||||
location.getY(),
|
||||
location.getZ(),
|
||||
location.getPitch(),
|
||||
location.getYaw()
|
||||
),
|
||||
location.getWorld().getName()
|
||||
);
|
||||
stream = Stream.concat(stream, Stream.of(herePacket, locationPacket, locationPacketPW));
|
||||
}
|
||||
return stream.toList();
|
||||
}
|
||||
|
||||
public enum InstanceFailureReason implements FailureReason {
|
||||
|
||||
+20
-9
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.destination.core;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -8,22 +9,30 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.utils.position.VectorPosition;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link ExactDestination}.
|
||||
*/
|
||||
public final class ExactDestinationInstance extends DestinationInstance<ExactDestinationInstance, ExactDestination> {
|
||||
private final UnloadedWorldLocation location;
|
||||
private final String worldName;
|
||||
private final EntityPosition position;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param location The location to teleport to.
|
||||
* @param destination The parent destination.
|
||||
* @param worldName The name of the world.
|
||||
* @param position The position in the world.
|
||||
*/
|
||||
ExactDestinationInstance(@NotNull ExactDestination destination, @NotNull UnloadedWorldLocation location) {
|
||||
ExactDestinationInstance(@NotNull ExactDestination destination,
|
||||
@NotNull String worldName,
|
||||
@NotNull EntityPosition position) {
|
||||
super(destination);
|
||||
this.location = location;
|
||||
this.worldName = worldName;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,10 +40,13 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Option<Location> getLocation(@NotNull Entity teleportee) {
|
||||
if (location.getWorld() == null) {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world == null) {
|
||||
return Option.none();
|
||||
}
|
||||
return Option.of(location.toBukkitLocation());
|
||||
Location destinationLocation = position.toBukkitLocation(teleportee.getLocation());
|
||||
destinationLocation.setWorld(world);
|
||||
return Option.of(destinationLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +70,7 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Option<String> getFinerPermissionSuffix() {
|
||||
return Option.of(location.getWorld()).map(World::getName);
|
||||
return Option.of(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +78,6 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String serialise() {
|
||||
return location.getWorldName() + ":" + location.getX() + "," + location.getY()
|
||||
+ "," + location.getZ() + ":" + location.getPitch() + ":" + location.getYaw();
|
||||
return worldName + ":" + position.toString();
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,10 @@ public final class PlayerDestination implements Destination<PlayerDestination, P
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<PlayerDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<PlayerDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
Player player = PlayerFinder.get(destinationParams);
|
||||
if (player == null) {
|
||||
return Attempt.failure(InstanceFailureReason.PLAYER_NOT_FOUND, Replace.PLAYER.with(destinationParams));
|
||||
|
||||
@@ -60,7 +60,10 @@ public final class WorldDestination implements Destination<WorldDestination, Wor
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<WorldDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<WorldDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
String[] items = REPatterns.COLON.split(destinationParams, 3);
|
||||
String worldName = items[0];
|
||||
MultiverseWorld world = getMultiverseWorld(worldName);
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.common.base.Strings;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
|
||||
/**
|
||||
* Filter content and text based on regex matching.
|
||||
@@ -68,9 +69,12 @@ public class RegexContentFilter implements ContentFilter {
|
||||
if (!hasValidRegex()) {
|
||||
return false;
|
||||
}
|
||||
String text = ChatColor.stripColor(String.valueOf(value)).toLowerCase();
|
||||
String text = ChatTextFormatter.removeColor(String.valueOf(value));
|
||||
if (text == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return regexPattern.matcher(text).find();
|
||||
return regexPattern.matcher(text.toLowerCase()).find();
|
||||
} catch (PatternSyntaxException ignored) {
|
||||
Logging.warning("Error parsing regex '%s' for input '%s'", regexString, text);
|
||||
return false;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.mvplugins.multiverse.core.exceptions.utils.position;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
public class PositionParseException extends MultiverseException {
|
||||
public PositionParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import jakarta.inject.Inject;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -15,6 +14,7 @@ import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventClass;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.IgnoreIfCancelled;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.SkipIfEventExist;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
/**
|
||||
@@ -87,9 +87,10 @@ final class MVChatListener implements CoreListener {
|
||||
|
||||
String prefixChatFormat = config.getPrefixChatFormat();
|
||||
prefixChatFormat = prefixChatFormat.replace("%world%", worldName).replace("%chat%", chat);
|
||||
prefixChatFormat = ChatColor.translateAlternateColorCodes('&', prefixChatFormat);
|
||||
|
||||
event.setFormat(prefixChatFormat);
|
||||
prefixChatFormat = ChatTextFormatter.colorize(prefixChatFormat);
|
||||
if (prefixChatFormat != null) {
|
||||
event.setFormat(prefixChatFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private String getWorldName(Player player) {
|
||||
|
||||
@@ -92,7 +92,8 @@ final class MVEntityListener implements CoreListener {
|
||||
// Always allow custom command and plugins to spawn creatures
|
||||
if (event.getSpawnReason() == SpawnReason.CUSTOM
|
||||
|| event.getSpawnReason() == SpawnReason.COMMAND
|
||||
|| event.getSpawnReason() == SpawnReason.BREEDING) {
|
||||
|| event.getSpawnReason() == SpawnReason.BREEDING
|
||||
|| event.getSpawnReason() == SpawnReason.SPAWNER_EGG) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,15 @@ import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.EventRunnable;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventClass;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventPriorityKey;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportQueue;
|
||||
import org.mvplugins.multiverse.core.utils.result.ResultChain;
|
||||
@@ -68,6 +71,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
private final DestinationsProvider destinationsProvider;
|
||||
private final EnforcementHandler enforcementHandler;
|
||||
private final DimensionFinder dimensionFinder;
|
||||
private final CorePermissionsChecker corePermissionsChecker;
|
||||
|
||||
private final Map<String, String> playerWorld = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -84,7 +88,8 @@ final class MVPlayerListener implements CoreListener {
|
||||
Provider<MVCommandManager> commandManagerProvider,
|
||||
DestinationsProvider destinationsProvider,
|
||||
EnforcementHandler enforcementHandler,
|
||||
DimensionFinder dimensionFinder) {
|
||||
DimensionFinder dimensionFinder,
|
||||
CorePermissionsChecker corePermissionsChecker) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.worldManagerProvider = worldManagerProvider;
|
||||
@@ -97,6 +102,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.enforcementHandler = enforcementHandler;
|
||||
this.dimensionFinder = dimensionFinder;
|
||||
this.corePermissionsChecker = corePermissionsChecker;
|
||||
}
|
||||
|
||||
private WorldManager getWorldManager() {
|
||||
@@ -195,59 +201,68 @@ final class MVPlayerListener implements CoreListener {
|
||||
return Option.of(mvWorld.getSpawnLocation());
|
||||
}
|
||||
|
||||
@EventMethod
|
||||
@EventClass("org.spigotmc.event.player.PlayerSpawnLocationEvent")
|
||||
@EventPriorityKey("mvcore-player-spawn-location")
|
||||
void playerSpawnLocation(PlayerSpawnLocationEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
MultiverseWorld world = getWorldManager().getLoadedWorld(player.getWorld()).getOrNull();
|
||||
if (world == null) {
|
||||
Logging.finer("Player joined in a world that is not managed by Multiverse.");
|
||||
return;
|
||||
}
|
||||
if (!player.hasPlayedBefore()) {
|
||||
handleFirstSpawn(event);
|
||||
} else {
|
||||
handleJoinLocation(event);
|
||||
}
|
||||
this.handleGameModeAndFlight(player, event.getSpawnLocation().getWorld());
|
||||
}
|
||||
EventRunnable playerSpawnLocation() {
|
||||
return new EventRunnable<PlayerSpawnLocationEvent>() {
|
||||
@Override
|
||||
public void onEvent(PlayerSpawnLocationEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
MultiverseWorld world = getWorldManager().getLoadedWorld(player.getWorld()).getOrNull();
|
||||
if (world == null) {
|
||||
Logging.finer("Player joined in a world that is not managed by Multiverse.");
|
||||
return;
|
||||
}
|
||||
if (!player.hasPlayedBefore()) {
|
||||
handleFirstSpawn(event);
|
||||
} else {
|
||||
handleJoinLocation(event);
|
||||
}
|
||||
handleGameModeAndFlight(player, event.getSpawnLocation().getWorld());
|
||||
}
|
||||
|
||||
private void handleFirstSpawn(PlayerSpawnLocationEvent event) {
|
||||
if (!config.getFirstSpawnOverride()) {
|
||||
Logging.finer("FirstSpawnOverride is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
Logging.fine("Moving NEW player to(firstspawnoverride): %s", config.getFirstSpawnLocation());
|
||||
destinationsProvider.parseDestination(config.getFirstSpawnLocation())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in FirstSpawnLocation in config is invalid")))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in FirstSpawnLocation in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
private void handleFirstSpawn(PlayerSpawnLocationEvent event) {
|
||||
if (!config.getFirstSpawnOverride()) {
|
||||
Logging.finer("FirstSpawnOverride is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
Logging.fine("Moving NEW player to(firstspawnoverride): %s", config.getFirstSpawnLocation());
|
||||
destinationsProvider.parseDestination(config.getFirstSpawnLocation())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in FirstSpawnLocation in config is invalid")))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in FirstSpawnLocation in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
|
||||
private void handleJoinLocation(PlayerSpawnLocationEvent event) {
|
||||
if (!config.getEnableJoinDestination()) {
|
||||
Logging.finer("JoinDestination is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
if (config.getJoinDestination().isBlank()) {
|
||||
Logging.warning("Joindestination is enabled but no destination has been specified in config!");
|
||||
return;
|
||||
}
|
||||
Logging.finer("JoinDestination is " + config.getJoinDestination());
|
||||
destinationsProvider.parseDestination(config.getJoinDestination())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in JoinDestination in config is invalid")))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in JoinDestination in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
private void handleJoinLocation(PlayerSpawnLocationEvent event) {
|
||||
if (!config.getEnableJoinDestination()) {
|
||||
Logging.finer("JoinDestination is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
if (config.getJoinDestination().isBlank()) {
|
||||
Logging.warning("Joindestination is enabled but no destination has been specified in config!");
|
||||
return;
|
||||
}
|
||||
if (corePermissionsChecker.hasJoinLocationBypassPermission(event.getPlayer())) {
|
||||
Logging.finer("Player %s has bypass permission for JoinDestination", event.getPlayer().getName());
|
||||
return;
|
||||
}
|
||||
Logging.finer("JoinDestination is " + config.getJoinDestination());
|
||||
destinationsProvider.parseDestination(config.getJoinDestination())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in JoinDestination in config is invalid")))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in JoinDestination in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.reasons.LoadFailureReason;
|
||||
import org.mvplugins.multiverse.core.world.reasons.UnloadFailureReason;
|
||||
@@ -68,7 +69,7 @@ final class MVWorldListener implements CoreListener {
|
||||
worldManager.getUnloadedWorld(event.getWorld().getName())
|
||||
.peek(world -> {
|
||||
Logging.fine("Loading world: " + world.getName());
|
||||
worldManager.loadWorld(world).onFailure(failure -> {
|
||||
worldManager.loadWorld(LoadWorldOptions.world(world)).onFailure(failure -> {
|
||||
if (failure.getFailureReason() != LoadFailureReason.WORLD_ALREADY_LOADING) {
|
||||
Logging.severe("Failed to load world: " + failure);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* All the translation keys for the core plugin
|
||||
*/
|
||||
@@ -149,6 +151,7 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
MODIFY_WORLD_DESCRIPTION,
|
||||
MODIFY_PROPERTY_DESCRIPTION,
|
||||
MODIFY_VALUE_DESCRIPTION,
|
||||
MODIFY_PROPERTYREMOVED,
|
||||
MODIFY_CANNOTHAVEVALUE,
|
||||
MODIFY_SPECIFYVALUE,
|
||||
MODIFY_SUCCESS,
|
||||
@@ -181,6 +184,10 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
SETSPAWN_DESCRIPTION,
|
||||
SETSPAWN_LOCATION_DESCRIPTION,
|
||||
SETSPAWN_WORLD_DESCRIPTION,
|
||||
SETSPAWN_UNSAFE,
|
||||
SETSPAWN_SUCCESS,
|
||||
SETSPAWN_FAILED,
|
||||
SETSPAWN_NOTMVWORLD,
|
||||
|
||||
// /mv spawn
|
||||
SPAWN_DESCRIPTION,
|
||||
@@ -301,11 +308,13 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
IMPORTWORLD_WORLDEXISTUNLOADED,
|
||||
IMPORTWORLD_WORLDEXISTLOADED,
|
||||
IMPORTWORLD_WORLDFOLDERINVALID,
|
||||
IMPORTWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
|
||||
LOADWORLD_WORLDALREADYLOADING,
|
||||
LOADWORLD_WORLDNONEXISTENT,
|
||||
LOADWORLD_WORLDEXISTFOLDER,
|
||||
LOADWORLD_WORLDEXISTLOADED,
|
||||
LOADWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
|
||||
REMOVEWORLD_WORLDNONEXISTENT,
|
||||
|
||||
@@ -337,6 +346,11 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
EXCEPTION_MULTIVERSEWORLD_UNLOADPLAYERSINWORLD,
|
||||
EXCEPTION_MULTIVERSEWORLD_UNLOADERROR,
|
||||
|
||||
// multiverse position parse exception
|
||||
EXCEPTION_POSITIONPARSE_INVALIDDIRECTION,
|
||||
EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
|
||||
EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
|
||||
|
||||
// generic
|
||||
GENERIC_SUCCESS,
|
||||
GENERIC_FAILURE,
|
||||
@@ -347,7 +361,8 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
|
||||
// END CHECKSTYLE-SUPPRESSION: Javadoc
|
||||
|
||||
private final MessageKey key = MessageKey.of("mv-core." + this.name().replace('_', '.').toLowerCase());
|
||||
private final MessageKey key = MessageKey.of("mv-core." + this.name().replace('_', '.')
|
||||
.toLowerCase(Locale.ENGLISH));
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
||||
@@ -105,6 +105,7 @@ public final class MessageReplacement {
|
||||
DESTINATION(replace("{destination}")),
|
||||
ERROR(replace("{error}")),
|
||||
GAMERULE(replace("{gamerule}")),
|
||||
LOCATION(replace("{location}")),
|
||||
NAME(replace("{name}")),
|
||||
PLAYER(replace("{player}")),
|
||||
REASON(replace("{reason}")),
|
||||
|
||||
@@ -108,8 +108,8 @@ public abstract class MultiverseModule extends JavaPlugin {
|
||||
*
|
||||
* @deprecated Use {@link #registerDynamicListeners(Class)} with the new DynamicListener API.
|
||||
*/
|
||||
@Deprecated(since = "5.0.0", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0.0")
|
||||
@Deprecated(since = "5.0", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
protected void registerEvents(Class<? extends Listener> listenerClass) {
|
||||
var pluginManager = getServer().getPluginManager();
|
||||
Try.run(() -> serviceLocator.getAllServices(listenerClass).forEach(
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.permissions;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
@@ -15,6 +16,11 @@ import static org.mvplugins.multiverse.core.permissions.PermissionUtils.concatPe
|
||||
|
||||
@Service
|
||||
public final class CorePermissions {
|
||||
/**
|
||||
* Permission to bypass the join location.
|
||||
*/
|
||||
static final String JOINLOCATION_BYPASS = "mv.bypass.joinlocation";
|
||||
|
||||
/**
|
||||
* Permission to access a world.
|
||||
*/
|
||||
@@ -52,6 +58,12 @@ public final class CorePermissions {
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void registerBasePermissions() {
|
||||
pluginManager.addPermission(new Permission(JOINLOCATION_BYPASS, PermissionDefault.FALSE));
|
||||
Logging.fine("Successfully registered base permissions");
|
||||
}
|
||||
|
||||
public Try<Void> addWorldPermissions(@NotNull MultiverseWorld world) {
|
||||
return Try.run(() -> {
|
||||
pluginManager.addPermission(new Permission(
|
||||
|
||||
+16
-2
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.permissions;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -50,6 +51,19 @@ public final class CorePermissionsChecker {
|
||||
this.worldManager = worldManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the sender has permission to bypass the join location restriction.
|
||||
*
|
||||
* @param sender The command sender.
|
||||
* @return True if the sender has bypass permission, false otherwise.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean hasJoinLocationBypassPermission(@NotNull CommandSender sender) {
|
||||
return hasPermission(sender, CorePermissions.JOINLOCATION_BYPASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the sender has permission to access the specified world.
|
||||
*
|
||||
@@ -349,8 +363,8 @@ public final class CorePermissionsChecker {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public static Scope getApplicableScope(CommandSender sender, Entity entity) {
|
||||
if (sender instanceof Entity senderEntity && senderEntity.equals(entity)) {
|
||||
public static Scope getApplicableScope(CommandSender teleporter, Entity entity) {
|
||||
if (teleporter instanceof Entity senderEntity && senderEntity.equals(entity)) {
|
||||
return Scope.SELF;
|
||||
}
|
||||
return Scope.OTHER;
|
||||
|
||||
+110
-19
@@ -11,6 +11,7 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
@@ -20,6 +21,7 @@ import org.mvplugins.multiverse.core.utils.result.AsyncAttempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,7 @@ public final class AsyncSafetyTeleporterAction {
|
||||
|
||||
private final @NotNull Either<Location, DestinationInstance<?, ?>> locationOrDestination;
|
||||
private boolean checkSafety;
|
||||
private PassengerMode passengerMode = PassengerModes.DEFAULT;
|
||||
private @Nullable CommandSender teleporter = null;
|
||||
|
||||
AsyncSafetyTeleporterAction(
|
||||
@@ -58,18 +61,32 @@ public final class AsyncSafetyTeleporterAction {
|
||||
* Sets whether to check for safe location before teleport.
|
||||
*
|
||||
* @param checkSafety Whether to check for safe location
|
||||
* @return A new {@link AsyncSafetyTeleporterAction} to be chained
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*/
|
||||
public AsyncSafetyTeleporterAction checkSafety(boolean checkSafety) {
|
||||
this.checkSafety = checkSafety;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the passenger mode
|
||||
*
|
||||
* @param passengerMode The passenger mode
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public AsyncSafetyTeleporterAction passengerMode(@NotNull PassengerMode passengerMode) {
|
||||
this.passengerMode = passengerMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the teleporter.
|
||||
*
|
||||
* @param issuer The issuer
|
||||
* @return A new {@link AsyncSafetyTeleporterAction} to be chained
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*/
|
||||
public AsyncSafetyTeleporterAction by(@NotNull BukkitCommandIssuer issuer) {
|
||||
return by(issuer.getIssuer());
|
||||
@@ -79,7 +96,7 @@ public final class AsyncSafetyTeleporterAction {
|
||||
* Sets the teleporter.
|
||||
*
|
||||
* @param teleporter The teleporter
|
||||
* @return A new {@link AsyncSafetyTeleporterAction} to be chained
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*/
|
||||
public AsyncSafetyTeleporterAction by(@NotNull CommandSender teleporter) {
|
||||
this.teleporter = teleporter;
|
||||
@@ -94,7 +111,36 @@ public final class AsyncSafetyTeleporterAction {
|
||||
* @return A list of async futures that represent the teleportation result of each entity
|
||||
*/
|
||||
public <T extends Entity> AsyncAttemptsAggregate<Void, TeleportFailureReason> teleport(@NotNull List<T> teleportees) {
|
||||
return AsyncAttemptsAggregate.allOf(teleportees.stream().map(this::teleport).toList());
|
||||
return AsyncAttemptsAggregate.allOfAggregate(teleportees.stream().map(this::teleportSingle).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports one parent entity. Multiple entities may be teleported depending on {@link #passengerMode(PassengerMode)}.
|
||||
*
|
||||
* @param teleportee The entity to teleport
|
||||
* @return A list of async future that represents the teleportation result
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public AsyncAttemptsAggregate<Void, TeleportFailureReason> teleportSingle(@NotNull Entity teleportee) {
|
||||
var localTeleporter = this.teleporter == null ? teleportee : this.teleporter;
|
||||
|
||||
return getLocation(teleportee).mapAttempt(this::doSafetyCheck)
|
||||
.onSuccess(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.addToQueue(localTeleporter, player);
|
||||
}
|
||||
})
|
||||
.transform(
|
||||
location -> doAsyncTeleport(teleportee, location),
|
||||
failure -> AsyncAttemptsAggregate.allOf(AsyncAttempt.failure(failure))
|
||||
)
|
||||
.thenRun(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.popFromQueue(player.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,21 +148,14 @@ public final class AsyncSafetyTeleporterAction {
|
||||
*
|
||||
* @param teleportee The entity to teleport
|
||||
* @return An async future that represents the teleportation result
|
||||
*
|
||||
* @deprecated Use {@link #teleportSingle(Entity)} instead, as teleport of single entity may result in multiple
|
||||
* teleports due to vehicle and passengers based on {@link #passengerMode(PassengerMode)}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.1")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public AsyncAttempt<Void, TeleportFailureReason> teleport(@NotNull Entity teleportee) {
|
||||
var localTeleporter = this.teleporter == null ? teleportee : this.teleporter;
|
||||
return AsyncAttempt.fromAttempt(getLocation(teleportee).mapAttempt(this::doSafetyCheck))
|
||||
.onSuccess(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.addToQueue(localTeleporter, player);
|
||||
}
|
||||
})
|
||||
.mapAsyncAttempt(location -> doAsyncTeleport(teleportee, location))
|
||||
.thenRun(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.popFromQueue(player.getName());
|
||||
}
|
||||
});
|
||||
return teleportSingle(teleportee).getAttempts().get(0);
|
||||
}
|
||||
|
||||
private Attempt<Location, TeleportFailureReason> getLocation(@NotNull Entity teleportee) {
|
||||
@@ -160,9 +199,61 @@ public final class AsyncSafetyTeleporterAction {
|
||||
return Attempt.success(safeLocation);
|
||||
}
|
||||
|
||||
private AsyncAttempt<Void, TeleportFailureReason> doAsyncTeleport(
|
||||
private AsyncAttemptsAggregate<Void, TeleportFailureReason> doAsyncTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location) {
|
||||
@NotNull Location location
|
||||
) {
|
||||
if (passengerMode.isDismountVehicle() && teleportee.isInsideVehicle()) {
|
||||
Entity vehicle = teleportee.getVehicle();
|
||||
if (vehicle != null) {
|
||||
return doVehicleTeleport(teleportee, location, vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
List<Entity> passengers = teleportee.getPassengers();
|
||||
if (passengerMode.isDismountPassengers() && !passengers.isEmpty()) {
|
||||
passengers.forEach(teleportee::removePassenger);
|
||||
if (passengerMode.isPassengersFollow()) {
|
||||
return doPassengersTeleport(teleportee, location, passengers);
|
||||
}
|
||||
}
|
||||
|
||||
return AsyncAttemptsAggregate.allOf(doSingleTeleport(teleportee, location));
|
||||
}
|
||||
|
||||
private AsyncAttemptsAggregate<Void, TeleportFailureReason> doVehicleTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location,
|
||||
@NotNull Entity vehicle
|
||||
) {
|
||||
if (passengerMode.isVehicleFollow()) {
|
||||
return doPassengersTeleport(vehicle, location, vehicle.getPassengers());
|
||||
}
|
||||
teleportee.leaveVehicle();
|
||||
return doAsyncTeleport(teleportee, location);
|
||||
}
|
||||
|
||||
private AsyncAttemptsAggregate<Void, TeleportFailureReason> doPassengersTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location,
|
||||
@NotNull List<Entity> passengers
|
||||
) {
|
||||
List<Entity> toTeleport = new ArrayList<>(passengers);
|
||||
toTeleport.addFirst(teleportee);
|
||||
|
||||
return AsyncAttemptsAggregate.allOfAggregate(toTeleport.stream()
|
||||
.map(passenger -> doAsyncTeleport(passenger, location))
|
||||
.toList())
|
||||
.onSuccess(() -> Bukkit.getScheduler().runTask(multiverseCore, () -> {
|
||||
passengers.forEach(teleportee::addPassenger);
|
||||
Logging.finer("Mounted %d passengers to %s", passengers.size(), teleportee.getName());
|
||||
}));
|
||||
}
|
||||
|
||||
private AsyncAttempt<Void, TeleportFailureReason> doSingleTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location
|
||||
) {
|
||||
return AsyncAttempt.of(PaperLib.teleportAsync(teleportee, location), exception -> {
|
||||
Logging.warning("Failed to teleport %s to %s: %s",
|
||||
teleportee.getName(), location, exception.getMessage());
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Defines how passengers and vehicles on an entity should be handled when the entity is teleported.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public interface PassengerMode {
|
||||
/**
|
||||
* Defines whether the passengers should be removed from the parent entity when the parent entity is teleported.
|
||||
* <br/>
|
||||
* Teleports between worlds may fail if passengers are not removed.
|
||||
*
|
||||
* @return Whether the passengers should be removed
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isDismountPassengers();
|
||||
|
||||
/**
|
||||
* Defines whether the passengers should follow the parent entity when the parent entity is teleported.
|
||||
* <br/>
|
||||
* This only applies of passengers are removed from the parent entity, i.e. {@link #isDismountPassengers()} is true.
|
||||
*
|
||||
* @return Whether the passengers should follow
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isPassengersFollow();
|
||||
|
||||
/**
|
||||
* Defines whether the entity should dismount from the vehicle when the entity is teleported.
|
||||
* <br/>
|
||||
* Teleport between worlds may fail if entity is not dismounted.
|
||||
*
|
||||
* @return Whether the entity should dismount
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isDismountVehicle();
|
||||
|
||||
/**
|
||||
* Defines whether the vehicle and other passengers on it should follow the target entity on the vehicle when
|
||||
* the target entity is teleported.
|
||||
* <br/>
|
||||
* This only applies if the entity is dismounted from the vehicle, i.e. {@link #isDismountVehicle()} is true.
|
||||
*
|
||||
* @return Whether the vehicle should follow
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isVehicleFollow();
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Enum shorthands for {@link PassengerMode} to define common passenger modes configurations.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public enum PassengerModes implements PassengerMode {
|
||||
/**
|
||||
* All passengers and vehicles are handled by the server. This usually means that entities with passengers
|
||||
* or in vehicles will not be able to teleport to a different world until they manually dismount.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DEFAULT(false, false, false, false),
|
||||
|
||||
/**
|
||||
* All passengers will be removed from the parent entity before the teleport. Passengers will be left behind.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DISMOUNT_PASSENGERS(true, false, false, false),
|
||||
|
||||
/**
|
||||
* Parent entity will be dismounted from the vehicle before the teleport. Vehicle will be left behind.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DISMOUNT_VEHICLE(true, false, true, false),
|
||||
|
||||
/**
|
||||
* All passengers and vehicles will be removed from the parent entity before the teleport. Passengers and vehicles
|
||||
* will be left behind.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DISMOUNT_ALL(true, false, true, false),
|
||||
|
||||
/**
|
||||
* All passengers will teleport together with the parent entity.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
RETAIN_PASSENGERS(true, true, false, false),
|
||||
|
||||
/**
|
||||
* All vehicles will teleport together with the parent entity.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
RETAIN_VEHICLE(false, false, true, true),
|
||||
|
||||
/**
|
||||
* All passengers and vehicles will teleport together with the parent entity.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
RETAIN_ALL(true, true, true, true),
|
||||
;
|
||||
|
||||
private final boolean dismountPassengers;
|
||||
private final boolean passengersFollow;
|
||||
private final boolean dismountVehicle;
|
||||
private final boolean vehicleFollow;
|
||||
|
||||
PassengerModes(boolean dismountPassengers, boolean mountPassengers, boolean dismountVehicle, boolean mountVehicle) {
|
||||
this.dismountPassengers = dismountPassengers;
|
||||
this.passengersFollow = mountPassengers;
|
||||
this.dismountVehicle = dismountVehicle;
|
||||
this.vehicleFollow = mountVehicle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isDismountPassengers() {
|
||||
return dismountPassengers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPassengersFollow() {
|
||||
return passengersFollow;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isDismountVehicle() {
|
||||
return dismountVehicle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isVehicleFollow() {
|
||||
return vehicleFollow;
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public final class FileUtils {
|
||||
*/
|
||||
public @Nullable File getServerProperties() {
|
||||
if (this.serverProperties == null) {
|
||||
this.serverProperties = findFileFromServerDirectory("server.properties");
|
||||
this.serverProperties = findFileFromServerDirectory(config.getServerPropertiesPath());
|
||||
Logging.finer("server.properties: %s", this.serverProperties);
|
||||
}
|
||||
return this.serverProperties;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* Utility class for formatting Minecraft time to real-world time format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public final class MinecraftTimeFormatter {
|
||||
|
||||
private static final double TIME_MULTIPLIER = 3.6;
|
||||
private static final long DAY_SECONDS = 24 * 60 * 60;
|
||||
private static final long START_OFFSET = 6 * 60 * 60;
|
||||
|
||||
/**
|
||||
* Formats Minecraft time to 12-hour format.
|
||||
*
|
||||
* @param time The Minecraft time to format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String format12h(long time) {
|
||||
return formatTime(time, "hh:mm a");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats Minecraft time to 24-hour format.
|
||||
*
|
||||
* @param time The Minecraft time to format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String format24h(long time) {
|
||||
return formatTime(time, "HH:mm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats Minecraft time to the specified format.
|
||||
* See <a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter documentation</a>
|
||||
* for available patterns.
|
||||
*
|
||||
* @param time The Minecraft time to format.
|
||||
* @param format The format string for the time.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String formatTime(long time, String format) {
|
||||
// Convert Minecraft time to real-world time
|
||||
long realTime = (long) ((time * TIME_MULTIPLIER) + START_OFFSET) % DAY_SECONDS; // Minecraft ticks to seconds
|
||||
|
||||
// Convert seconds to LocalTime
|
||||
LocalTime localTime = LocalTime.ofSecondOfDay(realTime);
|
||||
|
||||
return Try.of(() -> DateTimeFormatter.ofPattern(format))
|
||||
.map(localTime::format)
|
||||
.getOrElse("invalid time format: " + format);
|
||||
}
|
||||
|
||||
private MinecraftTimeFormatter() {
|
||||
// No instantiation
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ExactStringMatcher is a StringMatcher that matches strings against a set of exact values.
|
||||
* It can be initialized with a single string, a collection of strings, or can have exact matches added later.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class ExactStringMatcher implements StringMatcher {
|
||||
private final Set<String> exactMatches;
|
||||
|
||||
/**
|
||||
* Creates a new ExactStringMatcher with no initial matches. Use {@link #addExactMatch(String)} to add matches later.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public ExactStringMatcher() {
|
||||
this.exactMatches = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ExactStringMatcher with a single exact match.
|
||||
*
|
||||
* @param exactMatch the exact string to match against
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public ExactStringMatcher(@NotNull String exactMatch) {
|
||||
this.exactMatches = new HashSet<>();
|
||||
this.exactMatches.add(exactMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ExactStringMatcher with multiple exact matches.
|
||||
*
|
||||
* @param exactMatches the collection of exact strings to match against
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public ExactStringMatcher(@NotNull Collection<String> exactMatches) {
|
||||
this.exactMatches = new HashSet<>(exactMatches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an exact match string to this matcher.
|
||||
*
|
||||
* @param value the exact string to add to the matcher
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public void addExactMatch(@NotNull String value) {
|
||||
this.exactMatches.add(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
return exactMatches.contains(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MatcherGroup is a collection of StringMatchers that can match against a string.
|
||||
* It works with all format supported by {@link StringMatcher#fromString(String)}.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class MatcherGroup implements StringMatcher {
|
||||
|
||||
private final ExactStringMatcher exactMatcher;
|
||||
private final List<StringMatcher> stringMatchers;
|
||||
|
||||
/**
|
||||
* Creates a new empty MatcherGroup.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MatcherGroup() {
|
||||
this.exactMatcher = new ExactStringMatcher();
|
||||
this.stringMatchers = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MatcherGroup with multiple strings to be parsed into matchers.
|
||||
*
|
||||
* @param matchStrings the collection of match strings
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MatcherGroup(@NotNull Collection<String> matchStrings) {
|
||||
this();
|
||||
for (String matchString : matchStrings) {
|
||||
addMatcher(matchString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MatcherGroup with a single exact match.
|
||||
*
|
||||
* @param matchString the single match string
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public void addMatcher(@Nullable String matchString) {
|
||||
if (matchString == null || matchString.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (isExact(matchString)) {
|
||||
exactMatcher.addExactMatch(matchString);
|
||||
} else {
|
||||
stringMatchers.add(StringMatcher.fromString(matchString));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExact(@NotNull String matcherString) {
|
||||
return !matcherString.contains("*") && !matcherString.startsWith("r=");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an existing matcher to the group.
|
||||
*
|
||||
* @param matcher the StringMatcher to add
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public void addMatcher(@NotNull StringMatcher matcher) {
|
||||
stringMatchers.add(matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
if (exactMatcher.matches(value)) {
|
||||
return true;
|
||||
}
|
||||
for (StringMatcher matcher : stringMatchers) {
|
||||
if (matcher.matches(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MatcherGroup{" + "exactMatcher=" + exactMatcher +
|
||||
", stringMatchers=" + stringMatchers +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* RegexStringMatcher is a StringMatcher that matches strings against a regex pattern.
|
||||
* It can be initialized with a regex string, which can optionally start with 'r=' to indicate it's a regex.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class RegexStringMatcher implements StringMatcher {
|
||||
private final @NotNull String regexString;
|
||||
private final @Nullable Pattern regexPattern;
|
||||
|
||||
/**
|
||||
* Creates a new RegexStringMatcher with a regex string. 'r=' prefix will be stripped if present.
|
||||
*
|
||||
* @param regexString the regex string to match against. If it starts with 'r=', that part will be ignored.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public RegexStringMatcher(@NotNull String regexString) {
|
||||
this.regexString = regexString;
|
||||
this.regexPattern = compileRegex(regexString);
|
||||
}
|
||||
|
||||
private Pattern compileRegex(String regexString) {
|
||||
if (regexString.startsWith("r=")) {
|
||||
regexString = regexString.substring(2);
|
||||
}
|
||||
|
||||
String finalRegexString = regexString;
|
||||
return Try.of(() -> Pattern.compile(finalRegexString))
|
||||
.onFailure(ex -> Logging.warning("Failed to compile regex '%s': %s",
|
||||
finalRegexString, ex.getMessage()))
|
||||
.getOrNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
if (regexPattern == null || value == null) {
|
||||
return false;
|
||||
}
|
||||
return regexPattern.matcher(value).matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* StringMatcher is an interface for matching strings against various patterns.
|
||||
* It currently built-in supports exact matches, wildcard matches, and regex matches.
|
||||
* <br />
|
||||
* Implement this interface to create your own custom string matchers.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public interface StringMatcher {
|
||||
|
||||
/**
|
||||
* Creates a StringMatcher from a string representation. When the string starts with "r=", it is treated as a regex.
|
||||
* If the string contains a '*', it is treated as a wildcard match.
|
||||
*
|
||||
* @param matcherString the string to be parsed into a matcher.
|
||||
* @return a StringMatcher instance based on the provided string.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
static @NotNull StringMatcher fromString(@NotNull String matcherString) {
|
||||
if (matcherString.startsWith("r=")) {
|
||||
return new RegexStringMatcher(matcherString);
|
||||
} else if (matcherString.contains("*")) {
|
||||
return new WildcardStringMatcher(matcherString);
|
||||
} else {
|
||||
return new ExactStringMatcher(matcherString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given value matches the pattern defined by this StringMatcher.
|
||||
*
|
||||
* @param value the string to match against the pattern.
|
||||
* @return true if the value matches, false otherwise.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
boolean matches(@Nullable String value);
|
||||
|
||||
/**
|
||||
* Filters a list of strings, returning only those that match the pattern defined by this StringMatcher.
|
||||
* This method is a convenience for applying the matcher to a collection of strings.
|
||||
*
|
||||
* @param values the list of strings to filter.
|
||||
* @return A list of strings that match the pattern defined by this StringMatcher.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
default @NotNull List<String> filter(@NotNull List<String> values) {
|
||||
return values.stream()
|
||||
.filter(this::matches)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* WildcardStringMatcher is a StringMatcher that matches strings against a wildcard pattern.
|
||||
* It supports '*' as a wildcard character, which can match any sequence of characters.
|
||||
* <br />
|
||||
* For example, the wildcard "foo*bar" will match any string that starts with "foo" and ends with "bar",
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class WildcardStringMatcher implements StringMatcher {
|
||||
|
||||
private final String wildcard;
|
||||
private final Pattern pattern;
|
||||
|
||||
/**
|
||||
* Creates a new WildcardStringMatcher with a wildcard string.
|
||||
* The wildcard string can contain '*' characters, which will be replaced with a regex equivalent.
|
||||
*
|
||||
* @param wildcard the wildcard string to match against.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public WildcardStringMatcher(@NotNull String wildcard) {
|
||||
this.wildcard = wildcard;
|
||||
this.pattern = Try.of(() -> Pattern.compile(("\\Q" + wildcard + "\\E").replace("*", "\\E.*\\Q")))
|
||||
.onFailure(ex -> Logging.warning("Failed to compile wildcard '%s': %s",
|
||||
wildcard, ex.getMessage()))
|
||||
.getOrNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
if (pattern == null || value == null) {
|
||||
return false;
|
||||
}
|
||||
return pattern.matcher(value).matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
/**
|
||||
* Represents a position for an entity in 3D space, including both coordinates and facing direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class EntityPosition {
|
||||
|
||||
/**
|
||||
* Creates an EntityPosition with absolute coordinates and direction.
|
||||
*
|
||||
* @param x The absolute X coordinate.
|
||||
* @param y The absolute Y coordinate.
|
||||
* @param z The absolute Z coordinate.
|
||||
* @param pitch The absolute pitch (vertical angle).
|
||||
* @param yaw The absolute yaw (horizontal angle).
|
||||
* @return A new EntityPosition instance with the specified absolute coordinates and direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition ofAbsolute(double x, double y, double z, double pitch, double yaw) {
|
||||
return new EntityPosition(
|
||||
VectorPosition.ofAbsolute(x, y, z),
|
||||
FaceDirection.ofAbsolute(pitch, yaw)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EntityPosition from a Bukkit Location with absolute coordinates and direction.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new EntityPosition instance representing the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition ofLocation(Location location) {
|
||||
return new EntityPosition(VectorPosition.ofLocation(location), FaceDirection.ofLocation(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an EntityPosition from a string representation.
|
||||
* The expected format is "<x>,<y>,<z>:<pitch>:<yaw>" for absolute coordinates and direction,
|
||||
* or "<x>,<y>,<z>" for absolute coordinates with default direction (0 pitch, 0 yaw).
|
||||
* <br>
|
||||
* Relative coordinates and direction can be specified using the '~' prefix, e.g., "~10,~,~-10:0:90".
|
||||
*
|
||||
* @param positionStr The string representation of the position.
|
||||
* @return A new EntityPosition instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition fromString(String positionStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COLON.split(positionStr, 2);
|
||||
return parts.length == 2
|
||||
? new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.fromString(parts[1]))
|
||||
: new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.ofAbsolute(0, 0));
|
||||
}
|
||||
|
||||
private final VectorPosition vector;
|
||||
private final FaceDirection direction;
|
||||
|
||||
/**
|
||||
* Creates a new EntityPosition with the specified vector and direction.
|
||||
*
|
||||
* @param vector The vector position (coordinates).
|
||||
* @param direction The facing direction (pitch and yaw).
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public EntityPosition(VectorPosition vector, FaceDirection direction) {
|
||||
this.vector = vector;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vector position (coordinates).
|
||||
*
|
||||
* @return The vector position.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public VectorPosition getVector() {
|
||||
return vector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the facing direction (pitch and yaw).
|
||||
*
|
||||
* @return The facing direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public FaceDirection getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments a given Bukkit Location by applying the relative components of this EntityPosition.
|
||||
* This modifies the provided Location in place.
|
||||
*
|
||||
* @param base The base Bukkit Location to augment and offset for relative positioning as required.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location base) {
|
||||
vector.augmentBukkitLocation(base);
|
||||
direction.augmentBukkitLocation(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this EntityPosition to a new Bukkit Location based on a given base Location.
|
||||
* This does not modify the base Location, but returns a new Location instance.
|
||||
*
|
||||
* @param base The base Bukkit Location to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Location representing this EntityPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Location toBukkitLocation(Location base) {
|
||||
return new Location(
|
||||
base.getWorld(),
|
||||
vector.getX().getValue(base.getX()),
|
||||
vector.getY().getValue(base.getY()),
|
||||
vector.getZ().getValue(base.getZ()),
|
||||
(float) direction.getYaw().getValue(base.getYaw()),
|
||||
(float) direction.getPitch().getValue(base.getPitch())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return vector + ":" + direction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents a direction to face, defined by pitch and yaw.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class FaceDirection {
|
||||
|
||||
/**
|
||||
* Creates a FaceDirection with absolute pitch and yaw.
|
||||
*
|
||||
* @param pitch The absolute pitch (vertical angle).
|
||||
* @param yaw The absolute yaw (horizontal angle).
|
||||
* @return A new FaceDirection instance with the specified absolute pitch and yaw.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection ofAbsolute(double pitch, double yaw) {
|
||||
return new FaceDirection(
|
||||
PositionNumber.ofAbsolute(pitch),
|
||||
PositionNumber.ofAbsolute(yaw)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pitch and yaw from a Bukkit Location to create an FaceDirection with absolute values.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new FaceDirection instance representing the direction of the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection ofLocation(org.bukkit.Location location) {
|
||||
return new FaceDirection(
|
||||
PositionNumber.ofAbsolute(location.getPitch()),
|
||||
PositionNumber.ofAbsolute(location.getYaw())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a FaceDirection from a string representation.
|
||||
* The expected format is "<pitch>:<yaw>" for absolute pitch and yaw.
|
||||
* <br>
|
||||
* Relative pitch and yaw can be specified using the '~' prefix, e.g., "~:~5".
|
||||
*
|
||||
* @param directionStr The string representation of the direction.
|
||||
* @return A new FaceDirection instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection fromString(String directionStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COLON.split(directionStr, 2);
|
||||
if (parts.length != 2) {
|
||||
throw new PositionParseException(Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDDIRECTION,
|
||||
replace("{format}").with(directionStr)));
|
||||
}
|
||||
//TODO: Add support for compass directions (N, S, E, W, NE, NW, SE, SW) for yaw
|
||||
PositionNumber pitch = PositionNumber.fromString(parts[0]);
|
||||
PositionNumber yaw = PositionNumber.fromString(parts[1]);
|
||||
return new FaceDirection(pitch, yaw);
|
||||
}
|
||||
|
||||
private final PositionNumber pitch;
|
||||
private final PositionNumber yaw;
|
||||
|
||||
/**
|
||||
* Creates a new FaceDirection with the specified pitch and yaw.
|
||||
*
|
||||
* @param pitch The pitch (vertical angle).
|
||||
* @param yaw The yaw (horizontal angle).
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public FaceDirection(PositionNumber pitch, PositionNumber yaw) {
|
||||
this.pitch = pitch;
|
||||
this.yaw = yaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pitch component of this FaceDirection.
|
||||
*
|
||||
* @return The pitch number representation.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getPitch() {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the yaw component of this FaceDirection.
|
||||
*
|
||||
* @return The yaw number representation.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getYaw() {
|
||||
return yaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this FaceDirection to a given Bukkit Location, modifying its pitch and yaw.
|
||||
* <br>
|
||||
* Relative pitch and yaw will be offset based on the current values in the Location.
|
||||
*
|
||||
* @param base The Bukkit Location to modify.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location base) {
|
||||
base.setPitch((float) pitch.getValue(base.getPitch()));
|
||||
base.setYaw((float) yaw.getValue(base.getYaw()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pitch + ":" + yaw;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents a number that can be either absolute or relative (prefixed with '~').
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public sealed interface PositionNumber permits PositionNumber.Relative, PositionNumber.Absolute {
|
||||
|
||||
/**
|
||||
* Creates an absolute PositionNumber. Absolute numbers represent fixed coordinates that will not be adjusted
|
||||
* based on any base value.
|
||||
*
|
||||
* @param value the absolute value
|
||||
* @return a PositionNumber representing the absolute value
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber ofAbsolute(double value) {
|
||||
return new Absolute(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a relative PositionNumber. Relative numbers are prefixed with '~' and represent an offset
|
||||
* from a base value. For example, a relative PositionNumber of '~5' means 5 units more than the base value,
|
||||
* while '~ -3' means 3 units less than the base value.
|
||||
*
|
||||
* @param value the relative offset value
|
||||
* @return a PositionNumber representing the relative offset
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber ofRelative(double value) {
|
||||
return new Relative(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a PositionNumber from a string. The string can represent either an absolute number (e.g., "10.5")
|
||||
* or a relative number prefixed with '~' (e.g., "~5" or "~ -3"). If the string is "~" with no number,
|
||||
* it is treated as a relative value of 0.
|
||||
*
|
||||
* @param string the string to parse
|
||||
* @return a PositionNumber representing the parsed value.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber fromString(String string) throws PositionParseException {
|
||||
if (string.startsWith("~")) {
|
||||
if (string.length() == 1) {
|
||||
return new Relative(0);
|
||||
}
|
||||
return new Relative(tryParseDouble(string.substring(1)));
|
||||
}
|
||||
return new Absolute(tryParseDouble(string));
|
||||
}
|
||||
|
||||
private static double tryParseDouble(String str) throws PositionParseException {
|
||||
return Try.of(() -> Double.parseDouble(str))
|
||||
.getOrElseThrow(throwable -> new PositionParseException(
|
||||
Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
|
||||
replace("{number}").with(str))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the effective value based on the base value.
|
||||
* For absolute PositionNumbers, this returns the stored value.
|
||||
* For relative PositionNumbers, this returns base + stored value.
|
||||
*
|
||||
* @param base the base value to use for relative calculations
|
||||
* @return the calculated effective value
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
double getValue(double base);
|
||||
|
||||
/**
|
||||
* Checks if this PositionNumber is relative.
|
||||
*
|
||||
* @return true if this is a relative PositionNumber, false if absolute
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
boolean isRelative();
|
||||
|
||||
/**
|
||||
* Checks if this PositionNumber is absolute.
|
||||
*
|
||||
* @return true if this is an absolute PositionNumber, false if relative
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
boolean isAbsolute();
|
||||
|
||||
/**
|
||||
* Gets the raw stored value without any base adjustment.
|
||||
*
|
||||
* @return the raw value (for relative, this is the offset; for absolute, this is the fixed value)
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
double getRawValue();
|
||||
|
||||
final class Relative implements PositionNumber {
|
||||
|
||||
private final double value;
|
||||
|
||||
Relative(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue(double base) {
|
||||
return base + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelative() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbsolute() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "~" + value;
|
||||
}
|
||||
}
|
||||
|
||||
final class Absolute implements PositionNumber {
|
||||
|
||||
private final double value;
|
||||
|
||||
Absolute(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue(double base) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelative() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbsolute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Double.toString(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents an x, y, z position in 3D space, with support for absolute and relative coordinates.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class VectorPosition {
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition with absolute coordinates.
|
||||
* @param x The absolute x coordinate.
|
||||
* @param y The absolute y coordinate.
|
||||
* @param z The absolute z coordinate.
|
||||
* @return A new VectorPosition instance with the specified absolute coordinates.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition ofAbsolute(double x, double y, double z) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(x),
|
||||
PositionNumber.ofAbsolute(y),
|
||||
PositionNumber.ofAbsolute(z)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition from a Bukkit Vector, using absolute coordinates.
|
||||
*
|
||||
* @param vector The Bukkit Vector to convert.
|
||||
* @return A new VectorPosition instance representing the given vector.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition ofVector(Vector vector) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(vector.getX()),
|
||||
PositionNumber.ofAbsolute(vector.getY()),
|
||||
PositionNumber.ofAbsolute(vector.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition from a Bukkit Location, using absolute coordinates.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new VectorPosition instance representing the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
public static VectorPosition ofLocation(Location location) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(location.getX()),
|
||||
PositionNumber.ofAbsolute(location.getY()),
|
||||
PositionNumber.ofAbsolute(location.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a VectorPosition from a string representation.
|
||||
* The expected format is "<x>,<y>,<z>" for absolute or relative coordinates.
|
||||
* <br>
|
||||
* Relative coordinates can be specified using the '~' prefix, e.g., "~10,~,~-10".
|
||||
*
|
||||
* @param coordStr The string representation of the coordinates.
|
||||
* @return A new VectorPosition instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition fromString(String coordStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COMMA.split(coordStr);
|
||||
if (parts.length != 3) {
|
||||
throw new PositionParseException(Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
|
||||
replace("{format}").with(coordStr)));
|
||||
}
|
||||
return new VectorPosition(
|
||||
PositionNumber.fromString(parts[0]),
|
||||
PositionNumber.fromString(parts[1]),
|
||||
PositionNumber.fromString(parts[2])
|
||||
);
|
||||
}
|
||||
|
||||
private final PositionNumber x;
|
||||
private final PositionNumber y;
|
||||
private final PositionNumber z;
|
||||
|
||||
/**
|
||||
* Creates a new VectorPosition with the specified PositionNumbers for x, y, and z.
|
||||
*
|
||||
* @param x The PositionNumber for the x coordinate.
|
||||
* @param y The PositionNumber for the y coordinate.
|
||||
* @param z The PositionNumber for the z coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public VectorPosition(PositionNumber x, PositionNumber y, PositionNumber z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the x coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the x coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the y coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the y coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the z coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the z coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the given Bukkit Vector in place by applying this VectorPosition's coordinates.
|
||||
* Relative coordinates will adjust the existing values, while absolute coordinates will set them directly.
|
||||
*
|
||||
* @param base The Bukkit Vector to augment.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitVector(Vector base) {
|
||||
base.setX(x.getValue(base.getX()));
|
||||
base.setY(y.getValue(base.getY()));
|
||||
base.setZ(z.getValue(base.getZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the given Bukkit Location in place by applying this VectorPosition's coordinates.
|
||||
* Relative coordinates will adjust the existing values, while absolute coordinates will set them directly.
|
||||
*
|
||||
* @param location The Bukkit Location to augment.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location location) {
|
||||
location.setX(x.getValue(location.getX()));
|
||||
location.setY(y.getValue(location.getY()));
|
||||
location.setZ(z.getValue(location.getZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this VectorPosition to a new Bukkit Vector based on a given base Vector.
|
||||
* This does not modify the base Vector, but returns a new Vector instance.
|
||||
*
|
||||
* @param base The base Bukkit Vector to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Vector representing this VectorPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Vector toBukkitVector(Vector base) {
|
||||
return new Vector(
|
||||
x.getValue(base.getX()),
|
||||
y.getValue(base.getY()),
|
||||
z.getValue(base.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this VectorPosition to a new Bukkit Location based on a given base Location.
|
||||
* This does not modify the base Location, but returns a new Location instance.
|
||||
*
|
||||
* @param base The base Bukkit Location to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Location representing this VectorPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Location toBukkitLocation(Location base) {
|
||||
return new Location(
|
||||
base.getWorld(),
|
||||
x.getValue(base.getX()),
|
||||
y.getValue(base.getY()),
|
||||
z.getValue(base.getZ()),
|
||||
base.getYaw(),
|
||||
base.getPitch()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return x + "," + y + "," + z;
|
||||
}
|
||||
}
|
||||
+83
-16
@@ -1,5 +1,8 @@
|
||||
package org.mvplugins.multiverse.core.utils.result;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -36,7 +39,39 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return An instance of {@link AsyncAttemptsAggregate}.
|
||||
*/
|
||||
public static <T, F extends FailureReason> AsyncAttemptsAggregate<T, F> allOf(AsyncAttempt<T, F>... attempts) {
|
||||
return new AsyncAttemptsAggregate<>(List.of(attempts));
|
||||
return allOf(List.of(attempts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple {@link AsyncAttemptsAggregate} lists into a single one.
|
||||
*
|
||||
* @param attempts The asynchronous attempts aggregates to combine.
|
||||
* @param <T> The type of the successful result.
|
||||
* @param <F> The type representing failure reasons.
|
||||
* @return An instance of {@link AsyncAttemptsAggregate}.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static <T, F extends FailureReason> AsyncAttemptsAggregate<T, F> allOfAggregate(List<AsyncAttemptsAggregate<T, F>> attempts) {
|
||||
return new AsyncAttemptsAggregate<>(attempts.stream()
|
||||
.flatMap(a -> a.attempts.stream())
|
||||
.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple {@link AsyncAttemptsAggregate} varargs arrays into a single one.
|
||||
*
|
||||
* @param attempts The asynchronous attempts aggregates to combine.
|
||||
* @param <T> The type of the successful result.
|
||||
* @param <F> The type representing failure reasons.
|
||||
* @return An instance of {@link AsyncAttemptsAggregate}.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static <T, F extends FailureReason> AsyncAttemptsAggregate<T, F> allOfAggregate(AsyncAttemptsAggregate<T, F>... attempts) {
|
||||
return allOfAggregate(List.of(attempts));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,22 +82,40 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return An instance of {@link AsyncAttemptsAggregate} with no attempts.
|
||||
*/
|
||||
public static <T, F extends FailureReason> AsyncAttemptsAggregate<T, F> emptySuccess() {
|
||||
return new AsyncAttemptsAggregate<>(CompletableFuture.completedFuture(AttemptsAggregate.emptySuccess()));
|
||||
return new AsyncAttemptsAggregate<>(
|
||||
Collections.emptyList(),
|
||||
CompletableFuture.completedFuture(AttemptsAggregate.emptySuccess())
|
||||
);
|
||||
}
|
||||
|
||||
private final List<AsyncAttempt<T, F>> attempts;
|
||||
private final CompletableFuture<AttemptsAggregate<T, F>> future;
|
||||
|
||||
private AsyncAttemptsAggregate(List<AsyncAttempt<T, F>> attempts) {
|
||||
future = CompletableFuture.allOf(attempts.stream().map(AsyncAttempt::getFuture).toArray(CompletableFuture[]::new))
|
||||
this.attempts = attempts;
|
||||
this.future = CompletableFuture.allOf(attempts.stream().map(AsyncAttempt::getFuture).toArray(CompletableFuture[]::new))
|
||||
.thenApply(v -> AttemptsAggregate.allOf(attempts.stream()
|
||||
.map(AsyncAttempt::getFuture)
|
||||
.map(CompletableFuture::join).toList()));
|
||||
}
|
||||
|
||||
private AsyncAttemptsAggregate(CompletableFuture<AttemptsAggregate<T, F>> future) {
|
||||
private AsyncAttemptsAggregate(List<AsyncAttempt<T, F>> attempts, CompletableFuture<AttemptsAggregate<T, F>> future) {
|
||||
this.attempts = attempts;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immutable copy of the list of asynchronous attempts that this aggregate represents.
|
||||
*
|
||||
* @return The list of asynchronous attempts.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public List<AsyncAttempt<T, F>> getAttempts() {
|
||||
return attempts.stream().toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the provided {@link Runnable} if there are successful attempts.
|
||||
*
|
||||
@@ -70,8 +123,7 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return A new {@link AsyncAttemptsAggregate} instance.
|
||||
*/
|
||||
public AsyncAttemptsAggregate<T, F> onSuccess(Runnable runnable) {
|
||||
return new AsyncAttemptsAggregate<>(
|
||||
future.thenApply(aggregate -> aggregate.onSuccess(runnable)));
|
||||
return newFuture(future.thenApply(aggregate -> aggregate.onSuccess(runnable)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,8 +133,7 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return A new {@link AsyncAttemptsAggregate} instance.
|
||||
*/
|
||||
public AsyncAttemptsAggregate<T, F> onFailure(Runnable runnable) {
|
||||
return new AsyncAttemptsAggregate<>(
|
||||
future.thenApply(aggregate -> aggregate.onFailure(runnable)));
|
||||
return newFuture(future.thenApply(aggregate -> aggregate.onFailure(runnable)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,8 +143,7 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return A new {@link AsyncAttemptsAggregate} instance.
|
||||
*/
|
||||
public AsyncAttemptsAggregate<T, F> onSuccess(Consumer<List<Attempt<T, F>>> successConsumer) {
|
||||
return new AsyncAttemptsAggregate<>(
|
||||
future.thenApply(aggregate -> aggregate.onSuccess(successConsumer)));
|
||||
return newFuture(future.thenApply(aggregate -> aggregate.onSuccess(successConsumer)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,8 +153,7 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return A new {@link AsyncAttemptsAggregate} instance.
|
||||
*/
|
||||
public AsyncAttemptsAggregate<T, F> onFailure(Consumer<List<Attempt<T, F>>> failureConsumer) {
|
||||
return new AsyncAttemptsAggregate<>(
|
||||
future.thenApply(aggregate -> aggregate.onFailure(failureConsumer)));
|
||||
return newFuture(future.thenApply(aggregate -> aggregate.onFailure(failureConsumer)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,8 +163,7 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return A new {@link AsyncAttemptsAggregate} instance.
|
||||
*/
|
||||
public AsyncAttemptsAggregate<T, F> onSuccessCount(Consumer<Integer> successConsumer) {
|
||||
return new AsyncAttemptsAggregate<>(
|
||||
future.thenApply(aggregate -> aggregate.onSuccessCount(successConsumer)));
|
||||
return newFuture(future.thenApply(aggregate -> aggregate.onSuccessCount(successConsumer)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +173,26 @@ public final class AsyncAttemptsAggregate<T, F extends FailureReason> {
|
||||
* @return A new {@link AsyncAttemptsAggregate} instance.
|
||||
*/
|
||||
public AsyncAttemptsAggregate<T, F> onFailureCount(Consumer<Map<F, Long>> failureConsumer) {
|
||||
return new AsyncAttemptsAggregate<>(
|
||||
future.thenApply(aggregate -> aggregate.onFailureCount(failureConsumer)));
|
||||
return newFuture(future.thenApply(aggregate -> aggregate.onFailureCount(failureConsumer)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an action after all async attempts are completed.
|
||||
*
|
||||
* @param runnable The action to execute after all async attempts are completed.
|
||||
* @return A new {@link AsyncAttemptsAggregate} instance.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public AsyncAttemptsAggregate<T, F> thenRun(Runnable runnable) {
|
||||
return newFuture(future.thenApply(aggregate -> {
|
||||
runnable.run();
|
||||
return aggregate;
|
||||
}));
|
||||
}
|
||||
|
||||
private AsyncAttemptsAggregate<T, F> newFuture(CompletableFuture<AttemptsAggregate<T, F>> future) {
|
||||
return new AsyncAttemptsAggregate<>(attempts, future);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.vavr.control.Either;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
|
||||
@@ -117,6 +120,29 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
return this instanceof Failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this {@link Attempt} instance to an equivalent {@link Try} representation. Defaults to a
|
||||
* {@link MultiverseException} with failure message if this is a failure attempt.
|
||||
*
|
||||
* @return A {@link Try} instance representing the result of this {@code Attempt}.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
Try<T> toTry();
|
||||
|
||||
/**
|
||||
* Converts this attempt to a {@code Try} instance. If this attempt represents a failure, the
|
||||
* provided exception supplier will be invoked to create a failed try.
|
||||
*
|
||||
* @param throwableFunction A function that provides a throwable in case of failure.
|
||||
* @return The {@link Try} instance corresponding to this attempt.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
Try<T> toTry(Function<Failure<T, F>, Throwable> throwableFunction);
|
||||
|
||||
default Attempt<T, F> thenRun(Runnable runnable) {
|
||||
runnable.run();
|
||||
return this;
|
||||
@@ -155,7 +181,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(mapper.apply(get()));
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +196,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(mapper.get());
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +211,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return mapper.apply(get());
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +226,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return mapper.get();
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +241,26 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(get());
|
||||
} else {
|
||||
return new Failure<>(failureReason, getFailureMessage());
|
||||
return new Failure<>(failureReason, getFailureMessage(), (Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps attempt result to another value.
|
||||
*
|
||||
* @param successMapper Action taken if the attempt is a success
|
||||
* @param failureMapper Action taken if the attempt is a failure
|
||||
* @param <U> The transformed value type
|
||||
* @return The transformed value
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default <U> U transform(Function<T, U> successMapper, Function<F, U> failureMapper) {
|
||||
if (this instanceof Success) {
|
||||
return successMapper.apply(get());
|
||||
} else {
|
||||
return failureMapper.apply(getFailureReason());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +378,16 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<T> toTry() {
|
||||
return Try.success(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<T> toTry(Function<Failure<T, F>, Throwable> throwableFunction) {
|
||||
return Try.success(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getFailureReason() {
|
||||
throw new UnsupportedOperationException("No failure reason as attempt is a success");
|
||||
@@ -360,10 +415,20 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
final class Failure<T, F extends FailureReason> implements Attempt<T, F> {
|
||||
private final F failureReason;
|
||||
private final Message message;
|
||||
private final Failure<?, ?> causeBy;
|
||||
|
||||
Failure(F failureReason, Message message) {
|
||||
this(failureReason, message, null);
|
||||
}
|
||||
|
||||
Failure(Failure<?, F> failure) {
|
||||
this(failure.failureReason, failure.message, failure.causeBy);
|
||||
}
|
||||
|
||||
Failure(F failureReason, Message message, Failure<?, ?> causeBy) {
|
||||
this.failureReason = failureReason;
|
||||
this.message = message;
|
||||
this.causeBy = causeBy;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -386,6 +451,16 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
throw exceptionSupplier.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<T> toTry() {
|
||||
return Try.failure(new MultiverseException(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<T> toTry(Function<Failure<T, F>, Throwable> throwableFunction) {
|
||||
return Try.failure(throwableFunction.apply(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getFailureReason() {
|
||||
return failureReason;
|
||||
@@ -400,6 +475,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
public String toString() {
|
||||
return "Failure{"
|
||||
+ "reason=" + failureReason
|
||||
+ (causeBy != null ? ", causeBy=" + causeBy : "")
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,21 @@ package org.mvplugins.multiverse.core.utils.result;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
|
||||
/**
|
||||
* Represents a failure reason for an {@link Attempt}.
|
||||
*/
|
||||
public interface FailureReason extends MessageKeyProvider {
|
||||
/**
|
||||
* A generic failure reason that can be used when no specific reason is applicable.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
FailureReason GENERIC = new FailureReason() { };
|
||||
|
||||
default MessageKey getMessageKey() {
|
||||
return MVCorei18n.GENERIC_FAILURE.getMessageKey();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,21 @@ package org.mvplugins.multiverse.core.utils.result;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
|
||||
/**
|
||||
* Represents a success reason for an {@link Attempt}.
|
||||
*/
|
||||
public interface SuccessReason extends MessageKeyProvider {
|
||||
/**
|
||||
* A generic success reason that can be used when no specific reason is applicable.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
SuccessReason GENERIC = new SuccessReason() { };
|
||||
|
||||
default MessageKey getMessageKey() {
|
||||
return MVCorei18n.GENERIC_SUCCESS.getMessageKey();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.mvplugins.multiverse.core.utils.text;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
final class AdventureTextFormatter implements TextFormatter {
|
||||
@Override
|
||||
public void sendFormattedMessage(@NotNull CommandSender sender, @Nullable String message) {
|
||||
String colorizedMessage = colorize(message);
|
||||
if (colorizedMessage == null) {
|
||||
return; // Avoid sending null messages
|
||||
}
|
||||
sender.sendMessage(colorizedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String removeColor(@Nullable String message) {
|
||||
TextComponent sectionComponent = LegacyComponentSerializer.legacySection().deserializeOrNull(colorize(message));
|
||||
return PlainTextComponentSerializer.plainText().serializeOrNull(sectionComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String removeAmpColor(@Nullable String message) {
|
||||
return PlainTextComponentSerializer.plainText().serializeOrNull(toAmpComponent(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String removeSectionColor(@Nullable String message) {
|
||||
return PlainTextComponentSerializer.plainText().serializeOrNull(toSectionComponent(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String colorize(@Nullable String message) {
|
||||
return LegacyComponentSerializer.legacySection().serializeOrNull(toAmpComponent(message));
|
||||
}
|
||||
|
||||
private @Nullable TextComponent toAmpComponent(@Nullable String message) {
|
||||
return LegacyComponentSerializer.legacyAmpersand().deserializeOrNull(message);
|
||||
}
|
||||
|
||||
private @Nullable TextComponent toSectionComponent(@Nullable String message) {
|
||||
return LegacyComponentSerializer.legacySection().deserializeOrNull(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.mvplugins.multiverse.core.utils.text;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
final class ChatColorTextFormatter implements TextFormatter {
|
||||
@Override
|
||||
public void sendFormattedMessage(@NotNull CommandSender sender, String message) {
|
||||
sender.sendMessage(colorize(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String removeColor(@Nullable String message) {
|
||||
return ChatColor.stripColor(colorize(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String removeAmpColor(@Nullable String message) {
|
||||
return removeColor(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String removeSectionColor(@Nullable String message) {
|
||||
return ChatColor.stripColor(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String colorize(@Nullable String message) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
return ChatColor.translateAlternateColorCodes('&', message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.mvplugins.multiverse.core.utils.text;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
/**
|
||||
* Utility class to format chat messages. Uses Kyori Adventure if available for better format support such as &#rrggbb
|
||||
* hex codes. Falls back to bukkit's ChatColor if Kyori Adventure is not available.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public final class ChatTextFormatter {
|
||||
|
||||
private static final TextFormatter wrapper;
|
||||
|
||||
static {
|
||||
if (ReflectHelper.hasClass("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer")
|
||||
&& ReflectHelper.hasClass("net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer")) {
|
||||
wrapper = new AdventureTextFormatter();
|
||||
} else {
|
||||
wrapper = new ChatColorTextFormatter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message with color formatting applied.
|
||||
*
|
||||
* @param sender The sender to send the message to
|
||||
* @param message The message to send
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static void sendFormattedMessage(@NotNull CommandSender sender, @Nullable String message) {
|
||||
wrapper.sendFormattedMessage(sender, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all color formatting from the message.
|
||||
*
|
||||
* @param message The text to remove color from
|
||||
* @return The text with color formatting removed
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
public static @Nullable String removeColor(@Nullable String message) {
|
||||
return wrapper.removeColor(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes & color formatting from the message.
|
||||
*
|
||||
* @param message The text to remove color from
|
||||
* @return The text with color formatting removed
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static @Nullable String removeAmpColor(@Nullable String message) {
|
||||
return wrapper.removeAmpColor(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes § color formatting from the message.
|
||||
*
|
||||
* @param message The text to remove color from
|
||||
* @return The text with color formatting removed
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static @Nullable String removeSectionColor(@Nullable String message) {
|
||||
return wrapper.removeSectionColor(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies color formatting to the message.
|
||||
*
|
||||
* @param message The text to apply color to
|
||||
* @return The text with color formatting
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static @Nullable String colorize(@Nullable String message) {
|
||||
return wrapper.colorize(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.mvplugins.multiverse.core.utils.text;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
interface TextFormatter {
|
||||
void sendFormattedMessage(@NotNull CommandSender sender, @Nullable String message);
|
||||
|
||||
@Nullable String removeColor(@Nullable String message);
|
||||
|
||||
@Nullable String removeAmpColor(@Nullable String message);
|
||||
|
||||
@Nullable String removeSectionColor(@Nullable String message);
|
||||
|
||||
@Nullable String colorize(@Nullable String message);
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Difficulty;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
@@ -16,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
}
|
||||
|
||||
void updateColourlessAlias() {
|
||||
colourlessAlias = ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', getAliasOrName()));
|
||||
colourlessAlias = ChatTextFormatter.removeColor(getAliasOrName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldPropertyChangedEvent;
|
||||
import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
@@ -37,11 +38,13 @@ final class WorldConfigNodes {
|
||||
private final NodeGroup nodes = new NodeGroup();
|
||||
private WorldManager worldManager;
|
||||
private EnforcementHandler enforcementHandler;
|
||||
private CoreConfig config;
|
||||
private MultiverseWorld world = null;
|
||||
|
||||
WorldConfigNodes(@NotNull MultiverseCore multiverseCore) {
|
||||
this.worldManager = multiverseCore.getServiceLocator().getService(WorldManager.class);
|
||||
this.enforcementHandler = multiverseCore.getServiceLocator().getService(EnforcementHandler.class);
|
||||
this.config = multiverseCore.getServiceLocator().getService(CoreConfig.class);
|
||||
}
|
||||
|
||||
MultiverseWorld getWorld() {
|
||||
@@ -238,13 +241,13 @@ final class WorldConfigNodes {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
if (newValue == null || newValue instanceof NullSpawnLocation) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
bukkitWorld.setSpawnLocation(newValue.getBlockX(), newValue.getBlockY(), newValue.getBlockZ());
|
||||
bukkitWorld.setSpawnLocation(newValue);
|
||||
newValue.setWorld(bukkitWorld);
|
||||
});
|
||||
}));
|
||||
|
||||
final ConfigNode<EntitySpawnConfig> enititySpawnConfig = node(ConfigNode.builder("spawning", EntitySpawnConfig.class)
|
||||
.defaultValue(() -> EntitySpawnConfig.fromSection(new MemoryConfiguration()))
|
||||
.defaultValue(() -> EntitySpawnConfig.fromSection(config, new MemoryConfiguration()))
|
||||
.hidden()
|
||||
.serializer(new NodeSerializer<>() {
|
||||
@Override
|
||||
@@ -252,7 +255,7 @@ final class WorldConfigNodes {
|
||||
ConfigurationSection spawnSection = (object instanceof ConfigurationSection section)
|
||||
? section
|
||||
: new MemoryConfiguration();
|
||||
return EntitySpawnConfig.fromSection(spawnSection);
|
||||
return EntitySpawnConfig.fromSection(config, spawnSection);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -19,12 +20,12 @@ import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -45,10 +46,12 @@ import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissions;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
import org.mvplugins.multiverse.core.utils.ServerProperties;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.utils.FileUtils;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
@@ -61,7 +64,9 @@ import org.mvplugins.multiverse.core.world.options.CreateWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.ImportWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.KeepWorldSettingsOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.RegenWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.reasons.CloneFailureReason;
|
||||
import org.mvplugins.multiverse.core.world.reasons.CreateFailureReason;
|
||||
@@ -139,6 +144,7 @@ public final class WorldManager {
|
||||
*
|
||||
* @return The result of the load.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public Try<Void> initAllWorlds() {
|
||||
return updateWorldsFromConfig().andThenTry(() -> {
|
||||
importExistingWorlds();
|
||||
@@ -166,7 +172,11 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private void removeWorldsNotInConfigs(Collection<String> removedWorlds) {
|
||||
removedWorlds.forEach(worldName -> removeWorld(worldName)
|
||||
removedWorlds.forEach(worldName -> getWorld(worldName)
|
||||
.fold(
|
||||
() -> Attempt.failure(FailureReason.GENERIC, Message.of("world already removed")),
|
||||
world -> removeWorld(RemoveWorldOptions.world(world))
|
||||
)
|
||||
.onFailure(failure -> Logging.severe("Failed to unload world %s: %s", worldName, failure))
|
||||
.onSuccess(success -> Logging.fine("Unloaded world %s as it was removed from config", worldName)));
|
||||
}
|
||||
@@ -217,8 +227,9 @@ public final class WorldManager {
|
||||
if (isLoadedWorld(world) || !world.isAutoLoad()) {
|
||||
return;
|
||||
}
|
||||
loadWorld(world).onFailure(failure -> Logging.severe("Failed to load world %s: %s",
|
||||
world.getName(), failure));
|
||||
loadWorld(LoadWorldOptions.world(world))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to load world %s: %s", world.getName(), failure));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,23 +288,30 @@ public final class WorldManager {
|
||||
* @param options The options for customizing the import of an existing world folder.
|
||||
* @return The result of the import.
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, ImportFailureReason> importWorld(
|
||||
ImportWorldOptions options) {
|
||||
return validateImportWorldOptions(options).mapAttempt(this::doImportWorld);
|
||||
}
|
||||
|
||||
private Attempt<ImportWorldOptions, ImportFailureReason> validateImportWorldOptions(
|
||||
ImportWorldOptions options) {
|
||||
public Attempt<LoadedMultiverseWorld, ImportFailureReason> importWorld(ImportWorldOptions options) {
|
||||
String worldName = options.worldName();
|
||||
if (!worldNameChecker.isValidWorldName(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.INVALID_WORLDNAME, worldName);
|
||||
} else if (!worldNameChecker.isValidWorldFolder(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, worldName);
|
||||
} else if (isLoadedWorld(worldName)) {
|
||||
if (isLoadedWorld(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_EXIST_LOADED, worldName);
|
||||
} else if (isWorld(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_EXIST_UNLOADED, worldName);
|
||||
}
|
||||
|
||||
World bukkitWorld = Bukkit.getWorld(worldName);
|
||||
if (bukkitWorld != null) {
|
||||
// World is already loaded, maybe by another plugin
|
||||
return doImportBukkitWorld(options, bukkitWorld);
|
||||
}
|
||||
|
||||
return validateImportWorldOptions(options).mapAttempt(this::doImportWorld);
|
||||
}
|
||||
|
||||
private Attempt<ImportWorldOptions, ImportFailureReason> validateImportWorldOptions(ImportWorldOptions options) {
|
||||
String worldName = options.worldName();
|
||||
if (!worldNameChecker.isValidWorldName(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.INVALID_WORLDNAME, worldName);
|
||||
} else if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, worldName);
|
||||
}
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
@@ -318,6 +336,23 @@ public final class WorldManager {
|
||||
});
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportBukkitWorld(ImportWorldOptions options, World bukkitWorld) {
|
||||
if (options.environment() != bukkitWorld.getEnvironment()) {
|
||||
return Attempt.failure(ImportFailureReason.BUKKIT_ENVIRONMENT_MISMATCH,
|
||||
Replace.WORLD.with(bukkitWorld.getName()),
|
||||
replace("{bukkitEnvironment}").with(bukkitWorld.getEnvironment().name()),
|
||||
replace("{mvEnvironment}").with(options.environment().name()));
|
||||
}
|
||||
|
||||
LoadedMultiverseWorld loadedWorld = newLoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
generatorProvider.parseGeneratorString(options.worldName(), options.generator()),
|
||||
options.biome(),
|
||||
options.useSpawnAdjust());
|
||||
pluginManager.callEvent(new MVWorldImportedEvent(loadedWorld));
|
||||
return Attempt.success(loadedWorld);
|
||||
}
|
||||
|
||||
private MultiverseWorld newMultiverseWorld(String worldName, WorldConfig worldConfig) {
|
||||
MultiverseWorld mvWorld = new MultiverseWorld(worldName, worldConfig, config);
|
||||
worldsMap.put(mvWorld.getName(), mvWorld);
|
||||
@@ -370,10 +405,14 @@ public final class WorldManager {
|
||||
*
|
||||
* @param worldName The name of the world to load.
|
||||
* @return The result of the load.
|
||||
*
|
||||
* @deprecated Use {@link #loadWorld(LoadWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<LoadedMultiverseWorld, LoadFailureReason> loadWorld(@NotNull String worldName) {
|
||||
return getWorld(worldName)
|
||||
.map(this::loadWorld)
|
||||
.map(world -> loadWorld(LoadWorldOptions.world(world)))
|
||||
.getOrElse(() -> worldNameChecker.isValidWorldFolder(worldName)
|
||||
? worldActionResult(LoadFailureReason.WORLD_EXIST_FOLDER, worldName)
|
||||
: worldActionResult(LoadFailureReason.WORLD_NON_EXISTENT, worldName));
|
||||
@@ -384,12 +423,30 @@ public final class WorldManager {
|
||||
*
|
||||
* @param world The world to load.
|
||||
* @return The result of the load.
|
||||
*
|
||||
* @deprecated Use {@link #loadWorld(LoadWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<LoadedMultiverseWorld, LoadFailureReason> loadWorld(@NotNull MultiverseWorld world) {
|
||||
return validateWorldToLoad(world).mapAttempt(this::doLoadWorld);
|
||||
return loadWorld(LoadWorldOptions.world(world));
|
||||
}
|
||||
|
||||
private Attempt<MultiverseWorld, LoadFailureReason> validateWorldToLoad(@NotNull MultiverseWorld mvWorld) {
|
||||
/**
|
||||
* Loads an existing world in config.
|
||||
*
|
||||
* @param options The options for customizing the loading of a world.
|
||||
* @return The result of the load.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public Attempt<LoadedMultiverseWorld, LoadFailureReason> loadWorld(@NotNull LoadWorldOptions options) {
|
||||
return validateWorldToLoad(options).mapAttempt(this::doLoadWorld);
|
||||
}
|
||||
|
||||
private Attempt<LoadWorldOptions, LoadFailureReason> validateWorldToLoad(@NotNull LoadWorldOptions options) {
|
||||
MultiverseWorld mvWorld = options.world();
|
||||
if (loadTracker.contains(mvWorld.getName())) {
|
||||
// This is to prevent recursive calls by WorldLoadEvent
|
||||
Logging.fine("World already loading: " + mvWorld.getName());
|
||||
@@ -398,15 +455,19 @@ public final class WorldManager {
|
||||
Logging.severe("World already loaded: " + mvWorld.getName());
|
||||
return worldActionResult(LoadFailureReason.WORLD_EXIST_LOADED, mvWorld.getName());
|
||||
}
|
||||
return worldActionResult(mvWorld);
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, LoadFailureReason> doLoadWorld(@NotNull MultiverseWorld mvWorld) {
|
||||
private Attempt<LoadedMultiverseWorld, LoadFailureReason> doLoadWorld(@NotNull LoadWorldOptions options) {
|
||||
MultiverseWorld mvWorld = options.world();
|
||||
|
||||
World bukkitWorld = Bukkit.getWorld(mvWorld.getName());
|
||||
if (bukkitWorld != null) {
|
||||
// World already loaded, maybe by another plugin
|
||||
Logging.finer("World already loaded in bukkit: " + mvWorld.getName());
|
||||
return newLoadedMultiverseWorld(mvWorld, bukkitWorld);
|
||||
return doLoadBukkitWorld(bukkitWorld, mvWorld);
|
||||
}
|
||||
|
||||
if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(mvWorld.getName())) {
|
||||
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
|
||||
}
|
||||
|
||||
WorldCreator worldCreator = WorldCreator.name(mvWorld.getName())
|
||||
@@ -419,6 +480,18 @@ public final class WorldManager {
|
||||
.mapAttempt(newBukkitWorld -> newLoadedMultiverseWorld(mvWorld, newBukkitWorld));
|
||||
}
|
||||
|
||||
private @NotNull Attempt<LoadedMultiverseWorld, LoadFailureReason> doLoadBukkitWorld(World bukkitWorld, MultiverseWorld mvWorld) {
|
||||
if (bukkitWorld.getEnvironment() != mvWorld.getEnvironment()) {
|
||||
return Attempt.failure(LoadFailureReason.BUKKIT_ENVIRONMENT_MISMATCH,
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
replace("{bukkitEnvironment}").with(bukkitWorld.getEnvironment().name()),
|
||||
replace("{mvEnvironment}").with(mvWorld.getEnvironment().name()));
|
||||
}
|
||||
// World already loaded, maybe by another plugin
|
||||
Logging.finer("World already loaded in bukkit: " + mvWorld.getName());
|
||||
return newLoadedMultiverseWorld(mvWorld, bukkitWorld);
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, LoadFailureReason> newLoadedMultiverseWorld(MultiverseWorld mvWorld, World bukkitWorld) {
|
||||
WorldConfig worldConfig = worldsConfigManager.getWorldConfig(mvWorld.getName()).get(); //TODO: null check here, but logically it should never be null.
|
||||
LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld(
|
||||
@@ -480,11 +553,14 @@ public final class WorldManager {
|
||||
*
|
||||
* @param worldName The name of the world to remove.
|
||||
* @return The result of the remove.
|
||||
*
|
||||
* @deprecated Get the {@link MultiverseWorld} yourself and use {@link #removeWorld(RemoveWorldOptions)} instead.
|
||||
*/
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(
|
||||
@NotNull String worldName) {
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull String worldName) {
|
||||
return getWorld(worldName)
|
||||
.map(this::removeWorld)
|
||||
.map(world -> removeWorld(RemoveWorldOptions.world(world)))
|
||||
.getOrElse(() -> worldActionResult(RemoveFailureReason.WORLD_NON_EXISTENT, worldName));
|
||||
}
|
||||
|
||||
@@ -494,11 +570,13 @@ public final class WorldManager {
|
||||
*
|
||||
* @param world The multiverse world to remove.
|
||||
* @return The result of the remove.
|
||||
*
|
||||
* @deprecated Use {@link #removeWorld(RemoveWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull MultiverseWorld world) {
|
||||
return getLoadedWorld(world).fold(
|
||||
() -> removeWorldFromConfig(world),
|
||||
this::removeWorld);
|
||||
return removeWorld(RemoveWorldOptions.world(world));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,9 +585,40 @@ public final class WorldManager {
|
||||
*
|
||||
* @param loadedWorld The multiverse world to remove.
|
||||
* @return The result of the remove.
|
||||
*
|
||||
* @deprecated Use {@link #removeWorld(RemoveWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull LoadedMultiverseWorld loadedWorld) {
|
||||
return unloadWorld(UnloadWorldOptions.world(loadedWorld))
|
||||
return removeWorld(RemoveWorldOptions.world(loadedWorld));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing multiverse world. It will be deleted from the worlds config and will no longer be known
|
||||
* to Multiverse. World files will not be deleted.
|
||||
*
|
||||
* @param options The options for customizing the removal of a world.
|
||||
* @return The result of the remove action.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull RemoveWorldOptions options) {
|
||||
MultiverseWorld world = options.world();
|
||||
return getLoadedWorld(world).fold(
|
||||
() -> removeWorldFromConfig(world),
|
||||
loadedWorld -> unloadBeforeRemoveWorld(loadedWorld, options)
|
||||
);
|
||||
}
|
||||
|
||||
private Attempt<String, RemoveFailureReason> unloadBeforeRemoveWorld(@NotNull LoadedMultiverseWorld loadedWorld,
|
||||
@NotNull RemoveWorldOptions options) {
|
||||
UnloadWorldOptions unloadWorldOptions = UnloadWorldOptions.world(loadedWorld)
|
||||
.saveBukkitWorld(options.saveBukkitWorld())
|
||||
.unloadBukkitWorld(options.unloadBukkitWorld());
|
||||
|
||||
return unloadWorld(unloadWorldOptions)
|
||||
.transform(RemoveFailureReason.UNLOAD_FAILED)
|
||||
.mapAttempt(this::removeWorldFromConfig);
|
||||
}
|
||||
@@ -540,12 +649,18 @@ public final class WorldManager {
|
||||
*/
|
||||
public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull DeleteWorldOptions options) {
|
||||
return getLoadedWorld(options.world()).fold(
|
||||
() -> loadWorld(options.world())
|
||||
.transform(DeleteFailureReason.LOAD_FAILED)
|
||||
.mapAttempt(world -> doDeleteWorld(world, options)),
|
||||
() -> loadThenDeleteWorld(options),
|
||||
world -> doDeleteWorld(world, options));
|
||||
}
|
||||
|
||||
private Attempt<String, DeleteFailureReason> loadThenDeleteWorld(@NotNull DeleteWorldOptions options) {
|
||||
return loadWorld(LoadWorldOptions.world(options.world()))
|
||||
.fold(
|
||||
ignore -> worldActionResult(DeleteFailureReason.LOAD_FAILED, options.world().getName()),
|
||||
loadedWorld -> doDeleteWorld(loadedWorld, options)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing multiverse world entirely. Warning: This will delete all world files.
|
||||
*
|
||||
@@ -563,7 +678,11 @@ public final class WorldManager {
|
||||
? Attempt.failure(DeleteFailureReason.EVENT_CANCELLED)
|
||||
: Attempt.success(null);
|
||||
})
|
||||
.mapAttempt(() -> removeWorld(world).transform(DeleteFailureReason.REMOVE_FAILED))
|
||||
.mapAttempt(() -> removeWorld(RemoveWorldOptions
|
||||
.world(world)
|
||||
.unloadBukkitWorld(true)
|
||||
.saveBukkitWorld(false)
|
||||
).transform(DeleteFailureReason.REMOVE_FAILED))
|
||||
.mapAttempt(() -> fileUtils.deleteFolder(worldFolder.get(), options.keepFiles()).fold(
|
||||
exception -> worldActionResult(DeleteFailureReason.FAILED_TO_DELETE_FOLDER,
|
||||
world.getName(), exception),
|
||||
@@ -631,7 +750,7 @@ public final class WorldManager {
|
||||
private Attempt<CloneWorldOptions, CloneFailureReason> cloneWorldCopyFolder(@NotNull CloneWorldOptions options) {
|
||||
if (options.saveBukkitWorld()) {
|
||||
Logging.finer("Saving bukkit world before cloning: " + options.world().getName());
|
||||
options.world().getBukkitWorld().peek(World::save);
|
||||
options.world().getBukkitWorld().peek(this::saveWorldWithFlush);
|
||||
}
|
||||
File worldFolder = options.world().getBukkitWorld().map(World::getWorldFolder).get();
|
||||
File newWorldFolder = new File(Bukkit.getWorldContainer(), options.newWorldName());
|
||||
@@ -641,6 +760,17 @@ public final class WorldManager {
|
||||
success -> worldActionResult(options));
|
||||
}
|
||||
|
||||
// This method is only available since 1.21
|
||||
private final Method saveWithFlush = ReflectHelper.getMethod(World.class, "save", boolean.class);
|
||||
private void saveWorldWithFlush(World world) {
|
||||
if (saveWithFlush != null) {
|
||||
Logging.fine("Using world save method with flush...");
|
||||
ReflectHelper.invokeMethod(world, saveWithFlush, true);
|
||||
} else {
|
||||
world.save();
|
||||
}
|
||||
}
|
||||
|
||||
private void cloneWorldTransferData(@NotNull CloneWorldOptions options, @NotNull LoadedMultiverseWorld newWorld) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = transferData(options, options.world());
|
||||
dataTransfer.pasteAllTo(newWorld);
|
||||
@@ -764,6 +894,7 @@ public final class WorldManager {
|
||||
}).andFinally(() -> {
|
||||
this.loadTracker.remove(worldCreator.name());
|
||||
}).fold(throwable -> Attempt.failure(WorldCreatorFailureReason.BUKKIT_CREATION_FAILED,
|
||||
Replace.WORLD.with(worldCreator.name()),
|
||||
Replace.ERROR.with(throwable)),
|
||||
Attempt::success);
|
||||
}
|
||||
@@ -899,9 +1030,13 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private Option<MultiverseWorld> getUnloadedWorldByAlias(@Nullable String alias) {
|
||||
if (alias == null || alias.isEmpty()) {
|
||||
return Option.none();
|
||||
}
|
||||
String colourlessAlias = ChatTextFormatter.removeColor(alias);
|
||||
return Option.ofOptional(worldsMap.values().stream()
|
||||
.filter(world -> !world.isLoaded())
|
||||
.filter(world -> world.getColourlessAlias().equalsIgnoreCase(ChatColor.stripColor(alias)))
|
||||
.filter(world -> world.getColourlessAlias().equalsIgnoreCase(colourlessAlias))
|
||||
.findFirst());
|
||||
}
|
||||
|
||||
@@ -933,7 +1068,7 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable World world) {
|
||||
return world == null ? Option.none() : Option.of((LoadedMultiverseWorld) loadedWorldsMap.get(world.getName()));
|
||||
return world == null ? Option.none() : Option.of(loadedWorldsMap.get(world.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -943,7 +1078,7 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable MultiverseWorld world) {
|
||||
return world == null ? Option.none() : Option.of((LoadedMultiverseWorld) loadedWorldsMap.get(world.getName()));
|
||||
return world == null ? Option.none() : Option.of(loadedWorldsMap.get(world.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -953,7 +1088,7 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable String worldName) {
|
||||
return Option.of((LoadedMultiverseWorld) loadedWorldsMap.get(worldName));
|
||||
return Option.of(loadedWorldsMap.get(worldName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -968,9 +1103,12 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private Option<LoadedMultiverseWorld> getLoadedWorldByAlias(@Nullable String alias) {
|
||||
if (alias == null || alias.isEmpty()) {
|
||||
return Option.none();
|
||||
}
|
||||
return Option.ofOptional(loadedWorldsMap.values().stream()
|
||||
.filter(world -> world.getColourlessAlias().equalsIgnoreCase(ChatColor.stripColor(alias)))
|
||||
.map(world -> (LoadedMultiverseWorld) world)
|
||||
.filter(world -> world.getColourlessAlias()
|
||||
.equalsIgnoreCase(ChatTextFormatter.removeColor(alias)))
|
||||
.findFirst());
|
||||
}
|
||||
|
||||
@@ -981,7 +1119,6 @@ public final class WorldManager {
|
||||
*/
|
||||
public Collection<LoadedMultiverseWorld> getLoadedWorlds() {
|
||||
return loadedWorldsMap.values().stream()
|
||||
.map(world -> (LoadedMultiverseWorld) world)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
/**
|
||||
* Manages the worlds.yml file.
|
||||
@@ -81,10 +82,11 @@ final class WorldsConfigManager {
|
||||
}
|
||||
|
||||
// Copy old config file to `worlds.yml.old`
|
||||
Path oldWorldConfig = worldConfigFile.toPath().getParent().resolve(CONFIG_FILENAME + ".old");
|
||||
Files.copy(worldConfigFile.toPath(), oldWorldConfig, COPY_ATTRIBUTES);
|
||||
Path oldWorldConfig = worldConfigFile.toPath().getParent()
|
||||
.resolve(CONFIG_FILENAME + ".old." + System.currentTimeMillis());
|
||||
Files.copy(worldConfigFile.toPath(), oldWorldConfig, COPY_ATTRIBUTES, REPLACE_EXISTING);
|
||||
|
||||
return configData.replace("==: MVWorld", "")
|
||||
return configData.replace("==: MVWorld", "w@: world")
|
||||
.replace("==: MVSpawnSettings", "")
|
||||
.replace("==: MVSpawnSubSettings", "")
|
||||
.replace("==: MVEntryFee", "");
|
||||
@@ -92,16 +94,26 @@ final class WorldsConfigManager {
|
||||
.andThenTry(configData -> Files.writeString(worldConfigFile.toPath(), configData))
|
||||
.andThenTry(() -> {
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(worldConfigFile);
|
||||
List<ConfigurationSection> worlds = config.getConfigurationSection("worlds")
|
||||
.getKeys(false)
|
||||
.stream()
|
||||
.map(worldName -> config.getConfigurationSection("worlds." + worldName))
|
||||
.toList();
|
||||
|
||||
ConfigurationSection worldsSection = config.getConfigurationSection("worlds");
|
||||
if (worldsSection == null) {
|
||||
worldsSection = config.createSection("worlds");
|
||||
}
|
||||
|
||||
List<String> worldNames = getOldConfigWorldNames(worldsSection);
|
||||
|
||||
Map<String, ConfigurationSection> worldConfigMap = new HashMap<>();
|
||||
for (String worldName : worldNames) {
|
||||
ConfigurationSection worldSection = worldsSection.getConfigurationSection(worldName);
|
||||
if (worldSection != null) {
|
||||
worldConfigMap.put(worldName, worldSection);
|
||||
}
|
||||
}
|
||||
|
||||
config.set("worlds", null);
|
||||
|
||||
for (ConfigurationSection world : worlds) {
|
||||
config.createSection(world.getName(), world.getValues(true));
|
||||
for (Map.Entry<String, ConfigurationSection> entry : worldConfigMap.entrySet()) {
|
||||
config.set(encodeWorldName(entry.getKey()), entry.getValue());
|
||||
}
|
||||
config.save(worldConfigFile);
|
||||
})
|
||||
@@ -115,13 +127,46 @@ final class WorldsConfigManager {
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull List<String> getOldConfigWorldNames(ConfigurationSection worldsSection) {
|
||||
List<String> worldNames = new ArrayList<>();
|
||||
recursiveGetOldConfigWorldNames(worldsSection, worldNames);
|
||||
return worldNames;
|
||||
}
|
||||
|
||||
private void recursiveGetOldConfigWorldNames(ConfigurationSection section, List<String> worldNames) {
|
||||
Set<String> keys = section.getKeys(false);
|
||||
if (keys.isEmpty()) {
|
||||
// No keys in this section, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (keys.contains("w@")) {
|
||||
// this is the world data section already, get path without the "worlds." prefix
|
||||
worldNames.add(section.getCurrentPath().substring(7));
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : keys) {
|
||||
ConfigurationSection dataSection = section.getConfigurationSection(key);
|
||||
if (dataSection == null) {
|
||||
// Something is wrong with the config, skip this key
|
||||
continue;
|
||||
}
|
||||
recursiveGetOldConfigWorldNames(dataSection, worldNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the worlds.yml file and creates a WorldConfig for each world in the file if it doesn't already exist.
|
||||
*
|
||||
* @return A tuple containing a list of the new WorldConfigs added and a list of the worlds removed from the config.
|
||||
*/
|
||||
private NewAndRemovedWorlds parseNewAndRemovedWorlds() {
|
||||
Set<String> allWorldsInConfig = worldsConfig.getKeys(false);
|
||||
List<String> allWorldsInConfig = worldsConfig.getKeys(false)
|
||||
.stream()
|
||||
.map(this::decodeWorldName)
|
||||
.toList();
|
||||
|
||||
List<WorldConfig> newWorldsAdded = new ArrayList<>();
|
||||
|
||||
for (String worldName : allWorldsInConfig) {
|
||||
@@ -174,7 +219,7 @@ final class WorldsConfigManager {
|
||||
worldConfig.save().onFailure(e -> {
|
||||
throw new RuntimeException("Failed to save world config: " + worldName, e);
|
||||
});
|
||||
worldsConfig.set(worldName, worldConfig.getConfigurationSection());
|
||||
worldsConfig.set(encodeWorldName(worldName), worldConfig.getConfigurationSection());
|
||||
});
|
||||
worldsConfig.save(worldConfigFile);
|
||||
}).onFailure(e -> {
|
||||
@@ -214,7 +259,7 @@ final class WorldsConfigManager {
|
||||
*/
|
||||
public void deleteWorldConfig(@NotNull String worldName) {
|
||||
worldConfigMap.remove(worldName);
|
||||
worldsConfig.set(worldName, null);
|
||||
worldsConfig.set(encodeWorldName(worldName), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,11 +270,23 @@ final class WorldsConfigManager {
|
||||
* @return The {@link ConfigurationSection} for the given world.
|
||||
*/
|
||||
private ConfigurationSection getWorldConfigSection(String worldName) {
|
||||
worldName = encodeWorldName(worldName);
|
||||
return worldsConfig.isConfigurationSection(worldName)
|
||||
? worldsConfig.getConfigurationSection(worldName)
|
||||
: worldsConfig.createSection(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dot is a special character in YAML that causes sub-path issues.
|
||||
*/
|
||||
private String encodeWorldName(String worldName) {
|
||||
return worldName.replace(".", "[dot]");
|
||||
}
|
||||
|
||||
private String decodeWorldName(String worldName) {
|
||||
return worldName.replace("[dot]", ".");
|
||||
}
|
||||
|
||||
private static final class ConfigMigratedException extends RuntimeException {
|
||||
private ConfigMigratedException() {
|
||||
super("Config migrated");
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
package org.mvplugins.multiverse.core.world.entity;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class EntitySpawnConfig {
|
||||
|
||||
private final CoreConfig config;
|
||||
private final Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig;
|
||||
|
||||
EntitySpawnConfig(Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig) {
|
||||
EntitySpawnConfig(CoreConfig config, Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig) {
|
||||
this.config = config;
|
||||
this.spawnCategoriesConfig = spawnCategoriesConfig;
|
||||
}
|
||||
|
||||
public SpawnCategoryConfig getSpawnCategoryConfig(SpawnCategory spawnCategory) {
|
||||
return spawnCategoriesConfig.computeIfAbsent(spawnCategory,
|
||||
computeSpawnCategory -> new SpawnCategoryConfig(computeSpawnCategory, new MemoryConfiguration()));
|
||||
computeSpawnCategory -> new SpawnCategoryConfig(
|
||||
config,
|
||||
computeSpawnCategory,
|
||||
new MemoryConfiguration()
|
||||
));
|
||||
}
|
||||
|
||||
public boolean shouldAllowSpawn(Entity entity) {
|
||||
@@ -52,17 +58,17 @@ public final class EntitySpawnConfig {
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static EntitySpawnConfig fromSection(ConfigurationSection section) {
|
||||
Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig = new HashMap<>();
|
||||
public static EntitySpawnConfig fromSection(CoreConfig config, ConfigurationSection section) {
|
||||
Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig = new LinkedHashMap<>();
|
||||
section.getValues(false).forEach((key, value) -> {
|
||||
if (!(value instanceof ConfigurationSection sectionPart)) {
|
||||
Logging.warning("Invalid spawn category config for " + key + ": " + value);
|
||||
return;
|
||||
}
|
||||
SpawnCategory spawnCategory = SpawnCategory.valueOf(key.toUpperCase());
|
||||
spawnCategoriesConfig.put(spawnCategory, new SpawnCategoryConfig(spawnCategory, sectionPart));
|
||||
spawnCategoriesConfig.put(spawnCategory, new SpawnCategoryConfig(config, spawnCategory, sectionPart));
|
||||
});
|
||||
return new EntitySpawnConfig(spawnCategoriesConfig);
|
||||
return new EntitySpawnConfig(config, spawnCategoriesConfig);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.handle.MemoryConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
@@ -21,6 +22,7 @@ import java.util.List;
|
||||
|
||||
public final class SpawnCategoryConfig {
|
||||
|
||||
private final CoreConfig config;
|
||||
private final SpawnCategory spawnCategory;
|
||||
private final MemoryConfigurationHandle handle;
|
||||
private final StringPropertyHandle stringPropertyHandle;
|
||||
@@ -28,7 +30,8 @@ public final class SpawnCategoryConfig {
|
||||
|
||||
private MultiverseWorld world;
|
||||
|
||||
SpawnCategoryConfig(SpawnCategory spawnCategory, ConfigurationSection section) {
|
||||
SpawnCategoryConfig(CoreConfig config, SpawnCategory spawnCategory, ConfigurationSection section) {
|
||||
this.config = config;
|
||||
this.spawnCategory = spawnCategory;
|
||||
this.nodes = new Nodes();
|
||||
this.handle = MemoryConfigurationHandle.builder(section, nodes.nodes)
|
||||
@@ -57,23 +60,39 @@ public final class SpawnCategoryConfig {
|
||||
return;
|
||||
}
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
if (!isSpawn()) {
|
||||
if (getExceptions().isEmpty()) {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: 0", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, 0);
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: -1", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, -1);
|
||||
}
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: %d", world.getName(), spawnCategory, getTickRate());
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, getTickRate());
|
||||
}
|
||||
Logging.finer("World %s %s setSpawnLimit: %d", world.getName(), spawnCategory, getSpawnLimit());
|
||||
bukkitWorld.setSpawnLimit(spawnCategory, getSpawnLimit());
|
||||
applyTickPerSpawns(bukkitWorld);
|
||||
applySpawnLimit(bukkitWorld);
|
||||
});
|
||||
}
|
||||
|
||||
private void applyTickPerSpawns(World bukkitWorld) {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
Logging.finer("World %s %s skipping setTicksPerSpawns due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
if (!isSpawn()) {
|
||||
if (getExceptions().isEmpty()) {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: 0", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, 0);
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: -1", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, -1);
|
||||
}
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: %d", world.getName(), spawnCategory, getTickRate());
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, getTickRate());
|
||||
}
|
||||
}
|
||||
|
||||
private void applySpawnLimit(World bukkitWorld) {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
Logging.finer("Skipping World %s %s setSpawnLimit due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
Logging.finer("World %s %s setSpawnLimit: %d", world.getName(), spawnCategory, getSpawnLimit());
|
||||
bukkitWorld.setSpawnLimit(spawnCategory, getSpawnLimit());
|
||||
}
|
||||
|
||||
public StringPropertyHandle getStringPropertyHandle() {
|
||||
return stringPropertyHandle;
|
||||
}
|
||||
|
||||
+26
-12
@@ -27,6 +27,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.utils.FileUtils;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
@@ -38,15 +39,17 @@ import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
public final class GeneratorProvider implements Listener {
|
||||
private final Map<String, String> defaultGenerators;
|
||||
private final Map<String, GeneratorPlugin> generatorPlugins;
|
||||
private final CoreConfig coreConfig;
|
||||
private final FileUtils fileUtils;
|
||||
|
||||
@Inject
|
||||
GeneratorProvider(@NotNull MultiverseCore multiverseCore, @NotNull FileUtils fileUtils) {
|
||||
GeneratorProvider(@NotNull MultiverseCore plugin, @NotNull CoreConfig coreConfig, @NotNull FileUtils fileUtils) {
|
||||
this.coreConfig = coreConfig;
|
||||
this.fileUtils = fileUtils;
|
||||
defaultGenerators = new HashMap<>();
|
||||
generatorPlugins = new HashMap<>();
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(this, multiverseCore);
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
loadDefaultWorldGenerators();
|
||||
loadPluginGenerators();
|
||||
}
|
||||
@@ -73,11 +76,14 @@ public final class GeneratorProvider implements Listener {
|
||||
* Find generator plugins from plugins loaded and register them.
|
||||
*/
|
||||
private void loadPluginGenerators() {
|
||||
Arrays.stream(Bukkit.getPluginManager().getPlugins()).forEach(plugin -> {
|
||||
if (!coreConfig.getAutoDetectGeneratorPlugins()) {
|
||||
return;
|
||||
}
|
||||
for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
|
||||
if (testIsGeneratorPlugin(plugin)) {
|
||||
registerGeneratorPlugin(new SimpleGeneratorPlugin(plugin.getName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,14 +94,19 @@ public final class GeneratorProvider implements Listener {
|
||||
*/
|
||||
private boolean testIsGeneratorPlugin(Plugin plugin) {
|
||||
String worldName = Bukkit.getWorlds().stream().findFirst().map(World::getName).orElse("world");
|
||||
return Try.of(() -> plugin.getDefaultWorldGenerator(worldName, "") != null)
|
||||
.recover(IllegalArgumentException.class, true)
|
||||
.recover(throwable -> {
|
||||
Logging.warning("Plugin %s threw an exception when testing if it is a generator plugin!",
|
||||
plugin.getName());
|
||||
throwable.printStackTrace();
|
||||
return false;
|
||||
}).getOrElse(false);
|
||||
try {
|
||||
return plugin.getDefaultWorldGenerator(worldName, null) != null;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Some plugins throw this if they don't support world generation
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
Logging.warning("Plugin %s threw an exception when testing if it is a generator plugin! ",
|
||||
plugin.getName());
|
||||
Logging.warning("This is NOT a bug in Multiverse. Do NOT report this to Multiverse support.");
|
||||
t.printStackTrace();
|
||||
// Assume it's a generator plugin since it tried to do something, most likely the id is wrong.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,6 +217,9 @@ public final class GeneratorProvider implements Listener {
|
||||
*/
|
||||
@EventHandler
|
||||
private void onPluginEnable(PluginEnableEvent event) {
|
||||
if (!coreConfig.getAutoDetectGeneratorPlugins()) {
|
||||
return;
|
||||
}
|
||||
if (!testIsGeneratorPlugin(event.getPlugin())) {
|
||||
Logging.finest("Plugin %s is not a generator plugin.", event.getPlugin().getName());
|
||||
return;
|
||||
|
||||
+8
-8
@@ -22,13 +22,13 @@ import java.util.Objects;
|
||||
* This is useful to store location with world that may not be loaded yet or have been unloaded at some point.
|
||||
*/
|
||||
@SerializableAs("UnloadedWorldLocation")
|
||||
public final class UnloadedWorldLocation extends Location {
|
||||
public class UnloadedWorldLocation extends Location {
|
||||
|
||||
public static UnloadedWorldLocation fromLocation(@NotNull Location location) {
|
||||
return new UnloadedWorldLocation(location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
|
||||
}
|
||||
|
||||
private String worldName;
|
||||
private @Nullable String worldName;
|
||||
|
||||
public UnloadedWorldLocation(@Nullable String worldName, double x, double y, double z) {
|
||||
super(null, x, y, z);
|
||||
@@ -67,7 +67,7 @@ public final class UnloadedWorldLocation extends Location {
|
||||
this.worldName = worldName;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
public @Nullable String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
@@ -160,10 +160,10 @@ public final class UnloadedWorldLocation extends Location {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 19 * hash + worldName.hashCode();
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getX()) ^ (Double.doubleToLongBits(this.getX()) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getY()) ^ (Double.doubleToLongBits(this.getY()) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getZ()) ^ (Double.doubleToLongBits(this.getZ()) >>> 32));
|
||||
hash = 19 * hash + String.valueOf(worldName).hashCode();
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getX()));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getY()));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getZ()));
|
||||
hash = 19 * hash + Float.floatToIntBits(this.getPitch());
|
||||
hash = 19 * hash + Float.floatToIntBits(this.getYaw());
|
||||
return hash;
|
||||
@@ -171,7 +171,7 @@ public final class UnloadedWorldLocation extends Location {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Location{" +
|
||||
return "UnloadedWorldLocation{" +
|
||||
"world=" + worldName +
|
||||
",x=" + getX() +
|
||||
",y=" + getY() +
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -24,6 +25,7 @@ public final class ImportWorldOptions {
|
||||
private World.Environment environment = World.Environment.NORMAL;
|
||||
private String generator = null;
|
||||
private boolean useSpawnAdjust = true;
|
||||
private boolean doFolderCheck = true;
|
||||
|
||||
ImportWorldOptions(String worldName) {
|
||||
this.worldName = worldName;
|
||||
@@ -119,4 +121,30 @@ public final class ImportWorldOptions {
|
||||
public boolean useSpawnAdjust() {
|
||||
return useSpawnAdjust;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to ensure the world folder is a valid world before importing it.
|
||||
*
|
||||
* @param doFolderCheckInput Whether to do the folder check
|
||||
* @return This {@link ImportWorldOptions} instance
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public @NotNull ImportWorldOptions doFolderCheck(boolean doFolderCheckInput) {
|
||||
this.doFolderCheck = doFolderCheckInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to ensure the world folder is a valid world before importing it.
|
||||
*
|
||||
* @return Whether to do the folder check
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean doFolderCheck() {
|
||||
return doFolderCheck;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Options for customizing the loading of a world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public final class LoadWorldOptions {
|
||||
|
||||
/**
|
||||
* Creates a new {@link LoadWorldOptions} instance with the given world.
|
||||
*
|
||||
* @param world The world to load.
|
||||
* @return A new {@link LoadWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public static LoadWorldOptions world(MultiverseWorld world) {
|
||||
return new LoadWorldOptions(world);
|
||||
}
|
||||
|
||||
private final MultiverseWorld world;
|
||||
private boolean doFolderCheck = true;
|
||||
|
||||
LoadWorldOptions(MultiverseWorld world) {
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world to load.
|
||||
*
|
||||
* @return The world to load.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MultiverseWorld world() {
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to check if the world folder is valid before loading the world.
|
||||
* <br />
|
||||
* This helps to prevent deleted world folders from being re-created.
|
||||
*
|
||||
* @return Whether to check if the world folder is valid before loading the world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean doFolderCheck() {
|
||||
return doFolderCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to check if the world folder is valid before loading the world.
|
||||
*
|
||||
* @return This {@link LoadWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public LoadWorldOptions doFolderCheck(boolean doFolderCheck) {
|
||||
this.doFolderCheck = doFolderCheck;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Options for customizing the removal of a world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public final class RemoveWorldOptions {
|
||||
|
||||
/**
|
||||
* Creates a new {@link RemoveWorldOptions} instance with the given world. The world may be a loaded or unloaded.
|
||||
*
|
||||
* @param world The world to remove.
|
||||
* @return A new {@link RemoveWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public static RemoveWorldOptions world(MultiverseWorld world) {
|
||||
return new RemoveWorldOptions(world);
|
||||
}
|
||||
|
||||
private final MultiverseWorld world;
|
||||
private boolean saveBukkitWorld = true;
|
||||
private boolean unloadBukkitWorld = true;
|
||||
|
||||
private RemoveWorldOptions(MultiverseWorld world) {
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world to remove. The world may be a loaded or unloaded.
|
||||
*
|
||||
* @return The world to remove.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MultiverseWorld world() {
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to save the Bukkit world before removing it.
|
||||
* <br />
|
||||
* This option only applies when {@link #unloadBukkitWorld()} is true.
|
||||
*
|
||||
* @param saveBukkitWorldInput Whether to save the Bukkit world.
|
||||
* @return This {@link RemoveWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public RemoveWorldOptions saveBukkitWorld(boolean saveBukkitWorldInput) {
|
||||
this.saveBukkitWorld = saveBukkitWorldInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to save the Bukkit world before removing it. By default, this is true.
|
||||
* <br />
|
||||
* This option only applies when {@link #unloadBukkitWorld()} is true.
|
||||
*
|
||||
* @return Whether to save the Bukkit world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean saveBukkitWorld() {
|
||||
return saveBukkitWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to unload the Bukkit world before removing it. This option is usually only used if the world is
|
||||
* managed by another plugin and you want to untrack it from Multiverse without unloading from the server.
|
||||
*
|
||||
* @param unloadBukkitWorldInput Whether to unload the Bukkit world.
|
||||
* @return This {@link RemoveWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public RemoveWorldOptions unloadBukkitWorld(boolean unloadBukkitWorldInput) {
|
||||
this.unloadBukkitWorld = unloadBukkitWorldInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to unload the Bukkit world before removing it. By default, this is true.
|
||||
*
|
||||
* @return Whether to unload the Bukkit world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean unloadBukkitWorld() {
|
||||
return unloadBukkitWorld;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.world.reasons;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
@@ -30,6 +31,14 @@ public enum ImportFailureReason implements FailureReason {
|
||||
*/
|
||||
WORLD_EXIST_LOADED(MVCorei18n.IMPORTWORLD_WORLDEXISTLOADED),
|
||||
|
||||
/**
|
||||
* The import environment input does not match the loaded Bukkit world's environment.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
BUKKIT_ENVIRONMENT_MISMATCH(MVCorei18n.IMPORTWORLD_BUKKITENVIRONMENTMISMATCH),
|
||||
|
||||
/**
|
||||
* Bukkit API failed to create the world.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.world.reasons;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
@@ -25,11 +26,27 @@ public enum LoadFailureReason implements FailureReason {
|
||||
*/
|
||||
WORLD_EXIST_FOLDER(MVCorei18n.LOADWORLD_WORLDEXISTFOLDER),
|
||||
|
||||
/**
|
||||
* The world folder is invalid.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
WORLD_FOLDER_INVALID(MVCorei18n.IMPORTWORLD_WORLDFOLDERINVALID),
|
||||
|
||||
/**
|
||||
* The world is already loaded.
|
||||
*/
|
||||
WORLD_EXIST_LOADED(MVCorei18n.LOADWORLD_WORLDEXISTLOADED),
|
||||
|
||||
/**
|
||||
* The mv world's environment does not match the loaded Bukkit world's environment.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
BUKKIT_ENVIRONMENT_MISMATCH(MVCorei18n.LOADWORLD_BUKKITENVIRONMENTMISMATCH),
|
||||
|
||||
/**
|
||||
* Bukkit API failed to create the world.
|
||||
*/
|
||||
|
||||
@@ -3,8 +3,10 @@ package org.mvplugins.multiverse.core.world.reasons;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
/**
|
||||
* Result of a world removal operation.
|
||||
@@ -12,7 +14,11 @@ import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
public enum RemoveFailureReason implements FailureReason {
|
||||
/**
|
||||
* The world does not exist.
|
||||
*
|
||||
* @deprecated No longer in use as {@link WorldManager#removeWorld(String)} is now deprecated.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.2")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
WORLD_NON_EXISTENT(MVCorei18n.REMOVEWORLD_WORLDNONEXISTENT),
|
||||
|
||||
/**
|
||||
|
||||
@@ -132,6 +132,7 @@ mv-core.modify.description=Modifies a world property of a given world.
|
||||
mv-core.modify.world.description=Name of world you want to modify.
|
||||
mv-core.modify.property.description=Property you want to modify.
|
||||
mv-core.modify.value.description=Value of property.
|
||||
mv-core.modify.propertyremoved=&cYou cannot modify '&3{property}&c' because it has been changed or removed. Please use '&a{replacement}&c' instead.
|
||||
mv-core.modify.cannothavevalue=You cannot specify a value to {action} '{property}'.
|
||||
mv-core.modify.specifyvalue=You must specify a value to {action} '{property}'.
|
||||
mv-core.modify.success=&aSuccessfully {action} '&9{property}&a' to '&9{value}&a' in world &9{world}&a.
|
||||
@@ -164,6 +165,10 @@ mv-core.root.help=&aSee &f/mv help&a for commands available.
|
||||
mv-core.setspawn.description=Sets the spawn location of the specified world
|
||||
mv-core.setspawn.location.description=Location of the new spawn
|
||||
mv-core.setspawn.world.description=Target world to set spawn of (defaults to player's current world)
|
||||
mv-core.setspawn.unsafe=&cThe new spawn location is unsafe! If this is intentional, you can disable safety checks with &6--unsafe&c flag.
|
||||
mv-core.setspawn.success=&aSuccessfully set spawn in &6{world}&a to &6{location}&a!
|
||||
mv-core.setspawn.failed=&cFailed to set spawn in &6{world}&c to &6{location}&c. {error}
|
||||
mv-core.setspawn.notmvworld=&cUnable to set spawn for &c{world} as it is not a Multiverse world.
|
||||
|
||||
# /mv spawn
|
||||
mv-core.spawn.description=Teleports the specified player to the spawn of the world they are in
|
||||
@@ -250,17 +255,17 @@ mv-core.destination.cannon.failurereason.invalidformat=&cCannon destination form
|
||||
mv-core.destination.exact.failurereason.invalidformat=&cExact destination format is: &6e:worldname:x,y,z:pitch:yaw
|
||||
mv-core.destination.player.failurereason.playernotfound=&cPlayer '&6{player}&c' does not exist or is not online!
|
||||
mv-core.destination.shared.failurereason.invalidcoordinatesformat=You must specify a valid coordinate in this format: &6{x},{y},{z}
|
||||
mv-core.destination.shared.failurereason.invalidnumberformat=Value is not a valid number. {error}.
|
||||
mv-core.destination.shared.failurereason.invalidnumberformat=&cInvalid destination input. {error}
|
||||
mv-core.destination.shared.failurereason.worldnotfound=&cWorld '&6{world}&c' does not exist or is not loaded!
|
||||
mv-core.destination.parse.failurereason.invaliddestinationid=Invalid destination id: {id}. Valid destination ids are: {ids}
|
||||
|
||||
# teleport failure reason
|
||||
mv-core.teleportfailurereason.null.destination=Destination is invalid!
|
||||
mv-core.teleportfailurereason.null.location=Location is invalid!
|
||||
mv-core.teleportfailurereason.null.world=&cLocation's world is invalid!
|
||||
mv-core.teleportfailurereason.unsafe.location=Location is unsafe! Use `--unsafe` to override.
|
||||
mv-core.teleportfailurereason.teleport.failed=Something rejected the teleport.
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=An error occurred during the teleport. See console for more details.
|
||||
mv-core.teleportfailurereason.null.destination=The destination is invalid!
|
||||
mv-core.teleportfailurereason.null.location=The location is invalid!
|
||||
mv-core.teleportfailurereason.null.world=&cThe target world is unloaded or does not exist!
|
||||
mv-core.teleportfailurereason.unsafe.location=The location is deemed unsafe! If this is intentional, use the `--unsafe` flag to override.
|
||||
mv-core.teleportfailurereason.teleport.failed=The teleport was rejected. Please ensure another plugin is not preventing the teleport and check server logs for any errors.
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=An error occurred during the teleport. Check server logs for more details.
|
||||
mv-core.teleportfailurereason.event.cancelled=The teleport was cancelled by another plugin.
|
||||
|
||||
# world manager result
|
||||
@@ -276,19 +281,21 @@ mv-core.createworld.worldexistunloaded=World '{world}' already exists, but it's
|
||||
mv-core.createworld.worldexistloaded=World '{world}' already exists!
|
||||
|
||||
mv-core.deleteworld.worldnonexistent=World '{world}' not found!
|
||||
mv-core.deleteworld.loadfailed=Unable to load world '{world}', does the world folder exist?&f You can run '&a/mv remove {world}&f' to remove it from Multiverse, or attempt to load again with '&a/mv load {world}&f'.
|
||||
mv-core.deleteworld.worldfoldernotfound=World '{world}' folder not found!
|
||||
mv-core.deleteworld.loadfailed=Unable to load world '{world}', is the world folder deleted or corrupted?&f You can run '&a/mv remove {world}&f' to remove it from Multiverse, or attempt to load again with '&a/mv load {world}&f'.
|
||||
mv-core.deleteworld.worldfoldernotfound=&cUnable to locate '{world}' folder! You may '&a/mv remove {world}&c' to remove it from Multiverse, and manually delete the folder if it exists.
|
||||
mv-core.deleteworld.failedtodeletefolder=Failed to delete world folder '{world}': {error}\n&fSee console for more details.
|
||||
|
||||
mv-core.importworld.invalidworldname=World '{world}' contains invalid characters!
|
||||
mv-core.importworld.worldexistunloaded=World '{world}' already exists, but it's not loaded!&f Type '&a/mv load {world}&f' if you wish to load it.
|
||||
mv-core.importworld.worldexistloaded=World '{world}' already exists!
|
||||
mv-core.importworld.worldfolderinvalid=World '{world}' folder contents does not seem to be a valid world!
|
||||
mv-core.importworld.worldfolderinvalid=World '{world}' folder contents does not seem to be a valid world!\n&cIf the server software does something different with world folders, or you are very certain the world is valid, use '&3--skip-folder-check&c' flag.
|
||||
mv-core.importworld.bukkitenvironmentmismatch=Environment mismatch detected!&f The world '{world}' is already loaded with environment '{bukkitEnvironment}', but Multiverse is trying to import it with environment '{mvEnvironment}'.
|
||||
|
||||
mv-core.loadworld.worldalreadyloading=World '{world}' is already loading! Please wait...
|
||||
mv-core.loadworld.worldnonexistent=World '{world}' not found! Use '&a/mv create {world} <environment>&f' to create it.
|
||||
mv-core.loadworld.worldexistfolder=World '{world}' exists in server folders, but it's not known to Multiverse!&f Type '&a/mv import {world} <environment>&f' if you wish to import it.
|
||||
mv-core.loadworld.worldexistloaded=World '{world}' is already loaded!
|
||||
mv-core.loadworld.bukkitenvironmentmismatch=Environment mismatch detected!&f The world '{world}' is already loaded with environment '{bukkitEnvironment}', but Multiverse is trying to load it with environment '{mvEnvironment}'.
|
||||
|
||||
mv-core.removeworld.worldnonexistent=World '{world}' not found!
|
||||
|
||||
@@ -320,6 +327,11 @@ mv-core.exception.multiverseworld.unloaddefaultworld=&cYou can't unload the defa
|
||||
mv-core.exception.multiverseworld.unloadplayersinworld=&cThere are still &6{count}&c player(s) in the world! Use '&6--remove-players&c' flag to your command to teleport all players out of the world.
|
||||
mv-core.exception.multiverseworld.unloaderror=&cAn unknown error occurred while unloading world: &6{world}&c.\n&cSee console for more details.
|
||||
|
||||
# multiverse position parse exception
|
||||
mv-core.exception.positionparse.invaliddirection=&cInvalid direction string format: {format}. Expected format: <pitch>:<yaw>
|
||||
mv-core.exception.positionparse.invalidcoordinates=&cInvalid coordinates format: {format}. Expected format: <x>,<y>,<z>
|
||||
mv-core.exception.positionparse.invalidnumber=&cInvalid number format: {number}. Expects a numeric value.
|
||||
|
||||
# generic
|
||||
mv-core.generic.success=Success!
|
||||
mv-core.generic.failure=Failed!
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
# configuration
|
||||
mv-core.config.save.failed=No se han podido guardar los cambios que se han hecho al archivo config.yml de Multiverse-Core. ¡Estos camios serán temporales!
|
||||
mv-core.config.node.notfound=Nodo no encontrado en la configuración: {node}
|
||||
|
||||
# /mv check
|
||||
mv-core.check.description=Comprueba si un jugador puede teletransportarse a algún sitio.
|
||||
mv-core.check.player.description=Jugador sobre el que se hará la comprobación.
|
||||
mv-core.check.destination.description=Una localización de destino, p. ej.: el nombre de un mundo.
|
||||
mv-core.check.haspermission=&a{player} puede teletransportarse a {destination}
|
||||
mv-core.check.nopermission=&c{player} no se puede teletransportar a {destination}
|
||||
mv-core.check.location=La localización de su destino es: &f{location}
|
||||
|
||||
# /mv clone
|
||||
mv-core.clone.description=Clona un mundo.
|
||||
mv-core.clone.world.description=El mundo a clonar.
|
||||
mv-core.clone.newWorld.description=El nombre del nuevo mundo clonado.
|
||||
mv-core.clone.cloning=Clonando mundo '{world}' a '{newworld}'...
|
||||
mv-core.clone.success=&a¡Mundo clonado a '{world}'!
|
||||
|
||||
# /mv config
|
||||
mv-core.config.description=Muestra o cambia un valor de uno de los ajustes de la configuración.
|
||||
mv-core.config.name.description=El nombre del ajuste sobre el que se quiere cambiar su valor o mostrarlo.
|
||||
mv-core.config.value.description=El valor al que está establecido dicho ajuste. Si no se especifica ningún valor, se mostrará el valor actual.
|
||||
mv-core.config.show.success=&aEl ajuste '&6{name}&a' está establecido como '&6{value}&a'.
|
||||
mv-core.config.show.error=&cNo se ha podido mostrar el valor del ajuste '&6{name}&c'.\n&c{error}
|
||||
mv-core.config.set.success=&aSe ha establecido el ajuste '&6{name}&a' a '&6{value}&a'.
|
||||
mv-core.config.set.error=&cNo se ha podido establecer el ajuste '&6{name}&c' a '&6{value}&c'.\n&c{error}
|
||||
|
||||
# /mv confirm
|
||||
mv-core.confirm.description=Confirma comandos peligrosos antes de ejecutarlos.
|
||||
|
||||
# /mv coordinates
|
||||
mv-core.coordinates.description=Simple y llanamente te muestra tus coordenadas.
|
||||
mv-core.coordinates.info.title=&b--- Información de la localización ---
|
||||
mv-core.coordinates.info.world=&bMundo: &f{world}
|
||||
mv-core.coordinates.info.alias=&bAlias: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&bEscala del mundo: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&bCoordenadas: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&bDirección: &f{direction}
|
||||
|
||||
# /mv create
|
||||
mv-core.create.description=Genera un nuevo mundo y lo carga.
|
||||
mv-core.create.name.description=Nombre del nuevo mundo.
|
||||
mv-core.create.environment.description=El ambiente del nuevo mundo. Para saber cual será, ejecuta: /mv environments
|
||||
mv-core.create.flags.description=Ajustes adicionales del mundo. Para saber cuáles son, ve a https://gg.gg/nn8bl para conocer todos los posibles ajustes («flags» en inglés).
|
||||
mv-core.create.properties=Creando mundo {worldName} con las siguientes propiedades:
|
||||
mv-core.create.properties.environment=- Ambiente: &f{environment}
|
||||
mv-core.create.properties.seed=- Semilla: &f{seed}
|
||||
mv-core.create.properties.worldtype=- Tipo de mundo: &f{worldType}
|
||||
mv-core.create.properties.adjustspawn=- Ajustar punto de aparición («spawn» en inglés): &f{adjustSpawn}
|
||||
mv-core.create.properties.biome=- Bioma: &f{biome}
|
||||
mv-core.create.properties.generator=- Generador: &f{generator}
|
||||
mv-core.create.properties.generatorsettings=- Ajustes del generador: &f{generatorSettings}
|
||||
|
||||
mv-core.create.properties.structures=- Estructuras: &f{structures}
|
||||
mv-core.create.loading=Creando mundo...
|
||||
mv-core.create.success=&a¡Se ha creado el mundo '{world}'!
|
||||
|
||||
# /mv debug
|
||||
mv-core.debug.info.description=Muestra el nivel de depuración («debug») actual.
|
||||
mv-core.debug.info.off=&fEl modo de depuración de Multiverse está &cOFF&f.
|
||||
mv-core.debug.info.on=&fEl modo de depuración de Multiverse está en el &anivel {level}&f.
|
||||
mv-core.debug.change.description=Cambia el nivel de depuración.
|
||||
mv-core.debug.change.syntax=nivel
|
||||
mv-core.debug.change.level.description=Nivel de depuración al que se desea pasar.
|
||||
|
||||
# /mv delete
|
||||
mv-core.delete.description=Elimina un mundo en tu servidor PERMANENTEMENTE.
|
||||
mv-core.delete.deleting=Eliminando mundo '{world}'...
|
||||
mv-core.delete.prompt=¿Estás seguro de que quieres eliminar el mundo '{world}'?
|
||||
mv-core.delete.success=&a¡Se ha eliminado el mundo '{world}'!
|
||||
|
||||
# /mv dumps
|
||||
mv-core.dumps.description=Vuelca la información de la versión a la consola o a servicios de copiado y pegado («paste services» en inglés).
|
||||
mv-core.dumps.url.list={service} : {link}
|
||||
|
||||
# /mv gamerule set
|
||||
mv-core.gamerule.set.description=Cambia una «regla de juego» (gamerule) en uno o más mundos.
|
||||
mv-core.gamerule.set.gamerule.description=Gamerule que se desea cambiar.
|
||||
mv-core.gamerule.set.value.description=Valor de la gamerule.
|
||||
mv-core.gamerule.set.world.description=Mundo al que se quiere aplicar el cambio a dicha gamerule. Por defecto, será el mundo en el que esté el jugador que ejecute este comando.
|
||||
mv-core.gamerule.set.failed=No se ha podido establecer la gamerule {gamerule} a {value} en {world}. &fDebería ser {type}.
|
||||
mv-core.gamerule.set.success.single=&aSe ha establecido la gamerule {gamerule} a {value} en {world}.
|
||||
mv-core.gamerule.set.success.multiple=&aSe ha establecido la gamerule {gamerule} a {value} en {count} mundos.
|
||||
|
||||
# /mv gamerule reset
|
||||
mv-core.gamerule.reset.description=Reestablece una «regla de juego» (gamerule) en uno o más mundos a su valor por defecto.
|
||||
mv-core.gamerule.reset.gamerule.description=Gamerule a reestablecer.
|
||||
mv-core.gamerule.reset.world.description=Mundo sobre el que se quiere reestablecer dicha gamerule. Por defecto, será el mundo en el que esté el jugador que ejecute este comando.
|
||||
mv-core.gamerule.reset.failed=No se ha podido reestablecer la gamerule {gamerule} en {world}.
|
||||
mv-core.gamerule.reset.success.single=&aSe ha reestablecido la gamerule {gamerule} en {world}.
|
||||
mv-core.gamerule.reset.success.multiple=&aSe ha reestablecido la gamerule {gamerule} en {count} mundos.
|
||||
|
||||
# /mv gamerule list
|
||||
mv-core.gamerule.list.description=Muestra el valor de las gamerules de un mundo en específico.
|
||||
mv-core.gamerule.list.description.page=La página para ver.
|
||||
mv-core.gamerule.list.description.world=El mundo sobre el que se va a realizar la comprobación.
|
||||
mv-core.gamerule.list.title= --- Valor de las gamerules de {world} ---
|
||||
|
||||
# /mv generators
|
||||
mv-core.generators.description=Muestra los generadores conocidos por Multiverse
|
||||
mv-core.generators.description.flags=Filtro - solo muestra las entradas que coincidan. Página - la página a mostrar.
|
||||
mv-core.generators.empty=&cNo se ha encontrado ningún Plugin de Generación.
|
||||
|
||||
# /mv import
|
||||
mv-core.import.description=Importa la carpeta de un mundo ya existente.
|
||||
mv-core.import.name.description=Nombre de la carpeta de dicho mundo.
|
||||
mv-core.import.env.description=El ambiente del mundo. Para entender cual es, mira: /mv env
|
||||
mv-core.import.other.description=Ajustes adicional del mundo. Para saber cuales son, ve a https://gg.gg/nn8c2
|
||||
mv-core.import.importing=Iniciando la importación del mundo '{world}'...
|
||||
mv-core.import.success=&a¡Se ha importado el mundo '{world}'!
|
||||
|
||||
# /mv info
|
||||
mv-core.info.description=Muestra todas las propiedades que se han configurado para un mundo en concreto.
|
||||
mv-core.info.description.world=Mundo sobre el que se quiere ejecutar el comando.
|
||||
mv-core.info.header=&a&l---- Información del mundo: &f&l{world}&a&l ----
|
||||
mv-core.info.nocontent=&c¡No se ha encontrado ninguna propiedad configurada para dicho mundo!
|
||||
|
||||
# /mv list
|
||||
mv-core.list.description=Muestra una lista de todos los mundos a los que puedes entrar.
|
||||
mv-core.list.header=&6====[ Lista de mundos de Multiverse ]====
|
||||
mv-core.list.nocontent=&cNo se han encontrado mundos, ¡o no tienes acceso a ellos!
|
||||
|
||||
# /mv load
|
||||
mv-core.load.description=Carga un mundo. El mundo a cargar debe estar ya en el archivo worlds.yml. Si no es así, debes ejecutar primero /mv import para importar el mundo.
|
||||
mv-core.load.world.description=Nombre del mundo que quieres cargar.
|
||||
mv-core.load.loading=Cargando mundo '{world}'...
|
||||
mv-core.load.success=&a¡Se ha cargado el mundo '{world}'!
|
||||
|
||||
# /mv modify
|
||||
mv-core.modify.description=Modifica una propiedad de un mundo en concreto.
|
||||
mv-core.modify.world.description=Nombre del mundo que quieres modificar.
|
||||
mv-core.modify.property.description=Propiedad que quieres modificar.
|
||||
mv-core.modify.value.description=Valor al que quieres establecer dicha propiedad.
|
||||
mv-core.modify.cannothavevalue=No puedes especificar un valor para {action} '{property}'.
|
||||
mv-core.modify.specifyvalue=Debes especificar un valor para {action} '{property}'.
|
||||
mv-core.modify.success=&aSe ha {action} '&9{property}&a' a '&9{value}&a' en el mundo &9{world}&a.
|
||||
mv-core.modify.failure=&cNo se ha podido {action} '&9{property}&c' a '&9{value}&c' en el mundo &9{world}&c.\n&c{error}
|
||||
mv-core.modify.failure.novalue=&cNo se ha podido {action} '&9{property}&c' en el mundo &9{world}&c.\n&c{error}
|
||||
|
||||
# /mv regen
|
||||
mv-core.regen.description=Regenera un mundo en tu servidor. El estado previo se perderá PERMANENTEMENTE.
|
||||
mv-core.regen.world.description=Mundo que quieres regenerar.
|
||||
mv-core.regen.other.description=Ajustes adicionales del mundo. Para saber cuales son, mira: http://gg.gg/nn8lk
|
||||
mv-core.regen.regenerating=Regenerando mundo '{world}'...
|
||||
mv-core.regen.prompt=¿Estás seguro de que quieres regenerar el mundo '{world}'?
|
||||
mv-core.regen.success=&a¡Se ha regenerado el mundo '{world}'!
|
||||
|
||||
# /mv reload
|
||||
mv-core.reload.description=Recarga los archivos de configuración para todos los módulos de Multiverse.
|
||||
mv-core.reload.reloading=&6Recargando la configuración del Plugin Multiverse...
|
||||
mv-core.reload.success=&a¡Recarga completada!
|
||||
|
||||
# /mv remove
|
||||
mv-core.remove.description=Descarga un mundo de multiverse y lo elimina de worlds.yml. Esto NO elimina la carpeta de dicho mundo.
|
||||
mv-core.remove.world.description=Mundo que quieres eliminar del conocimiento de Multiverse.
|
||||
mv-core.remove.success=&a¡Se ha eliminado el mundo '{world}' de worlds.yml!
|
||||
|
||||
# /mv
|
||||
mv-core.root.title=&a{name} versión {version}
|
||||
mv-core.root.help=&aEjecuta &f/mv help&a para ver los comandos disponibles.
|
||||
|
||||
# /mv setspawn
|
||||
mv-core.setspawn.description=Establece el punto de aparición («spawn» en inglés) en el mundo especificado.
|
||||
mv-core.setspawn.location.description=Localización del nuevo punto de aparición.
|
||||
mv-core.setspawn.world.description=Mundo sobre el que se quiere establecer el nuevo punto de aparición. Por defecto, será el mismo que en el mundo en donde se encuentra el jugador que ejecute el comando.
|
||||
mv-core.setspawn.unsafe=&c¡El nuevo punto de aparición es inseguro! Si esto es intencional, puedes deshabilitar las comprobaciones de seguridad con el ajuste («flag» en inglés) &6--unsafe&c.
|
||||
mv-core.setspawn.success=&a¡Se ha establecido el punto de aparición del mundo &6{world}&a en &6{location}&a!
|
||||
mv-core.setspawn.failed=&cNo se ha podido establecer el punto de aparición del mundo &6{world}&c en &6{location}&c. {error}
|
||||
mv-core.setspawn.notmvworld=&cNo se ha podido establecer el punto de aparición del mundo &c{world}, pues no es un mundo Multiverse (no está en worlds.yml).
|
||||
|
||||
# /mv spawn
|
||||
mv-core.spawn.description=Teletransporta el jugador especificado al punto de aparición del mundo en el que están.
|
||||
mv-core.spawn.player.description=El jugador
|
||||
mv-core.spawn.success=¡Se ha teletransportado a {player} al punto de aparición de '{world}'!
|
||||
mv-core.spawn.failed=No se ha podido teletransportar a {player} al punto de aparición de '{world}'. {reason}
|
||||
|
||||
# /mv tp
|
||||
mv-core.teleport.description=¡Te permite teletransportarte a un lugar en concreto en tu servidor!
|
||||
mv-core.teleport.player.description=Jugador sobre el que se va a ejecutar la teletransportación.
|
||||
mv-core.teleport.destination.description=Localización, puede ser el nombre de un mundo.
|
||||
mv-core.teleport.toomanyplayers=&cNo puedes teletransportar más de {count} jugador al mismo tiempo.
|
||||
mv-core.teleport.success=Se ha teletransportado a {player} a {destination}.
|
||||
mv-core.teleport.failed=No se ha podido teletransportar a {player} a {destination}. {reason}
|
||||
|
||||
# /mv unload
|
||||
mv-core.unload.description=Descarga (en el sentido contrario a "cargar") un mundo Multiverse. Esto NO elimina la carpeta del mundo. Esto NO lo elimina del archivo de configuración.
|
||||
mv-core.unload.world.description=Nombre del mundo que quieres descargar.
|
||||
mv-core.unload.unloading=Descargando mundo '{world}'...
|
||||
mv-core.unload.success=&a¡Se ha descargado el mundo '{world}'!
|
||||
|
||||
# /mv usage
|
||||
mv-core.usage.description=Muestra el uso de los comandos de Multiverse-Core.
|
||||
|
||||
# /mv version
|
||||
mv-core.version.description=Muestra la versión actual de Multiverse y sus autores.
|
||||
mv-core.version.mv=Versión de Multiverse Core: &fv{version}
|
||||
mv-core.version.authors=Autores de la versión actual de Multiverse Core: &f{authors}
|
||||
mv-core.version.secretcode=Código especial: &fFRN002
|
||||
|
||||
# /mv who
|
||||
# /mv whoall
|
||||
mv-core.who.description=Muestra los jugadores que hay en un mundo en específico.
|
||||
mv-core.who.all.description=Muestra los jugadores que hay en todos los mundos.
|
||||
mv-core.who.world.description=Nombre del mundo del que quieres ver la lista de jugadores.
|
||||
mv-core.who.flags.description=Filtro - solo muestra las entradas coincidentes. Página - la página para mostrar
|
||||
mv-core.who.empty=&7&ovacío
|
||||
mv-core.who.header=&b====[ Lista de jugadores de los mundos Multiverse ]====
|
||||
|
||||
# /mv worldborder
|
||||
mv-core.worldborder.center.nothingchanged=&cNada ha cambbiado. El borde del mundo ya está centrado ahí en el mundo '&6{world}&c'.
|
||||
mv-core.worldborder.center.success=&rSe ha establecido el centro del mundo a &6{x}&r, &6{z}&r para el mundo '&6{world}&r'.
|
||||
|
||||
mv-core.worldborder.damageamount.nothingchanged=&cNada ha cambiado. El daño que causa el borde del mundo ya está establecido para esa cantidad en el mundo '&6{world}&c'.
|
||||
mv-core.worldborder.damageamount.success=&rSe ha establecido el daño del borde del mundo a {amount} por bloque cada segundo para el mundo '&6{world}&r'.
|
||||
|
||||
mv-core.worldborder.damagebuffer.nothingchanged=&cNada ha cambiado. El búfer de daño del borde del mundo ya está a esa distancia en el mundo '&6{world}&c'.
|
||||
mv-core.worldborder.damagebuffer.success=&rSe ha establecido el búfer de daño del borde del mundo a {distance} bloque(s) para el mundo '&6{world}&r'.
|
||||
|
||||
mv-core.worldborder.get.size=&rEl borde del mundo está en {size} bloque(s) de ancho para el mundo '&6{world}&r'.
|
||||
|
||||
mv-core.worldborder.set.nothingchanged=&cNada ha cambiado. El borde del mundo ya está en ese tamaño en el mundo '&6{world}&c'.
|
||||
mv-core.worldborder.set.immediate=&rEstablece el borde del mundo a {size} bloque(s) de ancho para el mundo '&6{world}&r'.
|
||||
mv-core.worldborder.set.shrinking=&rReduciendo el borde del mundo a {size} bloque(s) de ancho a lo largo de {time} segundo(s) en el mundo '&6{world}&r'.
|
||||
mv-core.worldborder.set.growing=&rAumentando el borde del mundo a {size} bloque(s) de ancho a lo largo de {time} segundo(s) en el mundo '&6{world}&r'.
|
||||
|
||||
mv-core.worldborder.warningdistance.nothingchanged=&cNada ha cambiado. El aviso del borde del mundo ya está a esa distancia en el mundo '&6{world}&c'.
|
||||
mv-core.worldborder.warningdistance.success=&rSe ha establecido la distancia del aviso del borde del mundo a {distance} bloque(s) en el mundo '&6{world}&r'.
|
||||
|
||||
mv-core.worldborder.warningtime.nothingchanged=&cNada ha cambiado. El aviso del borde del mundo ya se ha establecido para esa cantidad de tiempo en el mundo '&6{world}&c'.
|
||||
mv-core.worldborder.warningtime.success=&rSe ha establecido el tiempo de duración del aviso del borde del mundo a {time} segundo(s) en el mundo '&6{world}&r'.
|
||||
|
||||
# commands error
|
||||
mv-core.commands.error.playersonly=&cEste comando solo puede ser usado por jugadores.
|
||||
mv-core.commands.error.multiverseworldonly=&cEsto solo puede ser usado en mundos Multiverse.
|
||||
|
||||
# entry check
|
||||
mv-core.entrycheck.blacklisted=El mundo '{world}' está en la lista negra («blacklist» en inglés).
|
||||
mv-core.entrycheck.notenoughmoney=No tienes suficiente dinero para pagar la tarifa de entrada. Necesitas pagar {amount}.
|
||||
mv-core.entrycheck.cannotpayentryfee=No tienes lo necesario para pagar la tarifa de entrada.
|
||||
mv-core.entrycheck.exceedplayerlimit=El mundo ha llegado a su límite de jugadores.
|
||||
mv-core.entrycheck.noworldaccess=No tienes permiso para entrar al mundo.
|
||||
|
||||
# economy
|
||||
mv-core.economy.item.deposit=&a{price}&f ha sido depositado en tu inventario.
|
||||
mv-core.economy.item.withdraw=&fSe te ha cobrado &a{price}.
|
||||
mv-core.economy.vault.deposit=&2{price}&f ha sido depositado en tu cuenta.
|
||||
mv-core.economy.vault.withdraw=&fSe te ha cobrado &2{price}.
|
||||
|
||||
# multiverse parse destination failure reason
|
||||
mv-core.destination.anchor.failurereason.anchornotfound=&c¡El anclaje '&6{anchor}&c' no existe!
|
||||
mv-core.destination.bed.failurereason.playernotfound=&c¡El jugador '&6{player}&c' no existe o no está en línea! Para teletransportarte a su cama, usa 'playerbed'.
|
||||
mv-core.destination.cannon.failurereason.invalidformat=&cEl formato canon del destino es: &6ca:nombredelmundo:x,y,z:inclinación:aceleración:velocidad
|
||||
mv-core.destination.exact.failurereason.invalidformat=&cEl formato de destino exacto es: &6e:nombredelmundo:x,y,z:inclinación:aceleración
|
||||
mv-core.destination.player.failurereason.playernotfound=&c¡El jugador '&6{player}&c' no existe o no está en línea!
|
||||
mv-core.destination.shared.failurereason.invalidcoordinatesformat=Debes especificar una coordenada válida siguiendo este formato: &6{x},{y},{z}
|
||||
mv-core.destination.shared.failurereason.invalidnumberformat=El valor provisto no es un número. {error}.
|
||||
mv-core.destination.shared.failurereason.worldnotfound=&c¡El mundo '&6{world}&c' no existe o no está cargado!
|
||||
mv-core.destination.parse.failurereason.invaliddestinationid=ID de destino inválido: {id}. Las ID de destino válidas son: {ids}
|
||||
|
||||
# teleport failure reason
|
||||
mv-core.teleportfailurereason.null.destination=¡El destino es inválido!
|
||||
mv-core.teleportfailurereason.null.location=¡La localización es inválida!
|
||||
mv-core.teleportfailurereason.null.world=&c¡La localización del mundo es inválida!
|
||||
mv-core.teleportfailurereason.unsafe.location=¡La localización es insegura! Usa el ajuste («flag») `--unsafe` para sobrepasar esta limitación.
|
||||
mv-core.teleportfailurereason.teleport.failed=Algo ha evitado la teletransportación.
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=Un error ha ocurrido durante la teletransportación. Mira la consola para ver los detalles.
|
||||
mv-core.teleportfailurereason.event.cancelled=El teletransporte fue cancelado por otro plugin.
|
||||
|
||||
# world manager result
|
||||
mv-core.cloneworld.invalidworldname=¡El mundo '{world}' contiene caracteres inválidos!
|
||||
mv-core.cloneworld.worldexistfolder=¡El mundo '{world}' ya existe en los archivos del servidor! Necesitas eliminarlo antes de clonarlo.
|
||||
mv-core.cloneworld.worldexistunloaded=¡El mundo '{world}' ya existe, pero está descargado! Necesitas eliminarlo antes de clonarlo.
|
||||
mv-core.cloneworld.worldexistloaded=¡El mundo '{world}' ya existe! Necesitas eliminarlo antes de clonarlo.
|
||||
mv-core.cloneworld.copyfailed=No se ha podido copiar el mundo a '{world}': {error}\n&fMira la consola para ver los detalles.
|
||||
|
||||
mv-core.createworld.invalidworldname=¡El mundo '{world}' contiene caracteres inválidos!
|
||||
mv-core.createworld.worldexistfolder=¡El mundo '{world}' ya existe en las carpetas del servidor!&f Ejecuta '&a/mv import {world} <ambiente>&f' si deseas importarlo.
|
||||
mv-core.createworld.worldexistunloaded=El mundo '{world}' ya existe, ¡pero no está cargado!&f Ejecuta '&a/mv load {world}&f' si deseas cargarlo.
|
||||
mv-core.createworld.worldexistloaded=¡Ya existe el mundo '{world}'!
|
||||
|
||||
mv-core.deleteworld.worldnonexistent=¡El mundo '{world}' contiene caracteres inválidos!
|
||||
mv-core.deleteworld.loadfailed=No se ha podido cargar la carpeta del mundo '{world}', ¿existe esa carpeta?&f Puedes ejecutar '&a/mv remove {world}&f' para eliminarlo de Multiverse, o puedes intentar cargarlo otra vez ejecutando '&a/mv load {world}&f'.
|
||||
mv-core.deleteworld.worldfoldernotfound=¡No se ha encontrado la carpeta del mundo '{world}'!
|
||||
mv-core.deleteworld.failedtodeletefolder=No se ha podido borrar la carpeta del mundo '{world}': {error}\n&fMira la consola para ver los detalles.
|
||||
|
||||
mv-core.importworld.invalidworldname=¡El mundo '{world}' contiene caracteres inválidos!
|
||||
mv-core.importworld.worldexistunloaded=¡El mundo '{world}' ya existe, pero no está cargado!&f Ejecuta '&a/mv load {world}&f' si deseas cargarlo.
|
||||
mv-core.importworld.worldexistloaded=¡El mundo '{world}' ya existe!
|
||||
mv-core.importworld.worldfolderinvalid=¡Los contenidos de la carpeta del mundo '{world}' no parecen contener un mundo válido!
|
||||
|
||||
mv-core.loadworld.worldalreadyloading=¡Ya se está cargando el mundo '{world}'! Por favor, espera...
|
||||
mv-core.loadworld.worldnonexistent=¡No se ha encontrado el mundo '{world}'! Ejecuta '&a/mv create {world} <ambiente>&f' para crearlo.
|
||||
mv-core.loadworld.worldexistfolder=El mundo '{world}' existe en los archivos del servidor, ¡pero Multiverse no sabe en qué carpeta en concreto!&f Ejecuta '&a/mv import {world} <ambiente>&f' si deseas importarlo.
|
||||
mv-core.loadworld.worldexistloaded=¡El mundo '{world}' ya está cargado!
|
||||
|
||||
mv-core.removeworld.worldnonexistent=¡No se ha encontrado el mundo '{world}'!
|
||||
|
||||
mv-core.unloadworld.worldalreadyunloading=¡El mundo '{world}' ya se está descargando! Por favor, espera...
|
||||
mv-core.unloadworld.worldnonexistent=¡El mundo '{world}' no existe!
|
||||
mv-core.unloadworld.worldunloaded=¡El mundo '{world}' ya está descargado!
|
||||
mv-core.unloadworld.bukkitunloadfailed=Bukkit a fallado al descargar el mundo '{world}': {error}
|
||||
|
||||
mv-core.worldcreator.invalidbiomeprovider=&c¡Proveedor de biomas inválido '&6{biome}&c'! {error}\n&cMira la consola para ver los detalles.
|
||||
mv-core.worldcreator.invalidchunkgenerator=&c¡Generador de chunks inválido '&6{generator}&c'! {error}\n&cEsto es probablemente un problema con tu plugin generador, NO con Multiverse. Mira la consola para ver los detalles.
|
||||
mv-core.worldcreator.bukkitcreationfailed=&cBukkit ha fallado al crear el mundo '{world}': {error}\n&cSMira la consola para ver los detalles.
|
||||
|
||||
# queue command result
|
||||
mv-core.queuecommand.nocommandinqueue=&cNo tienes ningún comando en la cola.
|
||||
mv-core.queuecommand.invalidotp=&cNúmero OTP inválido '&6{otp}&c'. Por favor, inténtalo de nuevo...
|
||||
mv-core.queuecommand.commandexecutionerror=&cError ejecutando el comando en la cola: {error}\n&fMira la consola para ver los detalles.
|
||||
|
||||
# content display messages
|
||||
mv-core.contentdisplay.nocontent=&cNo hay ningún contenido para mostrar.
|
||||
mv-core.contentdisplay.filter=&7[Filtro '{filter}']
|
||||
mv-core.contentdisplay.page=&7[Página {current} de {total}]
|
||||
mv-core.contentdisplay.invalidpage=&cNúmero de página inválido. Por favor, introduce el número de una página que esté entre &31&c y &3{total}&c.
|
||||
mv-core.contentdisplay.empty=&7&ovacío
|
||||
mv-core.contentdisplay.null=&7&onull
|
||||
|
||||
# multiverse world exception
|
||||
mv-core.exception.multiverseworld.createnull=¡El mundo que se ha creado se ha vuelto null! (NullPointerException)
|
||||
mv-core.exception.multiverseworld.unloaddefaultworld=&c¡No puedes descargar (en el sentido contrario a «cargar») el mundo predeterminado! El mundo &6{world}&c es el mundo por defecto establecido en server.properties '&6level-name&c'.
|
||||
mv-core.exception.multiverseworld.unloadplayersinworld=&c¡Aún hay &6{count}&c jugador(es) en el mundo! Usa el ajuste («flag») '&6--remove-players&c' al ejecutar el comando para teletransportar a todos los jugadores fuera del mundo.
|
||||
mv-core.exception.multiverseworld.unloaderror=&cUn error desconocido ha ocurrido cuando se estaba descargando el mundo: &6{world}&c.\n&cMira la consola para ver los detalles.
|
||||
|
||||
# generic
|
||||
mv-core.generic.success=¡Correctamente!
|
||||
mv-core.generic.failure=¡Fallido!
|
||||
mv-core.generic.error=¡Error!
|
||||
mv-core.generic.null=¡Null!
|
||||
mv-core.generic.you=tú
|
||||
@@ -54,12 +54,12 @@ mv-core.create.properties.generatorsettings=- 生成器设置: &f{generatorSetti
|
||||
|
||||
mv-core.create.properties.structures=- 结构: &f{structures}
|
||||
mv-core.create.loading=创建世界中……
|
||||
mv-core.create.success=&a世界 '{world}' 已经创建!
|
||||
mv-core.create.success=&a世界 '{world}' 已经创建!
|
||||
|
||||
# /mv debug
|
||||
mv-core.debug.info.description=显示当前的调试等级。
|
||||
mv-core.debug.info.off=&fMultiverse 调试模式是 &cOFF&f.
|
||||
mv-core.debug.info.on=&fMultiverse 调试模式处于 &a等级{level}&f.
|
||||
mv-core.debug.info.off=&fMultiverse 调试模式是 &cOFF&f。
|
||||
mv-core.debug.info.on=&fMultiverse 调试模式处于 &a等级{level}&f。
|
||||
mv-core.debug.change.description=改变调试等级。
|
||||
mv-core.debug.change.syntax=等级
|
||||
mv-core.debug.change.level.description=需要设置的调试等级。
|
||||
@@ -164,6 +164,10 @@ mv-core.root.help=&a查看 &f/mv help&a 了解可以执行的指令。
|
||||
mv-core.setspawn.description=为某个特定的世界设置出生点。
|
||||
mv-core.setspawn.location.description=新出生点的位置。
|
||||
mv-core.setspawn.world.description=需要设置出生点的目标世界(默认为玩家的当前世界)
|
||||
mv-core.setspawn.unsafe=&c新的生成位置不安全!如果这是有意为之,你可以通过使用&6--unsafe&c标志来禁用安全检查。
|
||||
mv-core.setspawn.success=&a成功在&6{world}&a中将生成点设置为&6{location}&a!
|
||||
mv-core.setspawn.failed=&c在&6{world}&c中将生成点设置为&6{location}&c时失败。{error}
|
||||
mv-core.setspawn.notmvworld=&c无法为&c{world}设置生成点,因为它不是一个Multiverse世界。
|
||||
|
||||
# /mv spawn
|
||||
mv-core.spawn.description=传送某个特定的玩家到他们所在世界的出生点。
|
||||
@@ -237,6 +241,12 @@ mv-core.entrycheck.cannotpayentryfee=你没有能力支付入场费。
|
||||
mv-core.entrycheck.exceedplayerlimit=该世界已经达到最大玩家数量限制。
|
||||
mv-core.entrycheck.noworldaccess=你没有权限进入该世界。
|
||||
|
||||
# economy
|
||||
mv-core.economy.item.deposit=&a{price}&f已存入你的背包。
|
||||
mv-core.economy.item.withdraw=&f你已被收取&a{price}。
|
||||
mv-core.economy.vault.deposit=&2{price}&f已存入你的账户。
|
||||
mv-core.economy.vault.withdraw=&f你已被收取&2{price}。
|
||||
|
||||
# multiverse parse destination failure reason
|
||||
mv-core.destination.anchor.failurereason.anchornotfound=&c锚点 '&6{anchor}&c' 不存在!
|
||||
mv-core.destination.bed.failurereason.playernotfound=&玩家 '&6{player}&c' 不存在或不在线!如果想传送到自己的床,使用 'playerbed'。
|
||||
@@ -244,7 +254,7 @@ mv-core.destination.cannon.failurereason.invalidformat=&c大炮目标格式是
|
||||
mv-core.destination.exact.failurereason.invalidformat=&c精确目标格式是:&6e:worldname:x,y,z:pitch:yaw
|
||||
mv-core.destination.player.failurereason.playernotfound=&c玩家 '&6{player}&c' 不存在或不在线!
|
||||
mv-core.destination.shared.failurereason.invalidcoordinatesformat=你必须指定一个具有以下格式有效的坐标:&6{x},{y},{z}
|
||||
mv-core.destination.shared.failurereason.invalidnumberformat=给定的值不是一个有效的数字。{error}.
|
||||
mv-core.destination.shared.failurereason.invalidnumberformat=给定的值不是一个有效的数字。{error}。
|
||||
mv-core.destination.shared.failurereason.worldnotfound=&c世界 '&6{world}&c' 不存在或着未被加载!
|
||||
mv-core.destination.parse.failurereason.invaliddestinationid=无效的目标ID:{id}。有效的目标ID有:{ids}
|
||||
|
||||
@@ -271,7 +281,7 @@ mv-core.createworld.worldexistloaded=世界 '{world}' 已经存在!
|
||||
|
||||
mv-core.deleteworld.worldnonexistent=世界 '{world}' 不存在!
|
||||
mv-core.deleteworld.loadfailed=不能加载世界 '{world}',请确认世界文件夹是否存在。&f 你可以执行 '&a/mv remove {world}&f' 将它从 Multiverse 中移除,或者使用 '&a/mv load {world}&f' 尝试重新加载。
|
||||
mv-core.deleteworld.worldfoldernotfound=世界 '{world}' folder not found!
|
||||
mv-core.deleteworld.worldfoldernotfound=世界 '{world}' folder not found!
|
||||
mv-core.deleteworld.failedtodeletefolder=删除世界 '{world}' 的文件夹失败:{error}\n&f查看控制台获取更多信息。
|
||||
|
||||
mv-core.importworld.invalidworldname=世界 '{world}' 包含无效的字符!
|
||||
@@ -286,13 +296,13 @@ mv-core.loadworld.worldexistloaded=世界 '{world}' 已经存在!
|
||||
|
||||
mv-core.removeworld.worldnonexistent=世界 '{world}' 不存在!
|
||||
|
||||
mv-core.unloadworld.worldalreadyunloading=世界 '{world}' 已经在卸载! 请稍后……
|
||||
mv-core.unloadworld.worldalreadyunloading=世界 '{world}' 已经在卸载! 请稍后……
|
||||
mv-core.unloadworld.worldnonexistent=世界 '{world}' 不存在!
|
||||
mv-core.unloadworld.worldunloaded=世界 '{world}' 已经卸载!
|
||||
mv-core.unloadworld.bukkitunloadfailed=Bukkit 卸载世界 '{world}' 失败:{error}
|
||||
|
||||
mv-core.worldcreator.invalidbiomeprovider=&c无效的生物群系 '&6{biome}&c'!{error}\n&c查看控制台获取更多信息。
|
||||
mv-core.worldcreator.invalidchunkgenerator=&c无效的区块生成器 '&6{generator}&c'! {error}\n&c这更像是生成器插件的问题,而不是 Multiverse。查看控制台获取更多信息。
|
||||
mv-core.worldcreator.invalidchunkgenerator=&c无效的区块生成器 '&6{generator}&c'! {error}\n&c这更像是生成器插件的问题,而不是 Multiverse。查看控制台获取更多信息。
|
||||
mv-core.worldcreator.bukkitcreationfailed=&cBukkit 创建世界 '{world}' 失败:{error}\n&c查看控制台获取更多信息。
|
||||
|
||||
# queue command result
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.mvplugins.multiverse.core.commands
|
||||
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.permissions.PermissionAttachment
|
||||
import org.mockbukkit.mockbukkit.command.ConsoleCommandSenderMock
|
||||
import org.mockbukkit.mockbukkit.entity.PlayerMock
|
||||
@@ -8,6 +7,7 @@ import org.mvplugins.multiverse.core.TestWithMockBukkit
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales
|
||||
import org.mvplugins.multiverse.core.locale.message.Message
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter
|
||||
import org.mvplugins.multiverse.core.world.WorldManager
|
||||
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions
|
||||
import kotlin.test.BeforeTest
|
||||
@@ -47,8 +47,8 @@ abstract class AbstractCommandTest : TestWithMockBukkit() {
|
||||
|
||||
fun assertCommandOutput(message : Message) {
|
||||
assertEquals(
|
||||
ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&',message.formatted(locales))),
|
||||
ChatColor.stripColor(player.nextMessage())
|
||||
ChatTextFormatter.removeColor(message.formatted(locales)),
|
||||
ChatTextFormatter.removeColor(player.nextMessage())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import org.bukkit.ChatColor
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n
|
||||
import org.mvplugins.multiverse.core.locale.message.Message
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
@@ -15,7 +16,7 @@ class VersionCommandTest : AbstractCommandTest() {
|
||||
@Test
|
||||
fun `Run version command as console`() {
|
||||
assertTrue(Bukkit.dispatchCommand(console, "mv version"))
|
||||
val output = ChatColor.stripColor(console.nextMessage())
|
||||
val output = ChatTextFormatter.removeColor(console.nextMessage())
|
||||
assertEquals("Multiverse Core Version v" + multiverseCore.getDescription().getVersion(), output)
|
||||
}
|
||||
|
||||
|
||||
@@ -81,8 +81,8 @@ class ConfigTest : TestWithMockBukkit() {
|
||||
assertTrue(config.stringPropertyHandle.setProperty("enforce-access", true).isSuccess)
|
||||
assertEquals(true, config.stringPropertyHandle.getProperty("enforce-access").get())
|
||||
|
||||
assertTrue(config.stringPropertyHandle.setProperty("first-spawn-location", "world2").isSuccess)
|
||||
assertEquals("world2", config.stringPropertyHandle.getProperty("first-spawn-location").get())
|
||||
assertTrue(config.stringPropertyHandle.setProperty("chat-prefix-format", "%world% %chat%").isSuccess)
|
||||
assertEquals("%world% %chat%", config.stringPropertyHandle.getProperty("chat-prefix-format").get())
|
||||
|
||||
assertTrue(config.stringPropertyHandle.setProperty("global-debug", 1).isSuccess)
|
||||
assertEquals(1, config.stringPropertyHandle.getProperty("global-debug").get())
|
||||
@@ -93,8 +93,8 @@ class ConfigTest : TestWithMockBukkit() {
|
||||
assertTrue(config.stringPropertyHandle.setPropertyString("enforce-access", "true").isSuccess)
|
||||
assertEquals(true, config.stringPropertyHandle.getProperty("enforce-access").get())
|
||||
|
||||
assertTrue(config.stringPropertyHandle.setPropertyString("first-spawn-location", "world2").isSuccess)
|
||||
assertEquals("world2", config.stringPropertyHandle.getProperty("first-spawn-location").get())
|
||||
assertTrue(config.stringPropertyHandle.setPropertyString("chat-prefix-format", "%world% %chat%").isSuccess)
|
||||
assertEquals("%world% %chat%", config.stringPropertyHandle.getProperty("chat-prefix-format").get())
|
||||
|
||||
assertTrue(config.stringPropertyHandle.setPropertyString("global-debug", "1").isSuccess)
|
||||
assertEquals(1, config.stringPropertyHandle.getProperty("global-debug").get())
|
||||
|
||||
@@ -69,6 +69,22 @@ class DestinationTest : TestWithMockBukkit() {
|
||||
assertEquals("e:world:1.2,2.0,3.0:10.5:9.5", destination.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Exact destination instance with relative position`() {
|
||||
val destination = destinationsProvider.parseDestination("e:world:~1.5,2,~-3:10.5:~9.5").orNull
|
||||
assertTrue(destination is ExactDestinationInstance)
|
||||
val expectedLocation = Location(
|
||||
world.bukkitWorld.orNull,
|
||||
player.location.x + 1.5,
|
||||
2.0,
|
||||
player.location.z - 3.0,
|
||||
player.location.yaw + 9.5F,
|
||||
10.5F
|
||||
)
|
||||
assertLocationEquals(expectedLocation, destination.getLocation(player).orNull)
|
||||
assertEquals("e:world:~1.5,2.0,~-3.0:10.5:~9.5", destination.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Exact destination instance from location`() {
|
||||
val exactDestination = serviceLocator.getActiveService(ExactDestination::class.java).takeIf { it != null } ?: run {
|
||||
@@ -123,6 +139,7 @@ class DestinationTest : TestWithMockBukkit() {
|
||||
assertTrue(destinationsProvider.parseDestination("b:invalid-bed").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("ca:invalid-cannon").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("e:world:1,2,x").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("e:world:1,2,3:").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("pl:invalid-player").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("w:invalid-world").isFailure)
|
||||
// todo: should we make invalid yaw for WorldDestination fail?
|
||||
|
||||
Loaded 100 of 107 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user