mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 16:50:03 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c86c41f98 | ||
|
|
0afd4dbb62 | ||
|
|
f527fbd693 | ||
|
|
b50687db3b | ||
|
|
a28a071e1e | ||
|
|
66dc376633 | ||
|
|
5e8e9380e3 | ||
|
|
4a16fa2297 | ||
|
|
b9e07a85d0 | ||
|
|
53c7459c1d | ||
|
|
112505bc93 | ||
|
|
e27fe73faa | ||
|
|
9bc2b4dfaf | ||
|
|
3ba8beefc5 | ||
|
|
05b6b380e0 | ||
|
|
b58ea2e04b | ||
|
|
f4405874d1 | ||
|
|
247685c93d | ||
|
|
490107b5d5 | ||
|
|
122e35103a | ||
|
|
25eeefbc77 | ||
|
|
6b5ce2aebb | ||
|
|
e9905ca281 | ||
|
|
e002282b13 | ||
|
|
39711527fa | ||
|
|
8d9b29ff87 | ||
|
|
82fe350afa | ||
|
|
a7b4b9bbda | ||
|
|
15f511b660 | ||
|
|
8347cf8fbb | ||
|
|
4c27fea26b | ||
|
|
4111da8fd6 | ||
|
|
167f6f8a37 | ||
|
|
325b5d7643 | ||
|
|
5dfb098d5c | ||
|
|
6c50bfb08e | ||
|
|
37c0580828 | ||
|
|
e1a42cec92 | ||
|
|
a6cc8bd68e | ||
|
|
ee4cd18522 | ||
|
|
b6c309a332 | ||
|
|
87571e7037 | ||
|
|
0822b1460a | ||
|
|
1220998c12 | ||
|
|
e078c5e5dd | ||
|
|
c1ef8f656b | ||
|
|
86e2112d87 | ||
|
|
8a77dc807e | ||
|
|
77382dacf3 | ||
|
|
588a8e1baf | ||
|
|
5bd37d17c3 | ||
|
|
815637e00f | ||
|
|
78194389eb | ||
|
|
2db55fab3b | ||
|
|
c505da76ec | ||
|
|
6152620541 | ||
|
|
cf70b3b839 | ||
|
|
527085713c | ||
|
|
f446c94dbc | ||
|
|
aac0111dde | ||
|
|
f66e673ea9 | ||
|
|
1434ca176a | ||
|
|
212b68437e | ||
|
|
05d01884ad | ||
|
|
65c399740e | ||
|
|
72ee0d5bed | ||
|
|
1afecd40ea | ||
|
|
00944ed3b2 | ||
|
|
5f659a80d8 | ||
|
|
5f9b0af8d1 | ||
|
|
5c44c05f6f | ||
|
|
1699a47011 | ||
|
|
96deee719b | ||
|
|
8a60761098 | ||
|
|
a38b4e03b4 | ||
|
|
db9b725e10 | ||
|
|
d43b23e7ea | ||
|
|
86d77ae796 | ||
|
|
86bf785c84 | ||
|
|
626537290d | ||
|
|
9fa47727f4 | ||
|
|
ae3bc6d201 | ||
|
|
b1d126397e | ||
|
|
8ab9993e97 | ||
|
|
13b949c692 | ||
|
|
2b41c1367e | ||
|
|
fde3f388bd | ||
|
|
eb3968b7d6 | ||
|
|
fed1fdcbea | ||
|
|
bfb6fea5f5 | ||
|
|
5b02452a3d | ||
|
|
4a2bfc74fa | ||
|
|
f0539441cf | ||
|
|
efe490d8c4 | ||
|
|
3074cfab17 | ||
|
|
7a5dd44feb | ||
|
|
88a9ee5b6d | ||
|
|
46f67240e8 | ||
|
|
89e765b2e8 | ||
|
|
b8bdec22d3 | ||
|
|
792913cff7 | ||
|
|
6dcb40fdcb | ||
|
|
2bb601098a | ||
|
|
4bcebd51f5 | ||
|
|
e207eb5d0d | ||
|
|
f181bb7ade | ||
|
|
7207d9303f | ||
|
|
82799cdbe8 | ||
|
|
3629babdbc | ||
|
|
eed3353e2b | ||
|
|
147564caab | ||
|
|
30e50866bd | ||
|
|
5dde7608f2 | ||
|
|
2f9915c62e | ||
|
|
e62e37831b | ||
|
|
ef9934f3c6 | ||
|
|
691bcd0ffa | ||
|
|
148644970d | ||
|
|
ad20ec94c5 | ||
|
|
f7d86884b4 | ||
|
|
0e0e4e0434 | ||
|
|
8e878b1ed0 | ||
|
|
e1c6af2f38 | ||
|
|
b52d65757a | ||
|
|
ff2135c775 | ||
|
|
c4e4e311e2 | ||
|
|
3e4ded52e3 | ||
|
|
4ef8390ea3 | ||
|
|
bca71e1acc | ||
|
|
343da8c301 | ||
|
|
d34f3adccc | ||
|
|
b3862faf88 | ||
|
|
8bffffdc65 | ||
|
|
79a71fa58f | ||
|
|
fdd9257e2c | ||
|
|
55d1347d4f | ||
|
|
29cc97486b | ||
|
|
8d06b92edd | ||
|
|
ce3b6ab700 | ||
|
|
58ac0f505c | ||
|
|
c068225950 |
No files matched your search
@@ -11,9 +11,9 @@ jobs:
|
||||
pull-requests: write
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: dbelyaev/action-checkstyle@v0.8.4
|
||||
- uses: dbelyaev/action-checkstyle@v3
|
||||
continue-on-error: true
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -107,9 +107,9 @@ 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.10, 1.21.9, 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
|
||||
game_versions: 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 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
|
||||
loaders: bukkit, spigot, paper, purpur
|
||||
dependencies: ${{ inputs.modrinth_dependencies }}
|
||||
|
||||
- name: Upload to DBO
|
||||
@@ -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.10, 1.21.9, 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
|
||||
game_versions: 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 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.10"]}'
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-26.1.2"]}'
|
||||
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
|
||||
+2
-2
@@ -25,9 +25,9 @@ repositories {
|
||||
}
|
||||
|
||||
configure(apiDependencies) {
|
||||
serverApiVersion = '1.21.8-R0.1-SNAPSHOT'
|
||||
serverApiVersion = '1.21.11-R0.1-SNAPSHOT'
|
||||
mockBukkitServerApiVersion = '1.21'
|
||||
mockBukkitVersion = '4.84.0'
|
||||
mockBukkitVersion = '4.100.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -124,9 +124,9 @@ public class MultiverseCore extends MultiverseModule {
|
||||
public void onDisable() {
|
||||
super.onDisable();
|
||||
MultiverseCoreApi.shutdown();
|
||||
saveAllConfigs();
|
||||
shutdownDependencyInjection();
|
||||
PluginServiceLocatorFactory.get().shutdown();
|
||||
Logging.info("- Disabled");
|
||||
Logging.shutdown();
|
||||
}
|
||||
|
||||
@@ -215,22 +215,23 @@ public class MultiverseCore extends MultiverseModule {
|
||||
*/
|
||||
private Try<Void> saveAllConfigs() {
|
||||
return configProvider.get().save()
|
||||
.flatMap(ignore -> worldManagerProvider.get().saveWorldsConfig())
|
||||
.flatMap(ignore ->anchorManagerProvider.get().saveAllAnchors())
|
||||
.onFailure(e -> {
|
||||
Logging.severe("Failed to save configs, things may not work as expected.");
|
||||
});
|
||||
.andThenTry(() -> worldManagerProvider.get().saveWorldsConfig())
|
||||
.andThenTry(() -> anchorManagerProvider.get().saveAllAnchors())
|
||||
.onFailure(e ->
|
||||
Logging.severe("Failed to save all configs, things may not work as expected. %s",
|
||||
e.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the enable message.
|
||||
*/
|
||||
private void logEnableMessage() {
|
||||
Logging.config("\u001B[32mVersion %s (API v%s) Enabled - By %s\u001B[39m",
|
||||
Logging.config("\u001B[32mVersion %s (API v%s) Enabled - By %s\u001B[0m",
|
||||
this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(getDescription().getAuthors()));
|
||||
|
||||
if (configProvider.get().isShowingDonateMessage()) {
|
||||
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a small donation: https://github.com/sponsors/Multiverse\u001B[39m");
|
||||
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a " +
|
||||
"small donation: https://github.com/sponsors/Multiverse\u001B[0m");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
@@ -12,11 +13,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
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.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,12 +29,17 @@ import java.util.Locale;
|
||||
final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
|
||||
private final MultiverseCore plugin;
|
||||
private final CoreConfig coreConfig;
|
||||
private final WorldManager worldManager;
|
||||
private final MVEconomist economist;
|
||||
|
||||
@Inject
|
||||
public PlaceholderExpansionHook(MultiverseCore plugin, WorldManager worldManager, MVEconomist economist) {
|
||||
public PlaceholderExpansionHook(@NotNull MultiverseCore plugin,
|
||||
@NotNull CoreConfig coreConfig,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull MVEconomist economist) {
|
||||
this.plugin = plugin;
|
||||
this.coreConfig = coreConfig;
|
||||
this.worldManager = worldManager;
|
||||
this.economist = economist;
|
||||
}
|
||||
@@ -42,6 +50,13 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return super.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String msg) {
|
||||
if (coreConfig.getWarnInvalidPapiFormat()) {
|
||||
super.warning(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "multiverse-core";
|
||||
@@ -80,130 +95,102 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return null;
|
||||
}
|
||||
|
||||
final var placeholder = paramsArray.removeFirst();
|
||||
final var placeholder = paramsArray.remove(0).toLowerCase(Locale.ENGLISH);
|
||||
|
||||
String worldName = parseWorldName(offlinePlayer, paramsArray);
|
||||
if (worldName == null) return null;
|
||||
|
||||
return worldManager.getLoadedWorld(worldName)
|
||||
.onEmpty(() -> warning("Multiverse World not found: " + worldName))
|
||||
.map(world -> getWorldPlaceHolderValue(placeholder, paramsArray, world))
|
||||
.getOrNull();
|
||||
return parseWorldName(offlinePlayer, paramsArray)
|
||||
.flatMap(worldName -> worldManager.getWorld(worldName)
|
||||
.onEmpty(() -> warning("Multiverse World not found: " + worldName)))
|
||||
.flatMap(world -> world.asLoadedWorld()
|
||||
.flatMap(loadedWorld -> getLoadedWorldPlaceHolderValue(placeholder, paramsArray, loadedWorld))
|
||||
.orElse(() -> getWorldPlaceHolderValue(placeholder, paramsArray, world)))
|
||||
.getOrElse(() -> coreConfig.getInvalidPapiFormatReturnsBlank() ? "" : null);
|
||||
}
|
||||
|
||||
private @Nullable String parseWorldName(OfflinePlayer offlinePlayer, List<String> paramsArray) {
|
||||
private @NotNull Option<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();
|
||||
return Option.of(player.getWorld().getName());
|
||||
} else {
|
||||
warning("You must specify a world name for non-player placeholders");
|
||||
return null;
|
||||
return Option.none();
|
||||
}
|
||||
}
|
||||
|
||||
// Try get from params
|
||||
String paramWorldName = paramsArray.getLast();
|
||||
if (worldManager.isLoadedWorld(paramWorldName)) {
|
||||
paramsArray.removeLast();
|
||||
return paramWorldName;
|
||||
String paramWorldName = paramsArray.get(paramsArray.size() - 1);
|
||||
if (worldManager.isWorld(paramWorldName)) {
|
||||
paramsArray.remove(paramsArray.size() - 1);
|
||||
return Option.of(paramWorldName);
|
||||
}
|
||||
|
||||
// Param not a world, fallback to player
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
return player.getWorld().getName();
|
||||
return Option.of(player.getWorld().getName());
|
||||
}
|
||||
warning("Multiverse World not found: " + paramWorldName);
|
||||
return null;
|
||||
return Option.none();
|
||||
}
|
||||
|
||||
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(Locale.ENGLISH)) {
|
||||
case "alias" -> {
|
||||
return world.getAliasOrName();
|
||||
}
|
||||
case "animalspawn" -> {
|
||||
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.ANIMAL).isSpawn());
|
||||
}
|
||||
case "autoheal" -> {
|
||||
return String.valueOf(world.getAutoHeal());
|
||||
}
|
||||
case "blacklist" -> {
|
||||
return String.join(", ", world.getWorldBlacklist());
|
||||
}
|
||||
case "currency" -> {
|
||||
return String.valueOf(world.getCurrency());
|
||||
}
|
||||
case "difficulty" -> {
|
||||
return world.getDifficulty().toString();
|
||||
}
|
||||
case "entryfee" -> {
|
||||
return economist.formatPrice(world.getPrice(), world.getCurrency());
|
||||
}
|
||||
case "environment" -> {
|
||||
return world.getEnvironment().toString().toLowerCase();
|
||||
}
|
||||
case "flight" -> {
|
||||
return String.valueOf(world.isAllowFlight());
|
||||
}
|
||||
case "gamemode" -> {
|
||||
return world.getGameMode().toString().toLowerCase();
|
||||
}
|
||||
case "generator" -> {
|
||||
return world.getGenerator();
|
||||
}
|
||||
case "hunger" -> {
|
||||
return String.valueOf(world.isHunger());
|
||||
}
|
||||
case "monstersspawn" -> {
|
||||
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.MONSTER).isSpawn());
|
||||
}
|
||||
case "name" -> {
|
||||
return world.getName();
|
||||
}
|
||||
case "playerlimit" -> {
|
||||
return String.valueOf(world.getPlayerLimit());
|
||||
}
|
||||
case "price" -> {
|
||||
return String.valueOf(world.getPrice());
|
||||
}
|
||||
case "pvp" -> {
|
||||
return String.valueOf(world.getPvp());
|
||||
}
|
||||
case "seed" -> {
|
||||
return String.valueOf(world.getSeed());
|
||||
}
|
||||
case "time" -> {
|
||||
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");
|
||||
}
|
||||
case "weather" -> {
|
||||
return String.valueOf(world.isAllowWeather());
|
||||
private @NotNull Option<String> getWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull MultiverseWorld world) {
|
||||
return Option.of(switch (placeholder) {
|
||||
case "alias" -> world.getAliasOrName();
|
||||
case "animalspawn" -> String.valueOf(world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(SpawnCategory.ANIMAL)
|
||||
.isSpawn());
|
||||
case "autoheal" -> String.valueOf(world.getAutoHeal());
|
||||
case "blacklist" -> String.join(", ", world.getWorldBlacklist());
|
||||
case "currency" -> String.valueOf(world.getCurrency());
|
||||
case "difficulty" -> world.getDifficulty().toString();
|
||||
case "entryfee" -> economist.formatPrice(world.getPrice(), world.getCurrency());
|
||||
case "environment" -> world.getEnvironment().toString().toLowerCase();
|
||||
case "flight" -> String.valueOf(world.isAllowFlight());
|
||||
case "gamemode" -> world.getGameMode().toString().toLowerCase();
|
||||
case "generator" -> world.getGenerator();
|
||||
case "hunger" -> String.valueOf(world.isHunger());
|
||||
case "isloaded" -> String.valueOf(world.isLoaded());
|
||||
case "monstersspawn" -> String.valueOf(world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(SpawnCategory.MONSTER)
|
||||
.isSpawn());
|
||||
case "name" -> world.getName();
|
||||
case "playerlimit" -> String.valueOf(world.getPlayerLimit());
|
||||
case "price" -> String.valueOf(world.getPrice());
|
||||
case "pvp" -> String.valueOf(world.getPvp());
|
||||
case "seed" -> String.valueOf(world.getSeed());
|
||||
case "weather" -> String.valueOf(world.isAllowWeather());
|
||||
case "playercount", "time", "type" -> {
|
||||
warning("Placeholder '" + placeholder + "' is only available for loaded worlds.");
|
||||
yield null;
|
||||
}
|
||||
default -> {
|
||||
warning("Unknown placeholder: " + placeholder);
|
||||
return null;
|
||||
warning("Unknown Placeholder: " + placeholder);
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull Option<String> getLoadedWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
return Option.of(switch (placeholder) {
|
||||
case "playercount" -> String.valueOf(world.getBukkitWorld()
|
||||
.map(World::getPlayers)
|
||||
.map(List::size)
|
||||
.getOrElse(-1));
|
||||
case "time" -> {
|
||||
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.get(0) : "";
|
||||
long time = world.getBukkitWorld().map(World::getTime).getOrElse(0L);
|
||||
yield switch (timeFormat) {
|
||||
case "" -> String.valueOf(time);
|
||||
case "12h" -> MinecraftTimeFormatter.format12h(time);
|
||||
case "24h" -> MinecraftTimeFormatter.format24h(time);
|
||||
default -> MinecraftTimeFormatter.formatTime(time, timeFormat);
|
||||
};
|
||||
}
|
||||
case "type" -> world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
|
||||
default -> null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.bukkit.GameRule;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -43,16 +44,16 @@ 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;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorPlugin;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
|
||||
import static org.mvplugins.multiverse.core.utils.StringFormatter.addonToCommaSeperated;
|
||||
import static org.mvplugins.multiverse.core.utils.StringFormatter.addOnToCommaSeparated;
|
||||
|
||||
@Service
|
||||
public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
@@ -100,7 +101,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
|
||||
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName);
|
||||
registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue);
|
||||
registerAsyncCompletion("playersarray", this::suggestPlayersArray);
|
||||
registerCompletion("playersarray", this::suggestPlayersArray); // getting online players cannot be async
|
||||
registerStaticCompletion("propsmodifyaction", suggestEnums(PropertyModifyAction.class));
|
||||
registerStaticCompletion("spawncategories", suggestEnums(SpawnCategory.class));
|
||||
registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName);
|
||||
@@ -162,7 +163,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
}
|
||||
}
|
||||
if (context.hasConfig("multiple")) {
|
||||
return addonToCommaSeperated(context.getInput(), handler.getCompletions(context));
|
||||
return addOnToCommaSeparated(context.getInput(), handler.getCompletions(context));
|
||||
}
|
||||
return handler.getCompletions(context);
|
||||
}
|
||||
@@ -224,18 +225,22 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
// Most likely console did not specify a player
|
||||
return Collections.<String>emptyList();
|
||||
}
|
||||
if (context.hasConfig("othersOnly") && (players.length == 1 && players[0].equals(context.getIssuer().getIssuer()))) {
|
||||
CommandSender sender = context.getIssuer().getIssuer();
|
||||
if (context.hasConfig("othersOnly") && (players.length == 1 && players[0].equals(sender))) {
|
||||
return Collections.<String>emptyList();
|
||||
}
|
||||
return suggestDestinationsWithPerms(context.getIssuer().getIssuer(), players, context.getInput());
|
||||
return suggestDestinationsWithPerms(sender, Arrays.asList(players), context.getInput());
|
||||
})
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
private Collection<String> suggestDestinationsWithPerms(CommandSender teleporter, Player[] players, String deststring) {
|
||||
private Collection<String> suggestDestinationsWithPerms(CommandSender teleporter, List<Entity> teleportees, String deststring) {
|
||||
return destinationsProvider.suggestDestinations(teleporter, deststring).stream()
|
||||
.filter(packet -> !config.getSimplifiedDestinationTabCompletion()
|
||||
|| packet.destination() instanceof WorldDestination
|
||||
|| deststring.startsWith(packet.destination().getIdentifier() + ":"))
|
||||
.filter(packet -> corePermissionsChecker
|
||||
.checkDestinationPacketPermission(teleporter, Arrays.asList(players), packet))
|
||||
.checkDestinationPacketPermission(teleporter, teleportees, packet))
|
||||
.map(DestinationSuggestionPacket::parsableString)
|
||||
.toList();
|
||||
}
|
||||
@@ -279,19 +284,16 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
case "both" -> {
|
||||
return worldManager.getWorlds().stream()
|
||||
.map(MultiverseWorld::getTabCompleteName)
|
||||
.map(StringFormatter::quoteMultiWordString)
|
||||
.toList();
|
||||
}
|
||||
case "loaded" -> {
|
||||
return worldManager.getLoadedWorlds().stream()
|
||||
.map(MultiverseWorld::getTabCompleteName)
|
||||
.map(StringFormatter::quoteMultiWordString)
|
||||
.toList();
|
||||
}
|
||||
case "unloaded" -> {
|
||||
return worldManager.getUnloadedWorlds().stream()
|
||||
.map(MultiverseWorld::getTabCompleteName)
|
||||
.map(StringFormatter::quoteMultiWordString)
|
||||
.toList();
|
||||
}
|
||||
case "potential" -> {
|
||||
@@ -332,7 +334,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
matchedPlayers.add(name);
|
||||
}
|
||||
}
|
||||
return addonToCommaSeperated(context.getInput(), matchedPlayers);
|
||||
return addOnToCommaSeparated(context.getInput(), matchedPlayers);
|
||||
}
|
||||
|
||||
private Collection<String> suggestSpawnCategoryPropsName(BukkitCommandCompletionContext context) {
|
||||
|
||||
@@ -11,6 +11,7 @@ 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.Option;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -272,13 +273,25 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
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;
|
||||
})
|
||||
.fromInput((context, input) -> PlayerFinder
|
||||
.tryGetMulti(input, context.getSender())
|
||||
.map(list -> list.toArray(new Player[0]))
|
||||
.map(arr -> (arr.length == 0) ? null : arr)
|
||||
.getOrElseThrow(failure -> {
|
||||
if (failure instanceof MVInvalidCommandArgument mvFailure) {
|
||||
throw mvFailure;
|
||||
}
|
||||
throw new InvalidCommandArgument(failure.getLocalizedMessage() + " "
|
||||
+ Option.of(failure.getCause()).map(Throwable::getLocalizedMessage).getOrElse(""));
|
||||
}))
|
||||
.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."));
|
||||
.inputOnlyFailMessage((context, input) -> {
|
||||
if (PlayerFinder.isSelector(input)) {
|
||||
return Message.of("No player(s) matched selector: " + input + ".");
|
||||
}
|
||||
return Message.of("Player(s) " + input + " not found.");
|
||||
});
|
||||
}
|
||||
|
||||
private PlayerLocation parsePlayerLocation(BukkitCommandExecutionContext context) {
|
||||
|
||||
@@ -112,9 +112,9 @@ public class MVCommandIssuer extends BukkitCommandIssuer {
|
||||
} else {
|
||||
var formatter = getManager().getFormat(messageType);
|
||||
if (formatter != null) {
|
||||
sendMessage(formatter.format(message.formatted()));
|
||||
sendMessage(formatter.format(message.formatted(getManager().getLocales(), this)));
|
||||
} else {
|
||||
sendMessage(message.formatted());
|
||||
sendMessage(message.formatted(getManager().getLocales(), this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ public class MVRootCommand extends BukkitRootCommand {
|
||||
@Override
|
||||
public List<String> getTabCompletions(CommandIssuer sender, String alias, String[] args, boolean commandsOnly, boolean isAsync) {
|
||||
String[] quoteFormatedArgs = StringFormatter.parseQuotesInArgs(args).toArray(String[]::new);
|
||||
return super.getTabCompletions(sender, alias, quoteFormatedArgs, commandsOnly, isAsync);
|
||||
return super.getTabCompletions(sender, alias, quoteFormatedArgs, commandsOnly, isAsync)
|
||||
.stream()
|
||||
.map(StringFormatter::quoteMultiWordString)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
+47
-70
@@ -123,48 +123,8 @@ public final class IssuerAwareContextBuilder<T> {
|
||||
*/
|
||||
@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));
|
||||
};
|
||||
validateRequiredVariables();
|
||||
return context -> resolveValue(context, GenericIssuerAwareValue::new).value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,49 +138,66 @@ public final class IssuerAwareContextBuilder<T> {
|
||||
*/
|
||||
@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
|
||||
validateRequiredVariables();
|
||||
return context -> resolveValue(context, createValue);
|
||||
}
|
||||
|
||||
private void validateRequiredVariables() {
|
||||
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", "");
|
||||
private <I extends IssuerAwareValue> I resolveValue(BukkitCommandExecutionContext context, BiFunction<Boolean, T, I> createValue) {
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
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));
|
||||
int maxArgForAware = context.getFlagValue("maxArgForAware", Integer.MAX_VALUE);
|
||||
long argLengthWithoutFlags = context.getArgs().stream()
|
||||
.takeWhile(value -> !value.startsWith("-") && !value.isEmpty()) // ignore the flags
|
||||
.count();
|
||||
|
||||
if (resolve.equals("issuerAware") && argLengthWithoutFlags <= maxArgForAware) {
|
||||
if (issuer.isPlayer()) {
|
||||
Player player = issuer.getPlayer();
|
||||
result = fromPlayer.apply(context, player);
|
||||
if (result != null) {
|
||||
return createValue.apply(true, result);
|
||||
}
|
||||
throw new InvalidCommandArgument(issuerAwareInputFailMessage.apply(context, input).formatted(issuer));
|
||||
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));
|
||||
};
|
||||
throw new InvalidCommandArgument(inputOnlyFailMessage.apply(context, input).formatted(issuer));
|
||||
}
|
||||
|
||||
private static class GenericIssuerAwareValue<T> extends IssuerAwareValue {
|
||||
private final T value;
|
||||
|
||||
public GenericIssuerAwareValue(boolean byIssuer, T value) {
|
||||
super(byIssuer);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions;
|
||||
|
||||
@@ -48,7 +48,7 @@ class CloneCommand extends CoreCommand {
|
||||
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.clone.world.description}")
|
||||
LoadedMultiverseWorld world,
|
||||
MultiverseWorld world,
|
||||
|
||||
@Syntax("<new-world-name>")
|
||||
@Description("{@@mv-core.clone.newWorld.description}")
|
||||
@@ -114,7 +114,7 @@ class CloneCommand extends CoreCommand {
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvcl|mvclone")
|
||||
void onCloneCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String newWorldName, String[] flags) {
|
||||
void onCloneCommand(MVCommandIssuer issuer, MultiverseWorld world, String newWorldName, String[] flags) {
|
||||
super.onCloneCommand(issuer, world, newWorldName, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,12 +67,10 @@ class ConfigCommand extends CoreCommand {
|
||||
private void updateConfigValue(MVCommandIssuer issuer, String name, String 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(stringPropertyHandle.getProperty(name).getOrNull()));
|
||||
})
|
||||
.andThenTry(config::save)
|
||||
.onSuccess(ignore -> issuer.sendMessage(MVCorei18n.CONFIG_SET_SUCCESS,
|
||||
Replace.NAME.with(name),
|
||||
Replace.VALUE.with(stringPropertyHandle.getProperty(name).getOrNull())))
|
||||
.onFailure(e -> issuer.sendMessage(MVCorei18n.CONFIG_SET_ERROR,
|
||||
Replace.NAME.with(name),
|
||||
Replace.VALUE.with(value),
|
||||
|
||||
@@ -19,7 +19,6 @@ 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.CommandValueFlag;
|
||||
@@ -27,6 +26,7 @@ import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt.Failure;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
@@ -53,7 +53,7 @@ class CreateCommand extends CoreCommand {
|
||||
@CommandPermission("multiverse.core.create")
|
||||
@CommandCompletion("@empty @environments @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
|
||||
+ "--no-structures --biome <biome>]")
|
||||
+ "--no-structures --biome <biome> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.description}")
|
||||
void onCreateCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -68,7 +68,7 @@ class CreateCommand extends CoreCommand {
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
|
||||
+ "--no-structures --biome <biome>]")
|
||||
+ "--no-structures --biome <biome> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.flags.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -78,14 +78,15 @@ class CreateCommand extends CoreCommand {
|
||||
issuer.sendInfo(MVCorei18n.CREATE_LOADING);
|
||||
|
||||
worldManager.createWorld(CreateWorldOptions.worldName(worldName)
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.seed(parsedFlags.flagValue(flags.seed))
|
||||
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.generator(parsedFlags.flagValue(flags.generator, ""))
|
||||
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
|
||||
.generateStructures(!parsedFlags.hasFlag(flags.noStructures)))
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.seed(parsedFlags.flagValue(flags.seed))
|
||||
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.generator(parsedFlags.flagValue(flags.generator, ""))
|
||||
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
|
||||
.generateStructures(!parsedFlags.hasFlag(flags.noStructures))
|
||||
.worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties))))
|
||||
.onSuccess(newWorld -> messageSuccess(issuer, newWorld))
|
||||
.onFailure(failure -> messageFailure(issuer, failure));
|
||||
}
|
||||
@@ -180,6 +181,10 @@ class CreateCommand extends CoreCommand {
|
||||
.addAlias("-b")
|
||||
.completion(input -> biomeProviderFactory.suggestBiomeString(input))
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<String> properties = flag(CommandValueFlag.builder("--properties", String.class)
|
||||
.addAlias("-p")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -136,6 +137,9 @@ final class DumpsService {
|
||||
}
|
||||
|
||||
private String getPluginList() {
|
||||
return " - " + StringUtils.join(plugin.getServer().getPluginManager().getPlugins(), "\n - ");
|
||||
return " - " + StringUtils.join(Arrays.stream(plugin.getServer().getPluginManager().getPlugins())
|
||||
.map(Object::toString)
|
||||
.sorted()
|
||||
.toList(), "\n - ");
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
void onInfoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@@ -91,7 +91,7 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
void onModifyCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=4")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@@ -113,11 +113,10 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
.getSpawnCategoryConfig(spawnCategory)
|
||||
.getStringPropertyHandle()
|
||||
.modifyPropertyString(property, value, action)
|
||||
.andThenTry(worldManager::saveWorldsConfig)
|
||||
.onSuccess(ignore -> issuer.sendInfo("Successfully set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName()))
|
||||
.onFailure(e -> issuer.sendError("Unable to set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName() + ": " + e.getMessage()));
|
||||
|
||||
worldManager.saveWorldsConfig();
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class GameruleCommand extends CoreCommand {
|
||||
@Description("{@@mv-core.gamerule.set.value.description}")
|
||||
GameRuleValue gameRuleValue,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=2")
|
||||
@Syntax("[World or *]")
|
||||
@Description("{@@mv-core.gamerule.set.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
@@ -114,7 +114,7 @@ class GameruleCommand extends CoreCommand {
|
||||
@Description("{@@mv-core.gamerule.reset.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[World or *]")
|
||||
@Description("{@@mv-core.gamerule.reset.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
@@ -152,7 +152,7 @@ class GameruleCommand extends CoreCommand {
|
||||
void onGameruleListCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.gamerule.list.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@@ -61,8 +61,8 @@ class InfoCommand extends CoreCommand {
|
||||
public void onInfoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("<world>")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.info.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
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;
|
||||
@@ -38,7 +38,7 @@ class LoadCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("load")
|
||||
@CommandPermission("multiverse.core.load")
|
||||
@CommandCompletion("@mvworlds:scope=unloaded")
|
||||
@CommandCompletion("@mvworlds:scope=unloaded @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.load.description}")
|
||||
void onLoadCommand(
|
||||
@@ -49,7 +49,7 @@ class LoadCommand extends CoreCommand {
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players]")
|
||||
@Syntax("[--skip-folder-check]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -67,9 +67,9 @@ class LoadCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
private static final class Flags extends FlagBuilder {
|
||||
|
||||
private static final String NAME = "mvunload";
|
||||
private static final String NAME = "mvload";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
|
||||
@@ -49,12 +49,12 @@ class ModifyCommand extends CoreCommand {
|
||||
"@propsmodifyaction:notByIssuerForArg=arg1|@mvworldpropsname:byIssuerForArg=arg1 " +
|
||||
"@mvworldpropsname:notByIssuerForArg=arg1|@mvworldpropsvalue:byIssuerForArg=arg1 " +
|
||||
"@mvworldpropsvalue:notByIssuerForArg=arg1")
|
||||
@Syntax("[world] <set|add|remove|reset> <property> <value>")
|
||||
@Syntax("[world] <set|add|remove|reset> <property> [value]")
|
||||
@Description("{@@mv-core.modify.description}")
|
||||
void onModifyCommand(// SUPPRESS CHECKSTYLE: ParameterNumber
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=3")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.modify.world.description}")
|
||||
@NotNull MultiverseWorldValue worldValue,
|
||||
@@ -94,28 +94,28 @@ class ModifyCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
StringPropertyHandle worldPropertyHandle = world.getStringPropertyHandle();
|
||||
worldPropertyHandle.modifyPropertyString(issuer.getIssuer(), propertyName, propertyValue, action).onSuccess(ignore -> {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_SUCCESS,
|
||||
worldPropertyHandle.modifyPropertyString(issuer.getIssuer(), propertyName, propertyValue, action)
|
||||
.andThenTry(worldManager::saveWorldsConfig)
|
||||
.onSuccess(ignore -> issuer.sendMessage(MVCorei18n.MODIFY_SUCCESS,
|
||||
replace("{action}").with(action.name().toLowerCase()),
|
||||
replace("{property}").with(propertyName),
|
||||
Replace.VALUE.with(worldPropertyHandle.getProperty(propertyName).getOrNull()),
|
||||
Replace.WORLD.with(world.getName()));
|
||||
worldManager.saveWorldsConfig();
|
||||
}).onFailure(exception -> {
|
||||
if (propertyValue == null) {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_FAILURE_NOVALUE,
|
||||
replace("{action}").with(action.name().toLowerCase()),
|
||||
replace("{property}").with(propertyName),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(exception.getMessage()));
|
||||
} else {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_FAILURE,
|
||||
replace("{action}").with(action.name().toLowerCase()),
|
||||
replace("{property}").with(propertyName),
|
||||
Replace.VALUE.with(propertyValue),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(exception.getMessage()));
|
||||
}
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.onFailure(exception -> {
|
||||
if (propertyValue == null) {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_FAILURE_NOVALUE,
|
||||
replace("{action}").with(action.name().toLowerCase()),
|
||||
replace("{property}").with(propertyName),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(exception.getMessage()));
|
||||
} else {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_FAILURE,
|
||||
replace("{action}").with(action.name().toLowerCase()),
|
||||
replace("{property}").with(propertyName),
|
||||
Replace.VALUE.with(propertyValue),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(exception.getMessage()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ final class PurgeAllEntitiesCommand extends CoreCommand {
|
||||
void onPurgeAllEntitiesCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ final class PurgeEntitiesCommand extends CoreCommand {
|
||||
void onPurgeEntityCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
|
||||
@@ -63,7 +63,7 @@ class SetSpawnCommand extends CoreCommand {
|
||||
|
||||
worldManager.getLoadedWorld(location.getWorld())
|
||||
.peek(mvWorld -> mvWorld.setSpawnLocation(location)
|
||||
.flatMap(ignore -> worldManager.saveWorldsConfig())
|
||||
.andThenTry(worldManager::saveWorldsConfig)
|
||||
.onSuccess(ignore -> issuer.sendMessage(MVCorei18n.SETSPAWN_SUCCESS,
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.LOCATION.with(prettyLocation(location))))
|
||||
|
||||
@@ -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.getFirst().getFailureMessage())));
|
||||
Replace.REASON.with(failure.get(0).getFailureMessage())));
|
||||
}
|
||||
|
||||
private void handleMultiTeleport(MVCommandIssuer issuer, LoadedMultiverseWorld mvWorld,
|
||||
|
||||
@@ -19,7 +19,10 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
|
||||
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.PageFilterFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.UnsafeFlags;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
@@ -35,14 +38,14 @@ final class TeleportCommand extends CoreCommand {
|
||||
private final CoreConfig config;
|
||||
private final CorePermissionsChecker permissionsChecker;
|
||||
private final AsyncSafetyTeleporter safetyTeleporter;
|
||||
private final UnsafeFlags flags;
|
||||
private final Flags flags;
|
||||
|
||||
@Inject
|
||||
TeleportCommand(
|
||||
@NotNull CoreConfig config,
|
||||
@NotNull CorePermissionsChecker permissionsChecker,
|
||||
@NotNull AsyncSafetyTeleporter safetyTeleporter,
|
||||
@NotNull UnsafeFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
this.config = config;
|
||||
this.permissionsChecker = permissionsChecker;
|
||||
@@ -54,14 +57,14 @@ final class TeleportCommand extends CoreCommand {
|
||||
@Subcommand("teleport|tp")
|
||||
@CommandPermission("@mvteleport")
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvteleportother|@destinations:byIssuerForArg=arg1 "
|
||||
+ "@destinations:notByIssuerForArg=arg1|@flags:byIssuerForArg=arg1,groupName=" + UnsafeFlags.NAME + " "
|
||||
+ "@flags:notByIssuerForArg=arg1,groupName=" + UnsafeFlags.NAME)
|
||||
+ "@destinations:notByIssuerForArg=arg1|@flags:byIssuerForArg=arg1,groupName=" + Flags.NAME + " "
|
||||
+ "@flags:notByIssuerForArg=arg1,groupName=" + Flags.NAME)
|
||||
@Syntax("[player] <destination> [--unsafe]")
|
||||
@Description("{@@mv-core.teleport.description}")
|
||||
void onTeleportCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@co.aikar.commands.annotation.Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[player]")
|
||||
@Description("{@@mv-core.teleport.player.description}")
|
||||
PlayerArrayValue playersValue,
|
||||
@@ -103,16 +106,21 @@ final class TeleportCommand extends CoreCommand {
|
||||
.checkSafety(!parsedFlags.hasFlag(flags.unsafe) && destination.checkTeleportSafety())
|
||||
.passengerMode(config.getPassengerMode())
|
||||
.teleportSingle(player)
|
||||
.onSuccess(() -> issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(getYouOrName(issuer, player)),
|
||||
Replace.DESTINATION.with(destination.toString())))
|
||||
.onSuccess(() -> {
|
||||
if (parsedFlags.hasFlag(flags.silent)) {
|
||||
return;
|
||||
}
|
||||
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(getYouOrName(issuer, player)),
|
||||
Replace.DESTINATION.with(destination.getDisplayMessage()));
|
||||
})
|
||||
.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.DESTINATION.with(destination.getDisplayMessage()),
|
||||
Replace.REASON.with(Message.of(entry.getKey())));
|
||||
}
|
||||
});
|
||||
@@ -136,18 +144,38 @@ final class TeleportCommand extends CoreCommand {
|
||||
.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"),
|
||||
Replace.DESTINATION.with(destination.toString())))
|
||||
.onSuccessCount(successCount -> {
|
||||
if (parsedFlags.hasFlag(flags.silent)) {
|
||||
return;
|
||||
}
|
||||
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(successCount + " players"),
|
||||
Replace.DESTINATION.with(destination.getDisplayMessage()));
|
||||
})
|
||||
.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(entry.getValue() + " players"),
|
||||
Replace.DESTINATION.with(destination.toString()),
|
||||
Replace.DESTINATION.with(destination.getDisplayMessage()),
|
||||
Replace.REASON.with(Message.of(entry.getKey())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends UnsafeFlags {
|
||||
|
||||
private static final String NAME = "mvteleport";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag silent = flag(CommandFlag.builder("--silent")
|
||||
.addAlias("-s")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -74,12 +74,12 @@ class WhoCommand extends CoreCommand {
|
||||
@Subcommand("who")
|
||||
@CommandPermission("multiverse.core.list.who")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + PageFilterFlags.NAME)
|
||||
@Syntax("<world> [--page <page>] [--filter <filter>]")
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.who.description}")
|
||||
void onWhoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("<world>")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.who.world.description}")
|
||||
LoadedMultiverseWorld inputtedWorld,
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("[time]")
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -54,7 +54,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("[z]")
|
||||
double z,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -79,7 +79,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<damage>")
|
||||
double damage,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -103,7 +103,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<distance>")
|
||||
double distance,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -124,7 +124,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
void onWorldBorderGet(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -147,7 +147,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("[time]")
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -179,7 +179,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<distance>")
|
||||
int distance,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -203,7 +203,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<time>")
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
|
||||
@@ -8,16 +8,13 @@ import java.util.Locale;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
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;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.ConfirmMode;
|
||||
import org.mvplugins.multiverse.core.config.handle.CommentedConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
@@ -28,7 +25,6 @@ import org.mvplugins.multiverse.core.config.migration.action.InvertBoolMigratorA
|
||||
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;
|
||||
@@ -190,6 +186,16 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.enforceFlight);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setGamemodeAndFlightEnforceDelay(int delayTicks) {
|
||||
return configHandle.set(configNodes.gamemodeAndFlightEnforceDelay, delayTicks);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public int getGamemodeAndFlightEnforceDelay() {
|
||||
return configHandle.get(configNodes.gamemodeAndFlightEnforceDelay);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnRate(boolean applyEntitySpawnRate) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnRate, applyEntitySpawnRate);
|
||||
@@ -497,6 +503,26 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.registerPapiHook);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setWarnInvalidPapiFormat(boolean warnInvalidPapiFormat) {
|
||||
return configHandle.set(configNodes.warnInvalidPapiFormat, warnInvalidPapiFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public boolean getWarnInvalidPapiFormat() {
|
||||
return configHandle.get(configNodes.warnInvalidPapiFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setInvalidPapiFormatReturnsBlank(boolean invalidPapiFormatReturnsBlank) {
|
||||
return configHandle.set(configNodes.invalidPapiFormatReturnsBlank, invalidPapiFormatReturnsBlank);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public boolean getInvalidPapiFormatReturnsBlank() {
|
||||
return configHandle.get(configNodes.invalidPapiFormatReturnsBlank);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -539,6 +565,16 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.resolveAliasName);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public Try<Void> setSimplifiedDestinationTabCompletion(boolean simplifiedDestinationTabCompletion) {
|
||||
return configHandle.set(configNodes.simplifiedDestinationTabCompletion, simplifiedDestinationTabCompletion);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public boolean getSimplifiedDestinationTabCompletion() {
|
||||
return configHandle.get(configNodes.simplifiedDestinationTabCompletion);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -615,6 +651,16 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.eventPriorityPlayerTeleport);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setEventPriorityPlayerWorldChange(EventPriority eventPriorityPlayerWorldChange) {
|
||||
return configHandle.set(configNodes.eventPriorityPlayerWorldChange, eventPriorityPlayerWorldChange);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public EventPriority getEventPriorityPlayerWorldChange() {
|
||||
return configHandle.get(configNodes.eventPriorityPlayerWorldChange);
|
||||
}
|
||||
|
||||
public Try<Void> setBukkitYmlPath(String bukkitYmlPath) {
|
||||
return configHandle.set(configNodes.bukkitYmlPath, bukkitYmlPath);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
@@ -134,13 +135,27 @@ final class CoreConfigNodes {
|
||||
.name("enforce-flight")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> gamemodeAndFlightEnforceDelay = node(ConfigNode.builder("world.gamemode-and-flight-enforce-delay", Integer.class)
|
||||
.comment("")
|
||||
.comment("Sets the delay in ticks before Multiverse enforces gamemode and flight ability on world change.")
|
||||
.comment("Increase this value if you are experiencing issues with other plugins overriding gamemode or flight ability.")
|
||||
.comment("Or set to 0 to enforce immediately during world change event.")
|
||||
.defaultValue(1)
|
||||
.name("gamemode-and-flight-enforce-delay")
|
||||
.suggester((sender -> List.of("0", "1", "2", "5", "10")))
|
||||
.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.")
|
||||
.comment("!!!NOTE: This will only apply after a server restart!")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-rate")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'apply-entity-spawn-rate' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnLimit = node(ConfigNode.builder("world.apply-entity-spawn-limit", Boolean.class)
|
||||
@@ -148,8 +163,12 @@ final class CoreConfigNodes {
|
||||
.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.")
|
||||
.comment("!!!NOTE: This will only apply after a server restart!")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-limit")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'apply-entity-spawn-limit' config option will only take" +
|
||||
" full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoPurgeEntities = node(ConfigNode.builder("world.auto-purge-entities", Boolean.class)
|
||||
@@ -201,8 +220,12 @@ final class CoreConfigNodes {
|
||||
|
||||
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("Configures how passengers and vehicles are handled when an entity is teleported. This config is")
|
||||
.comment("only applied to /mvtp, join-destination and first-spawn-override. It will not touch any other")
|
||||
.comment("teleport by other plugins or commands.")
|
||||
.comment("========= Available options: =========")
|
||||
.comment(" default: Server will handle passengers and vehicles. On papermc 1.21.10+, all will be retained by default.")
|
||||
.comment(" On older versions, all will be dismounted if world changes.")
|
||||
.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.")
|
||||
@@ -380,12 +403,29 @@ final class CoreConfigNodes {
|
||||
.name("register-papi-hook")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> warnInvalidPapiFormat = node(ConfigNode.builder("messaging.warn-invalid-papi-format", Boolean.class)
|
||||
.comment("")
|
||||
.comment("This config option defines whether or not Multiverse should warn about invalid PlaceholderAPI formats.")
|
||||
.comment("Only applies if PlaceholderAPI is installed, and only to multiverse placeholders.")
|
||||
.defaultValue(true)
|
||||
.name("warn-invalid-papi-format")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> invalidPapiFormatReturnsBlank = node(ConfigNode.builder("messaging.invalid-papi-format-returns-blank", Boolean.class)
|
||||
.comment("")
|
||||
.comment("By default, invalid formats will return the original placeholder string.")
|
||||
.comment("This config option defines whether or not Multiverse should show invalid PlaceholderAPI formats as blank.")
|
||||
.comment("Only applies if PlaceholderAPI is installed, and only to multiverse placeholders.")
|
||||
.defaultValue(false)
|
||||
.name("invalid-papi-format-returns-blank")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Locale> defaultLocale = node(ConfigNode.builder("messaging.default-locale", Locale.class)
|
||||
.comment("")
|
||||
.comment("This config option defines the default language Multiverse should use.")
|
||||
.defaultValue(Locale.ENGLISH)
|
||||
.name("default-locale")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
commandManager.get().getLocales().setDefaultLocale(newValue))
|
||||
.build());
|
||||
|
||||
@@ -395,9 +435,8 @@ final class CoreConfigNodes {
|
||||
.comment("If the player's language does not have a translation, it will use the default language set above instead.")
|
||||
.defaultValue(true)
|
||||
.name("per-player-locale")
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
commandManager.get().usePerIssuerLocale(newValue);
|
||||
})
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
commandManager.get().usePerIssuerLocale(newValue))
|
||||
.build());
|
||||
|
||||
private final ConfigHeaderNode commandHeader = node(ConfigHeaderNode.builder("command")
|
||||
@@ -413,6 +452,15 @@ final class CoreConfigNodes {
|
||||
.name("resolve-alias-name")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> simplifiedDestinationTabCompletion = node(ConfigNode.builder("command.simplified-destination-tab-completion", Boolean.class)
|
||||
.comment("")
|
||||
.comment("If this is set to true, Multiverse will only suggest simple destination formats in tab completion.")
|
||||
.comment("This means only world names will be suggested without other destination type such as `e:worldname:x,y,z` or `p:playername`.")
|
||||
.comment("Note: This DOES NOT prevent players from using the destinations, as that is controlled by permissions.")
|
||||
.defaultValue(false)
|
||||
.name("simplified-destination-tab-completion")
|
||||
.build());
|
||||
|
||||
final ConfigNode<ConfirmMode> confirmMode = node(ConfigNode.builder("command.confirm-mode", ConfirmMode.class)
|
||||
.comment("")
|
||||
.comment("This config option defines whether `/mv confirm` is needed before running a DANGEROUS action.")
|
||||
@@ -467,8 +515,11 @@ final class CoreConfigNodes {
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerPortalEvent.")
|
||||
.name("event-priority-player-portal")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-portal", newValue))
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-portal' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerRespawn = node(ConfigNode.builder("event-priority.player-respawn", EventPriority.class)
|
||||
@@ -476,16 +527,23 @@ final class CoreConfigNodes {
|
||||
.name("event-priority-player-respawn")
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerRespawnEvent.")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-respawn", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-respawn' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerSpawnLocation = node(ConfigNode.builder("event-priority.player-spawn-location", EventPriority.class)
|
||||
.defaultValue(EventPriority.NORMAL)
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerSpawnLocationEvent.")
|
||||
.name("event-priority-player-spawn-location").onSetValue((oldValue, newValue) ->
|
||||
.name("event-priority-player-spawn-location")
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-spawn-location", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-spawn-location' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerTeleport = node(ConfigNode.builder("event-priority.player-teleport", EventPriority.class)
|
||||
@@ -493,8 +551,23 @@ final class CoreConfigNodes {
|
||||
.name("event-priority-player-teleport")
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerTeleportEvent.")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-teleport", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-teleport' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerWorldChange = node(ConfigNode.builder("event-priority.player-changed-world", EventPriority.class)
|
||||
.defaultValue(EventPriority.NORMAL)
|
||||
.name("event-priority-player-changed-world")
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerChangedWorldEvent.")
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-changed-world", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-changed-world' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
private final ConfigHeaderNode miscHeader = node(ConfigHeaderNode.builder("misc")
|
||||
@@ -507,6 +580,9 @@ final class CoreConfigNodes {
|
||||
.comment("Note: this config option needs a server restart to take effect.")
|
||||
.defaultValue("bukkit.yml")
|
||||
.name("bukkit-yml-path")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'bukkit-yml-path' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<String> serverPropertiesPath = node(ConfigNode.builder("misc.server-properties-path", String.class)
|
||||
@@ -515,6 +591,9 @@ final class CoreConfigNodes {
|
||||
.comment("Note: this config option needs a server restart to take effect.")
|
||||
.defaultValue("server.properties")
|
||||
.name("server-properties-path")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'server-properties-path' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoDetectGeneratorPlugins = node(ConfigNode.builder("misc.auto-detect-generator-plugins", Boolean.class)
|
||||
@@ -541,7 +620,7 @@ final class CoreConfigNodes {
|
||||
.validator(value -> (value < 0 || value > 3)
|
||||
? Try.failure(new MultiverseException("Debug level must be between 0 and 3."))
|
||||
: Try.success(null))
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (newValue != Logging.getDebugLevel()) {
|
||||
Logging.setDebugLevel(newValue);
|
||||
pluginManager.callEvent(new MVDebugModeEvent(newValue));
|
||||
@@ -555,7 +634,7 @@ final class CoreConfigNodes {
|
||||
.comment("This will only work if the above 'global-debug' is set to 1 or more.")
|
||||
.defaultValue(false)
|
||||
.name("debug-permissions")
|
||||
.onSetValue((oldValue, newValue) -> PermissionUtils.setDebugPermissions(newValue))
|
||||
.onLoadAndChange((oldValue, newValue) -> PermissionUtils.setDebugPermissions(newValue))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> silentStart = node(ConfigNode.builder("misc.silent-start", Boolean.class)
|
||||
@@ -563,7 +642,7 @@ final class CoreConfigNodes {
|
||||
.comment("If true, the startup console messages will no longer show.")
|
||||
.defaultValue(false)
|
||||
.name("silent-start")
|
||||
.onSetValue((oldValue, newValue) -> Logging.setShowingConfig(!newValue))
|
||||
.onLoadAndChange((oldValue, newValue) -> Logging.setShowingConfig(!newValue))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> showDonationMessage = node(ConfigNode.builder("misc.show-donation-message", Boolean.class)
|
||||
|
||||
+24
-3
@@ -3,19 +3,22 @@ package org.mvplugins.multiverse.core.config.handle;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.migration.ConfigMigrator;
|
||||
import org.mvplugins.multiverse.core.config.node.ListValueNode;
|
||||
import org.mvplugins.multiverse.core.config.node.Node;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.ValueNode;
|
||||
|
||||
@@ -84,7 +87,8 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
});
|
||||
|
||||
nodeValueMap.forEach((valueNode, value) -> {
|
||||
valueNode.onSetValue(value, value);
|
||||
valueNode.onLoad(value);
|
||||
valueNode.onLoadAndChange(Bukkit.getConsoleSender(), null, value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,10 +160,27 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public <T> Try<Void> set(@NotNull ValueNode<T> node, T value) {
|
||||
return set(Bukkit.getConsoleSender(), node, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a node, if the validator is not null, it will be tested first.
|
||||
*
|
||||
* @param sender The sender who triggered the change.
|
||||
* @param node The node to set the value of.
|
||||
* @param value The value to set.
|
||||
* @param <T> The type of the node value.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public <T> Try<Void> set(@NotNull CommandSender sender, @NotNull ValueNode<T> node, T value) {
|
||||
return node.validate(value).map(ignore -> {
|
||||
T oldValue = get(node);
|
||||
nodeValueMap.put(node, value);
|
||||
node.onSetValue(oldValue, get(node));
|
||||
node.onLoadAndChange(sender, oldValue, value);
|
||||
node.onChange(sender, oldValue, value);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public class StringPropertyHandle {
|
||||
public Try<Void> setPropertyString(@NotNull CommandSender sender, @Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ValueNode.class)
|
||||
.flatMap(node -> node.parseFromString(sender, value)
|
||||
.flatMap(parsedValue -> handle.set(node, parsedValue)));
|
||||
.flatMap(parsedValue -> handle.set(sender, node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,9 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeChangeCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeValueCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeChangeCallback;
|
||||
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;
|
||||
@@ -53,7 +56,9 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
protected @Nullable NodeSerializer<T> serializer;
|
||||
protected @Nullable Function<T, Try<Void>> validator;
|
||||
protected @Nullable BiConsumer<T, T> onSetValue;
|
||||
protected @Nullable NodeValueCallback<T> onLoad;
|
||||
protected @Nullable NodeChangeCallback<T> onLoadAndChange;
|
||||
protected @Nullable NodeChangeCallback<T> onChange;
|
||||
|
||||
protected ConfigNode(
|
||||
@NotNull String path,
|
||||
@@ -66,7 +71,9 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
@Nullable NodeStringParser<T> stringParser,
|
||||
@Nullable NodeSerializer<T> serializer,
|
||||
@Nullable Function<T, Try<Void>> validator,
|
||||
@Nullable BiConsumer<T, T> onSetValue) {
|
||||
@Nullable NodeValueCallback<T> onLoad,
|
||||
@Nullable NodeChangeCallback<T> onLoadAndChange,
|
||||
@Nullable NodeChangeCallback<T> onChange) {
|
||||
super(path, comments);
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
@@ -82,7 +89,9 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
? serializer
|
||||
: DefaultSerializerProvider.getDefaultSerializer(type);
|
||||
this.validator = validator;
|
||||
this.onSetValue = onSetValue;
|
||||
this.onLoad = onLoad;
|
||||
this.onLoadAndChange = onLoadAndChange;
|
||||
this.onChange = onChange;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,9 +195,29 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onSetValue(@Nullable T oldValue, @Nullable T newValue) {
|
||||
if (onSetValue != null) {
|
||||
onSetValue.accept(oldValue, newValue);
|
||||
public void onLoad(@Nullable T value) {
|
||||
if (onLoad != null) {
|
||||
onLoad.run(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoadAndChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue) {
|
||||
if (onLoadAndChange != null) {
|
||||
onLoadAndChange.run(sender, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue) {
|
||||
if (onChange != null) {
|
||||
onChange.run(sender, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +237,9 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
protected @Nullable NodeSerializer<T> serializer;
|
||||
protected @Nullable Function<T, Try<Void>> validator;
|
||||
protected @Nullable BiConsumer<T, T> onSetValue;
|
||||
protected @Nullable NodeValueCallback<T> onLoad;
|
||||
protected @Nullable NodeChangeCallback<T> onLoadAndChange;
|
||||
protected @Nullable NodeChangeCallback<T> onChange;
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
@@ -368,15 +399,83 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is loaded.
|
||||
*
|
||||
* @param onLoad The action to be performed.
|
||||
* @return This builder.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onLoad(@NotNull NodeValueCallback<T> onLoad) {
|
||||
this.onLoad = this.onLoad == null ? onLoad : this.onLoad.then(onLoad);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is loaded and changed.
|
||||
*
|
||||
* @param onLoadAndChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onLoadAndChange(@NotNull NodeChangeCallback<T> onLoadAndChange) {
|
||||
this.onLoadAndChange = this.onLoadAndChange == null ? onLoadAndChange : this.onLoadAndChange.then(onLoadAndChange);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is loaded and changed.
|
||||
*
|
||||
* @param onLoadAndChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onLoadAndChange(@NotNull SenderNodeChangeCallback<T> onLoadAndChange) {
|
||||
return onLoadAndChange((NodeChangeCallback<T>) onLoadAndChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is changed.
|
||||
*
|
||||
* @param onChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onChange(@NotNull NodeChangeCallback<T> onChange) {
|
||||
this.onChange = this.onChange == null ? onChange : this.onChange.then(onChange);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is changed.
|
||||
*
|
||||
* @param onChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onChange(@NotNull SenderNodeChangeCallback<T> onChange) {
|
||||
return onChange((NodeChangeCallback<T>) onChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is set.
|
||||
*
|
||||
* @param onSetValue The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @deprecated Use {@link #onLoadAndChange(NodeChangeCallback)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.4", forRemoval = true)
|
||||
public @NotNull B onSetValue(@NotNull BiConsumer<T, T> onSetValue) {
|
||||
this.onSetValue = this.onSetValue == null ? onSetValue : this.onSetValue.andThen(onSetValue);
|
||||
return self();
|
||||
return onLoadAndChange(onSetValue::accept);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -385,7 +484,8 @@ 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, aliases, defaultValue, suggester, stringParser, serializer, validator, onSetValue);
|
||||
name, type, aliases, defaultValue, suggester, stringParser, serializer, validator,
|
||||
onLoad, onLoadAndChange, onChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultStringParserProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeChangeCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeValueCallback;
|
||||
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;
|
||||
@@ -67,7 +69,9 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
@Nullable NodeStringParser<List<I>> stringParser,
|
||||
@Nullable NodeSerializer<List<I>> serializer,
|
||||
@Nullable Function<List<I>, Try<Void>> validator,
|
||||
@Nullable BiConsumer<List<I>, List<I>> onSetValue,
|
||||
@Nullable NodeValueCallback<List<I>> onLoad,
|
||||
@Nullable NodeChangeCallback<List<I>> onLoadAndChange,
|
||||
@Nullable NodeChangeCallback<List<I>> onChange,
|
||||
@NotNull Class<I> itemType,
|
||||
@Nullable NodeSuggester itemSuggester,
|
||||
@Nullable NodeStringParser<I> itemStringParser,
|
||||
@@ -75,7 +79,7 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
@Nullable Function<I, Try<Void>> itemValidator,
|
||||
@Nullable BiConsumer<I, I> onSetItemValue) {
|
||||
super(path, comments, name, type, aliases, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
validator, onSetValue);
|
||||
validator, onLoad, onLoadAndChange, onChange);
|
||||
this.itemType = itemType;
|
||||
this.itemSuggester = itemSuggester != null
|
||||
? itemSuggester
|
||||
@@ -105,8 +109,8 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
if (this.itemSerializer != null && this.serializer == null) {
|
||||
setDefaultSerialiser();
|
||||
}
|
||||
if (this.onSetItemValue != null && this.onSetValue == null) {
|
||||
setDefaultOnSetValue();
|
||||
if (this.onSetItemValue != null && this.onLoadAndChange == null) {
|
||||
setDefaultOnLoadAndChange();
|
||||
}
|
||||
if (this.defaultValue == null) {
|
||||
this.defaultValue = ArrayList::new;
|
||||
@@ -116,9 +120,9 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
private void setDefaultSuggester() {
|
||||
if (itemSuggester instanceof SenderNodeSuggester senderItemSuggester) {
|
||||
this.suggester = (SenderNodeSuggester)(sender, input) ->
|
||||
StringFormatter.addonToCommaSeperated(input, senderItemSuggester.suggest(sender, input));
|
||||
StringFormatter.addOnToCommaSeparated(input, senderItemSuggester.suggest(sender, input));
|
||||
} else {
|
||||
this.suggester = input -> StringFormatter.addonToCommaSeperated(input, itemSuggester.suggest(input));
|
||||
this.suggester = input -> StringFormatter.addOnToCommaSeparated(input, itemSuggester.suggest(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +172,8 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
};
|
||||
}
|
||||
|
||||
private void setDefaultOnSetValue() {
|
||||
this.onSetValue = (oldValue, newValue) -> {
|
||||
private void setDefaultOnLoadAndChange() {
|
||||
this.onLoadAndChange = (oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
oldValue.stream()
|
||||
.filter(value -> !newValue.contains(value))
|
||||
@@ -381,7 +385,9 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
stringParser,
|
||||
serializer,
|
||||
validator,
|
||||
onSetValue,
|
||||
onLoad,
|
||||
onLoadAndChange,
|
||||
onChange,
|
||||
itemType,
|
||||
itemSuggester,
|
||||
itemStringParser,
|
||||
|
||||
@@ -5,11 +5,13 @@ import java.util.Collection;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
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.mvplugins.multiverse.core.config.handle.BaseConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
|
||||
public interface ValueNode<T> extends Node {
|
||||
@@ -108,11 +110,51 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
Try<Void> validate(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is loaded by {@link BaseConfigurationHandle#load()}.
|
||||
*
|
||||
* @param value The loaded value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void onLoad(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is loaded or changed.
|
||||
*
|
||||
* @param sender The sender who triggered the change. If triggered by loading or no target sender is specified,
|
||||
* it will be the console sender.
|
||||
* @param oldValue The old value, will always be null on load.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void onLoadAndChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is changed by {@link BaseConfigurationHandle#set(ValueNode, Object)}.
|
||||
*
|
||||
* @param sender The sender who changed the value, or console sender if no target sender specified.
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void onChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is set.
|
||||
*
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @deprecated Use {@link #onLoadAndChange(CommandSender, Object, Object)} instead.
|
||||
*/
|
||||
void onSetValue(@Nullable T oldValue, @Nullable T newValue);
|
||||
@Deprecated(since = "5.4", forRemoval = true)
|
||||
default void onSetValue(@Nullable T oldValue, @Nullable T newValue) {
|
||||
onLoadAndChange(Bukkit.getConsoleSender(), oldValue, newValue);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A callback interface for handling changes to configuration node values.
|
||||
*
|
||||
* @param <T> The type of the node value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@FunctionalInterface
|
||||
public interface NodeChangeCallback<T> {
|
||||
/**
|
||||
* Called when the value of a node changes.
|
||||
*
|
||||
* @param oldValue The old value of the node.
|
||||
* @param newValue The new value of the node.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void run(T oldValue, T newValue);
|
||||
|
||||
/**
|
||||
* Called when the value of a node changes, with the sender of the change.
|
||||
*
|
||||
* @param sender The sender of the change.
|
||||
* @param oldValue The old value of the node.
|
||||
* @param newValue The new value of the node.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
default void run(CommandSender sender, T oldValue, T newValue) {
|
||||
run(oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chains another {@link NodeChangeCallback} to be executed after this one.
|
||||
*
|
||||
* @param after The callback to execute after this one.
|
||||
* @return A new {@link NodeChangeCallback} that executes both callbacks in order.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
default NodeChangeCallback<T> then(NodeChangeCallback<T> after) {
|
||||
return new SenderNodeChangeCallback<T>() {
|
||||
@Override
|
||||
public void run(CommandSender sender, T oldValue, T newValue) {
|
||||
NodeChangeCallback.this.run(sender, oldValue, newValue);
|
||||
after.run(sender, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(T oldValue, T newValue) {
|
||||
NodeChangeCallback.this.run(oldValue, newValue);
|
||||
after.run(oldValue, newValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Handler called when an action is performed on a node such as loading.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@FunctionalInterface
|
||||
public interface NodeValueCallback<T> {
|
||||
/**
|
||||
* Called when an action is performed on a node.
|
||||
*
|
||||
* @param value The value of the node.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void run(T value);
|
||||
|
||||
/**
|
||||
* Chains another {@link NodeValueCallback} to be executed after this one.
|
||||
*
|
||||
* @param after The callback to execute after this one.
|
||||
* @return A new {@link NodeValueCallback} that executes both callbacks in order.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
default NodeValueCallback<T> then(NodeValueCallback<T> after) {
|
||||
return (T value) -> {
|
||||
this.run(value);
|
||||
after.run(value);
|
||||
};
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A callback interface for handling changes to configuration node values with contextual information from the sender.
|
||||
*
|
||||
* @param <T> The type of the node value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@FunctionalInterface
|
||||
public interface SenderNodeChangeCallback<T> extends NodeChangeCallback<T> {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void run(CommandSender sender, T oldValue, T newValue);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@Override
|
||||
default void run(T oldValue, T newValue) {
|
||||
run(Bukkit.getConsoleSender(), oldValue, newValue);
|
||||
}
|
||||
}
|
||||
+3
@@ -11,8 +11,11 @@ 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.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@FunctionalInterface
|
||||
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.
|
||||
|
||||
@@ -4,8 +4,9 @@ import io.vavr.control.Option;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
|
||||
/**
|
||||
@@ -79,6 +80,20 @@ public abstract class DestinationInstance<I extends DestinationInstance<I, T>,
|
||||
*/
|
||||
public abstract @NotNull Option<String> getFinerPermissionSuffix();
|
||||
|
||||
/**
|
||||
* Gets a user-friendly display text for this destination instance. This is used when displaying the destination
|
||||
* to the player. By default, this returns the same as {@link #toString()}. Override this method to provide a more
|
||||
* user-friendly display text with colors, formatting and localization support.
|
||||
*
|
||||
* @return The display message.
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@NotNull
|
||||
public Message getDisplayMessage() {
|
||||
return Message.of(this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialises the destination instance to a savable string.
|
||||
*
|
||||
|
||||
+10
@@ -7,6 +7,7 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link AnchorDestination}.
|
||||
@@ -63,6 +64,15 @@ public final class AnchorDestinationInstance extends DestinationInstance<AnchorD
|
||||
return Option.of(anchorName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
//TODO Localize
|
||||
return Message.of("anchor '" + anchorName + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -20,8 +20,7 @@ import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker;
|
||||
|
||||
/**
|
||||
* {@link Destination} implementation for beds.
|
||||
@@ -30,7 +29,11 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.re
|
||||
public final class BedDestination implements Destination<BedDestination, BedDestinationInstance, BedDestination.InstanceFailureReason> {
|
||||
static final String OWN_BED_STRING = "playerbed";
|
||||
|
||||
BedDestination() {
|
||||
private final ConcurrentPlayerWorldTracker worldTracker;
|
||||
|
||||
@Inject
|
||||
BedDestination(@NotNull ConcurrentPlayerWorldTracker worldTracker) {
|
||||
this.worldTracker = worldTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +65,8 @@ public final class BedDestination implements Destination<BedDestination, BedDest
|
||||
@Override
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(
|
||||
@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
List<DestinationSuggestionPacket> collect = Bukkit.getOnlinePlayers().stream()
|
||||
.map(player -> new DestinationSuggestionPacket(this, player.getName(), player.getName()))
|
||||
List<DestinationSuggestionPacket> collect = worldTracker.getOnlinePlayers().stream()
|
||||
.map(player -> new DestinationSuggestionPacket(this, player, player))
|
||||
.collect(Collectors.toList());
|
||||
if (sender instanceof Player) {
|
||||
collect.add(new DestinationSuggestionPacket(this, OWN_BED_STRING, OWN_BED_STRING));
|
||||
|
||||
+11
-1
@@ -9,12 +9,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link BedDestination}.
|
||||
*/
|
||||
public final class BedDestinationInstance extends DestinationInstance<BedDestinationInstance, BedDestination> {
|
||||
private final Player player;
|
||||
private final @Nullable Player player;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -64,6 +65,15 @@ public final class BedDestinationInstance extends DestinationInstance<BedDestina
|
||||
return Option.of(player != null ? player.getName() : BedDestination.OWN_BED_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
//TODO Localize
|
||||
return Message.of(player == null ? "your bed/respawn point" : player.getName() + "'s bed/respawn point");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -23,14 +23,11 @@ import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseExce
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
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;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.*;
|
||||
|
||||
@@ -68,6 +65,7 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
public @NotNull ExactDestinationInstance fromLocation(@NotNull Location location) {
|
||||
return new ExactDestinationInstance(
|
||||
this,
|
||||
worldManager,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
);
|
||||
@@ -88,6 +86,7 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
.map(location -> Attempt.<ExactDestinationInstance, InstanceFailureReason>success(
|
||||
new ExactDestinationInstance(
|
||||
this,
|
||||
worldManager,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
)
|
||||
@@ -112,7 +111,7 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
|
||||
return Attempt.success(new ExactDestinationInstance(this, worldName, position));
|
||||
return Attempt.success(new ExactDestinationInstance(this, worldManager, worldName, position));
|
||||
}
|
||||
|
||||
//TODO: Extract to a world finder class
|
||||
|
||||
+17
-2
@@ -9,14 +9,16 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.utils.position.VectorPosition;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link ExactDestination}.
|
||||
*/
|
||||
public final class ExactDestinationInstance extends DestinationInstance<ExactDestinationInstance, ExactDestination> {
|
||||
private final WorldManager worldManager;
|
||||
private final String worldName;
|
||||
private final EntityPosition position;
|
||||
|
||||
@@ -28,9 +30,11 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
* @param position The position in the world.
|
||||
*/
|
||||
ExactDestinationInstance(@NotNull ExactDestination destination,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull String worldName,
|
||||
@NotNull EntityPosition position) {
|
||||
super(destination);
|
||||
this.worldManager = worldManager;
|
||||
this.worldName = worldName;
|
||||
this.position = position;
|
||||
}
|
||||
@@ -73,6 +77,17 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
return Option.of(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
String displayWorldName = worldManager.getWorld(worldName)
|
||||
.map(MultiverseWorld::getAliasOrName)
|
||||
.getOrElse(worldName);
|
||||
return Message.of(displayWorldName + " at " + position.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
+10
-5
@@ -4,7 +4,7 @@ import java.util.Collection;
|
||||
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -14,21 +14,26 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker;
|
||||
|
||||
/**
|
||||
* {@link Destination} implementation for players.s
|
||||
*/
|
||||
@Service
|
||||
public final class PlayerDestination implements Destination<PlayerDestination, PlayerDestinationInstance, PlayerDestination.InstanceFailureReason> {
|
||||
|
||||
private final ConcurrentPlayerWorldTracker playerWorldTracker;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the PlayerDestination.
|
||||
*/
|
||||
PlayerDestination() {
|
||||
@Inject
|
||||
PlayerDestination(@NotNull ConcurrentPlayerWorldTracker playerWorldTracker) {
|
||||
this.playerWorldTracker = playerWorldTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,8 +65,8 @@ public final class PlayerDestination implements Destination<PlayerDestination, P
|
||||
@Override
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(
|
||||
@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return Bukkit.getOnlinePlayers().stream()
|
||||
.map(p -> new DestinationSuggestionPacket(this, p.getName(), p.getName()))
|
||||
return playerWorldTracker.getOnlinePlayers().stream()
|
||||
.map(player -> new DestinationSuggestionPacket(this, player, player))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -9,6 +9,7 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -66,6 +67,14 @@ public final class PlayerDestinationInstance extends DestinationInstance<PlayerD
|
||||
return Option.of(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
return Message.of(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
+9
-4
@@ -8,12 +8,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link WorldDestination}.
|
||||
*/
|
||||
@@ -87,6 +84,14 @@ public final class WorldDestinationInstance extends DestinationInstance<WorldDes
|
||||
return Option.of(world.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
return Message.of(world.getAliasOrName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.mvplugins.multiverse.core.event.world;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Called when a world has been successfully cloned.
|
||||
@@ -10,14 +13,24 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
public final class MVWorldClonedEvent extends MultiverseWorldEvent<LoadedMultiverseWorld> {
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
private final LoadedMultiverseWorld fromWorld;
|
||||
private final MultiverseWorld fromWorld;
|
||||
|
||||
public MVWorldClonedEvent(LoadedMultiverseWorld world, LoadedMultiverseWorld fromWorld) {
|
||||
public MVWorldClonedEvent(LoadedMultiverseWorld world, MultiverseWorld fromWorld) {
|
||||
super(world);
|
||||
this.fromWorld = fromWorld;
|
||||
}
|
||||
|
||||
public LoadedMultiverseWorld getFromWorld() {
|
||||
/**
|
||||
* @deprecated Cloning can be done from unloaded worlds as well. Use {@link #getSourceWorld()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.6")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public @Nullable LoadedMultiverseWorld getFromWorld() {
|
||||
return fromWorld.asLoadedWorld().getOrNull();
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public @NotNull MultiverseWorld getSourceWorld() {
|
||||
return fromWorld;
|
||||
}
|
||||
|
||||
@@ -37,5 +50,4 @@ public final class MVWorldClonedEvent extends MultiverseWorldEvent<LoadedMultive
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.mvplugins.multiverse.core.dynamiclistener.annotations.IgnoreIfCancell
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.SkipIfEventExist;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker;
|
||||
|
||||
/**
|
||||
* Multiverse's Listener for players.
|
||||
@@ -24,16 +25,17 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
final class MVChatListener implements CoreListener {
|
||||
private final CoreConfig config;
|
||||
private final WorldManager worldManager;
|
||||
private final MVPlayerListener playerListener;
|
||||
private final ConcurrentPlayerWorldTracker playerWorldTracker;
|
||||
|
||||
@Inject
|
||||
MVChatListener(
|
||||
CoreConfig config,
|
||||
WorldManager worldManager,
|
||||
MVPlayerListener playerListener) {
|
||||
ConcurrentPlayerWorldTracker playerWorldTracker
|
||||
) {
|
||||
this.config = config;
|
||||
this.worldManager = worldManager;
|
||||
this.playerListener = playerListener;
|
||||
this.playerWorldTracker = playerWorldTracker;
|
||||
}
|
||||
|
||||
@EventClass("io.papermc.paper.event.player.AsyncChatEvent")
|
||||
@@ -94,12 +96,9 @@ final class MVChatListener implements CoreListener {
|
||||
}
|
||||
|
||||
private String getWorldName(Player player) {
|
||||
String world = playerListener.getPlayerWorld().get(player.getName());
|
||||
if (world == null) {
|
||||
world = player.getWorld().getName();
|
||||
playerListener.getPlayerWorld().put(player.getName(), world);
|
||||
}
|
||||
return this.worldManager.getLoadedWorld(world)
|
||||
String worldName = playerWorldTracker.getPlayerWorld(player.getName())
|
||||
.getOrElse(() -> player.getWorld().getName());
|
||||
return this.worldManager.getLoadedWorld(worldName)
|
||||
.map(mvworld -> mvworld.isHidden() ? "" : mvworld.getAliasOrName())
|
||||
.getOrElse("");
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
package org.mvplugins.multiverse.core.listeners;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
@@ -38,6 +36,7 @@ import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventPriorityKey;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.IgnoreIfCancelled;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
@@ -74,8 +73,6 @@ final class MVPlayerListener implements CoreListener {
|
||||
private final CorePermissionsChecker corePermissionsChecker;
|
||||
private final AsyncSafetyTeleporter asyncSafetyTeleporter;
|
||||
|
||||
private final Map<String, String> playerWorld = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
MVPlayerListener(
|
||||
MultiverseCore plugin,
|
||||
@@ -120,15 +117,6 @@ final class MVPlayerListener implements CoreListener {
|
||||
return getCommandManager().getLocales();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map of player and the world name they are in.
|
||||
*
|
||||
* @return the playerWorld-map
|
||||
*/
|
||||
Map<String, String> getPlayerWorld() {
|
||||
return playerWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a player respawns.
|
||||
*
|
||||
@@ -256,11 +244,11 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
private void teleportToDestinationOnJoin(Player player, DestinationInstance<?, ?> destination) {
|
||||
asyncSafetyTeleporter.to(destination).teleportSingle(player)
|
||||
asyncSafetyTeleporter.to(destination).passengerMode(config.getPassengerMode()).teleportSingle(player)
|
||||
.onSuccess(result -> Logging.fine("Player %s has been teleported on join",
|
||||
player.getName()))
|
||||
.onFailure(failure -> Logging.warning("Failed to teleport player %s on join: %s",
|
||||
player.getName(), failure.getFirst()));
|
||||
player.getName(), failure.get(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,11 +256,10 @@ final class MVPlayerListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
@DefaultEventPriority(EventPriority.MONITOR)
|
||||
@EventPriorityKey("mvcore-player-changed-world")
|
||||
@DefaultEventPriority(EventPriority.NORMAL)
|
||||
void playerChangedWorld(PlayerChangedWorldEvent event) {
|
||||
// Permissions now determine whether or not to handle a gamemode.
|
||||
this.handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld());
|
||||
playerWorld.put(event.getPlayer().getName(), event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,12 +270,28 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventPriorityKey("mvcore-player-teleport")
|
||||
@DefaultEventPriority(EventPriority.HIGHEST)
|
||||
void playerTeleport(PlayerTeleportEvent event) {
|
||||
Logging.finer("Got teleport event for player '"
|
||||
+ event.getPlayer().getName() + "' with cause '" + event.getCause() + "'");
|
||||
Player teleportee = event.getPlayer();
|
||||
|
||||
if (event.isCancelled()) {
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' was already cancelled.");
|
||||
return;
|
||||
}
|
||||
Player teleportee = event.getPlayer();
|
||||
|
||||
Logging.finer("Got teleport event for player '"
|
||||
+ teleportee.getName() + "' with cause '" + event.getCause() + "'");
|
||||
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) { // should never be null, but just in case
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' has null from-location or world.");
|
||||
return;
|
||||
}
|
||||
|
||||
Location toLocation = event.getTo();
|
||||
if (toLocation == null || toLocation.getWorld() == null) { // may be null on spigot
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' has null to-location or world.");
|
||||
return;
|
||||
}
|
||||
|
||||
Option<String> teleporterName = teleportQueue.popFromQueue(teleportee.getName());
|
||||
CommandSender teleporter = teleporterName.map(name -> {
|
||||
if (name.equalsIgnoreCase("CONSOLE")) {
|
||||
@@ -309,17 +312,17 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
Logging.finer("Teleporter %s is teleporting %s from %s to %s", teleporter.getName(), teleportee.getName(),
|
||||
event.getFrom(), event.getTo());
|
||||
fromLocation, toLocation);
|
||||
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld()).getOrNull();
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(fromLocation.getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(toLocation.getWorld()).getOrNull();
|
||||
if (toWorld == null) {
|
||||
Logging.fine("Player '" + teleportee.getName() + "' is teleporting to world '"
|
||||
+ event.getTo().getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ toLocation.getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ "actions will be taken by Multiverse-Core.");
|
||||
return;
|
||||
}
|
||||
if (event.getFrom().getWorld().equals(event.getTo().getWorld())) {
|
||||
if (fromLocation.getWorld().equals(toLocation.getWorld())) {
|
||||
// The player is Teleporting to the same world.
|
||||
Logging.finer("Player '" + teleportee.getName() + "' is teleporting to the same world.");
|
||||
this.stateSuccess(teleportee.getName(), toWorld.getName());
|
||||
@@ -353,20 +356,19 @@ final class MVPlayerListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@DefaultEventPriority(EventPriority.LOWEST)
|
||||
@IgnoreIfCancelled
|
||||
void playerPortalCheck(PlayerPortalEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (event.getFrom().getWorld() == null) {
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) {
|
||||
Logging.warning("PlayerPortalEvent's from world is null!");
|
||||
return;
|
||||
}
|
||||
if (event.getFrom().getWorld().getBlockAt(event.getFrom()).getType() == Material.NETHER_PORTAL) {
|
||||
if (fromLocation.getWorld().getBlockAt(fromLocation).getType() == Material.NETHER_PORTAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Player was actually outside of the portal, adjust the from location
|
||||
Location newLocation = blockSafety.findPortalBlockNextTo(event.getFrom());
|
||||
Location newLocation = blockSafety.findPortalBlockNextTo(fromLocation);
|
||||
if (newLocation != null) {
|
||||
event.setFrom(newLocation);
|
||||
}
|
||||
@@ -379,28 +381,33 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-portal")
|
||||
@DefaultEventPriority(EventPriority.HIGH)
|
||||
@IgnoreIfCancelled
|
||||
void playerPortal(PlayerPortalEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) { // should never be null, but just in case
|
||||
Logging.finer("PlayerPortalEvent's from world is null!");
|
||||
return;
|
||||
}
|
||||
if (event.getTo() == null || event.getTo().getWorld() == null) {
|
||||
|
||||
Location toLocation = event.getTo();
|
||||
if (toLocation == null || toLocation.getWorld() == null) { // may be null on spigot
|
||||
Logging.finer("PlayerPortalEvent's to world is null!");
|
||||
return;
|
||||
}
|
||||
if (config.isUsingCustomPortalSearch()) {
|
||||
event.setSearchRadius(config.getCustomPortalSearchRadius());
|
||||
}
|
||||
if (Objects.equals(event.getFrom().getWorld(), event.getTo().getWorld())) {
|
||||
if (Objects.equals(fromLocation.getWorld(), toLocation.getWorld())) {
|
||||
// The player is Portaling to the same world.
|
||||
Logging.finer("Player '" + event.getPlayer().getName() + "' is portaling to the same world.");
|
||||
return;
|
||||
}
|
||||
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld()).getOrNull();
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(fromLocation.getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(toLocation.getWorld()).getOrNull();
|
||||
if (toWorld == null) {
|
||||
Logging.fine("Player '" + event.getPlayer().getName() + "' is portaling to world '"
|
||||
+ event.getTo().getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ toLocation.getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ "actions will be taken by Multiverse-Core.");
|
||||
return;
|
||||
}
|
||||
@@ -414,21 +421,31 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the gamemode for the specified {@link Player}.
|
||||
* Handles the gamemode for the specified {@link Player}. Delays the enforcement if configured to do so
|
||||
* to ensure multiverse has final say over other plugins changing gamemodes.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param world The {@link World} the player is supposed to be in.
|
||||
*/
|
||||
private void handleGameModeAndFlight(final Player player, World world) {
|
||||
// We perform this task one tick later to MAKE SURE that the player actually reaches the
|
||||
// destination world, otherwise we'd be changing the player mode if they havent moved anywhere.
|
||||
this.server.getScheduler().runTaskLater(this.plugin, () -> {
|
||||
if (!player.isOnline() || !player.getWorld().equals(world)) {
|
||||
return;
|
||||
}
|
||||
Logging.finer("Handling gamemode and flight for player %s in world '%s'", player.getName(), world.getName());
|
||||
enforcementHandler.handleFlightEnforcement(player);
|
||||
enforcementHandler.handleGameModeEnforcement(player);
|
||||
}, 1L);
|
||||
if (config.getGamemodeAndFlightEnforceDelay() <= 0) {
|
||||
doGameModeAndFlightEnforcement(player, world);
|
||||
return;
|
||||
}
|
||||
server.getScheduler().runTaskLater(
|
||||
this.plugin,
|
||||
() -> doGameModeAndFlightEnforcement(player, world),
|
||||
config.getGamemodeAndFlightEnforceDelay()
|
||||
);
|
||||
}
|
||||
|
||||
private void doGameModeAndFlightEnforcement(Player player, World world) {
|
||||
if (!player.isOnline() || !player.getWorld().equals(world)) {
|
||||
Logging.finer("Player %s is no longer online or not in the expected world '%s'", player.getName(), world.getName());
|
||||
return;
|
||||
}
|
||||
Logging.finer("Handling gamemode and flight for player %s in world '%s'", player.getName(), world.getName());
|
||||
enforcementHandler.handleFlightEnforcement(player);
|
||||
enforcementHandler.handleGameModeEnforcement(player);
|
||||
}
|
||||
}
|
||||
@@ -292,6 +292,7 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
CLONEWORLD_WORLDEXISTFOLDER,
|
||||
CLONEWORLD_WORLDEXISTUNLOADED,
|
||||
CLONEWORLD_WORLDEXISTLOADED,
|
||||
CLONEWORLD_FROMWORLDFOLDERINVALID,
|
||||
CLONEWORLD_COPYFAILED,
|
||||
|
||||
CREATEWORLD_INVALIDWORLDNAME,
|
||||
|
||||
@@ -27,39 +27,12 @@ public final class LocalizedMessage extends Message implements MessageKeyProvide
|
||||
return messageKeyProvider.getMessageKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String[] getReplacements(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
return toReplacementsArray(locales, commandIssuer, replacements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String formatted(@NotNull CommandIssuer commandIssuer) {
|
||||
return formatted(commandIssuer.getManager().getLocales(), commandIssuer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String formatted(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
Objects.requireNonNull(locales, "locales must not be null");
|
||||
|
||||
String[] parsedReplacements = getReplacements(locales, commandIssuer);
|
||||
if (parsedReplacements.length == 0) {
|
||||
return locales.getMessage(commandIssuer, getMessageKey());
|
||||
}
|
||||
return ACFUtil.replaceStrings(locales.getMessage(commandIssuer, getMessageKey()), parsedReplacements);
|
||||
}
|
||||
|
||||
private static String[] toReplacementsArray(
|
||||
@NotNull Locales locales,
|
||||
@Nullable CommandIssuer commandIssuer,
|
||||
@NotNull MessageReplacement... replacements) {
|
||||
String[] replacementsArray = new String[replacements.length * 2];
|
||||
int i = 0;
|
||||
for (MessageReplacement replacement : replacements) {
|
||||
replacementsArray[i++] = replacement.getKey();
|
||||
replacementsArray[i++] = replacement.getReplacement().fold(
|
||||
str -> str,
|
||||
message -> message.formatted(locales, commandIssuer));
|
||||
}
|
||||
return replacementsArray;
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public sealed class Message permits LocalizedMessage {
|
||||
* @return The replacements
|
||||
*/
|
||||
public @NotNull String[] getReplacements(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
return getReplacements();
|
||||
return toReplacementsArray(locales, commandIssuer, replacements);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +152,7 @@ public sealed class Message permits LocalizedMessage {
|
||||
* @return The formatted, localized message
|
||||
*/
|
||||
public @NotNull String formatted(@NotNull CommandIssuer commandIssuer) {
|
||||
return formatted();
|
||||
return formatted(commandIssuer.getManager().getLocales(), commandIssuer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +166,11 @@ public sealed class Message permits LocalizedMessage {
|
||||
* @return The formatted, localized message
|
||||
*/
|
||||
public @NotNull String formatted(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
return formatted();
|
||||
String[] parsedReplacements = getReplacements(locales, commandIssuer);
|
||||
if (parsedReplacements.length == 0) {
|
||||
return raw();
|
||||
}
|
||||
return ACFUtil.replaceStrings(message, parsedReplacements);
|
||||
}
|
||||
|
||||
private static String[] toReplacementsArray(@NotNull MessageReplacement... replacements) {
|
||||
@@ -178,4 +182,19 @@ public sealed class Message permits LocalizedMessage {
|
||||
}
|
||||
return replacementsArray;
|
||||
}
|
||||
|
||||
private static String[] toReplacementsArray(
|
||||
@NotNull Locales locales,
|
||||
@Nullable CommandIssuer commandIssuer,
|
||||
@NotNull MessageReplacement... replacements) {
|
||||
String[] replacementsArray = new String[replacements.length * 2];
|
||||
int i = 0;
|
||||
for (MessageReplacement replacement : replacements) {
|
||||
replacementsArray[i++] = replacement.getKey();
|
||||
replacementsArray[i++] = replacement.getReplacement().fold(
|
||||
str -> str,
|
||||
message -> message.formatted(locales, commandIssuer));
|
||||
}
|
||||
return replacementsArray;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import static org.mvplugins.multiverse.core.permissions.PermissionUtils.concatPermission;
|
||||
import static org.mvplugins.multiverse.core.permissions.PermissionUtils.registerPermissionWithWildcards;
|
||||
|
||||
@Service
|
||||
public final class CorePermissions {
|
||||
@@ -60,28 +61,34 @@ public final class CorePermissions {
|
||||
|
||||
@PostConstruct
|
||||
void registerBasePermissions() {
|
||||
pluginManager.addPermission(new Permission(JOINLOCATION_BYPASS, PermissionDefault.FALSE));
|
||||
Logging.fine("Successfully registered base permissions");
|
||||
Try.run(() -> {
|
||||
registerPermissionWithWildcards(new Permission(JOINLOCATION_BYPASS, PermissionDefault.FALSE));
|
||||
}).onSuccess(ignore -> {
|
||||
Logging.fine("Successfully registered base permissions");
|
||||
}).onFailure(e -> {
|
||||
Logging.fine("Failed to register base permissions: %s", e.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
public Try<Void> addWorldPermissions(@NotNull MultiverseWorld world) {
|
||||
return Try.run(() -> {
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(WORLD_ACCESS, world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(WORLD_EXEMPT, world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(GAMEMODE_BYPASS, world.getName()), PermissionDefault.FALSE));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(PLAYERLIMIT_BYPASS, world.getName()), PermissionDefault.FALSE));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(SPAWN, world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(SPAWN, "self", world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(SPAWN, "other", world.getName()), PermissionDefault.OP));
|
||||
Logging.fine("Successfully registered permissions for world %s", world.getName());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to register permissions for world %s: %s",
|
||||
world.getName(), e.getMessage()));
|
||||
}
|
||||
|
||||
public Try<Void> removeWorldPermissions(@NotNull MultiverseWorld world) {
|
||||
@@ -90,20 +97,23 @@ public final class CorePermissions {
|
||||
pluginManager.removePermission(concatPermission(WORLD_EXEMPT, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(GAMEMODE_BYPASS, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(PLAYERLIMIT_BYPASS, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(SPAWN, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(SPAWN, "self", world.getName()));
|
||||
pluginManager.removePermission(concatPermission(SPAWN, "other", world.getName()));
|
||||
Logging.fine("Successfully removed permissions for world %s", world.getName());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to remove permissions for world %s: %s",
|
||||
world.getName(), e.getMessage()));
|
||||
}
|
||||
|
||||
public Try<Void> addDestinationPermissions(@NotNull Destination destination) {
|
||||
return Try.run(() -> {
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(TELEPORT, "self", destination.getIdentifier()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(TELEPORT, "other", destination.getIdentifier()), PermissionDefault.OP));
|
||||
Logging.fine("Successfully registered permissions for destination %s", destination.getIdentifier());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to register permissions for destination %s: %s",
|
||||
destination.getIdentifier(), e.getMessage()));
|
||||
}
|
||||
|
||||
public Try<Void> removeDestinationPermissions(@NotNull Destination destination) {
|
||||
@@ -111,6 +121,7 @@ public final class CorePermissions {
|
||||
pluginManager.removePermission(concatPermission(TELEPORT, "self", destination.getIdentifier()));
|
||||
pluginManager.removePermission(concatPermission(TELEPORT, "other", destination.getIdentifier()));
|
||||
Logging.fine("Successfully removed permissions for destination %s", destination.getIdentifier());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to remove permissions for destination %s: %s",
|
||||
destination.getIdentifier(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package org.mvplugins.multiverse.core.permissions;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class PermissionUtils {
|
||||
|
||||
@@ -20,6 +26,37 @@ public final class PermissionUtils {
|
||||
PermissionUtils.debugPermissions = debugPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a permission along with all its wildcard parents.
|
||||
* <br />
|
||||
* For example, registering "mv.bypass.joinlocation" will also register "mv.*" and "mv.bypass.*" as parents.
|
||||
*
|
||||
* @param permission The permission to register.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static void registerPermissionWithWildcards(Permission permission) {
|
||||
Bukkit.getServer().getPluginManager().addPermission(permission);
|
||||
String[] split = permission.getName().split("\\.");
|
||||
StringBuilder prefix = new StringBuilder();
|
||||
// Skip the last element since it's the actual permission
|
||||
Arrays.stream(Arrays.copyOfRange(split, 0, split.length - 1)).forEach(s -> {
|
||||
prefix.append(s).append(".");
|
||||
Permission perm = getOrAddPermission(prefix + "*");
|
||||
permission.addParent(perm, true);
|
||||
});
|
||||
}
|
||||
|
||||
private static Permission getOrAddPermission(String permission) {
|
||||
Permission perm = Bukkit.getServer().getPluginManager().getPermission(permission);
|
||||
if (perm == null) {
|
||||
perm = new Permission(permission, PermissionDefault.FALSE);
|
||||
Bukkit.getServer().getPluginManager().addPermission(perm);
|
||||
}
|
||||
return perm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins permissions with a dot.
|
||||
*
|
||||
|
||||
@@ -132,6 +132,10 @@ public final class BlockSafety {
|
||||
*/
|
||||
public boolean canSpawnAtBlockSafely(@NotNull Block block) {
|
||||
Logging.finest("Checking spawn safety for location: %s, %s, %s", block.getX(), block.getY(), block.getZ());
|
||||
if (!block.getWorld().getWorldBorder().isInside(block.getLocation())) {
|
||||
Logging.finest("Location is outside world border.");
|
||||
return false;
|
||||
}
|
||||
if (isUnsafeSpawnBody(block)) {
|
||||
// Player body will be stuck in solid
|
||||
Logging.finest("Unsafe location for player's body: " + block);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A map with case-insensitive String keys. All keys are stored in lower-case form.
|
||||
*
|
||||
* @param <T> the type of mapped values
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public class CaseInsensitiveStringMap<T> implements Map<String, T> {
|
||||
|
||||
private final Map<String, T> map;
|
||||
|
||||
public CaseInsensitiveStringMap() {
|
||||
map = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return map.containsKey(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(Object key) {
|
||||
return map.get(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T put(String key, T value) {
|
||||
return map.put(normalizeKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(Object key) {
|
||||
return map.remove(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NonNull Map<? extends String, ? extends T> m) {
|
||||
m.forEach((key, value) -> map.put(normalizeKey(key), value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<String> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<T> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<Entry<String, T>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
private String normalizeKey(Object key) {
|
||||
return String.valueOf(key).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -25,6 +26,7 @@ import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
|
||||
@@ -210,16 +212,19 @@ public final class FileUtils {
|
||||
|
||||
private final Path sourceDir;
|
||||
private final Path targetDir;
|
||||
private final List<String> excludeFiles;
|
||||
private final List<Path> excludeFiles;
|
||||
|
||||
private CopyDirFileVisitor(@NotNull Path sourceDir, @NotNull Path targetDir, @NotNull List<String> excludeFiles) {
|
||||
this.sourceDir = sourceDir;
|
||||
this.targetDir = targetDir;
|
||||
this.excludeFiles = excludeFiles;
|
||||
this.excludeFiles = excludeFiles.stream()
|
||||
.map(sourceDir::resolve)
|
||||
.toList();
|
||||
Logging.finest(this.excludeFiles.stream().map(Path::toString).collect(Collectors.joining(", ", "Exclude files: [", "]")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult preVisitDirectory(Path dir, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
public @NotNull FileVisitResult preVisitDirectory(@NonNull Path dir, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
Path newDir = targetDir.resolve(sourceDir.relativize(dir));
|
||||
if (!Files.isDirectory(newDir)) {
|
||||
Files.createDirectory(newDir);
|
||||
@@ -228,10 +233,10 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult visitFile(Path file, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
public @NotNull FileVisitResult visitFile(@NonNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
// Pass files that are set to ignore
|
||||
if (excludeFiles.contains(file.getFileName().toString())) {
|
||||
Logging.finest("Ignoring file: " + file.getFileName());
|
||||
if (excludeFiles.contains(file)) {
|
||||
Logging.finest("Ignoring file: " + file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
// Copy the files
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -76,22 +78,33 @@ public final class PlayerFinder {
|
||||
public static @NotNull List<Player> getMulti(@Nullable String playerIdentifiers,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
List<Player> playerResults = new ArrayList<>();
|
||||
return tryGetMulti(playerIdentifiers, sender)
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple {@link Player} based on many identifiers of name UUID or selector.
|
||||
*
|
||||
* @param playerIdentifiers An identifier of multiple names, UUIDs or selectors, separated by comma.
|
||||
* @param sender Target sender for selector.
|
||||
* @return A list of all the {@link Player} found.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static @NotNull Try<@NotNull List<Player>> tryGetMulti(@Nullable String playerIdentifiers,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
if (playerIdentifiers == null || Strings.isNullOrEmpty(playerIdentifiers)) {
|
||||
return playerResults;
|
||||
return Try.success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// TODO: Currently just assume entire string is a selector. Add support for comma seperated mixture of names, uuids and selectors
|
||||
if (isSelector(playerIdentifiers)) {
|
||||
Logging.finer("Using selector: %s", playerIdentifiers);
|
||||
List<Player> targetPlayers = getMultiBySelector(playerIdentifiers, sender);
|
||||
if (targetPlayers != null) {
|
||||
playerResults.addAll(targetPlayers);
|
||||
}
|
||||
return playerResults;
|
||||
return tryGetMultiBySelector(playerIdentifiers, sender);
|
||||
}
|
||||
|
||||
List<Player> playerResults = new ArrayList<>();
|
||||
String[] playerIdentifierArray = REPatterns.COMMA.split(playerIdentifiers);
|
||||
for (String playerIdentifier : playerIdentifierArray) {
|
||||
Player targetPlayer = getByName(playerIdentifier);
|
||||
@@ -104,15 +117,25 @@ public final class PlayerFinder {
|
||||
playerResults.add(targetPlayer);
|
||||
continue;
|
||||
}
|
||||
List<Player> targetPlayers = getMultiBySelector(playerIdentifier, sender);
|
||||
if (targetPlayers != null) {
|
||||
playerResults.addAll(targetPlayers);
|
||||
Try<@NotNull List<Player>> selectorParseResult = tryGetMultiBySelector(playerIdentifier, sender);
|
||||
if (selectorParseResult.isFailure()) {
|
||||
return Try.failure(selectorParseResult.getCause());
|
||||
}
|
||||
playerResults.addAll(selectorParseResult.getOrElse(Collections.emptyList()));
|
||||
}
|
||||
return playerResults;
|
||||
return Try.success(playerResults);
|
||||
}
|
||||
|
||||
private static boolean isSelector(@NotNull String playerIdentifier) {
|
||||
/**
|
||||
* Check if the player identifier is a selector.
|
||||
*
|
||||
* @param playerIdentifier An identifier of name, UUID or selector.
|
||||
* @return True if the identifier is a selector, else false.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static boolean isSelector(@NotNull String playerIdentifier) {
|
||||
return VANILLA_SELECTORS.stream().anyMatch(playerIdentifier::startsWith);
|
||||
}
|
||||
|
||||
@@ -167,7 +190,7 @@ public final class PlayerFinder {
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
List<Player> matchedPlayers = getMultiBySelector(playerSelector, sender);
|
||||
if (matchedPlayers == null || matchedPlayers.isEmpty()) {
|
||||
if (matchedPlayers.isEmpty()) {
|
||||
Logging.fine("No player found with selector '%s' for %s.", playerSelector, sender.getName());
|
||||
return null;
|
||||
}
|
||||
@@ -186,22 +209,36 @@ public final class PlayerFinder {
|
||||
* @param sender Target sender for selector.
|
||||
* @return A list of all the {@link Player} found.
|
||||
*/
|
||||
public static @Nullable List<Player> getMultiBySelector(@NotNull String playerSelector,
|
||||
public static @NotNull List<Player> getMultiBySelector(@NotNull String playerSelector,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
return tryGetMultiBySelector(playerSelector, sender)
|
||||
.onFailure(throwable -> Logging.warning(
|
||||
"Error selecting entities with selector '%s' for %s: %s",
|
||||
playerSelector, sender.getName(), throwable.getMessage()
|
||||
))
|
||||
.getOrElse(Collections::emptyList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple {@link Player} based on <a href="https://minecraft.gamepedia.com/Commands#Target_selectors">vanilla selectors</a>.
|
||||
*
|
||||
* @param playerSelector A target selector, usually starts with an '@'.
|
||||
* @param sender Target sender for selector.
|
||||
* @return A list of all the {@link Player} found.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static @NotNull Try<@NotNull List<Player>> tryGetMultiBySelector(@NotNull String playerSelector,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
if (playerSelector.charAt(0) != '@') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Bukkit.selectEntities(sender, playerSelector).stream()
|
||||
.filter(e -> e instanceof Player)
|
||||
.map(e -> ((Player) e))
|
||||
.collect(Collectors.toList());
|
||||
} catch (IllegalArgumentException e) {
|
||||
Logging.warning("An error occurred while parsing selector '%s' for %s. Is it is the correct format?",
|
||||
playerSelector, sender.getName());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
return Try.success(Collections.emptyList());
|
||||
}
|
||||
return Try.of(() -> Bukkit.selectEntities(sender, playerSelector).stream()
|
||||
.filter(e -> e instanceof Player)
|
||||
.map(e -> ((Player) e))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -16,7 +18,17 @@ public class REPatterns {
|
||||
public static final Pattern COMMA = get(",");
|
||||
public static final Pattern DOT = get("\\.");
|
||||
public static final Pattern EQUALS = get("=");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern HYPHEN = get("-");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern NAMESPACE = get("^[a-z0-9_.-]+$");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern NAMESPACE_KEY = get("^[a-z0-9/_.-]+$");
|
||||
public static final Pattern SEMICOLON = get(";");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern SPACE = get(" ");
|
||||
public static final Pattern UNDERSCORE = get("_");
|
||||
public static final Pattern UUID = get("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern YAML_KEY = get("^[a-zA-Z0-9\\[\\]_-]+$");
|
||||
}
|
||||
@@ -2,13 +2,17 @@ package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -30,15 +34,15 @@ public final class StringFormatter {
|
||||
* @param list the list of strings to join. If the list is empty, an empty string is returned.
|
||||
* @return the concatenated string
|
||||
*/
|
||||
public static @NotNull String joinAnd(List<String> list) {
|
||||
public static @NotNull String joinAnd(@Nullable List<String> list) {
|
||||
return join(list, ", ", " and ");
|
||||
}
|
||||
|
||||
public static @NotNull String join(Collection list, String separator) {
|
||||
public static @NotNull String join(@Nullable Collection<?> list, @NotNull String separator) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return list.stream().map(String::valueOf).collect(Collectors.joining(separator)).toString();
|
||||
return list.stream().map(String::valueOf).collect(Collectors.joining(separator));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +54,7 @@ public final class StringFormatter {
|
||||
* @param lastSeparator the separator to use before the last element. For example, " and ".
|
||||
* @return the concatenated string
|
||||
*/
|
||||
public static @NotNull String join(List<String> list, String separator, String lastSeparator) {
|
||||
public static @NotNull String join(@Nullable List<String> list, @NotNull String separator, @NotNull String lastSeparator) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
@@ -71,11 +75,30 @@ public final class StringFormatter {
|
||||
|
||||
/**
|
||||
* Appends a list of suggestions to the end of the input string, separated by commas.
|
||||
*
|
||||
* @param input The current input
|
||||
* @param addons The autocomplete suggestions
|
||||
* @return A collection of suggestions with the next suggestion appended
|
||||
*
|
||||
* @deprecated Method name has a spelling error. Use {@link #addOnToCommaSeparated(String, Collection)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.5")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public static Collection<String> addonToCommaSeperated(@Nullable String input, @NotNull Collection<String> addons) {
|
||||
return addOnToCommaSeparated(input, addons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a list of suggestions to the end of the input string, separated by commas.
|
||||
*
|
||||
* @param input The current input
|
||||
* @param addons The autocomplete suggestions
|
||||
* @return A collection of suggestions with the next suggestion appended
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public static Collection<String> addOnToCommaSeparated(@Nullable String input, @NotNull Collection<String> addons) {
|
||||
if (Strings.isNullOrEmpty(input)) {
|
||||
return addons;
|
||||
}
|
||||
@@ -94,7 +117,7 @@ public final class StringFormatter {
|
||||
* @param args The args to parse
|
||||
* @return The parsed args
|
||||
*/
|
||||
public static Collection<String> parseQuotesInArgs(String[] args) {
|
||||
public static @NotNull Collection<String> parseQuotesInArgs(@NotNull String[] args) {
|
||||
List<String> result = new ArrayList<>(args.length);
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
@@ -139,7 +162,28 @@ public final class StringFormatter {
|
||||
* @param input The string to add quotes to
|
||||
* @return The quoted string
|
||||
*/
|
||||
public static String quoteMultiWordString(String input) {
|
||||
return input.contains(" ") ? "\"" + input + "\"" : input;
|
||||
@Contract("null -> null")
|
||||
public static @Nullable String quoteMultiWordString(@Nullable String input) {
|
||||
return input != null && input.contains(" ") ? "\"" + input + "\"" : input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a CSV string of key=value pairs into a map.
|
||||
* E.g. "key1=value1,key2=value2" -> {key1=value1, key2=value2}
|
||||
*
|
||||
* @param input The CSV string to parse
|
||||
* @return The parsed map
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public static @Unmodifiable Map<String, String> parseCSVMap(@Nullable String input) {
|
||||
if (Strings.isNullOrEmpty(input)) {
|
||||
return Map.of();
|
||||
}
|
||||
return REPatterns.COMMA.splitAsStream(input)
|
||||
.map(s -> REPatterns.EQUALS.split(s, 2))
|
||||
.filter(parts -> parts.length == 2)
|
||||
.collect(Collectors.toUnmodifiableMap(parts -> parts[0], parts -> parts[1]));
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle API changes in {@link Bukkit} class.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public final class BukkitCompatibility {
|
||||
|
||||
private static final Option<Method> GET_LEVEL_DIRECTORY_METHOD;
|
||||
private static final Option<Method> GET_WORLD_NAMESPACED_KEY_METHOD;
|
||||
|
||||
static {
|
||||
GET_LEVEL_DIRECTORY_METHOD = Option.of(ReflectHelper.getMethod(Server.class, "getLevelDirectory"));
|
||||
GET_WORLD_NAMESPACED_KEY_METHOD = Option.of(ReflectHelper.getMethod(Bukkit.class, "getWorld", NamespacedKey.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the server is using the new dimension storage system introduced in PaperMC 26.1.
|
||||
* <br />
|
||||
* This is based of whether getLevelDirectory method exists in the server class,which is the main API change for
|
||||
* the new dimension storage system.
|
||||
*
|
||||
* @return True if the server is using the new dimension storage system, else false.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public static boolean isUsingNewDimensionStorage() {
|
||||
return GET_LEVEL_DIRECTORY_METHOD
|
||||
.flatMap(method -> Option.of(ReflectHelper.invokeMethod(Bukkit.getServer(), method)))
|
||||
.isDefined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder where all the worlds will be store. Before 26.1, all worlds are stored in the root directory
|
||||
* of the server, which can be obtained by {@link Server#getWorldContainer()}.
|
||||
* <br />
|
||||
* After 26.1, PaperMC changed all worlds are stored in the "[level]/dimensions/minecraft" folder under the world
|
||||
* level directory, which needs to be manually parsed.
|
||||
*
|
||||
* @return The location where all the worlds folders should be, depending on server's mc version.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
@NotNull
|
||||
public static Path getWorldFoldersDirectory() {
|
||||
Server server = Bukkit.getServer();
|
||||
return GET_LEVEL_DIRECTORY_METHOD.map(method -> ReflectHelper.invokeMethod(server, method))
|
||||
.filter(Path.class::isInstance)
|
||||
.map(Path.class::cast)
|
||||
.map(path -> path.resolve("dimensions/minecraft"))
|
||||
.getOrElse(() -> server.getWorldContainer().toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the world with the given name or namespaced key (e.g. minecraft:overworld) exists,
|
||||
* and return it if it does.
|
||||
* <br />
|
||||
* Note that some default world names have different namespaced key matched with them.
|
||||
* E.g.: world -> minecraft:overworld, world_nether -> minecraft:the_nether, world_the_end -> minecraft:the_end.
|
||||
*
|
||||
* @param nameOrKey Either a name or namespaced key string representation.
|
||||
* @return The world if it exists
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
@NotNull
|
||||
public static Option<World> getWorldByNameOrKey(@NotNull String nameOrKey) {
|
||||
return Option.of(Bukkit.getWorld(nameOrKey))
|
||||
.orElse(() -> GET_WORLD_NAMESPACED_KEY_METHOD
|
||||
.map(method -> ReflectHelper.invokeMethod(null, method, NamespacedKey.fromString(nameOrKey)))
|
||||
.filter(World.class::isInstance)
|
||||
.map(World.class::cast));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Contains classes to handle compatibility due to differing API methods across different server versions and the
|
||||
* API gaps between PaperMC and Spigot.
|
||||
*/
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
@@ -107,18 +107,14 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
*
|
||||
* @return Whether this attempt is a success.
|
||||
*/
|
||||
default boolean isSuccess() {
|
||||
return this instanceof Success;
|
||||
}
|
||||
boolean isSuccess();
|
||||
|
||||
/**
|
||||
* Returns whether this attempt is a failure.
|
||||
*
|
||||
* @return Whether this attempt is a failure.
|
||||
*/
|
||||
default boolean isFailure() {
|
||||
return this instanceof Failure;
|
||||
}
|
||||
boolean isFailure();
|
||||
|
||||
/**
|
||||
* Converts this {@link Attempt} instance to an equivalent {@link Try} representation. Defaults to a
|
||||
@@ -143,19 +139,25 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
Try<T> toTry(Function<Failure<T, F>, Throwable> throwableFunction);
|
||||
|
||||
/**
|
||||
* Runs the given runnable after this attempt regardless of success or failure.
|
||||
*
|
||||
* @param runnable The runnable.
|
||||
* @return This attempt.
|
||||
*/
|
||||
default Attempt<T, F> thenRun(Runnable runnable) {
|
||||
runnable.run();
|
||||
return this;
|
||||
}
|
||||
|
||||
default Attempt<T, F> thenAccept(Consumer<Either<T, F>> consumer) {
|
||||
if (this instanceof Success) {
|
||||
consumer.accept(Either.left(get()));
|
||||
} else {
|
||||
consumer.accept(Either.right(getFailureReason()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Accepts either the value or the failure reason depending on the result type.
|
||||
* This will run regardless of success or failure.
|
||||
*
|
||||
* @param consumer The consumer with either the value or the failure reason.
|
||||
* @return This attempt.
|
||||
*/
|
||||
Attempt<T, F> thenAccept(Consumer<Either<T, F>> consumer);
|
||||
|
||||
/**
|
||||
* Peeks at the value if this is a success attempt.
|
||||
@@ -163,12 +165,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param consumer The consumer with the value.
|
||||
* @return This attempt.
|
||||
*/
|
||||
default Attempt<T, F> peek(Consumer<T> consumer) {
|
||||
if (this instanceof Success) {
|
||||
consumer.accept(get());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Attempt<T, F> peek(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Maps the value to another value if this is a success attempt.
|
||||
@@ -177,13 +174,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param <U> The type of the new value.
|
||||
* @return The new attempt.
|
||||
*/
|
||||
default <U> Attempt<U, F> map(Function<? super T, ? extends U> mapper) {
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(mapper.apply(get()));
|
||||
} else {
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
<U> Attempt<U, F> map(Function<? super T, ? extends U> mapper);
|
||||
|
||||
/**
|
||||
* Maps the value to another attempt if this is a success attempt.
|
||||
@@ -192,13 +183,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param <U> The type of the new value.
|
||||
* @return The new attempt.
|
||||
*/
|
||||
default <U> Attempt<U, F> map(Supplier<? extends U> mapper) {
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(mapper.get());
|
||||
} else {
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
<U> Attempt<U, F> map(Supplier<? extends U> mapper);
|
||||
|
||||
/**
|
||||
* Maps the value to another attempt with the same fail reason if this is a success attempt.
|
||||
@@ -207,13 +192,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param <U> The type of the new value.
|
||||
* @return The new attempt.
|
||||
*/
|
||||
default <U> Attempt<U, F> mapAttempt(Function<? super T, Attempt<U, F>> mapper) {
|
||||
if (this instanceof Success) {
|
||||
return mapper.apply(get());
|
||||
} else {
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
<U> Attempt<U, F> mapAttempt(Function<? super T, Attempt<U, F>> mapper);
|
||||
|
||||
/**
|
||||
* Maps the value to another attempt with the same fail reason if this is a success attempt.
|
||||
@@ -222,13 +201,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param <U> The type of the new value.
|
||||
* @return The new attempt.
|
||||
*/
|
||||
default <U> Attempt<U, F> mapAttempt(Supplier<Attempt<U, F>> mapper) {
|
||||
if (this instanceof Success) {
|
||||
return mapper.get();
|
||||
} else {
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
<U> Attempt<U, F> mapAttempt(Supplier<Attempt<U, F>> mapper);
|
||||
|
||||
/**
|
||||
* Maps to another attempt with a different fail reason.
|
||||
@@ -237,13 +210,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param <UF> The type of the new fail reason.
|
||||
* @return The new attempt.
|
||||
*/
|
||||
default <UF extends FailureReason> Attempt<T, UF> transform(UF failureReason) {
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(get());
|
||||
} else {
|
||||
return new Failure<>(failureReason, getFailureMessage(), (Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
<UF extends FailureReason> Attempt<T, UF> transform(UF failureReason);
|
||||
|
||||
/**
|
||||
* Maps attempt result to another value.
|
||||
@@ -256,13 +223,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
<U> U transform(Function<T, U> successMapper, Function<F, U> failureMapper);
|
||||
|
||||
/**
|
||||
* Calls either the failure or success function depending on the result type.
|
||||
@@ -272,13 +233,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param <N> The type of the new value.
|
||||
* @return The result of the function.
|
||||
*/
|
||||
default <N> N fold(Function<Failure<T, F>, N> failureMapper, Function<T, N> successMapper) {
|
||||
if (this instanceof Success) {
|
||||
return successMapper.apply(get());
|
||||
} else {
|
||||
return failureMapper.apply((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
<N> N fold(Function<Failure<T, F>, N> failureMapper, Function<T, N> successMapper);
|
||||
|
||||
/**
|
||||
* Calls the given runnable if this is a success attempt.
|
||||
@@ -286,12 +241,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param runnable The runnable.
|
||||
* @return This attempt.
|
||||
*/
|
||||
default Attempt<T, F> onSuccess(Runnable runnable) {
|
||||
if (this instanceof Success) {
|
||||
runnable.run();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Attempt<T, F> onSuccess(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Calls the given consumer if this is a success attempt.
|
||||
@@ -299,12 +249,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param consumer The consumer with the value.
|
||||
* @return This attempt.
|
||||
*/
|
||||
default Attempt<T, F> onSuccess(Consumer<T> consumer) {
|
||||
if (this instanceof Success) {
|
||||
consumer.accept(get());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Attempt<T, F> onSuccess(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Calls the given consumer if this is a failure attempt.
|
||||
@@ -312,12 +257,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param runnable The runnable.
|
||||
* @return This attempt.
|
||||
*/
|
||||
default Attempt<T, F> onFailure(Runnable runnable) {
|
||||
if (this instanceof Failure) {
|
||||
runnable.run();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Attempt<T, F> onFailure(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Calls the given consumer if this is a failure attempt.
|
||||
@@ -325,12 +265,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param consumer The consumer with the failure instance.
|
||||
* @return This attempt.
|
||||
*/
|
||||
default Attempt<T, F> onFailure(Consumer<Failure<T, F>> consumer) {
|
||||
if (this instanceof Failure) {
|
||||
consumer.accept((Failure<T, F>) this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Attempt<T, F> onFailure(Consumer<Failure<T, F>> consumer);
|
||||
|
||||
/**
|
||||
* Calls the given runnable if this is a failure attempt.
|
||||
@@ -338,12 +273,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @param consumer The consumer with the failure reason.
|
||||
* @return This attempt.
|
||||
*/
|
||||
default Attempt<T, F> onFailureReason(Consumer<F> consumer) {
|
||||
if (this instanceof Failure) {
|
||||
consumer.accept(getFailureReason());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Attempt<T, F> onFailureReason(Consumer<F> consumer);
|
||||
|
||||
/**
|
||||
* Represents a successful attempt with a value.
|
||||
@@ -378,6 +308,26 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getFailureReason() {
|
||||
throw new UnsupportedOperationException("No failure reason as attempt is a success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getFailureMessage() {
|
||||
throw new UnsupportedOperationException("No failure message as attempt is a success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFailure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<T> toTry() {
|
||||
return Try.success(value);
|
||||
@@ -389,13 +339,83 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getFailureReason() {
|
||||
throw new UnsupportedOperationException("No failure reason as attempt is a success");
|
||||
public Attempt<T, F> thenAccept(Consumer<Either<T, F>> consumer) {
|
||||
consumer.accept(Either.left(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getFailureMessage() {
|
||||
throw new UnsupportedOperationException("No failure message as attempt is a success");
|
||||
public Attempt<T, F> peek(Consumer<T> consumer) {
|
||||
consumer.accept(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> map(Function<? super T, ? extends U> mapper) {
|
||||
return new Success<>(mapper.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> map(Supplier<? extends U> mapper) {
|
||||
return new Success<>(mapper.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> mapAttempt(Function<? super T, Attempt<U, F>> mapper) {
|
||||
return mapper.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> mapAttempt(Supplier<Attempt<U, F>> mapper) {
|
||||
return mapper.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <UF extends FailureReason> Attempt<T, UF> transform(UF failureReason) {
|
||||
return changeFailureType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> U transform(Function<T, U> successMapper, Function<F, U> failureMapper) {
|
||||
return successMapper.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N> N fold(Function<Failure<T, F>, N> failureMapper, Function<T, N> successMapper) {
|
||||
return successMapper.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onSuccess(Runnable runnable) {
|
||||
runnable.run();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onSuccess(Consumer<T> consumer) {
|
||||
consumer.accept(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onFailure(Runnable runnable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onFailure(Consumer<Failure<T, F>> consumer) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onFailureReason(Consumer<F> consumer) {
|
||||
return this;
|
||||
}
|
||||
|
||||
private <UF extends FailureReason> Attempt<T, UF> changeFailureType() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Attempt<T, UF> mappedSuccess = (Attempt<T, UF>) this;
|
||||
return mappedSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -451,6 +471,26 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
throw exceptionSupplier.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getFailureReason() {
|
||||
return failureReason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getFailureMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFailure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Try<T> toTry() {
|
||||
return Try.failure(new MultiverseException(message));
|
||||
@@ -462,13 +502,83 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getFailureReason() {
|
||||
return failureReason;
|
||||
public Attempt<T, F> thenAccept(Consumer<Either<T, F>> consumer) {
|
||||
consumer.accept(Either.right(failureReason));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getFailureMessage() {
|
||||
return message;
|
||||
public Attempt<T, F> peek(Consumer<T> consumer) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> map(Function<? super T, ? extends U> mapper) {
|
||||
return changeValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> map(Supplier<? extends U> mapper) {
|
||||
return changeValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> mapAttempt(Function<? super T, Attempt<U, F>> mapper) {
|
||||
return changeValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Attempt<U, F> mapAttempt(Supplier<Attempt<U, F>> mapper) {
|
||||
return changeValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <UF extends FailureReason> Attempt<T, UF> transform(UF failureReason) {
|
||||
return new Failure<>(failureReason, getFailureMessage(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> U transform(Function<T, U> successMapper, Function<F, U> failureMapper) {
|
||||
return failureMapper.apply(failureReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N> N fold(Function<Failure<T, F>, N> failureMapper, Function<T, N> successMapper) {
|
||||
return failureMapper.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onSuccess(Runnable runnable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onSuccess(Consumer<T> consumer) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onFailure(Runnable runnable) {
|
||||
runnable.run();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onFailure(Consumer<Failure<T, F>> consumer) {
|
||||
consumer.accept(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> onFailureReason(Consumer<F> consumer) {
|
||||
consumer.accept(failureReason);
|
||||
return this;
|
||||
}
|
||||
|
||||
private <U> Attempt<U, F> changeValueType() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Attempt<U, F> mappedFailure = (Attempt<U, F>) this;
|
||||
return mappedFailure;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.world;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Difficulty;
|
||||
@@ -10,6 +11,7 @@ import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -66,7 +68,8 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this world is loaded.
|
||||
* Gets whether this world is loaded. Note that this may return false even if this is a stored instance of
|
||||
* {@link LoadedMultiverseWorld} as worlds can be unloaded anytime.
|
||||
*
|
||||
* @return True if the world is loaded, else false.
|
||||
*/
|
||||
@@ -74,6 +77,23 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
return worldConfig.isLoadedWorld();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get loaded world instance. Only returns a value if the world is loaded at the time of calling, even
|
||||
* if this is a stored instance of {@link LoadedMultiverseWorld}.
|
||||
* <br/>
|
||||
* You may use {@link #isLoaded()} to check if the world is loaded first.
|
||||
*
|
||||
* @return Loaded world instance wrapped in an Option, or None if the world is not loaded.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Option<LoadedMultiverseWorld> asLoadedWorld() {
|
||||
return Option.of(worldConfig.getMVWorld())
|
||||
.filter(world -> world instanceof LoadedMultiverseWorld)
|
||||
.map(world -> (LoadedMultiverseWorld) world);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the properties handler of this world.
|
||||
*
|
||||
|
||||
@@ -130,7 +130,9 @@ final class WorldConfig {
|
||||
}
|
||||
|
||||
Try<Void> save() {
|
||||
return configHandle.save();
|
||||
return configHandle.save().onFailure(ex ->
|
||||
Logging.warning("Failed to save world config for world '%s'. %s",
|
||||
worldName, ex.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
ConfigurationSection getConfigurationSection() {
|
||||
@@ -379,6 +381,10 @@ final class WorldConfig {
|
||||
configNodes.setWorld(world);
|
||||
}
|
||||
|
||||
MultiverseWorld getMVWorld() {
|
||||
return configNodes.getWorld();
|
||||
}
|
||||
|
||||
boolean isLoadedWorld() {
|
||||
return configNodes.getWorld() instanceof LoadedMultiverseWorld;
|
||||
}
|
||||
@@ -390,7 +396,7 @@ final class WorldConfig {
|
||||
/**
|
||||
* Migrates the entry fee settings. Assumes entry fee is disabled if currency is not set.
|
||||
*/
|
||||
static final class EntryFeeMigrator implements MigratorAction {
|
||||
private static final class EntryFeeMigrator implements MigratorAction {
|
||||
@Override
|
||||
public void migrate(ConfigurationSection config) {
|
||||
String currency = config.getString("entry-fee.currency", "");
|
||||
|
||||
@@ -60,11 +60,15 @@ final class WorldConfigNodes {
|
||||
}
|
||||
|
||||
private <T> ConfigNode<T> node(ConfigNode.Builder<T, ?> nodeBuilder) {
|
||||
nodeBuilder.onSetValue((oldValue, newValue) -> {
|
||||
nodeBuilder.onChange((oldValue, newValue) -> {
|
||||
if (Objects.equals(oldValue, newValue)) return;
|
||||
if (world == null) return;
|
||||
MVWorldPropertyChangedEvent<?> mvWorldPropertyChangeEvent = new MVWorldPropertyChangedEvent<>(
|
||||
world, Option.of(nodeBuilder.name()).getOrElse(nodeBuilder.path()), oldValue, newValue);
|
||||
world,
|
||||
Option.of(nodeBuilder.name()).getOrElse(nodeBuilder.path()),
|
||||
oldValue,
|
||||
newValue
|
||||
);
|
||||
Bukkit.getPluginManager().callEvent(mvWorldPropertyChangeEvent);
|
||||
Logging.finer("MVWorldPropertyChangeEvent fired for world '%s' with name '%s' and value '%s'",
|
||||
world.getName(), nodeBuilder.path(), newValue);
|
||||
@@ -83,7 +87,7 @@ final class WorldConfigNodes {
|
||||
|
||||
final ConfigNode<String> alias = node(ConfigNode.builder("alias", String.class)
|
||||
.defaultValue("")
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (world == null) return;
|
||||
world.updateColourlessAlias();
|
||||
}));
|
||||
@@ -93,14 +97,14 @@ final class WorldConfigNodes {
|
||||
|
||||
final ConfigNode<Boolean> allowFlight = node(ConfigNode.builder("allow-flight", Boolean.class)
|
||||
.defaultValue(false)
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
enforcementHandler.handleAllFlightEnforcement(loadedWorld);
|
||||
}));
|
||||
|
||||
final ConfigNode<Boolean> allowWeather = node(ConfigNode.builder("allow-weather", Boolean.class)
|
||||
.defaultValue(true)
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
loadedWorld.getBukkitWorld().peek(world -> {
|
||||
if (!world.isClearWeather() && !newValue) {
|
||||
@@ -128,7 +132,7 @@ final class WorldConfigNodes {
|
||||
|
||||
final ConfigNode<Difficulty> difficulty = node(ConfigNode.builder("difficulty", Difficulty.class)
|
||||
.defaultValue(Difficulty.NORMAL)
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> bukkitWorld.setDifficulty(newValue));
|
||||
}));
|
||||
@@ -175,7 +179,7 @@ final class WorldConfigNodes {
|
||||
|
||||
final ConfigNode<GameMode> gamemode = node(ConfigNode.builder("gamemode", GameMode.class)
|
||||
.defaultValue(GameMode.SURVIVAL)
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
enforcementHandler.handleAllGameModeEnforcement(loadedWorld);
|
||||
}));
|
||||
@@ -194,7 +198,7 @@ final class WorldConfigNodes {
|
||||
final ConfigNode<Boolean> keepSpawnInMemory = node(ConfigNode
|
||||
.builder("keep-spawn-in-memory", Boolean.class)
|
||||
.defaultValue(true)
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> bukkitWorld.setKeepSpawnInMemory(newValue));
|
||||
}));
|
||||
@@ -208,7 +212,7 @@ final class WorldConfigNodes {
|
||||
|
||||
final ConfigNode<Boolean> pvp = node(ConfigNode.builder("pvp", Boolean.class)
|
||||
.defaultValue(true)
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> bukkitWorld.setPVP(newValue));
|
||||
}));
|
||||
@@ -237,12 +241,12 @@ final class WorldConfigNodes {
|
||||
final ConfigNode<SpawnLocation> spawnLocation = node(ConfigNode.builder("spawn-location", SpawnLocation.class)
|
||||
.defaultValue(NullSpawnLocation.get())
|
||||
.hidden()
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
if (newValue == null || newValue instanceof NullSpawnLocation) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
bukkitWorld.setSpawnLocation(newValue);
|
||||
newValue.setWorld(bukkitWorld);
|
||||
bukkitWorld.setSpawnLocation(newValue);
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -263,7 +267,7 @@ final class WorldConfigNodes {
|
||||
return object.toSection();
|
||||
}
|
||||
})
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
newValue.setWorldRef(world);
|
||||
newValue.applyConfigToWorld();
|
||||
}));
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -16,12 +15,15 @@ import java.util.stream.Collectors;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import com.google.common.base.Strings;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import io.vavr.control.Option;
|
||||
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.WorldBorder;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
@@ -46,8 +48,10 @@ 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.CaseInsensitiveStringMap;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
import org.mvplugins.multiverse.core.utils.ServerProperties;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.utils.FileUtils;
|
||||
@@ -85,12 +89,16 @@ import static org.mvplugins.multiverse.core.world.helpers.DataStore.WorldConfigS
|
||||
@Service // SUPPRESS CHECKSTYLE: ClassFanOutComplexity This is the world manager, it's going to be complex.
|
||||
public final class WorldManager {
|
||||
|
||||
private static final List<String> CLONE_IGNORE_FILES = Arrays.asList("uid.dat", "session.lock");
|
||||
private static final List<String> CLONE_IGNORE_FILES = List.of(
|
||||
"uid.dat", "session.lock", // All pre 26.1 format
|
||||
"data/paper/metadata.dat" // New papermc format for 26.1+
|
||||
);
|
||||
|
||||
private static final DimensionFormat DEFAULT_NETHER_FORMAT = new DimensionFormat("%overworld%_nether");
|
||||
private static final DimensionFormat DEFAULT_END_FORMAT = new DimensionFormat("%overworld%_the_end");
|
||||
|
||||
private final Map<String, MultiverseWorld> worldsMap;
|
||||
private final Map<String, LoadedMultiverseWorld> loadedWorldsMap;
|
||||
private final CaseInsensitiveStringMap<MultiverseWorld> worldsMap;
|
||||
private final CaseInsensitiveStringMap<LoadedMultiverseWorld> loadedWorldsMap;
|
||||
private final List<String> unloadTracker;
|
||||
private final List<String> loadTracker;
|
||||
private final WorldsConfigManager worldsConfigManager;
|
||||
@@ -133,8 +141,8 @@ public final class WorldManager {
|
||||
this.config = config;
|
||||
this.entityPurger = entityPurger;
|
||||
|
||||
this.worldsMap = new HashMap<>();
|
||||
this.loadedWorldsMap = new HashMap<>();
|
||||
this.worldsMap = new CaseInsensitiveStringMap<>();
|
||||
this.loadedWorldsMap = new CaseInsensitiveStringMap<>();
|
||||
this.unloadTracker = new ArrayList<>();
|
||||
this.loadTracker = new ArrayList<>();
|
||||
}
|
||||
@@ -173,12 +181,12 @@ public final class WorldManager {
|
||||
|
||||
private void removeWorldsNotInConfigs(Collection<String> removedWorlds) {
|
||||
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)));
|
||||
.map(world -> removeWorld(RemoveWorldOptions.world(world)))
|
||||
.getOrElse(() -> worldActionResult(RemoveFailureReason.WORLD_NON_EXISTENT, worldName))
|
||||
.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)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,30 +215,29 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private void importExistingBukkitWorld(World bukkitWorld) {
|
||||
if (bukkitWorld == null || isWorld(bukkitWorld.getName())) {
|
||||
return;
|
||||
}
|
||||
importWorld(ImportWorldOptions.worldName(bukkitWorld.getName())
|
||||
.environment(bukkitWorld.getEnvironment())
|
||||
.generator(generatorProvider.getDefaultGeneratorForWorld(bukkitWorld.getName())))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to import world %s: %s", bukkitWorld.getName(), failure))
|
||||
.onSuccess(success ->
|
||||
Logging.fine("Imported existing world %s", bukkitWorld.getName()));
|
||||
Option.of(bukkitWorld)
|
||||
.filter(world -> !isWorld(world.getName()))
|
||||
.map(world -> importWorld(
|
||||
ImportWorldOptions.worldName(world.getName())
|
||||
.environment(world.getEnvironment())
|
||||
.generator(generatorProvider.getDefaultGeneratorForWorld(world.getName())))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to import world %s: %s", world.getName(), failure))
|
||||
.onSuccess(newMVWorld ->
|
||||
Logging.fine("Imported existing world %s", newMVWorld.getName())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all worlds that are set to autoload.
|
||||
*/
|
||||
private void autoLoadWorlds() {
|
||||
getWorlds().forEach(world -> {
|
||||
if (isLoadedWorld(world) || !world.isAutoLoad()) {
|
||||
return;
|
||||
}
|
||||
loadWorld(LoadWorldOptions.world(world))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to load world %s: %s", world.getName(), failure));
|
||||
});
|
||||
getWorlds().stream()
|
||||
.filter(world -> !isLoadedWorld(world) && world.isAutoLoad())
|
||||
.forEach(world -> loadWorld(LoadWorldOptions.world(world))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to autoload world %s: %s", world.getName(), failure))
|
||||
.onSuccess(newMVWorld ->
|
||||
Logging.fine("Autoloaded world %s", newMVWorld.getName())));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,15 +278,21 @@ public final class WorldManager {
|
||||
.mapAttempt(creator -> addGeneratorToCreator(creator, generatorString))
|
||||
.mapAttempt(this::createBukkitWorld)
|
||||
.transform(CreateFailureReason.WORLD_CREATOR_FAILED)
|
||||
.map(bukkitWorld -> {
|
||||
LoadedMultiverseWorld loadedWorld = newLoadedMultiverseWorld(
|
||||
.map(bukkitWorld -> newLoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
generatorString,
|
||||
options.biome(),
|
||||
options.useSpawnAdjust());
|
||||
pluginManager.callEvent(new MVWorldCreatedEvent(loadedWorld));
|
||||
return loadedWorld;
|
||||
});
|
||||
options.useSpawnAdjust()))
|
||||
.peek(loadedWorld -> postCreateWorld(loadedWorld, options));
|
||||
}
|
||||
|
||||
private void postCreateWorld(LoadedMultiverseWorld loadedWorld, CreateWorldOptions options) {
|
||||
options.worldPropertyStrings().forEach((key, value) -> loadedWorld.getStringPropertyHandle()
|
||||
.setPropertyString(key, value)
|
||||
.onFailure(failure -> Logging.warning("Failed to set property '%s' to '%s' for world %s: %s",
|
||||
key, value, loadedWorld.getName(), failure.getMessage())));
|
||||
pluginManager.callEvent(new MVWorldCreatedEvent(loadedWorld));
|
||||
saveWorldsConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,22 +308,26 @@ public final class WorldManager {
|
||||
} 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);
|
||||
return Option.of(Bukkit.getWorld(worldName))
|
||||
.map(bukkitWorld -> doImportBukkitWorld(options, bukkitWorld))
|
||||
.getOrElse(() -> 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);
|
||||
} else if (options.doFolderCheck()) {
|
||||
//todo This is a duplicate of folder check in load world
|
||||
WorldNameChecker.FolderStatus folderStatus = worldNameChecker.checkFolder(options.worldName());
|
||||
if (!folderStatus.isLoadable()) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, options.worldName());
|
||||
}
|
||||
if (folderStatus == WorldNameChecker.FolderStatus.REQUIRES_MIGRATION) {
|
||||
Logging.info("World '%s' will be automatically migrated by PaperMC to the new dimension " +
|
||||
"location. If you face any issue with migration, please contact PaperMC support!",
|
||||
options.worldName());
|
||||
}
|
||||
}
|
||||
return worldActionResult(options);
|
||||
}
|
||||
@@ -325,15 +342,12 @@ public final class WorldManager {
|
||||
.mapAttempt(creator -> addGeneratorToCreator(creator, generatorString))
|
||||
.mapAttempt(this::createBukkitWorld)
|
||||
.transform(ImportFailureReason.WORLD_CREATOR_FAILED)
|
||||
.map(bukkitWorld -> {
|
||||
LoadedMultiverseWorld loadedWorld = newLoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
generatorString,
|
||||
options.biome(),
|
||||
options.useSpawnAdjust());
|
||||
pluginManager.callEvent(new MVWorldImportedEvent(loadedWorld));
|
||||
return loadedWorld;
|
||||
});
|
||||
.map(bukkitWorld -> newLoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
generatorString,
|
||||
options.biome(),
|
||||
options.useSpawnAdjust()))
|
||||
.peek(loadedWorld -> pluginManager.callEvent(new MVWorldImportedEvent(loadedWorld)));
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportBukkitWorld(ImportWorldOptions options, World bukkitWorld) {
|
||||
@@ -379,7 +393,7 @@ public final class WorldManager {
|
||||
// Properties from the bukkit world
|
||||
worldConfig.setDifficulty(world.getDifficulty());
|
||||
worldConfig.setKeepSpawnInMemory(world.getKeepSpawnInMemory());
|
||||
worldConfig.setScale(world.getCoordinateScale());
|
||||
worldConfig.setScale(getCoordinateScale(world));
|
||||
|
||||
worldConfig.save();
|
||||
|
||||
@@ -398,6 +412,17 @@ public final class WorldManager {
|
||||
return loadedWorld;
|
||||
}
|
||||
|
||||
private double getCoordinateScale(World world) {
|
||||
if (PaperLib.isPaper()) {
|
||||
return world.getCoordinateScale();
|
||||
}
|
||||
return switch (world.getEnvironment()) {
|
||||
case NORMAL -> 1;
|
||||
case NETHER -> 8;
|
||||
default -> 1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an existing world in config.
|
||||
*
|
||||
@@ -464,8 +489,16 @@ public final class WorldManager {
|
||||
return doLoadBukkitWorld(bukkitWorld, mvWorld);
|
||||
}
|
||||
|
||||
if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(mvWorld.getName())) {
|
||||
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
|
||||
if (options.doFolderCheck()) {
|
||||
WorldNameChecker.FolderStatus folderStatus = worldNameChecker.checkFolder(mvWorld.getName());
|
||||
if (!folderStatus.isLoadable()) {
|
||||
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
|
||||
}
|
||||
if (folderStatus == WorldNameChecker.FolderStatus.REQUIRES_MIGRATION) {
|
||||
Logging.info("World '%s' will be automatically migrated by PaperMC to the new dimension " +
|
||||
"location. If you face any issue with migration, please contact PaperMC support!",
|
||||
mvWorld.getName());
|
||||
}
|
||||
}
|
||||
|
||||
WorldCreator worldCreator = WorldCreator.name(mvWorld.getName())
|
||||
@@ -689,12 +722,14 @@ public final class WorldManager {
|
||||
|
||||
private Attempt<File, DeleteFailureReason> validateWorldToDelete(
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
File worldFolder = world.getBukkitWorld().map(World::getWorldFolder).getOrNull();
|
||||
if (worldFolder == null || !worldNameChecker.isValidWorldFolder(worldFolder)) {
|
||||
Logging.severe("Failed to get world folder for world: " + world.getName());
|
||||
return worldActionResult(DeleteFailureReason.WORLD_FOLDER_NOT_FOUND, world.getName());
|
||||
}
|
||||
return worldActionResult(worldFolder);
|
||||
return world.getBukkitWorld().map(World::getWorldFolder)
|
||||
.peek(folder -> Logging.finer("World folder for world %s is at: %s", world.getName(), folder.getPath()))
|
||||
.filter(worldNameChecker::isValidWorldFolder)
|
||||
.map(this::<File, DeleteFailureReason>worldActionResult)
|
||||
.getOrElse(() -> {
|
||||
Logging.severe("World folder does not exist for world: " + world.getName());
|
||||
return worldActionResult(DeleteFailureReason.WORLD_FOLDER_NOT_FOUND, world.getName());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -709,18 +744,19 @@ public final class WorldManager {
|
||||
.mapAttempt(validatedOptions -> {
|
||||
ImportWorldOptions importWorldOptions = ImportWorldOptions
|
||||
.worldName(validatedOptions.newWorldName())
|
||||
.biome(validatedOptions.world().getBiome())
|
||||
.environment(validatedOptions.world().getEnvironment())
|
||||
.generator(validatedOptions.world().getGenerator());
|
||||
.biome(validatedOptions.fromWorld().getBiome())
|
||||
.environment(validatedOptions.fromWorld().getEnvironment())
|
||||
.generator(validatedOptions.fromWorld().getGenerator());
|
||||
return importWorld(importWorldOptions).transform(CloneFailureReason.IMPORT_FAILED);
|
||||
})
|
||||
.onSuccess(newWorld -> {
|
||||
cloneWorldTransferData(options, newWorld);
|
||||
if (options.keepWorldConfig()) {
|
||||
newWorld.setSpawnLocation(options.world().getSpawnLocation());
|
||||
// Special case for spawn location as it's considered a hidden config
|
||||
newWorld.setSpawnLocation(options.fromWorld().getSpawnLocation());
|
||||
}
|
||||
saveWorldsConfig();
|
||||
pluginManager.callEvent(new MVWorldClonedEvent(newWorld, options.world()));
|
||||
pluginManager.callEvent(new MVWorldClonedEvent(newWorld, options.fromWorld()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -742,19 +778,24 @@ public final class WorldManager {
|
||||
if (worldNameChecker.hasWorldFolder(newWorldName)) {
|
||||
return worldActionResult(CloneFailureReason.WORLD_EXIST_FOLDER, newWorldName);
|
||||
}
|
||||
if (!worldNameChecker.isValidWorldFolder(options.fromWorld().getName())) {
|
||||
return worldActionResult(CloneFailureReason.FROM_WORLD_FOLDER_INVALID, options.fromWorld().getName());
|
||||
}
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
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(this::saveWorldWithFlush);
|
||||
options.fromWorld().asLoadedWorld().peek(loadedWorld -> {
|
||||
Logging.finer("Saving world before cloning: " + loadedWorld.getName());
|
||||
loadedWorld.getBukkitWorld().peek(this::saveWorldWithFlush);
|
||||
});
|
||||
}
|
||||
File worldFolder = options.world().getBukkitWorld().map(World::getWorldFolder).get();
|
||||
File newWorldFolder = new File(Bukkit.getWorldContainer(), options.newWorldName());
|
||||
File worldFolder = BukkitCompatibility.getWorldFoldersDirectory().resolve(options.fromWorld().getName()).toFile();
|
||||
File newWorldFolder = BukkitCompatibility.getWorldFoldersDirectory().resolve(options.newWorldName()).toFile();
|
||||
return fileUtils.copyFolder(worldFolder, newWorldFolder, CLONE_IGNORE_FILES).fold(
|
||||
exception -> worldActionResult(CloneFailureReason.COPY_FAILED,
|
||||
options.world().getName(), exception),
|
||||
options.fromWorld().getName(), exception),
|
||||
success -> worldActionResult(options));
|
||||
}
|
||||
|
||||
@@ -770,25 +811,30 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private void cloneWorldTransferData(@NotNull CloneWorldOptions options, @NotNull LoadedMultiverseWorld newWorld) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = transferData(options, options.world());
|
||||
dataTransfer.pasteAllTo(newWorld);
|
||||
}
|
||||
|
||||
private DataTransfer<LoadedMultiverseWorld> transferData(
|
||||
@NotNull KeepWorldSettingsOptions options, @NotNull LoadedMultiverseWorld world) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = new DataTransfer<>();
|
||||
|
||||
if (options.keepWorldConfig()) {
|
||||
dataTransfer.addDataStore(new WorldConfigStore(), world);
|
||||
}
|
||||
if (options.keepGameRule()) {
|
||||
dataTransfer.addDataStore(new GameRulesStore(), world);
|
||||
}
|
||||
if (options.keepWorldBorder()) {
|
||||
dataTransfer.addDataStore(new WorldBorderStore(), world);
|
||||
new DataTransfer<MultiverseWorld>()
|
||||
.addDataStore(new WorldConfigStore<>(), options.fromWorld())
|
||||
.pasteAllTo(newWorld);
|
||||
}
|
||||
|
||||
return dataTransfer;
|
||||
newWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
if (!options.keepWorldBorder()) {
|
||||
WorldBorder worldBorder = bukkitWorld.getWorldBorder();
|
||||
worldBorder.reset();
|
||||
}
|
||||
|
||||
if (!options.keepGameRule()) {
|
||||
Arrays.stream(bukkitWorld.getGameRules())
|
||||
.map(gameRuleName -> GameRule.getByName(gameRuleName))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(gameRule -> {
|
||||
Object gameRuleDefault = bukkitWorld.getGameRuleDefault(gameRule);
|
||||
if (gameRuleDefault != null) {
|
||||
bukkitWorld.setGameRule(gameRule, gameRuleDefault);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -799,11 +845,12 @@ public final class WorldManager {
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, RegenFailureReason> regenWorld(@NotNull RegenWorldOptions options) {
|
||||
LoadedMultiverseWorld world = options.world();
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = transferData(options, world);
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = regenWorldTransferData(options, world);
|
||||
boolean shouldKeepSpawnLocation = options.keepWorldConfig() && options.seed() == world.getSeed();
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
|
||||
CreateWorldOptions createWorldOptions = CreateWorldOptions.worldName(world.getName())
|
||||
.biome(world.getBiome())
|
||||
.environment(world.getEnvironment())
|
||||
.generateStructures(world.canGenerateStructures().getOrElse(true))
|
||||
.generator(world.getGenerator())
|
||||
@@ -827,6 +874,23 @@ public final class WorldManager {
|
||||
});
|
||||
}
|
||||
|
||||
private DataTransfer<LoadedMultiverseWorld> regenWorldTransferData(
|
||||
@NotNull KeepWorldSettingsOptions options, @NotNull LoadedMultiverseWorld world) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = new DataTransfer<>();
|
||||
|
||||
if (options.keepWorldConfig()) {
|
||||
dataTransfer.addDataStore(new WorldConfigStore<>(), world);
|
||||
}
|
||||
if (options.keepGameRule()) {
|
||||
dataTransfer.addDataStore(new GameRulesStore(), world);
|
||||
}
|
||||
if (options.keepWorldBorder()) {
|
||||
dataTransfer.addDataStore(new WorldBorderStore(), world);
|
||||
}
|
||||
|
||||
return dataTransfer;
|
||||
}
|
||||
|
||||
private <T, F extends FailureReason> Attempt<T, F> worldActionResult(@NotNull T value) {
|
||||
return Attempt.success(value);
|
||||
}
|
||||
@@ -936,12 +1000,12 @@ public final class WorldManager {
|
||||
* @return A list of all potential worlds.
|
||||
*/
|
||||
public List<String> getPotentialWorlds() {
|
||||
File[] files = Bukkit.getWorldContainer().listFiles();
|
||||
File[] files = BukkitCompatibility.getWorldFoldersDirectory().toFile().listFiles();
|
||||
if (files == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(files)
|
||||
.filter(file -> !isWorld(file.getName()))
|
||||
.filter(file -> BukkitCompatibility.getWorldByNameOrKey(file.getName()).isEmpty())
|
||||
.filter(worldNameChecker::isValidWorldFolder)
|
||||
.map(File::getName)
|
||||
.toList();
|
||||
@@ -966,7 +1030,9 @@ public final class WorldManager {
|
||||
* @return The world if it exists.
|
||||
*/
|
||||
public Option<MultiverseWorld> getWorld(@Nullable String worldName) {
|
||||
return getLoadedWorld(worldName).fold(() -> getUnloadedWorld(worldName), Option::of);
|
||||
return getLoadedWorld(worldName)
|
||||
.map(world -> (MultiverseWorld) world)
|
||||
.orElse(() -> getUnloadedWorld(worldName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -978,7 +1044,8 @@ public final class WorldManager {
|
||||
*/
|
||||
public Option<MultiverseWorld> getWorldByNameOrAlias(@Nullable String worldNameOrAlias) {
|
||||
return getLoadedWorldByNameOrAlias(worldNameOrAlias)
|
||||
.fold(() -> getUnloadedWorldByNameOrAlias(worldNameOrAlias), Option::of);
|
||||
.map(world -> (MultiverseWorld) world)
|
||||
.orElse(() -> getUnloadedWorldByNameOrAlias(worldNameOrAlias));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -993,7 +1060,8 @@ public final class WorldManager {
|
||||
public Collection<MultiverseWorld> getWorlds() {
|
||||
return worldsMap.values().stream()
|
||||
.map(world -> getLoadedWorld(world)
|
||||
.fold(() -> world, loadedWorld -> loadedWorld))
|
||||
.map(loadedWorld -> (MultiverseWorld) loadedWorld)
|
||||
.getOrElse(world))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -1004,7 +1072,7 @@ public final class WorldManager {
|
||||
* @return True if the world is a world is known to multiverse, but may or may not be loaded.
|
||||
*/
|
||||
public boolean isWorld(@Nullable String worldName) {
|
||||
return worldsMap.containsKey(worldName);
|
||||
return worldName != null && worldsMap.containsKey(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1014,7 +1082,9 @@ public final class WorldManager {
|
||||
* @return The world if it exists.
|
||||
*/
|
||||
public Option<MultiverseWorld> getUnloadedWorld(@Nullable String worldName) {
|
||||
return isLoadedWorld(worldName) ? Option.none() : Option.of(worldsMap.get(worldName));
|
||||
return isLoadedWorld(worldName)
|
||||
? Option.none()
|
||||
: Option.of(worldName).flatMap(name -> Option.of(worldsMap.get(name)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1066,7 +1136,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(loadedWorldsMap.get(world.getName()));
|
||||
return Option.of(world).flatMap(notNullWorld -> getLoadedWorld(notNullWorld.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1076,7 +1146,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(loadedWorldsMap.get(world.getName()));
|
||||
return Option.of(world).flatMap(notNullWorld -> getLoadedWorld(notNullWorld.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1086,7 +1156,8 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable String worldName) {
|
||||
return Option.of(loadedWorldsMap.get(worldName));
|
||||
return Option.of(worldName)
|
||||
.flatMap(name -> Option.of(loadedWorldsMap.get(name)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1147,7 +1218,7 @@ public final class WorldManager {
|
||||
* @return True if the world is a multiverse world that is loaded.
|
||||
*/
|
||||
public boolean isLoadedWorld(@Nullable String worldName) {
|
||||
return loadedWorldsMap.containsKey(worldName);
|
||||
return worldName != null && loadedWorldsMap.containsKey(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,8 +38,9 @@ public final class EntitySpawnConfig {
|
||||
}
|
||||
|
||||
public void applyConfigToWorld() {
|
||||
spawnCategoriesConfig.values()
|
||||
.forEach(SpawnCategoryConfig::applyConfigToWorld);
|
||||
for (SpawnCategory category : SpawnCategory.values()) {
|
||||
getSpawnCategoryConfig(category).applyConfigToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,14 +62,18 @@ public final class EntitySpawnConfig {
|
||||
@ApiStatus.Internal
|
||||
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;
|
||||
Map<String, Object> existingCategories = section.getValues(false);
|
||||
for (SpawnCategory category : SpawnCategory.values()) {
|
||||
Object value = existingCategories.get(category.toString().toLowerCase(Locale.ENGLISH));
|
||||
if (!(value instanceof ConfigurationSection)) {
|
||||
if (value != null) {
|
||||
Logging.warning("Invalid spawn category config for " + category + ": " + value);
|
||||
}
|
||||
value = section.createSection(category.toString());
|
||||
}
|
||||
SpawnCategory spawnCategory = SpawnCategory.valueOf(key.toUpperCase(Locale.ENGLISH));
|
||||
spawnCategoriesConfig.put(spawnCategory, new SpawnCategoryConfig(config, spawnCategory, sectionPart));
|
||||
});
|
||||
ConfigurationSection sectionPart = (ConfigurationSection) value;
|
||||
spawnCategoriesConfig.put(category, new SpawnCategoryConfig(config, category, sectionPart));
|
||||
}
|
||||
return new EntitySpawnConfig(config, spawnCategoriesConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.world.entity;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -14,6 +15,7 @@ import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.ListConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.Node;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
@@ -70,18 +72,17 @@ public final class SpawnCategoryConfig {
|
||||
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());
|
||||
if (!isSpawn() && getExceptions().isEmpty()) {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: 0", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, 0);
|
||||
return;
|
||||
}
|
||||
if (getTickRate() == -2) {
|
||||
Logging.finer("World %s %s skipping setTicksPerSpawns as tick-rate is UNSET", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
Logging.finer("World %s %s setTicksPerSpawns: %d", world.getName(), spawnCategory, getTickRate());
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, getTickRate());
|
||||
}
|
||||
|
||||
private void applySpawnLimit(World bukkitWorld) {
|
||||
@@ -89,6 +90,10 @@ public final class SpawnCategoryConfig {
|
||||
Logging.finer("Skipping World %s %s setSpawnLimit due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
if (getSpawnLimit() == -2) {
|
||||
Logging.finer("World %s %s skipping setSpawnLimit as spawn-limit is UNSET", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
Logging.finer("World %s %s setSpawnLimit: %d", world.getName(), spawnCategory, getSpawnLimit());
|
||||
bukkitWorld.setSpawnLimit(spawnCategory, getSpawnLimit());
|
||||
}
|
||||
@@ -161,19 +166,39 @@ public final class SpawnCategoryConfig {
|
||||
|
||||
final ConfigNode<Boolean> spawn = node(ConfigNode.builder("spawn", Boolean.class)
|
||||
.defaultValue(true)
|
||||
.onSetValue((oldValue, newValue) -> applyConfigToWorld())
|
||||
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> tickRate = node(ConfigNode.builder("tick-rate", Integer.class)
|
||||
.defaultValue(-1)
|
||||
.suggester(input -> List.of("-1", "10", "100", "400", "1000"))
|
||||
.onSetValue((oldValue, newValue) -> applyConfigToWorld())
|
||||
.defaultValue(-2)
|
||||
.suggester(input -> List.of("@unset", "@bukkit", "10", "100", "400", "1000"))
|
||||
.serializer(SpawnValueSerializer.INSTANCE)
|
||||
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
|
||||
.onChange((sender, oldValue, newValue) -> {
|
||||
if (!config.getApplyEntitySpawnRate()) {
|
||||
sender.sendMessage(ChatColor.RED + "Warning: Changing tick rates has no effect because " +
|
||||
"'apply-entity-spawn-rate' is disabled in the core config.");
|
||||
} else if (newValue == -2) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Note: Setting tick-rate to '@unset' may not reset to " +
|
||||
"the server default until the world is reloaded or server is restarted.");
|
||||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> spawnLimit = node(ConfigNode.builder("spawn-limit", Integer.class)
|
||||
.defaultValue(-1)
|
||||
.suggester(input -> List.of("-1", "10", "100", "400", "1000"))
|
||||
.onSetValue((oldValue, newValue) -> applyConfigToWorld())
|
||||
.defaultValue(-2)
|
||||
.suggester(input -> List.of("@unset", "@bukkit", "10", "100", "400", "1000"))
|
||||
.serializer(SpawnValueSerializer.INSTANCE)
|
||||
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
|
||||
.onChange((sender, oldValue, newValue) -> {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
sender.sendMessage(ChatColor.RED + "Warning: Changing spawn limits has no effect because " +
|
||||
"'apply-entity-spawn-limit' is disabled in the core config.");
|
||||
} else if (newValue == -2) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Note: Setting spawn-limit to '@unset' may not reset to " +
|
||||
"the server default until the world is reloaded or server is restarted.");
|
||||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
final ListConfigNode<EntityType> exceptions = node(ListConfigNode.listBuilder("exceptions", EntityType.class)
|
||||
@@ -181,7 +206,32 @@ public final class SpawnCategoryConfig {
|
||||
.itemSuggester(input -> SpawnCategoryMapper.getEntityTypes(spawnCategory).stream()
|
||||
.map(EntityType::name)
|
||||
.toList())
|
||||
.onSetValue((oldValue, newValue) -> applyConfigToWorld())
|
||||
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
|
||||
.build());
|
||||
}
|
||||
|
||||
private static class SpawnValueSerializer implements NodeSerializer<Integer> {
|
||||
private static final SpawnValueSerializer INSTANCE = new SpawnValueSerializer();
|
||||
|
||||
@Override
|
||||
public Integer deserialize(Object object, Class<Integer> type) {
|
||||
String str = String.valueOf(object);
|
||||
if (str.equalsIgnoreCase("@unset")) {
|
||||
return -2;
|
||||
}
|
||||
if (str.equalsIgnoreCase("@bukkit")) {
|
||||
return -1;
|
||||
}
|
||||
return Integer.parseInt(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Integer object, Class<Integer> type) {
|
||||
return switch (object) {
|
||||
case -2 -> "@unset";
|
||||
case -1 -> "@bukkit";
|
||||
default -> object;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Tracks which players are in which worlds, using a thread-safe map.
|
||||
* This allows async access to online players list and the world they are in.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@Service
|
||||
public final class ConcurrentPlayerWorldTracker implements Listener {
|
||||
|
||||
private final Map<String, String> playerWorldMap;
|
||||
|
||||
@Inject
|
||||
ConcurrentPlayerWorldTracker(@NotNull MultiverseCore plugin) {
|
||||
this.playerWorldMap = new ConcurrentHashMap<>();
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unmodifiable collection of all online player names on the server.
|
||||
*
|
||||
* @return Unmodifiable collection of online player names.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@NotNull
|
||||
@UnmodifiableView
|
||||
public Collection<String> getOnlinePlayers() {
|
||||
return Collections.unmodifiableCollection(playerWorldMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the world name a player is currently in.
|
||||
*
|
||||
* @param playerName Name of the player.
|
||||
* @return World name the player is in, or null if the player is not online.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@NotNull
|
||||
public Option<String> getPlayerWorld(String playerName) {
|
||||
return Option.of(playerWorldMap.get(playerName));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onPlayerJoin(PlayerJoinEvent event) {
|
||||
setPlayerWorld(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onPlayerChangedWorld(@NotNull PlayerChangedWorldEvent event) {
|
||||
setPlayerWorld(event.getPlayer());
|
||||
}
|
||||
|
||||
private void setPlayerWorld(Player player) {
|
||||
String playerName = player.getName();
|
||||
String worldName = player.getWorld().getName();
|
||||
playerWorldMap.put(playerName, worldName);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
String playerName = event.getPlayer().getName();
|
||||
playerWorldMap.remove(playerName);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.bukkit.World;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* A data store for storing and restoring data from an object.
|
||||
@@ -90,14 +91,14 @@ public interface DataStore<T> {
|
||||
/**
|
||||
* A {@link DataStore} for storing and restoring world properties for a multiverse world.
|
||||
*/
|
||||
class WorldConfigStore implements DataStore<LoadedMultiverseWorld> {
|
||||
class WorldConfigStore<T extends MultiverseWorld> implements DataStore<T> {
|
||||
private Map<String, Object> configMap;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public WorldConfigStore copyFrom(LoadedMultiverseWorld world) {
|
||||
public WorldConfigStore<T> copyFrom(T world) {
|
||||
this.configMap = new HashMap<>();
|
||||
StringPropertyHandle worldPropertyHandler = world.getStringPropertyHandle();
|
||||
worldPropertyHandler.getAllPropertyNames().forEach(name -> worldPropertyHandler.getProperty(name)
|
||||
@@ -111,7 +112,7 @@ public interface DataStore<T> {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public WorldConfigStore pasteTo(LoadedMultiverseWorld world) {
|
||||
public WorldConfigStore<T> pasteTo(T world) {
|
||||
if (configMap == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
|
||||
|
||||
/**
|
||||
* <p>Utility class in helping to check the status of a world name and it's associated world folder.</p>
|
||||
@@ -20,15 +23,27 @@ import org.jvnet.hk2.annotations.Service;
|
||||
@Service
|
||||
public final class WorldNameChecker {
|
||||
|
||||
private static final Pattern WORLD_NAME_PATTERN = Pattern.compile("[a-zA-Z0-9/._-]+");
|
||||
private static final Set<String> BLACKLIST_NAMES = Set.of(
|
||||
"cache",
|
||||
"config",
|
||||
"crash-reports",
|
||||
"libraries",
|
||||
"logs",
|
||||
"plugins",
|
||||
"versions");
|
||||
|
||||
private static final List<WorldFolderSchema> WORLD_FOLDER_SCHEMA = List.of(
|
||||
// OLD
|
||||
WorldFolderSchema.file("level.dat"),
|
||||
WorldFolderSchema.folder("DIM1"),
|
||||
WorldFolderSchema.folder("DIM-1"),
|
||||
// NEW
|
||||
WorldFolderSchema.file("paper-world.yml"),
|
||||
WorldFolderSchema.folder("data"),
|
||||
WorldFolderSchema.folder("entities"),
|
||||
WorldFolderSchema.folder("poi"),
|
||||
WorldFolderSchema.folder("region"));
|
||||
|
||||
/**
|
||||
* Checks if a world name is valid.
|
||||
*
|
||||
@@ -47,18 +62,21 @@ public final class WorldNameChecker {
|
||||
*/
|
||||
@NotNull
|
||||
public NameStatus checkName(@Nullable String worldName) {
|
||||
return Option.of(worldName).map(name -> {
|
||||
if (name.isEmpty()) {
|
||||
return NameStatus.EMPTY;
|
||||
}
|
||||
if (BLACKLIST_NAMES.contains(name)) {
|
||||
return NameStatus.BLACKLISTED;
|
||||
}
|
||||
if (!WORLD_NAME_PATTERN.matcher(name).matches()) {
|
||||
return NameStatus.INVALID_CHARS;
|
||||
}
|
||||
return NameStatus.VALID;
|
||||
}).getOrElse(NameStatus.EMPTY);
|
||||
return Option.of(worldName)
|
||||
.map(name -> name.toLowerCase(Locale.ENGLISH))
|
||||
.map(name -> {
|
||||
if (name.isEmpty()) {
|
||||
return NameStatus.EMPTY;
|
||||
}
|
||||
if (BLACKLIST_NAMES.contains(name)) {
|
||||
return NameStatus.BLACKLISTED;
|
||||
}
|
||||
if (!REPatterns.NAMESPACE_KEY.matcher(name).matches()) {
|
||||
return NameStatus.INVALID_CHARS;
|
||||
}
|
||||
return NameStatus.VALID;
|
||||
})
|
||||
.getOrElse(NameStatus.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +96,7 @@ public final class WorldNameChecker {
|
||||
* @return True if check result is valid, else false.
|
||||
*/
|
||||
public boolean isValidWorldFolder(@Nullable String worldName) {
|
||||
return checkFolder(worldName) == FolderStatus.VALID;
|
||||
return checkFolder(worldName).loadable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +106,7 @@ public final class WorldNameChecker {
|
||||
* @return True if check result is valid, else false.
|
||||
*/
|
||||
public boolean isValidWorldFolder(@Nullable File worldFolder) {
|
||||
return checkFolder(worldFolder) == FolderStatus.VALID;
|
||||
return checkFolder(worldFolder).loadable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +120,16 @@ public final class WorldNameChecker {
|
||||
if (worldName == null) {
|
||||
return FolderStatus.DOES_NOT_EXIST;
|
||||
}
|
||||
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
|
||||
|
||||
if (BukkitCompatibility.isUsingNewDimensionStorage()) {
|
||||
File oldWorldFolder = Bukkit.getWorldContainer().toPath().resolve(worldName).toFile();
|
||||
if (checkFolder(oldWorldFolder) == FolderStatus.VALID) {
|
||||
return FolderStatus.REQUIRES_MIGRATION;
|
||||
}
|
||||
worldName = worldName.toLowerCase(Locale.ENGLISH); // namespace is case-insensitive and stored as lowercase
|
||||
}
|
||||
|
||||
File worldFolder = BukkitCompatibility.getWorldFoldersDirectory().resolve(worldName).toFile();
|
||||
return checkFolder(worldFolder);
|
||||
}
|
||||
|
||||
@@ -117,7 +144,7 @@ public final class WorldNameChecker {
|
||||
if (worldFolder == null || !worldFolder.exists() || !worldFolder.isDirectory()) {
|
||||
return FolderStatus.DOES_NOT_EXIST;
|
||||
}
|
||||
if (!folderHasDat(worldFolder)) {
|
||||
if (!folderWorldSchemaCheck(worldFolder)) {
|
||||
return FolderStatus.NOT_A_WORLD;
|
||||
}
|
||||
return FolderStatus.VALID;
|
||||
@@ -130,9 +157,54 @@ public final class WorldNameChecker {
|
||||
* @param worldFolder The File that may be a world.
|
||||
* @return True if it looks like a world, else false.
|
||||
*/
|
||||
private boolean folderHasDat(@NotNull File worldFolder) {
|
||||
File[] files = worldFolder.listFiles((file, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(".dat"));
|
||||
return files != null && files.length > 0;
|
||||
private boolean folderWorldSchemaCheck(@NotNull File worldFolder) {
|
||||
return WORLD_FOLDER_SCHEMA.stream()
|
||||
.filter(schema -> schema.check(worldFolder))
|
||||
.count() >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to check if a file or folder exist.
|
||||
*/
|
||||
private interface WorldFolderSchema {
|
||||
|
||||
static WorldFolderSchema file(String path) {
|
||||
return new WorldFile(path);
|
||||
}
|
||||
|
||||
static WorldFolderSchema folder(String path) {
|
||||
return new WorldFolder(path);
|
||||
}
|
||||
|
||||
boolean check(File worldFolder);
|
||||
|
||||
final class WorldFile implements WorldFolderSchema {
|
||||
private final String path;
|
||||
|
||||
private WorldFile(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(File worldFolder) {
|
||||
File thisFolder = worldFolder.toPath().resolve(path).toFile();
|
||||
return thisFolder.exists() && thisFolder.isFile();
|
||||
}
|
||||
}
|
||||
|
||||
final class WorldFolder implements WorldFolderSchema {
|
||||
private final String path;
|
||||
|
||||
private WorldFolder(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(File worldFolder) {
|
||||
File thisFolder = worldFolder.toPath().resolve(path).toFile();
|
||||
return thisFolder.exists() && thisFolder.isDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,16 +239,39 @@ public final class WorldNameChecker {
|
||||
/**
|
||||
* Folder is valid.
|
||||
*/
|
||||
VALID,
|
||||
VALID(true),
|
||||
|
||||
/**
|
||||
* This folder will cause PaperMC to migrate to new dimension world folder in 26.1+
|
||||
*/
|
||||
REQUIRES_MIGRATION(true),
|
||||
|
||||
/**
|
||||
* Folder exist, but contents in it doesnt look like a world.
|
||||
*/
|
||||
NOT_A_WORLD,
|
||||
NOT_A_WORLD(false),
|
||||
|
||||
/**
|
||||
* Folder does not exist.
|
||||
*/
|
||||
DOES_NOT_EXIST
|
||||
DOES_NOT_EXIST(false),
|
||||
;
|
||||
|
||||
private final boolean loadable;
|
||||
|
||||
FolderStatus(boolean loadable) {
|
||||
this.loadable = loadable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this folder status is loadable, meaning it has the basic world data and can be loaded as a world.
|
||||
* Note that this does not guarantee the server will definitely load the world with no errors.
|
||||
*
|
||||
* @return True if folder probably is loadable by the server, else false.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public boolean isLoadable() {
|
||||
return loadable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Options for customizing the cloning of a world.
|
||||
@@ -9,17 +11,35 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
public final class CloneWorldOptions implements KeepWorldSettingsOptions {
|
||||
|
||||
/**
|
||||
* Creates a new {@link CloneWorldOptions} instance with the given world.
|
||||
* Creates a new {@link CloneWorldOptions} instance with the given loaded world.
|
||||
*
|
||||
* @param world The world to clone.
|
||||
* @param fromWorld The loaded world to clone.
|
||||
* @param newWorldName The name of the new world.
|
||||
* @return A new {@link CloneWorldOptions} instance.
|
||||
*
|
||||
* @deprecated Cloning can be done from unloaded worlds as well. Use {@link #fromTo(MultiverseWorld, String)} instead.
|
||||
*/
|
||||
public static CloneWorldOptions fromTo(LoadedMultiverseWorld world, String newWorldName) {
|
||||
return new CloneWorldOptions(world, newWorldName);
|
||||
@Deprecated(forRemoval = true, since = "5.6")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public static @NotNull CloneWorldOptions fromTo(@NotNull LoadedMultiverseWorld fromWorld, @NotNull String newWorldName) {
|
||||
return new CloneWorldOptions(fromWorld, newWorldName);
|
||||
}
|
||||
|
||||
private final LoadedMultiverseWorld world;
|
||||
/**
|
||||
* Creates a new {@link CloneWorldOptions} instance with the given world.
|
||||
*
|
||||
* @param fromWorld The world to clone.
|
||||
* @param newWorldName The name of the new world.
|
||||
* @return A new {@link CloneWorldOptions} instance.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public static @NotNull CloneWorldOptions fromTo(@NotNull MultiverseWorld fromWorld, @NotNull String newWorldName) {
|
||||
return new CloneWorldOptions(fromWorld, newWorldName);
|
||||
}
|
||||
|
||||
private final MultiverseWorld fromWorld;
|
||||
private final String newWorldName;
|
||||
private boolean keepGameRule = true;
|
||||
private boolean keepWorldConfig = true;
|
||||
@@ -27,18 +47,34 @@ public final class CloneWorldOptions implements KeepWorldSettingsOptions {
|
||||
|
||||
private boolean keepWorldBorder = true;
|
||||
|
||||
CloneWorldOptions(LoadedMultiverseWorld world, String newWorldName) {
|
||||
this.world = world;
|
||||
CloneWorldOptions(MultiverseWorld fromWorld, String newWorldName) {
|
||||
this.fromWorld = fromWorld;
|
||||
this.newWorldName = newWorldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world to clone.
|
||||
* Gets the loaded world to clone.
|
||||
*
|
||||
* @return The world to clone.
|
||||
*
|
||||
* @deprecated Cloning can be done from unloaded worlds as well. Use {@link #fromWorld()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.6")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public LoadedMultiverseWorld world() {
|
||||
return world;
|
||||
return fromWorld.asLoadedWorld().getOrNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the loaded world to clone.
|
||||
*
|
||||
* @return The world to clone.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public @NotNull MultiverseWorld fromWorld() {
|
||||
return fromWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +82,7 @@ public final class CloneWorldOptions implements KeepWorldSettingsOptions {
|
||||
*
|
||||
* @return The name of the new world.
|
||||
*/
|
||||
public String newWorldName() {
|
||||
public @NotNull String newWorldName() {
|
||||
return newWorldName;
|
||||
}
|
||||
|
||||
@@ -94,7 +130,6 @@ public final class CloneWorldOptions implements KeepWorldSettingsOptions {
|
||||
return keepWorldConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether to keep the world border of the world during cloning.
|
||||
*
|
||||
|
||||
@@ -3,8 +3,14 @@ package org.mvplugins.multiverse.core.world.options;
|
||||
import co.aikar.commands.ACFUtil;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Options for customizing the creation of a new world.
|
||||
@@ -31,6 +37,7 @@ public final class CreateWorldOptions {
|
||||
private boolean useSpawnAdjust = true;
|
||||
private WorldType worldType = WorldType.NORMAL;
|
||||
private boolean doFolderCheck = true;
|
||||
private final Map<String, String> worldPropertyStrings = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link CreateWorldOptions} instance with the given world name.
|
||||
@@ -260,4 +267,46 @@ public final class CreateWorldOptions {
|
||||
public boolean doFolderCheck() {
|
||||
return doFolderCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a world property string key-value pair. Overwrites any existing key.
|
||||
*
|
||||
* @param key The key of the world property string.
|
||||
* @param value The value of the world property string.
|
||||
* @return This {@link CreateWorldOptions} instance.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public @NotNull CreateWorldOptions worldPropertyString(@NotNull String key, @Nullable String value) {
|
||||
this.worldPropertyStrings.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple world property string key-value pairs. Overwrites any existing keys.
|
||||
*
|
||||
* @param worldProperties A map of world property string key-value pairs.
|
||||
* @return This {@link CreateWorldOptions} instance.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public @NotNull CreateWorldOptions worldPropertyStrings(@NotNull Map<@NotNull String, @Nullable String> worldProperties) {
|
||||
this.worldPropertyStrings.putAll(worldProperties);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an unmodifiable view of the world property strings. Use {@link #worldPropertyString(String, String)} and
|
||||
* {@link #worldPropertyStrings(Map)} to modify the world property strings.
|
||||
*
|
||||
* @return An unmodifiable view of the world property strings.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public @UnmodifiableView @NotNull Map<String, String> worldPropertyStrings() {
|
||||
return Collections.unmodifiableMap(worldPropertyStrings);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public final class UnloadWorldOptions {
|
||||
* Sets whether to unload the bukkit world.
|
||||
* <br />
|
||||
* By setting this to false, multiverse will essentially untrack the world, but the world itself is still loaded.
|
||||
* This should be only used in edge cases where the world is used by other plugins byt you don't want multiverse
|
||||
* This should be only used in edge cases where the world is used by other plugins but you don't want multiverse
|
||||
* to handle it.
|
||||
*
|
||||
* @param unloadBukkitWorldInput Whether to unload the bukkit 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;
|
||||
|
||||
@@ -30,6 +31,14 @@ public enum CloneFailureReason implements FailureReason {
|
||||
*/
|
||||
WORLD_EXIST_LOADED(MVCorei18n.CLONEWORLD_WORLDEXISTLOADED),
|
||||
|
||||
/**
|
||||
* The source world folder is invalid.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
FROM_WORLD_FOLDER_INVALID(MVCorei18n.CLONEWORLD_FROMWORLDFOLDERINVALID),
|
||||
|
||||
/**
|
||||
* Failed to copy the world folder contents.
|
||||
*/
|
||||
|
||||
@@ -273,6 +273,7 @@ mv-core.cloneworld.invalidworldname=World '{world}' contains invalid characters!
|
||||
mv-core.cloneworld.worldexistfolder=World '{world}' exists in server folders! You need to delete it first before cloning.
|
||||
mv-core.cloneworld.worldexistunloaded=World '{world}' already exists and it's unloaded! You need to delete it first before cloning.
|
||||
mv-core.cloneworld.worldexistloaded=World '{world}' already exists! You need to delete it first before cloning.
|
||||
mv-core.cloneworld.fromworldfolderinvalid=Source world '{world}' folder contents does not seem to be a valid world! Make sure it contains the correct world structure of data and region folders.
|
||||
mv-core.cloneworld.copyfailed=Failed to copy world to '{world}': {error}\n&fSee console for more details.
|
||||
|
||||
mv-core.createworld.invalidworldname=World '{world}' contains invalid characters!
|
||||
@@ -288,7 +289,7 @@ mv-core.deleteworld.failedtodeletefolder=Failed to delete world folder '{world}'
|
||||
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!\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.worldfolderinvalid=World '{world}' folder contents does not seem to be a valid world! Make sure it contains the correct world structure of data and region folders.\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...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# configuration
|
||||
mv-core.config.save.failed=保存 Multiverse-Core 的 config.yml 文件失败,所有操作都将是临时的!
|
||||
mv-core.config.node.notfound=在配置文件中无法找到节点: {node}
|
||||
mv-core.config.save.failed=保存 Multiverse-Core 的 config.yml 文件失败,所有修改都将是临时的!
|
||||
mv-core.config.node.notfound=在配置文件中无法找到节点: {node}
|
||||
|
||||
# /mv check
|
||||
mv-core.check.description=检查某位玩家是否可以将自己传送至某个地方。
|
||||
@@ -8,7 +8,7 @@ mv-core.check.player.description=要检查的玩家。
|
||||
mv-core.check.destination.description=目标的地址,例如某个世界的名字。
|
||||
mv-core.check.haspermission=&a{player} 可以传送至 {destination}
|
||||
mv-core.check.nopermission=&c{player} 不能传送至 {destination}
|
||||
mv-core.check.location=目标的地址是: &f{location}
|
||||
mv-core.check.location=目标的地址是: &f{location}
|
||||
|
||||
# /mv clone
|
||||
mv-core.clone.description=复制一个世界。
|
||||
@@ -19,7 +19,7 @@ mv-core.clone.success=&a已将世界复制到 '{world}'!
|
||||
|
||||
# /mv config
|
||||
mv-core.config.description=查看或设置某个配置的值。
|
||||
mv-core.config.name.description=需要查看或设置的配置。
|
||||
mv-core.config.name.description=需要设置或查看的配置。
|
||||
mv-core.config.value.description=需要设置的值。如果没有指定,将显示当前配置的值。
|
||||
mv-core.config.show.success=&a配置 '&6{name}&a' 已被设置为 '&6{value}&a'。
|
||||
mv-core.config.show.error=&c获取配置 '&6{name}&c' 的值失败。\n&c{error}
|
||||
@@ -30,29 +30,29 @@ mv-core.config.set.error=&c将配置 '&6{name}&c' 设置为 '&6{value}&c' 失败
|
||||
mv-core.confirm.description=在执行危险的指令前再一次进行确认。
|
||||
|
||||
# /mv coordinates
|
||||
mv-core.coordinates.description=发送你的坐标信息
|
||||
mv-core.coordinates.description=直接发送你的坐标
|
||||
mv-core.coordinates.info.title=&b--- 位置信息 ---
|
||||
mv-core.coordinates.info.world=&b世界: &f{world}
|
||||
mv-core.coordinates.info.alias=&b别名: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&b世界尺度: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&b坐标: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&b方向: &f{direction}
|
||||
mv-core.coordinates.info.world=&b世界: &f{world}
|
||||
mv-core.coordinates.info.alias=&b别名: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&b世界比例: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&b坐标: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&b方向: &f{direction}
|
||||
|
||||
# /mv create
|
||||
mv-core.create.description=创建一个新的世界并且加载。
|
||||
mv-core.create.name.description=新世界的名称。
|
||||
mv-core.create.environment.description=世界的环境。详见:/mv environments
|
||||
mv-core.create.environment.description=世界的环境。详见: /mv environments
|
||||
mv-core.create.flags.description=附加的世界设置。在 https://gg.gg/nn8bl 上查看所有可能的标志。
|
||||
mv-core.create.properties=使用以下属性创建世界 {worldName} :
|
||||
mv-core.create.properties.environment=- 环境: &f{environment}
|
||||
mv-core.create.properties.seed=- 种子: &f{seed}
|
||||
mv-core.create.properties.worldtype=- 世界类型: &f{worldType}
|
||||
mv-core.create.properties.adjustspawn=- 调节出生点: &f{adjustSpawn}
|
||||
mv-core.create.properties.biome=- 生物群系: &f{biome}
|
||||
mv-core.create.properties.generator=- 生成器: &f{generator}
|
||||
mv-core.create.properties.generatorsettings=- 生成器设置: &f{generatorSettings}
|
||||
mv-core.create.properties.environment=- 环境: &f{environment}
|
||||
mv-core.create.properties.seed=- 种子: &f{seed}
|
||||
mv-core.create.properties.worldtype=- 世界类型: &f{worldType}
|
||||
mv-core.create.properties.adjustspawn=- 调节出生点: &f{adjustSpawn}
|
||||
mv-core.create.properties.biome=- 生物群系: &f{biome}
|
||||
mv-core.create.properties.generator=- 生成器: &f{generator}
|
||||
mv-core.create.properties.generatorsettings=- 生成器设置: &f{generatorSettings}
|
||||
|
||||
mv-core.create.properties.structures=- 结构: &f{structures}
|
||||
mv-core.create.properties.structures=- 结构: &f{structures}
|
||||
mv-core.create.loading=创建世界中……
|
||||
mv-core.create.success=&a世界 '{world}' 已经创建!
|
||||
|
||||
@@ -65,14 +65,14 @@ mv-core.debug.change.syntax=等级
|
||||
mv-core.debug.change.level.description=需要设置的调试等级。
|
||||
|
||||
# /mv delete
|
||||
mv-core.delete.description=在服务器上永久删除某个世界。
|
||||
mv-core.delete.description=在服务器上“永久”删除某个世界。
|
||||
mv-core.delete.deleting=正在删除世界 '{world}'……
|
||||
mv-core.delete.prompt=你确定想要删除世界 '{world}' 吗?
|
||||
mv-core.delete.success=&a世界 '{world}' 已经删除!
|
||||
|
||||
# /mv dumps
|
||||
mv-core.dumps.description=导出版本信息到控制台或者粘贴服务。
|
||||
mv-core.dumps.url.list={service}:{link}
|
||||
mv-core.dumps.url.list={service}:{link}
|
||||
|
||||
# /mv gamerule set
|
||||
mv-core.gamerule.set.description=修改某个世界或某些世界的游戏规则。
|
||||
@@ -105,24 +105,24 @@ mv-core.generators.empty=&c未找到生成器插件。
|
||||
# /mv import
|
||||
mv-core.import.description=导入一个已经存在的世界文件夹。
|
||||
mv-core.import.name.description=世界文件夹的名称。
|
||||
mv-core.import.env.description=世界的环境。详见:/mv env
|
||||
mv-core.import.other.description=其他世界设置。详见:https://gg.gg/nn8c2
|
||||
mv-core.import.env.description=世界的环境。详见: /mv env
|
||||
mv-core.import.other.description=其他世界设置。详见: https://gg.gg/nn8c2
|
||||
mv-core.import.importing=开始导入世界 '{world}'……
|
||||
mv-core.import.success=&a已导入世界 '{world}'!
|
||||
|
||||
# /mv info
|
||||
mv-core.info.description=显示某个世界所有已配置的属性。
|
||||
mv-core.info.description.world=需要显示信息的世界。
|
||||
mv-core.info.header=&a&l---- 世界信息: &f&l{world}&a&l ----
|
||||
mv-core.info.header=&a&l---- 世界信息: &f&l{world}&a&l ----
|
||||
mv-core.info.nocontent=&c没有找到这样的世界属性!
|
||||
|
||||
# /mv list
|
||||
mv-core.list.description=显示所有可进入世界的列表。
|
||||
mv-core.list.description=显示你所有可进入世界的列表。
|
||||
mv-core.list.header=&6====[ 多世界列表 ]====
|
||||
mv-core.list.nocontent=&c未找到任何世界,或者你没有权限进入它们!
|
||||
|
||||
# /mv load
|
||||
mv-core.load.description=加载某个世界,世界必须已经在 worlds.yml,或者请使用 /mv import 导入。
|
||||
mv-core.load.description=加载某个世界。世界必须已经在 worlds.yml 中,或者请使用 /mv import 导入。
|
||||
mv-core.load.world.description=想要加载的世界名称。
|
||||
mv-core.load.loading=加载世界 '{world}' 中……
|
||||
mv-core.load.success=&a已加载世界 '{world}'!
|
||||
@@ -132,6 +132,7 @@ mv-core.modify.description=修改某个给定世界的属性。
|
||||
mv-core.modify.world.description=需要修改的世界的名称。
|
||||
mv-core.modify.property.description=需要修改的属性。
|
||||
mv-core.modify.value.description=需要修改属性的值。
|
||||
mv-core.modify.propertyremoved=&c你不能修改 '&3{property}&c',因为它已经改变或被移除。请使用 '&a{replacement}&c'。
|
||||
mv-core.modify.cannothavevalue=你不能指定一个值来 {action} '{property}'。
|
||||
mv-core.modify.specifyvalue=你必须指定一个值来 {action} '{property}'。
|
||||
mv-core.modify.success=&a成功在世界 &9{world}&a 中 {action} '&9{property}&a' 为 '&9{value}&a' 。
|
||||
@@ -139,12 +140,12 @@ mv-core.modify.failure=&c在世界 &9{world}&c 中 {action} '&9{property}&c' 为
|
||||
mv-core.modify.failure.novalue=&c在世界 &9{world}&c 中 {action} '&9{property}&c' 失败。\n&c{error}
|
||||
|
||||
# /mv regen
|
||||
mv-core.regen.description=在你的服务器上重新生成一个世界。之前的状态将会被永久丢弃。
|
||||
mv-core.regen.description=在你的服务器上重新生成一个世界。之前的状态将会“永久”丢失。
|
||||
mv-core.regen.world.description=你想要重新生成的世界。
|
||||
mv-core.regen.other.description=其他的世界设置。详见:http://gg.gg/nn8lk
|
||||
mv-core.regen.other.description=其他的世界设置。详见: http://gg.gg/nn8lk
|
||||
mv-core.regen.regenerating=重新生成世界 '{world}'……
|
||||
mv-core.regen.prompt=你确定想要重新生成世界 '{world}' 吗?
|
||||
mv-core.regen.success=&a已重新生成 世界 '{world}'!
|
||||
mv-core.regen.success=&a已重新生成世界 '{world}'!
|
||||
|
||||
# /mv reload
|
||||
mv-core.reload.description=重新加载所有 Multiverse 模块的配置文件。
|
||||
@@ -153,7 +154,7 @@ mv-core.reload.success=&a重新加载完成!
|
||||
|
||||
# /mv remove
|
||||
mv-core.remove.description=从 Multiverse 中卸载某个世界并从 worlds.yml 中移除。这不会删除世界的文件夹。
|
||||
mv-core.remove.world.description=你想要从MV认知中移除的世界。
|
||||
mv-core.remove.world.description=你想要从 Multiverse 认知中移除的世界。
|
||||
mv-core.remove.success=&a已移除世界 '{world}'!
|
||||
|
||||
# /mv
|
||||
@@ -164,10 +165,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-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=传送某个特定的玩家到他们所在世界的出生点。
|
||||
@@ -184,7 +185,7 @@ mv-core.teleport.success=已传送 {player} 到 {destination}。
|
||||
mv-core.teleport.failed=传送 {player} 到 {destination}失败。{reason}
|
||||
|
||||
# /mv unload
|
||||
mv-core.unload.description=从 Multiverse 中卸载某个世界。这不会移除世界的文件夹。这不会将它从配置文件中移除。
|
||||
mv-core.unload.description=从 Multiverse 中卸载某个世界。这“不会”移除世界的文件夹。这“不会”将它从配置文件中移除。
|
||||
mv-core.unload.world.description=你想要卸载的世界名称。
|
||||
mv-core.unload.unloading=正在卸载世界 '{world}'……
|
||||
mv-core.unload.success=&a已卸载 '{world}'!
|
||||
@@ -196,7 +197,7 @@ mv-core.usage.description=显示 Multiverse-Core 指令的用法。
|
||||
mv-core.version.description=显示版本和作者。
|
||||
mv-core.version.mv=Multiverse Core 版本 &fv{version}
|
||||
mv-core.version.authors=Multiverse Core 作者 &f{authors}
|
||||
mv-core.version.secretcode=特征码: &fFRN002
|
||||
mv-core.version.secretcode=特征码: &fFRN002
|
||||
|
||||
# /mv who
|
||||
# /mv whoall
|
||||
@@ -205,7 +206,7 @@ mv-core.who.all.description=列出所有世界的玩家
|
||||
mv-core.who.world.description=需要列出玩家的世界名称
|
||||
mv-core.who.flags.description=筛选 - 仅显示匹配的条目。页码 - 查看的页面序号。
|
||||
mv-core.who.empty=&7&o空
|
||||
mv-core.who.header=&b====[ 多世界玩家列表 ]====
|
||||
mv-core.who.header=&b====[ Multiverse世界玩家列表 ]====
|
||||
|
||||
# /mv worldborder
|
||||
mv-core.worldborder.center.nothingchanged=&c无变化。世界边界的中心在世界 '&6{world}&c' 中已经有相同的设置。
|
||||
@@ -214,8 +215,8 @@ mv-core.worldborder.center.success=&r已设置世界 '&6{world}&r' 边界的中
|
||||
mv-core.worldborder.damageamount.nothingchanged=&c无变化。世界边界的伤害在世界 '&6{world}&c' 中已经有相同的设置。
|
||||
mv-core.worldborder.damageamount.success=&r已设置世界 '&6{world}&r' 的边界伤害为每秒每格 {amount} 点伤害。
|
||||
|
||||
mv-core.worldborder.damagebuffer.nothingchanged=&c无变化。世界边界伤害的缓冲在世界 '&6{world}&c' 中已经有相同的设置。
|
||||
mv-core.worldborder.damagebuffer.success=&r已设置世界 '&6{world}&r' 边界伤害的缓冲为 {distance} 格。
|
||||
mv-core.worldborder.damagebuffer.nothingchanged=&c无变化。世界边界伤害的缓冲区在世界 '&6{world}&c' 中已经有相同的设置。
|
||||
mv-core.worldborder.damagebuffer.success=&r已设置世界 '&6{world}&r' 边界伤害的缓冲区为 {distance} 格。
|
||||
|
||||
mv-core.worldborder.get.size=&r世界 '&6{world}&r' 的边界宽度目前为 {size} 格。
|
||||
|
||||
@@ -232,7 +233,7 @@ mv-core.worldborder.warningtime.success=&r已设置世界 '&6{world}&r' 的边
|
||||
|
||||
# commands error
|
||||
mv-core.commands.error.playersonly=&c该指令仅玩家可以使用。
|
||||
mv-core.commands.error.multiverseworldonly=&c该指令仅可以在多世界中使用。
|
||||
mv-core.commands.error.multiverseworldonly=&c该指令仅可以在Multiverse世界中使用。
|
||||
|
||||
# entry check
|
||||
mv-core.entrycheck.blacklisted='{world}' 在黑名单中。
|
||||
@@ -242,29 +243,29 @@ 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}。
|
||||
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'。
|
||||
mv-core.destination.cannon.failurereason.invalidformat=&c大炮目标格式是:&6ca:worldname:x,y,z:pitch:yaw:speed
|
||||
mv-core.destination.exact.failurereason.invalidformat=&c精确目标格式是:&6e:worldname:x,y,z:pitch:yaw
|
||||
mv-core.destination.cannon.failurereason.invalidformat=&c大炮目标格式是: &6ca:worldname:x,y,z:pitch:yaw:speed
|
||||
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.invalidcoordinatesformat=你必须指定一个具有以下格式有效的坐标: &6{x},{y},{z}
|
||||
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}
|
||||
mv-core.destination.parse.failurereason.invaliddestinationid=无效的目标ID: {id}。有效的目标ID有: {ids}
|
||||
|
||||
# teleport failure reason
|
||||
mv-core.teleportfailurereason.null.destination=目标无效!
|
||||
mv-core.teleportfailurereason.null.location=位置无效!
|
||||
mv-core.teleportfailurereason.null.world=&c位置所在的世界无效!
|
||||
mv-core.teleportfailurereason.unsafe.location=位置是不安全的!使用 `--unsafe` 忽视该问题。
|
||||
mv-core.teleportfailurereason.teleport.failed=出于某些拒绝了该传送。
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=在传送过程中出现了一个错误。查看控制台获取更多信息。
|
||||
mv-core.teleportfailurereason.null.world=&c目标世界没有加载或者不存在!
|
||||
mv-core.teleportfailurereason.unsafe.location=位置被认为是不安全的!如果是有意为之,使用 `--unsafe` 强制执行。
|
||||
mv-core.teleportfailurereason.teleport.failed=传送被拒绝。请确保其他插件没有阻止传送,并查看服务器的日志获得错误信息。
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=在传送过程中出现了一个错误。查看服务器日志获取更多信息。
|
||||
mv-core.teleportfailurereason.event.cancelled=传送被另一个插件取消了。
|
||||
|
||||
# world manager result
|
||||
@@ -272,7 +273,8 @@ mv-core.cloneworld.invalidworldname=世界 '{world}' 包含无效的字符!
|
||||
mv-core.cloneworld.worldexistfolder=世界 '{world}' 已经存在于服务器的文件中!在复制前你需要先删除它。
|
||||
mv-core.cloneworld.worldexistunloaded=世界 '{world}' 已经存在并且未被加载!在复制前你需要先删除它。
|
||||
mv-core.cloneworld.worldexistloaded=世界 '{world}' 已经存在!在复制前你需要先删除它。
|
||||
mv-core.cloneworld.copyfailed=复制世界到 '{world}' 失败:{error}\n&f查看控制台获取更多信息。
|
||||
mv-core.cloneworld.fromworldfolderinvalid=源世界 '{world}' 文件夹的内容似乎不是一个有效的世界!请确保它包含正确的世界数据和区域文件夹结构。
|
||||
mv-core.cloneworld.copyfailed=复制世界到 '{world}' 失败: {error}\n&f查看控制台获取更多信息。
|
||||
|
||||
mv-core.createworld.invalidworldname=世界 '{world}' 包含无效的字符!
|
||||
mv-core.createworld.worldexistfolder=世界 '{world}' 已经存在于服务器的文件中!&f 如果你想要导入它,输入 '&a/mv import {world} <environment>&f'。
|
||||
@@ -280,35 +282,37 @@ mv-core.createworld.worldexistunloaded=世界 '{world}' 已经存在, 但是未
|
||||
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.failedtodeletefolder=删除世界 '{world}' 的文件夹失败:{error}\n&f查看控制台获取更多信息。
|
||||
mv-core.deleteworld.loadfailed=不能加载世界 '{world}',请确认世界文件夹是否被删除或者损坏。&f 你可以执行 '&a/mv remove {world}&f' 将它从 Multiverse 中移除,或者使用 '&a/mv load {world}&f' 尝试重新加载。
|
||||
mv-core.deleteworld.worldfoldernotfound=&c无法定位 '{world}' 文件夹!&f 你可以执行 '&a/mv remove {world}&c' 将它从 Multiverse 中移除,如果存在,也可以手动删除文件夹。
|
||||
mv-core.deleteworld.failedtodeletefolder=删除世界 '{world}' 的文件夹失败: {error}\n&f查看控制台获取更多信息。
|
||||
|
||||
mv-core.importworld.invalidworldname=世界 '{world}' 包含无效的字符!
|
||||
mv-core.importworld.worldexistunloaded=世界 '{world}' 已经存在, 但是未被加载!&f 如果你想要加载它,输入 '&a/mv load {world}&f'。
|
||||
mv-core.importworld.worldexistloaded=世界 '{world}' 已经存在!
|
||||
mv-core.importworld.worldfolderinvalid=世界 '{world}' 文件夹的内容看上去不像是一个有效的世界!
|
||||
mv-core.importworld.worldfolderinvalid=世界 '{world}' 文件夹的内容似乎不是一个有效的世界!请确保它包含正确的世界数据和区域文件夹结构。\n&c如果服务器软件对世界文件夹的处理方式不同,或者你确信世界是有效的,使用 '&3--skip-folder-check&c' 标志。
|
||||
mv-core.importworld.bukkitenvironmentmismatch=环境不匹配!&f 世界 '{world}' 已经用环境 '{bukkitEnvironment}' 加载,但 Multiverse 正在尝试用环境 '{mvEnvironment}' 导入它。
|
||||
|
||||
mv-core.loadworld.worldalreadyloading=世界 '{world}' 已经正在加载!请稍后……
|
||||
mv-core.loadworld.worldnonexistent=世界 '{world}' 不存在!使用 '&a/mv create {world} <environment>&f' 来创建。
|
||||
mv-core.loadworld.worldexistfolder=世界 '{world}' 已经存在于服务器的文件中, 但是未被 Multiverse 识别!&f 如果你想要导入它,输入 '&a/mv import {world} <environment>&f'。
|
||||
mv-core.loadworld.worldexistloaded=世界 '{world}' 已经存在!
|
||||
mv-core.loadworld.worldexistloaded=世界 '{world}' 已经加载!
|
||||
mv-core.loadworld.bukkitenvironmentmismatch=环境不匹配!&f 世界 '{world}' 已经用环境 '{bukkitEnvironment}' 加载,但 Multiverse 正在尝试用环境 '{mvEnvironment}' 加载它。
|
||||
|
||||
mv-core.removeworld.worldnonexistent=世界 '{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.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.bukkitcreationfailed=&cBukkit 创建世界 '{world}' 失败:{error}\n&c查看控制台获取更多信息。
|
||||
mv-core.worldcreator.bukkitcreationfailed=&cBukkit 创建世界 '{world}' 失败: {error}\n&c查看控制台获取更多信息。
|
||||
|
||||
# queue command result
|
||||
mv-core.queuecommand.nocommandinqueue=&c你没有任何指令在列队中。
|
||||
mv-core.queuecommand.invalidotp=&c无效的 OTP 数字 '&6{otp}&c'。请重试……
|
||||
mv-core.queuecommand.commandexecutionerror=&c在处理列队中命令时发生了错误:{error}\n&f查看控制台获取更多信息。
|
||||
mv-core.queuecommand.commandexecutionerror=&c在处理列队中命令时发生了错误: {error}\n&f查看控制台获取更多信息。
|
||||
|
||||
# content display messages
|
||||
mv-core.contentdisplay.nocontent=&c没有可显示的内容
|
||||
@@ -322,7 +326,12 @@ mv-core.contentdisplay.null=&7&o错误
|
||||
mv-core.exception.multiverseworld.createnull=创建的世界返回为null!
|
||||
mv-core.exception.multiverseworld.unloaddefaultworld=&c你不能卸载默认的世界!世界 &6{world}&c 是定义在 server.properties '&6level-name&c' 中的默认世界。
|
||||
mv-core.exception.multiverseworld.unloadplayersinworld=&c目前仍然有 &6{count}&c 位玩家在世界中!在命令中使用 '&6--remove-players&c' 标签将所有的玩家从世界中移除。
|
||||
mv-core.exception.multiverseworld.unloaderror=&c在卸载世界 &6{world}&c 过程中出现了一个位置的错误。\n&c查看控制台获取更多信息。
|
||||
mv-core.exception.multiverseworld.unloaderror=&c在卸载世界 &6{world}&c 过程中出现了一个未知的错误。\n&c查看控制台获取更多信息。
|
||||
|
||||
# multiverse position parse exception
|
||||
mv-core.exception.positionparse.invaliddirection=&c无效的方向字符串格式: {format}。预期的格式: <pitch>:<yaw>
|
||||
mv-core.exception.positionparse.invalidcoordinates=&c无效的坐标格式: {format}。预期的格式: <x>,<y>,<z>
|
||||
mv-core.exception.positionparse.invalidnumber=&c无效的数字格式: {number}。预期为一个数字值。
|
||||
|
||||
# generic
|
||||
mv-core.generic.success=成功!
|
||||
|
||||
@@ -39,6 +39,8 @@ public class MVServerMock extends ServerMock {
|
||||
world.getWorldFolder().mkdirs();
|
||||
createFile(new File(world.getWorldFolder(), "uid.dat"));
|
||||
createFile(new File(world.getWorldFolder(), "level.dat"));
|
||||
new File(world.getWorldFolder(), "region").mkdir();
|
||||
new File(world.getWorldFolder(), "data").mkdir();
|
||||
addWorld(world);
|
||||
return world;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.utils
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class StringFormatterTest {
|
||||
@Test
|
||||
@@ -42,5 +43,22 @@ class StringFormatterTest {
|
||||
"test",
|
||||
StringFormatter.quoteMultiWordString("test")
|
||||
)
|
||||
assertNull(StringFormatter.quoteMultiWordString(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `StringFormatter parseCSVMap`() {
|
||||
assertEquals(
|
||||
emptyMap<String, String>(),
|
||||
StringFormatter.parseCSVMap("")
|
||||
)
|
||||
assertEquals(
|
||||
mapOf("key" to "value"),
|
||||
StringFormatter.parseCSVMap("key=value")
|
||||
)
|
||||
assertEquals(
|
||||
mapOf("key1" to "value1", "key2" to "value2", "key3" to "value3"),
|
||||
StringFormatter.parseCSVMap("key1=value1,key2=value2,key3=value3")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,17 @@ import org.bukkit.World
|
||||
import org.bukkit.WorldType
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.mockbukkit.mockbukkit.matcher.plugin.PluginManagerFiredEventClassMatcher.hasFiredEventInstance
|
||||
import org.mockbukkit.mockbukkit.matcher.plugin.PluginManagerFiredEventClassMatcher.hasNotFiredEventInstance
|
||||
import org.mvplugins.multiverse.core.TestWithMockBukkit
|
||||
import org.mvplugins.multiverse.core.event.world.*
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldClonedEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldCreatedEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldDeleteEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldImportedEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldLoadedEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldPropertyChangedEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldRegeneratedEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldRemovedEvent
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldUnloadedEvent
|
||||
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions
|
||||
@@ -35,15 +44,15 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
world = worldManager.getLoadedWorld("world").get()
|
||||
assertNotNull(world)
|
||||
|
||||
assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess)
|
||||
world2 = worldManager.getLoadedWorld("world2").get()
|
||||
assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("World2")).isSuccess)
|
||||
world2 = worldManager.getLoadedWorld("World2").get()
|
||||
assertNotNull(world2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Create world with custom options`() {
|
||||
assertTrue(worldManager.createWorld(
|
||||
CreateWorldOptions.worldName("world_nether")
|
||||
CreateWorldOptions.worldName("Section/my-nether_world")
|
||||
.environment(World.Environment.NETHER)
|
||||
.generateStructures(false)
|
||||
.seed(1234L)
|
||||
@@ -51,11 +60,11 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
.worldType(WorldType.FLAT)
|
||||
).isSuccess)
|
||||
|
||||
val getWorld = worldManager.getLoadedWorld("world_nether")
|
||||
val getWorld = worldManager.getLoadedWorld("Section/my-nether_world")
|
||||
assertTrue(getWorld.isDefined)
|
||||
val world = getWorld.get()
|
||||
assertNotNull(world)
|
||||
assertEquals("world_nether", world.name)
|
||||
assertEquals("Section/my-nether_world", world.name)
|
||||
assertEquals(World.Environment.NETHER, world.environment)
|
||||
assertFalse(world.canGenerateStructures().get())
|
||||
assertEquals(1234L, world.seed)
|
||||
@@ -84,6 +93,18 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Create world failed - world exists and loaded (case insensitive)`() {
|
||||
assertEquals(
|
||||
CreateFailureReason.WORLD_EXIST_LOADED,
|
||||
worldManager.createWorld(CreateWorldOptions.worldName("WoRld2")).failureReason
|
||||
)
|
||||
assertEquals(
|
||||
CreateFailureReason.WORLD_EXIST_LOADED,
|
||||
worldManager.createWorld(CreateWorldOptions.worldName("WORLD2")).failureReason
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Create world failed - world exists but unloaded`() {
|
||||
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world)).isSuccess)
|
||||
@@ -109,6 +130,11 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
assertFalse(worldManager.getLoadedWorld("world").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("world").isDefined)
|
||||
|
||||
assertTrue(worldManager.removeWorld(RemoveWorldOptions.world(world2)).isSuccess)
|
||||
assertFalse(worldManager.getWorld("World2").isDefined)
|
||||
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
|
||||
|
||||
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldUnloadedEvent::class.java))
|
||||
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldRemovedEvent::class.java))
|
||||
}
|
||||
@@ -117,9 +143,18 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
fun `Delete world`() {
|
||||
assertTrue(File(Bukkit.getWorldContainer(), "world").isDirectory)
|
||||
assertTrue(worldManager.deleteWorld(DeleteWorldOptions.world(world)).isSuccess)
|
||||
assertFalse(worldManager.getWorld("world").isDefined)
|
||||
assertFalse(worldManager.getLoadedWorld("world").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("world").isDefined)
|
||||
assertFalse(File(Bukkit.getWorldContainer(), "world").isDirectory)
|
||||
|
||||
assertTrue(File(Bukkit.getWorldContainer(), "World2").isDirectory)
|
||||
assertTrue(worldManager.deleteWorld(DeleteWorldOptions.world(world2)).isSuccess)
|
||||
assertFalse(worldManager.getWorld("World2").isDefined)
|
||||
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
|
||||
assertFalse(File(Bukkit.getWorldContainer(), "World2").isDirectory)
|
||||
|
||||
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldDeleteEvent::class.java))
|
||||
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldUnloadedEvent::class.java))
|
||||
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldRemovedEvent::class.java))
|
||||
@@ -130,21 +165,21 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2).saveBukkitWorld(true)).isSuccess)
|
||||
assertFalse(world2.isLoaded)
|
||||
assertFalse(world2.bukkitWorld.isDefined)
|
||||
assertFalse(worldManager.getLoadedWorld("world2").isDefined)
|
||||
assertTrue(worldManager.getWorld("world2").isDefined)
|
||||
assertTrue(worldManager.getUnloadedWorld("world2").isDefined)
|
||||
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
|
||||
assertTrue(worldManager.getWorld("World2").isDefined)
|
||||
assertTrue(worldManager.getUnloadedWorld("World2").isDefined)
|
||||
|
||||
assertTrue(worldManager.loadWorld(LoadWorldOptions.world(world2)).isSuccess)
|
||||
assertTrue(world2.isLoaded)
|
||||
assertTrue(worldManager.getLoadedWorld("world2").flatMap{ w -> w.bukkitWorld }.isDefined)
|
||||
assertTrue(worldManager.getLoadedWorld("world2").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("world2").isDefined)
|
||||
assertTrue(worldManager.getLoadedWorld("World2").flatMap{ w -> w.bukkitWorld }.isDefined)
|
||||
assertTrue(worldManager.getLoadedWorld("World2").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load world failed - invalid world folder`() {
|
||||
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
|
||||
File(Bukkit.getWorldContainer(), "world2/").deleteRecursively()
|
||||
File(Bukkit.getWorldContainer(), "World2/").deleteRecursively()
|
||||
assertEquals(
|
||||
LoadFailureReason.WORLD_FOLDER_INVALID,
|
||||
worldManager.loadWorld(LoadWorldOptions.world(world2)).failureReason
|
||||
@@ -163,6 +198,8 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
fun `Load world failed - world folder exists but not imported`() {
|
||||
File(Bukkit.getWorldContainer(), "worldfolder").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "worldfolder/level.dat").createNewFile()
|
||||
File(Bukkit.getWorldContainer(), "worldfolder/data").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "worldfolder/region").mkdir()
|
||||
assertEquals(
|
||||
LoadFailureReason.WORLD_EXIST_FOLDER,
|
||||
worldManager.loadWorld("worldfolder").failureReason
|
||||
@@ -177,7 +214,7 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
.seed(4321L)
|
||||
).isSuccess)
|
||||
|
||||
val getWorld = worldManager.getLoadedWorld("world2")
|
||||
val getWorld = worldManager.getLoadedWorld("World2")
|
||||
assertTrue(getWorld.isDefined)
|
||||
val world = getWorld.get()
|
||||
assertNotNull(world)
|
||||
@@ -217,7 +254,7 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
fun `Clone world failed - target world exists and loaded`() {
|
||||
assertEquals(
|
||||
CloneFailureReason.WORLD_EXIST_LOADED,
|
||||
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason
|
||||
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "World2")).failureReason
|
||||
)
|
||||
}
|
||||
|
||||
@@ -226,7 +263,7 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
|
||||
assertEquals(
|
||||
CloneFailureReason.WORLD_EXIST_UNLOADED,
|
||||
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason
|
||||
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "World2")).failureReason
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,8 +280,10 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
fun `Get potential worlds`() {
|
||||
File(Bukkit.getWorldContainer(), "newworld1").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "newworld1/level.dat").createNewFile()
|
||||
File(Bukkit.getWorldContainer(), "newworld1/data").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "newworld2").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "newworld2/level.dat").createNewFile()
|
||||
File(Bukkit.getWorldContainer(), "newworld2/data").mkdir()
|
||||
assertEquals(setOf("newworld1", "newworld2"), worldManager.getPotentialWorlds().toSet())
|
||||
}
|
||||
|
||||
@@ -255,4 +294,60 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
assertEquals(world, worldManager.getWorldByNameOrAlias("testalias").orNull)
|
||||
assertNull(worldManager.getUnloadedWorldByNameOrAlias("testalias").orNull)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get world`() {
|
||||
assertEquals(world, worldManager.getLoadedWorld("world").orNull)
|
||||
assertNull(worldManager.getUnloadedWorld("world").orNull)
|
||||
assertEquals(world, worldManager.getWorld("world").orNull)
|
||||
|
||||
assertTrue(worldManager.isLoadedWorld("world"))
|
||||
assertFalse(worldManager.isUnloadedWorld("world"))
|
||||
assertTrue(worldManager.isWorld("world"))
|
||||
|
||||
val unloadedWorld = worldManager.unloadWorld(UnloadWorldOptions.world(world)).get()
|
||||
|
||||
assertNull(worldManager.getLoadedWorld("world").orNull)
|
||||
assertEquals(unloadedWorld, worldManager.getUnloadedWorld("world").orNull)
|
||||
assertEquals(unloadedWorld, worldManager.getWorld("world").orNull)
|
||||
|
||||
assertFalse(worldManager.isLoadedWorld("world"))
|
||||
assertTrue(worldManager.isUnloadedWorld("world"))
|
||||
assertTrue(worldManager.isWorld("world"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get world is case insensitive`() {
|
||||
assertEquals(world2, worldManager.getLoadedWorld("WORLD2").orNull)
|
||||
assertNull(worldManager.getUnloadedWorld("wOrld2").orNull)
|
||||
assertEquals(world2, worldManager.getWorld("wOrld2").orNull)
|
||||
|
||||
assertTrue(worldManager.isLoadedWorld("WoRlD2"))
|
||||
assertFalse(worldManager.isUnloadedWorld("WoRlD2"))
|
||||
assertTrue(worldManager.isWorld("wORLD2"))
|
||||
|
||||
val unloadedWorld2 = worldManager.unloadWorld(UnloadWorldOptions.world(world2)).get()
|
||||
|
||||
assertNull(worldManager.getLoadedWorld("wOrld2").orNull)
|
||||
assertEquals(unloadedWorld2, worldManager.getUnloadedWorld("wOrld2").orNull)
|
||||
assertEquals(unloadedWorld2, worldManager.getWorld("wORLD2").orNull)
|
||||
|
||||
assertFalse(worldManager.isLoadedWorld("WoRlD2"))
|
||||
assertTrue(worldManager.isUnloadedWorld("WoRlD2"))
|
||||
assertTrue(worldManager.isWorld("wORLD2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Set different value - MVWorldPropertyChangedEvent event is fired`() {
|
||||
server.pluginManager.clearEvents()
|
||||
world.scale = 8.0
|
||||
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldPropertyChangedEvent::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Set equal value - MVWorldPropertyChangedEvent event is not fired`() {
|
||||
server.pluginManager.clearEvents()
|
||||
world.scale = 1.0
|
||||
assertThat(server.pluginManager, hasNotFiredEventInstance(MVWorldPropertyChangedEvent::class.java))
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,22 @@ class WorldNameCheckerTest : TestWithMockBukkit() {
|
||||
fun `Blacklisted world name`() {
|
||||
assertEquals(WorldNameChecker.NameStatus.BLACKLISTED, worldNameChecker.checkName("logs"))
|
||||
assertEquals(WorldNameChecker.NameStatus.BLACKLISTED, worldNameChecker.checkName("plugins"))
|
||||
assertEquals(WorldNameChecker.NameStatus.BLACKLISTED, worldNameChecker.checkName("CACHE"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Valid world folder`() {
|
||||
File(Bukkit.getWorldContainer(), "test").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "test/level.dat").createNewFile()
|
||||
File(Bukkit.getWorldContainer(), "test/data").mkdir()
|
||||
assertEquals(WorldNameChecker.FolderStatus.VALID, worldNameChecker.checkFolder("test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Valid world folder v26-1 format`() {
|
||||
File(Bukkit.getWorldContainer(), "test").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "test/region").mkdir()
|
||||
File(Bukkit.getWorldContainer(), "test/data").mkdir()
|
||||
assertEquals(WorldNameChecker.FolderStatus.VALID, worldNameChecker.checkFolder("test"))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ world:
|
||||
enforce-access: false
|
||||
enforce-gamemode: true
|
||||
enforce-flight: true
|
||||
gamemode-and-flight-enforce-delay: 1
|
||||
apply-entity-spawn-rate: true
|
||||
apply-entity-spawn-limit: true
|
||||
auto-purge-entities: false
|
||||
@@ -36,11 +37,14 @@ messaging:
|
||||
enable-chat-prefix: false
|
||||
chat-prefix-format: '[%world%]%chat%'
|
||||
register-papi-hook: true
|
||||
warn-invalid-papi-format: true
|
||||
invalid-papi-format-returns-blank: false
|
||||
default-locale: en
|
||||
per-player-locale: true
|
||||
|
||||
command:
|
||||
resolve-alias-name: true
|
||||
simplified-destination-tab-completion: false
|
||||
confirm-mode: enable
|
||||
use-confirm-otp: true
|
||||
confirm-timeout: 30
|
||||
@@ -51,6 +55,7 @@ event-priority:
|
||||
player-respawn: low
|
||||
player-spawn-location: normal
|
||||
player-teleport: highest
|
||||
player-changed-world: normal
|
||||
|
||||
misc:
|
||||
bukkit-yml-path: bukkit.yml
|
||||
|
||||
@@ -33,7 +33,47 @@ world:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
world_nether:
|
||||
@@ -71,6 +111,46 @@ world_nether:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
@@ -33,6 +33,46 @@ world_nether:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
@@ -34,11 +34,46 @@ world:
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
spawn: falSe
|
||||
tick-rate: 123
|
||||
spawn-limit: -1
|
||||
exceptions: [cow]
|
||||
spawn-limit: '@unset'
|
||||
exceptions: [ cow ]
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: # should be parsed as list of string
|
||||
- a
|
||||
- 1
|
||||
@@ -78,6 +113,46 @@ world_nether:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
@@ -34,16 +34,46 @@ world_the_end:
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning:
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@bukkit'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@bukkit'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
world[dot]a[dot]b:
|
||||
@@ -82,16 +112,46 @@ world[dot]a[dot]b:
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning:
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@bukkit'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@bukkit'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist:
|
||||
- test
|
||||
version: 1.2
|
||||
@@ -131,16 +191,46 @@ world[dot]a[dot]c:
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning:
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@bukkit'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@bukkit'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist:
|
||||
- test
|
||||
version: 1.2
|
||||
@@ -33,7 +33,47 @@ world:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
world_nether:
|
||||
@@ -71,7 +111,47 @@ world_nether:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
new[dot]world:
|
||||
@@ -104,6 +184,46 @@ new[dot]world:
|
||||
seed: -9223372036854775808
|
||||
spawn-location:
|
||||
==: MVNullLocation (It's a bug if you see this in your config file)
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
@@ -33,7 +33,47 @@ world:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
world_nether:
|
||||
@@ -66,6 +106,46 @@ world_nether:
|
||||
seed: -9223372036854775808
|
||||
spawn-location:
|
||||
==: MVNullLocation (It's a bug if you see this in your config file)
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
@@ -34,17 +34,47 @@ world:
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
spawn: true
|
||||
tick-rate: 111
|
||||
spawn-limit: -1
|
||||
spawn-limit: '@unset'
|
||||
exceptions:
|
||||
- cow
|
||||
- pig
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
spawn: false
|
||||
tick-rate: -1
|
||||
spawn-limit: -1
|
||||
tick-rate: '@unset'
|
||||
spawn-limit: '@unset'
|
||||
exceptions: [ ]
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
@@ -83,6 +113,46 @@ world_nether:
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning: {}
|
||||
spawning:
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
water_underground_creature:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
ambient:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
axolotl:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
misc:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: '@unset'
|
||||
tick-rate: '@unset'
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
Reference in new issue
Block a user