mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 16:50:03 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30e50866bd | ||
|
|
5dde7608f2 | ||
|
|
2f9915c62e | ||
|
|
e62e37831b | ||
|
|
ef9934f3c6 | ||
|
|
691bcd0ffa | ||
|
|
148644970d | ||
|
|
ad20ec94c5 | ||
|
|
8e878b1ed0 | ||
|
|
e1c6af2f38 | ||
|
|
bca71e1acc | ||
|
|
343da8c301 | ||
|
|
d34f3adccc | ||
|
|
b3862faf88 | ||
|
|
a41476de89 | ||
|
|
fe75e497e4 | ||
|
|
4004554733 | ||
|
|
99037b2af5 | ||
|
|
ac46a5f64a | ||
|
|
4d805b921b | ||
|
|
a612854490 | ||
|
|
e85c14e319 | ||
|
|
3983af7b4c | ||
|
|
8853f88f3a | ||
|
|
08d361e0ec | ||
|
|
f985f2db06 | ||
|
|
82acca3ff9 | ||
|
|
d712c1eb09 | ||
|
|
25e9213ee9 | ||
|
|
9e79708fec | ||
|
|
65d1141289 | ||
|
|
63b2ffdf72 | ||
|
|
14d8ad7a5c | ||
|
|
33e0df925f | ||
|
|
1883edbb28 | ||
|
|
b7a2bd61ca | ||
|
|
a0673ac109 | ||
|
|
7735a33ccf | ||
|
|
5f24520685 | ||
|
|
608cb8611c | ||
|
|
a965b9ce56 | ||
|
|
981d368058 | ||
|
|
7507099a4e | ||
|
|
29b58b1655 | ||
|
|
a1081c1b6b | ||
|
|
3152b62668 | ||
|
|
0082cc9852 | ||
|
|
f60b656bed | ||
|
|
da5f5de5cd | ||
|
|
e9aacecc2f | ||
|
|
fa3adbe605 | ||
|
|
e33b2f7025 | ||
|
|
a9cc577754 | ||
|
|
02344a31cf | ||
|
|
2f4871ef71 | ||
|
|
9750fad4b7 | ||
|
|
df77372d96 | ||
|
|
039af3c219 | ||
|
|
09ec4f87ef | ||
|
|
f996f22f4b | ||
|
|
b230c5ae9c | ||
|
|
2561107697 | ||
|
|
70babc8656 | ||
|
|
bd09d6c950 | ||
|
|
b94de763ca | ||
|
|
22ca57543f | ||
|
|
25c70170d9 |
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,7 +107,7 @@ jobs:
|
||||
files: '["${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar"]'
|
||||
name: ${{ steps.release-info.outputs.tag_name }}
|
||||
changelog: ${{ steps.release-artifact.outputs.body }}
|
||||
game_versions: 1.21.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: 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
|
||||
dependencies: ${{ inputs.modrinth_dependencies }}
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
changelog: ${{ steps.release-artifact.outputs.body }}
|
||||
changelog_type: markdown
|
||||
display_name: ${{ steps.release-info.outputs.tag_name }}
|
||||
game_versions: 1.21.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: 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.8"]}'
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-1.21.10"]}'
|
||||
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.0'
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.2'
|
||||
}
|
||||
|
||||
group = 'org.mvplugins.multiverse.core'
|
||||
@@ -25,9 +25,9 @@ repositories {
|
||||
}
|
||||
|
||||
configure(apiDependencies) {
|
||||
serverApiVersion = '1.19.4-R0.1-SNAPSHOT'
|
||||
serverApiVersion = '1.21.8-R0.1-SNAPSHOT'
|
||||
mockBukkitServerApiVersion = '1.21'
|
||||
mockBukkitVersion = '4.72.2'
|
||||
mockBukkitVersion = '4.84.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mvplugins.multiverse.core;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
@@ -20,9 +19,8 @@ import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Service
|
||||
final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
@@ -82,7 +80,7 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return null;
|
||||
}
|
||||
|
||||
final var placeholder = paramsArray.removeFirst();
|
||||
final var placeholder = paramsArray.remove(0);
|
||||
|
||||
String worldName = parseWorldName(offlinePlayer, paramsArray);
|
||||
if (worldName == null) return null;
|
||||
@@ -105,9 +103,9 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
}
|
||||
|
||||
// Try get from params
|
||||
String paramWorldName = paramsArray.getLast();
|
||||
String paramWorldName = paramsArray.get(paramsArray.size() - 1);
|
||||
if (worldManager.isLoadedWorld(paramWorldName)) {
|
||||
paramsArray.removeLast();
|
||||
paramsArray.remove(paramsArray.size() - 1);
|
||||
return paramWorldName;
|
||||
}
|
||||
|
||||
@@ -123,7 +121,7 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
// Switch to find what specific placeholder we want
|
||||
switch (placeholder.toLowerCase()) {
|
||||
switch (placeholder.toLowerCase(Locale.ENGLISH)) {
|
||||
case "alias" -> {
|
||||
return world.getAliasOrName();
|
||||
}
|
||||
@@ -179,7 +177,7 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return String.valueOf(world.getSeed());
|
||||
}
|
||||
case "time" -> {
|
||||
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.getFirst() : "";
|
||||
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.get(0) : "";
|
||||
long time = world.getBukkitWorld().map(World::getTime).getOrElse(0L);
|
||||
switch (timeFormat) {
|
||||
case "" -> {
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -87,7 +88,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
registerAsyncCompletion("commands", this::suggestCommands);
|
||||
registerAsyncCompletion("destinations", this::suggestDestinations);
|
||||
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
|
||||
registerStaticCompletion("environments", suggestEnums(World.Environment.class));
|
||||
registerStaticCompletion("environments", List.of("normal", "nether", "the_end")); // Don't tab complete the "custom" environment
|
||||
registerAsyncCompletion("flags", this::suggestFlags);
|
||||
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules);
|
||||
@@ -176,7 +177,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
public <T extends Enum<T>> Collection<String> suggestEnums(Class<T> enumClass) {
|
||||
return EnumSet.allOf(enumClass).stream()
|
||||
.map(Enum::name)
|
||||
.map(String::toLowerCase)
|
||||
.map(name -> name.toLowerCase(Locale.ENGLISH))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -278,19 +279,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" -> {
|
||||
|
||||
@@ -116,7 +116,8 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
}
|
||||
|
||||
return destinationsProvider.parseDestination(context.getSender(), destination)
|
||||
.getOrThrow(failure -> MVInvalidCommandArgument.of(failure.getFailureMessage()));
|
||||
.getOrThrow(failure ->
|
||||
new InvalidCommandArgument(failure.getFailureMessage().formatted(context.getIssuer())));
|
||||
}
|
||||
|
||||
private GameRule<?> parseGameRule(BukkitCommandExecutionContext context) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.command.flag;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
@@ -220,7 +221,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
private void setEnumContext() {
|
||||
this.context = (String value) -> {
|
||||
try {
|
||||
return Enum.valueOf(type, value.toUpperCase());
|
||||
return Enum.valueOf(type, value.toUpperCase(Locale.ENGLISH));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidCommandArgument("Invalid value for argument " + key + ": " + value);
|
||||
}
|
||||
@@ -229,7 +230,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
|
||||
private void setEnumCompletion() {
|
||||
List<String> types = Arrays.stream(type.getEnumConstants())
|
||||
.map(typeClass -> typeClass.name().toLowerCase())
|
||||
.map(typeClass -> typeClass.name().toLowerCase(Locale.ENGLISH))
|
||||
.toList();
|
||||
|
||||
this.completion = (input) -> types;
|
||||
|
||||
@@ -113,9 +113,9 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
.getSpawnCategoryConfig(spawnCategory)
|
||||
.getStringPropertyHandle()
|
||||
.modifyPropertyString(property, value, action)
|
||||
.onSuccess(ignore -> issuer.sendMessage("Successfully set " + property + " to " + value
|
||||
.onSuccess(ignore -> issuer.sendInfo("Successfully set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName()))
|
||||
.onFailure(e -> issuer.sendMessage("Unable to set " + property + " to " + value
|
||||
.onFailure(e -> issuer.sendError("Unable to set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName() + ": " + e.getMessage()));
|
||||
|
||||
worldManager.saveWorldsConfig();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -190,6 +190,26 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.enforceFlight);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnRate(boolean applyEntitySpawnRate) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnRate, applyEntitySpawnRate);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getApplyEntitySpawnRate() {
|
||||
return configHandle.get(configNodes.applyEntitySpawnRate);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnLimit(boolean applyEntitySpawnLimit) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnLimit, applyEntitySpawnLimit);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getApplyEntitySpawnLimit() {
|
||||
return configHandle.get(configNodes.applyEntitySpawnLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -603,6 +623,26 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.bukkitYmlPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setServerPropertiesPath(String serverPropertiesPath) {
|
||||
return configHandle.set(configNodes.serverPropertiesPath, serverPropertiesPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public String getServerPropertiesPath() {
|
||||
return configHandle.get(configNodes.serverPropertiesPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setAutoDetectGeneratorPlugins(boolean autoDetectGeneratorPlugins) {
|
||||
return configHandle.set(configNodes.autoDetectGeneratorPlugins, autoDetectGeneratorPlugins);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getAutoDetectGeneratorPlugins() {
|
||||
return configHandle.get(configNodes.autoDetectGeneratorPlugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -76,13 +76,10 @@ final class CoreConfigNodes {
|
||||
.comment("# █░▀░█ █▄█ █▄▄ ░█░ █ ▀▄▀ ██▄ █▀▄ ▄█ ██▄ █▄▄ █▄█ █▀▄ ██▄ #")
|
||||
.comment("# #")
|
||||
.comment("# #")
|
||||
.comment("# WIKI: https://github.com/Multiverse/Multiverse-Core/wiki #")
|
||||
.comment("# WIKI: https://mvplugins.org/core/ #")
|
||||
.comment("# DISCORD: https://discord.gg/NZtfKky #")
|
||||
.comment("# BUG REPORTS: https://github.com/Multiverse/Multiverse-Core/issues #")
|
||||
.comment("# #")
|
||||
.comment("# #")
|
||||
.comment("# Each option in this file is documented and explained here: #")
|
||||
.comment("# ==> https://github.com/Multiverse/Multiverse-Core/wiki/config.yml #")
|
||||
.comment("# DONATE: https://github.com/sponsors/Multiverse #")
|
||||
.comment("# #")
|
||||
.comment("# #")
|
||||
.comment("# New options are added to this file automatically. If you manually made changes #")
|
||||
@@ -137,6 +134,24 @@ final class CoreConfigNodes {
|
||||
.name("enforce-flight")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnRate = node(ConfigNode.builder("world.apply-entity-spawn-rate", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `tick-rate` config in worlds.yml.")
|
||||
.comment("If disabled, the `tick-rate` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity spawn rate per world.")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-rate")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnLimit = node(ConfigNode.builder("world.apply-entity-spawn-limit", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `spawn-limit` config when a world is loaded.")
|
||||
.comment("If disabled, the `spawn-limit` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity limits per world.")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-limit")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoPurgeEntities = node(ConfigNode.builder("world.auto-purge-entities", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will purge entities on world load based world's entity spawn config.")
|
||||
@@ -187,7 +202,8 @@ 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(" 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.")
|
||||
@@ -494,6 +510,23 @@ final class CoreConfigNodes {
|
||||
.name("bukkit-yml-path")
|
||||
.build());
|
||||
|
||||
final ConfigNode<String> serverPropertiesPath = node(ConfigNode.builder("misc.server-properties-path", String.class)
|
||||
.comment("")
|
||||
.comment("Change this if you use a custom path for the server.properties file with `--config` startup flag.")
|
||||
.comment("Note: this config option needs a server restart to take effect.")
|
||||
.defaultValue("server.properties")
|
||||
.name("server-properties-path")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoDetectGeneratorPlugins = node(ConfigNode.builder("misc.auto-detect-generator-plugins", Boolean.class)
|
||||
.comment("")
|
||||
.comment("When enabled, Multiverse will attempt to automatically detect world generator plugins installed on your server.")
|
||||
.comment("This option only affects tab-completion within `/mv create` command and output of `/mv generators` command.")
|
||||
.comment("Disabling this will not have any affect on the usages of world generator plugins itself.")
|
||||
.defaultValue(true)
|
||||
.name("auto-detect-generator-plugins")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> globalDebug = node(ConfigNode.builder("misc.global-debug", Integer.class)
|
||||
.comment("")
|
||||
.comment("This is our debug flag to help identify issues with Multiverse.")
|
||||
@@ -537,6 +570,7 @@ final class CoreConfigNodes {
|
||||
final ConfigNode<Boolean> showDonationMessage = node(ConfigNode.builder("misc.show-donation-message", Boolean.class)
|
||||
.comment("")
|
||||
.comment("If you don't want to donate, you can set this to false and Multiverse will stop nagging you.")
|
||||
.comment("But if you want to donate, here is our Github Sponsors page: https://github.com/sponsors/Multiverse")
|
||||
.defaultValue(true)
|
||||
.name("show-donation-message")
|
||||
.build());
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
@@ -41,13 +42,13 @@ public final class DefaultStringParserProvider {
|
||||
}
|
||||
|
||||
private static final NodeStringParser<Enum> ENUM_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> Enum.valueOf(type, input.toUpperCase()));
|
||||
() -> Enum.valueOf(type, input.toUpperCase(Locale.ENGLISH)));
|
||||
|
||||
private static final NodeStringParser<String> STRING_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> input);
|
||||
|
||||
private static final NodeStringParser<Boolean> BOOLEAN_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> switch (String.valueOf(input).toLowerCase()) {
|
||||
() -> switch (String.valueOf(input).toLowerCase(Locale.ENGLISH)) {
|
||||
case "t", "true", "on", "y", "yes", "1", "allow" -> true;
|
||||
case "f", "false", "off", "n", "no", "0", "deny" -> false;
|
||||
default -> throw new MultiverseException("Unable to convert '" + input + "' to boolean. Please use 'true' or 'false'");
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.config.node.functions;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -43,7 +44,7 @@ public final class DefaultSuggesterProvider {
|
||||
private static NodeSuggester enumSuggester(Class<?> clazz) {
|
||||
return input -> Arrays.stream(clazz.getEnumConstants())
|
||||
.map(Object::toString)
|
||||
.map(String::toLowerCase)
|
||||
.map(name -> name.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -48,12 +48,12 @@ public final class DefaultSerializerProvider {
|
||||
if (type.isInstance(object)) {
|
||||
return (Enum<?>) object;
|
||||
}
|
||||
return Enum.valueOf(type, String.valueOf(object).toUpperCase());
|
||||
return Enum.valueOf(type, String.valueOf(object).toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Enum object, Class<Enum> type) {
|
||||
return object.name().toLowerCase();
|
||||
return object.name().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ public final class DefaultSerializerProvider {
|
||||
}
|
||||
String input = String.valueOf(object);
|
||||
//todo: this is a copy from string parser
|
||||
return switch (input.toLowerCase()) {
|
||||
return switch (input.toLowerCase(Locale.ENGLISH)) {
|
||||
case "t", "true", "on", "y", "yes", "1", "allow" -> true;
|
||||
case "f", "false", "off", "n", "no", "0", "deny" -> false;
|
||||
default -> throw new RuntimeException("Unable to convert '" + input + "' to boolean.");
|
||||
|
||||
+20
-28
@@ -19,8 +19,12 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.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;
|
||||
@@ -62,7 +66,11 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
* @return A new {@link ExactDestinationInstance}
|
||||
*/
|
||||
public @NotNull ExactDestinationInstance fromLocation(@NotNull Location location) {
|
||||
return new ExactDestinationInstance(this, new UnloadedWorldLocation(location));
|
||||
return new ExactDestinationInstance(
|
||||
this,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,12 +81,16 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
String[] items = REPatterns.COLON.split(destinationParams);
|
||||
String[] items = REPatterns.COLON.split(destinationParams, 2);
|
||||
if (items.length < 2) {
|
||||
if (items[0].equals("@here")) {
|
||||
return getLocationFromSender(sender)
|
||||
.map(location -> Attempt.<ExactDestinationInstance, InstanceFailureReason>success(
|
||||
new ExactDestinationInstance(this, new UnloadedWorldLocation(location))
|
||||
new ExactDestinationInstance(
|
||||
this,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
)
|
||||
))
|
||||
.getOrElse(() -> Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT)); // todo: specific failure reason for this case
|
||||
}
|
||||
@@ -86,41 +98,21 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
}
|
||||
|
||||
String worldName = items[0];
|
||||
String coordinates = items[1];
|
||||
String[] coordinatesParams = REPatterns.COMMA.split(coordinates);
|
||||
if (coordinatesParams.length != 3) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT);
|
||||
}
|
||||
String positionStr = items[1];
|
||||
|
||||
World world = getLoadedMultiverseWorld(worldName).flatMap(LoadedMultiverseWorld::getBukkitWorld).getOrNull();
|
||||
if (world == null) {
|
||||
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
UnloadedWorldLocation location;
|
||||
EntityPosition position;
|
||||
try {
|
||||
location = new UnloadedWorldLocation(
|
||||
world,
|
||||
Double.parseDouble(coordinatesParams[0]),
|
||||
Double.parseDouble(coordinatesParams[1]),
|
||||
Double.parseDouble(coordinatesParams[2])
|
||||
);
|
||||
} catch (NumberFormatException e) {
|
||||
position = EntityPosition.fromString(positionStr);
|
||||
} catch (PositionParseException e) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
|
||||
if (items.length == 4) {
|
||||
String pitch = items[2];
|
||||
String yaw = items[3];
|
||||
try {
|
||||
location.setPitch(Float.parseFloat(pitch));
|
||||
location.setYaw(Float.parseFloat(yaw));
|
||||
} catch (NumberFormatException e) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt.success(new ExactDestinationInstance(this, location));
|
||||
return Attempt.success(new ExactDestinationInstance(this, worldName, position));
|
||||
}
|
||||
|
||||
//TODO: Extract to a world finder class
|
||||
|
||||
+20
-9
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.destination.core;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -8,22 +9,30 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.utils.position.VectorPosition;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link ExactDestination}.
|
||||
*/
|
||||
public final class ExactDestinationInstance extends DestinationInstance<ExactDestinationInstance, ExactDestination> {
|
||||
private final UnloadedWorldLocation location;
|
||||
private final String worldName;
|
||||
private final EntityPosition position;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param location The location to teleport to.
|
||||
* @param destination The parent destination.
|
||||
* @param worldName The name of the world.
|
||||
* @param position The position in the world.
|
||||
*/
|
||||
ExactDestinationInstance(@NotNull ExactDestination destination, @NotNull UnloadedWorldLocation location) {
|
||||
ExactDestinationInstance(@NotNull ExactDestination destination,
|
||||
@NotNull String worldName,
|
||||
@NotNull EntityPosition position) {
|
||||
super(destination);
|
||||
this.location = location;
|
||||
this.worldName = worldName;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,10 +40,13 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Option<Location> getLocation(@NotNull Entity teleportee) {
|
||||
if (location.getWorld() == null) {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world == null) {
|
||||
return Option.none();
|
||||
}
|
||||
return Option.of(location.toBukkitLocation());
|
||||
Location destinationLocation = position.toBukkitLocation(teleportee.getLocation());
|
||||
destinationLocation.setWorld(world);
|
||||
return Option.of(destinationLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +70,7 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Option<String> getFinerPermissionSuffix() {
|
||||
return Option.of(location.getWorld()).map(World::getName);
|
||||
return Option.of(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +78,6 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String serialise() {
|
||||
return location.getWorldName() + ":" + location.getX() + "," + location.getY()
|
||||
+ "," + location.getZ() + ":" + location.getPitch() + ":" + location.getYaw();
|
||||
return worldName + ":" + position.toString();
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.mvplugins.multiverse.core.exceptions.utils.position;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
public class PositionParseException extends MultiverseException {
|
||||
public PositionParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
||||
@@ -43,7 +42,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void foodLevelChange(FoodLevelChangeEvent event) {
|
||||
void foodLevelChange(FoodLevelChangeEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
@@ -65,7 +64,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void entityRegainHealth(EntityRegainHealthEvent event) {
|
||||
void entityRegainHealth(EntityRegainHealthEvent event) {
|
||||
if (event.isCancelled() || event.getRegainReason() != RegainReason.REGEN) {
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +83,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventMethod
|
||||
public void creatureSpawn(CreatureSpawnEvent event) {
|
||||
void creatureSpawn(CreatureSpawnEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
@@ -110,7 +109,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* Handle Spawn Category settings for non-creature entities.
|
||||
*/
|
||||
@EventMethod
|
||||
public void entitySpawn(EntitySpawnEvent event) {
|
||||
void entitySpawn(EntitySpawnEvent event) {
|
||||
if (event.getEntity() instanceof LivingEntity) {
|
||||
// Handled by CreatureSpawnEvent
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerPortalEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
@@ -32,16 +33,16 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.EventRunnable;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventClass;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventPriorityKey;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportQueue;
|
||||
import org.mvplugins.multiverse.core.utils.result.ResultChain;
|
||||
@@ -52,7 +53,6 @@ import org.mvplugins.multiverse.core.world.entrycheck.EntryFeeResult;
|
||||
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder;
|
||||
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
|
||||
import org.spigotmc.event.player.PlayerSpawnLocationEvent;
|
||||
|
||||
/**
|
||||
* Multiverse's Listener for players.
|
||||
@@ -72,6 +72,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
private final EnforcementHandler enforcementHandler;
|
||||
private final DimensionFinder dimensionFinder;
|
||||
private final CorePermissionsChecker corePermissionsChecker;
|
||||
private final AsyncSafetyTeleporter asyncSafetyTeleporter;
|
||||
|
||||
private final Map<String, String> playerWorld = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -89,7 +90,8 @@ final class MVPlayerListener implements CoreListener {
|
||||
DestinationsProvider destinationsProvider,
|
||||
EnforcementHandler enforcementHandler,
|
||||
DimensionFinder dimensionFinder,
|
||||
CorePermissionsChecker corePermissionsChecker) {
|
||||
CorePermissionsChecker corePermissionsChecker,
|
||||
AsyncSafetyTeleporter asyncSafetyTeleporter) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.worldManagerProvider = worldManagerProvider;
|
||||
@@ -103,6 +105,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
this.enforcementHandler = enforcementHandler;
|
||||
this.dimensionFinder = dimensionFinder;
|
||||
this.corePermissionsChecker = corePermissionsChecker;
|
||||
this.asyncSafetyTeleporter = asyncSafetyTeleporter;
|
||||
}
|
||||
|
||||
private WorldManager getWorldManager() {
|
||||
@@ -113,7 +116,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
return commandManagerProvider.get();
|
||||
}
|
||||
|
||||
public PluginLocales getLocales() {
|
||||
private PluginLocales getLocales() {
|
||||
return getCommandManager().getLocales();
|
||||
}
|
||||
|
||||
@@ -122,7 +125,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
*
|
||||
* @return the playerWorld-map
|
||||
*/
|
||||
public Map<String, String> getPlayerWorld() {
|
||||
Map<String, String> getPlayerWorld() {
|
||||
return playerWorld;
|
||||
}
|
||||
|
||||
@@ -134,7 +137,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-respawn")
|
||||
@DefaultEventPriority(EventPriority.LOW)
|
||||
public void playerRespawn(PlayerRespawnEvent event) {
|
||||
void playerRespawn(PlayerRespawnEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
LoadedMultiverseWorld mvWorld = getWorldManager().getLoadedWorld(player.getWorld()).getOrNull();
|
||||
if (mvWorld == null) {
|
||||
@@ -201,68 +204,63 @@ final class MVPlayerListener implements CoreListener {
|
||||
return Option.of(mvWorld.getSpawnLocation());
|
||||
}
|
||||
|
||||
@EventClass("org.spigotmc.event.player.PlayerSpawnLocationEvent")
|
||||
@EventMethod
|
||||
// TODO: Consider if this key is needed anymore, need to remove from config.yml as well
|
||||
@EventPriorityKey("mvcore-player-spawn-location")
|
||||
EventRunnable playerSpawnLocation() {
|
||||
return new EventRunnable<PlayerSpawnLocationEvent>() {
|
||||
@Override
|
||||
public void onEvent(PlayerSpawnLocationEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
MultiverseWorld world = getWorldManager().getLoadedWorld(player.getWorld()).getOrNull();
|
||||
if (world == null) {
|
||||
Logging.finer("Player joined in a world that is not managed by Multiverse.");
|
||||
return;
|
||||
}
|
||||
if (!player.hasPlayedBefore()) {
|
||||
handleFirstSpawn(event);
|
||||
} else {
|
||||
handleJoinLocation(event);
|
||||
}
|
||||
handleGameModeAndFlight(player, event.getSpawnLocation().getWorld());
|
||||
}
|
||||
void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (player.hasPlayedBefore()) {
|
||||
handleJoinLocation(player);
|
||||
} else {
|
||||
handleFirstSpawn(player);
|
||||
}
|
||||
handleGameModeAndFlight(player, player.getWorld());
|
||||
}
|
||||
|
||||
private void handleFirstSpawn(PlayerSpawnLocationEvent event) {
|
||||
if (!config.getFirstSpawnOverride()) {
|
||||
Logging.finer("FirstSpawnOverride is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
Logging.fine("Moving NEW player to(firstspawnoverride): %s", config.getFirstSpawnLocation());
|
||||
destinationsProvider.parseDestination(config.getFirstSpawnLocation())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in FirstSpawnLocation in config is invalid")))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in FirstSpawnLocation in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
private void handleFirstSpawn(Player player) {
|
||||
if (!config.getFirstSpawnOverride()) {
|
||||
Logging.finer("FirstSpawnOverride is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
Logging.fine("Moving NEW player to(firstspawnoverride): %s", config.getFirstSpawnLocation());
|
||||
destinationsProvider.parseDestination(config.getFirstSpawnLocation())
|
||||
.peek(destination -> teleportToDestinationOnJoin(player, destination))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in FirstSpawnLocation in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
|
||||
private void handleJoinLocation(PlayerSpawnLocationEvent event) {
|
||||
if (!config.getEnableJoinDestination()) {
|
||||
Logging.finer("JoinDestination is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
if (config.getJoinDestination().isBlank()) {
|
||||
Logging.warning("Joindestination is enabled but no destination has been specified in config!");
|
||||
return;
|
||||
}
|
||||
if (corePermissionsChecker.hasJoinLocationBypassPermission(event.getPlayer())) {
|
||||
Logging.finer("Player %s has bypass permission for JoinDestination", event.getPlayer().getName());
|
||||
return;
|
||||
}
|
||||
Logging.finer("JoinDestination is " + config.getJoinDestination());
|
||||
destinationsProvider.parseDestination(config.getJoinDestination())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in JoinDestination in config is invalid")))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in JoinDestination in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
};
|
||||
private void handleJoinLocation(Player player) {
|
||||
if (!config.getEnableJoinDestination()) {
|
||||
Logging.finer("JoinDestination is disabled");
|
||||
// User has disabled the feature in config
|
||||
return;
|
||||
}
|
||||
if (config.getJoinDestination().isBlank()) {
|
||||
Logging.warning("Joindestination is enabled but no destination has been specified in config!");
|
||||
return;
|
||||
}
|
||||
if (corePermissionsChecker.hasJoinLocationBypassPermission(player)) {
|
||||
Logging.finer("Player %s has bypass permission for JoinDestination", player.getName());
|
||||
return;
|
||||
}
|
||||
Logging.finer("JoinDestination is " + config.getJoinDestination());
|
||||
destinationsProvider.parseDestination(config.getJoinDestination())
|
||||
.peek(destination -> teleportToDestinationOnJoin(player, destination))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in JoinDestination in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
|
||||
private void teleportToDestinationOnJoin(Player player, DestinationInstance<?, ?> destination) {
|
||||
asyncSafetyTeleporter.to(destination).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.get(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +269,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@DefaultEventPriority(EventPriority.MONITOR)
|
||||
public void playerChangedWorld(PlayerChangedWorldEvent event) {
|
||||
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());
|
||||
@@ -284,7 +282,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-teleport")
|
||||
@DefaultEventPriority(EventPriority.HIGHEST)
|
||||
public void playerTeleport(PlayerTeleportEvent event) {
|
||||
void playerTeleport(PlayerTeleportEvent event) {
|
||||
Logging.finer("Got teleport event for player '"
|
||||
+ event.getPlayer().getName() + "' with cause '" + event.getCause() + "'");
|
||||
if (event.isCancelled()) {
|
||||
@@ -355,7 +353,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@DefaultEventPriority(EventPriority.LOWEST)
|
||||
public void playerPortalCheck(PlayerPortalEvent event) {
|
||||
void playerPortalCheck(PlayerPortalEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
@@ -381,7 +379,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-portal")
|
||||
@DefaultEventPriority(EventPriority.HIGH)
|
||||
public void playerPortal(PlayerPortalEvent event) {
|
||||
void playerPortal(PlayerPortalEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import jakarta.inject.Inject;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.PortalType;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.entity.EntityPortalEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
@@ -48,7 +47,7 @@ final class MVPortalListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@IgnoreIfCancelled
|
||||
public void portalCreate(PortalCreateEvent event) {
|
||||
void portalCreate(PortalCreateEvent event) {
|
||||
Logging.fine("Attempting to create portal at '%s' with reason: %s",
|
||||
event.getWorld().getName(), event.getReason());
|
||||
|
||||
@@ -93,7 +92,7 @@ final class MVPortalListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@IgnoreIfCancelled
|
||||
public void playerInteract(PlayerInteractEvent event) {
|
||||
void playerInteract(PlayerInteractEvent event) {
|
||||
if (isCreateEndPortalInteraction(event)) {
|
||||
return;
|
||||
}
|
||||
@@ -122,7 +121,7 @@ final class MVPortalListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void entityPortal(EntityPortalEvent event) {
|
||||
void entityPortal(EntityPortalEvent event) {
|
||||
if (event.isCancelled() || event.getTo() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package org.mvplugins.multiverse.core.listeners;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.weather.ThunderChangeEvent;
|
||||
import org.bukkit.event.weather.WeatherChangeEvent;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -35,7 +34,7 @@ final class MVWeatherListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void weatherChange(WeatherChangeEvent event) {
|
||||
void weatherChange(WeatherChangeEvent event) {
|
||||
if (event.isCancelled() || !event.toWeatherState()) {
|
||||
return;
|
||||
}
|
||||
@@ -54,7 +53,7 @@ final class MVWeatherListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void thunderChange(ThunderChangeEvent event) {
|
||||
void thunderChange(ThunderChangeEvent event) {
|
||||
if (event.isCancelled() || !event.toThunderState()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -346,6 +346,11 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
EXCEPTION_MULTIVERSEWORLD_UNLOADPLAYERSINWORLD,
|
||||
EXCEPTION_MULTIVERSEWORLD_UNLOADERROR,
|
||||
|
||||
// multiverse position parse exception
|
||||
EXCEPTION_POSITIONPARSE_INVALIDDIRECTION,
|
||||
EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
|
||||
EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
|
||||
|
||||
// generic
|
||||
GENERIC_SUCCESS,
|
||||
GENERIC_FAILURE,
|
||||
|
||||
@@ -10,18 +10,16 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.MultiverseCoreApi;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commands.CoreCommand;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.DynamicListener;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.DynamicListenerRegistration;
|
||||
import org.mvplugins.multiverse.core.inject.PluginServiceLocator;
|
||||
import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory;
|
||||
import org.mvplugins.multiverse.core.inject.binder.PluginBinder;
|
||||
import org.mvplugins.multiverse.core.listeners.CoreListener;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Common plugin class for all Multiverse plugins.
|
||||
*/
|
||||
@@ -151,7 +149,7 @@ public abstract class MultiverseModule extends JavaPlugin {
|
||||
.andThen(pluginLocales -> {
|
||||
pluginLocales.addFileResClassLoader(this);
|
||||
pluginLocales.addBundleClassLoader(this.getClassLoader());
|
||||
pluginLocales.addMessageBundles(this.getDescription().getName().toLowerCase());
|
||||
pluginLocales.addMessageBundles(this.getName().toLowerCase(Locale.ENGLISH));
|
||||
})
|
||||
.onFailure(e -> {
|
||||
Logging.severe("Failed to register locales");
|
||||
|
||||
@@ -228,8 +228,8 @@ public final class LocationManipulation {
|
||||
if (orientation == null) {
|
||||
return 0;
|
||||
}
|
||||
if (ORIENTATION_INTS.containsKey(orientation.toLowerCase())) {
|
||||
return ORIENTATION_INTS.get(orientation.toLowerCase());
|
||||
if (ORIENTATION_INTS.containsKey(orientation.toLowerCase(Locale.ENGLISH))) {
|
||||
return ORIENTATION_INTS.get(orientation.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public final class FileUtils {
|
||||
*/
|
||||
public @Nullable File getServerProperties() {
|
||||
if (this.serverProperties == null) {
|
||||
this.serverProperties = findFileFromServerDirectory("server.properties");
|
||||
this.serverProperties = findFileFromServerDirectory(config.getServerPropertiesPath());
|
||||
Logging.finer("server.properties: %s", this.serverProperties);
|
||||
}
|
||||
return this.serverProperties;
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
/**
|
||||
* Represents a position for an entity in 3D space, including both coordinates and facing direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class EntityPosition {
|
||||
|
||||
/**
|
||||
* Creates an EntityPosition with absolute coordinates and direction.
|
||||
*
|
||||
* @param x The absolute X coordinate.
|
||||
* @param y The absolute Y coordinate.
|
||||
* @param z The absolute Z coordinate.
|
||||
* @param pitch The absolute pitch (vertical angle).
|
||||
* @param yaw The absolute yaw (horizontal angle).
|
||||
* @return A new EntityPosition instance with the specified absolute coordinates and direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition ofAbsolute(double x, double y, double z, double pitch, double yaw) {
|
||||
return new EntityPosition(
|
||||
VectorPosition.ofAbsolute(x, y, z),
|
||||
FaceDirection.ofAbsolute(pitch, yaw)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EntityPosition from a Bukkit Location with absolute coordinates and direction.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new EntityPosition instance representing the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition ofLocation(Location location) {
|
||||
return new EntityPosition(VectorPosition.ofLocation(location), FaceDirection.ofLocation(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an EntityPosition from a string representation.
|
||||
* The expected format is "<x>,<y>,<z>:<pitch>:<yaw>" for absolute coordinates and direction,
|
||||
* or "<x>,<y>,<z>" for absolute coordinates with default direction (0 pitch, 0 yaw).
|
||||
* <br>
|
||||
* Relative coordinates and direction can be specified using the '~' prefix, e.g., "~10,~,~-10:0:90".
|
||||
*
|
||||
* @param positionStr The string representation of the position.
|
||||
* @return A new EntityPosition instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition fromString(String positionStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COLON.split(positionStr, 2);
|
||||
return parts.length == 2
|
||||
? new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.fromString(parts[1]))
|
||||
: new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.ofAbsolute(0, 0));
|
||||
}
|
||||
|
||||
private final VectorPosition vector;
|
||||
private final FaceDirection direction;
|
||||
|
||||
/**
|
||||
* Creates a new EntityPosition with the specified vector and direction.
|
||||
*
|
||||
* @param vector The vector position (coordinates).
|
||||
* @param direction The facing direction (pitch and yaw).
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public EntityPosition(VectorPosition vector, FaceDirection direction) {
|
||||
this.vector = vector;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vector position (coordinates).
|
||||
*
|
||||
* @return The vector position.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public VectorPosition getVector() {
|
||||
return vector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the facing direction (pitch and yaw).
|
||||
*
|
||||
* @return The facing direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public FaceDirection getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments a given Bukkit Location by applying the relative components of this EntityPosition.
|
||||
* This modifies the provided Location in place.
|
||||
*
|
||||
* @param base The base Bukkit Location to augment and offset for relative positioning as required.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location base) {
|
||||
vector.augmentBukkitLocation(base);
|
||||
direction.augmentBukkitLocation(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this EntityPosition to a new Bukkit Location based on a given base Location.
|
||||
* This does not modify the base Location, but returns a new Location instance.
|
||||
*
|
||||
* @param base The base Bukkit Location to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Location representing this EntityPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Location toBukkitLocation(Location base) {
|
||||
return new Location(
|
||||
base.getWorld(),
|
||||
vector.getX().getValue(base.getX()),
|
||||
vector.getY().getValue(base.getY()),
|
||||
vector.getZ().getValue(base.getZ()),
|
||||
(float) direction.getYaw().getValue(base.getYaw()),
|
||||
(float) direction.getPitch().getValue(base.getPitch())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return vector + ":" + direction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents a direction to face, defined by pitch and yaw.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class FaceDirection {
|
||||
|
||||
/**
|
||||
* Creates a FaceDirection with absolute pitch and yaw.
|
||||
*
|
||||
* @param pitch The absolute pitch (vertical angle).
|
||||
* @param yaw The absolute yaw (horizontal angle).
|
||||
* @return A new FaceDirection instance with the specified absolute pitch and yaw.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection ofAbsolute(double pitch, double yaw) {
|
||||
return new FaceDirection(
|
||||
PositionNumber.ofAbsolute(pitch),
|
||||
PositionNumber.ofAbsolute(yaw)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pitch and yaw from a Bukkit Location to create an FaceDirection with absolute values.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new FaceDirection instance representing the direction of the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection ofLocation(org.bukkit.Location location) {
|
||||
return new FaceDirection(
|
||||
PositionNumber.ofAbsolute(location.getPitch()),
|
||||
PositionNumber.ofAbsolute(location.getYaw())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a FaceDirection from a string representation.
|
||||
* The expected format is "<pitch>:<yaw>" for absolute pitch and yaw.
|
||||
* <br>
|
||||
* Relative pitch and yaw can be specified using the '~' prefix, e.g., "~:~5".
|
||||
*
|
||||
* @param directionStr The string representation of the direction.
|
||||
* @return A new FaceDirection instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection fromString(String directionStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COLON.split(directionStr, 2);
|
||||
if (parts.length != 2) {
|
||||
throw new PositionParseException(Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDDIRECTION,
|
||||
replace("{format}").with(directionStr)));
|
||||
}
|
||||
//TODO: Add support for compass directions (N, S, E, W, NE, NW, SE, SW) for yaw
|
||||
PositionNumber pitch = PositionNumber.fromString(parts[0]);
|
||||
PositionNumber yaw = PositionNumber.fromString(parts[1]);
|
||||
return new FaceDirection(pitch, yaw);
|
||||
}
|
||||
|
||||
private final PositionNumber pitch;
|
||||
private final PositionNumber yaw;
|
||||
|
||||
/**
|
||||
* Creates a new FaceDirection with the specified pitch and yaw.
|
||||
*
|
||||
* @param pitch The pitch (vertical angle).
|
||||
* @param yaw The yaw (horizontal angle).
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public FaceDirection(PositionNumber pitch, PositionNumber yaw) {
|
||||
this.pitch = pitch;
|
||||
this.yaw = yaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pitch component of this FaceDirection.
|
||||
*
|
||||
* @return The pitch number representation.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getPitch() {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the yaw component of this FaceDirection.
|
||||
*
|
||||
* @return The yaw number representation.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getYaw() {
|
||||
return yaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this FaceDirection to a given Bukkit Location, modifying its pitch and yaw.
|
||||
* <br>
|
||||
* Relative pitch and yaw will be offset based on the current values in the Location.
|
||||
*
|
||||
* @param base The Bukkit Location to modify.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location base) {
|
||||
base.setPitch((float) pitch.getValue(base.getPitch()));
|
||||
base.setYaw((float) yaw.getValue(base.getYaw()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pitch + ":" + yaw;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents a number that can be either absolute or relative (prefixed with '~').
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public sealed interface PositionNumber permits PositionNumber.Relative, PositionNumber.Absolute {
|
||||
|
||||
/**
|
||||
* Creates an absolute PositionNumber. Absolute numbers represent fixed coordinates that will not be adjusted
|
||||
* based on any base value.
|
||||
*
|
||||
* @param value the absolute value
|
||||
* @return a PositionNumber representing the absolute value
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber ofAbsolute(double value) {
|
||||
return new Absolute(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a relative PositionNumber. Relative numbers are prefixed with '~' and represent an offset
|
||||
* from a base value. For example, a relative PositionNumber of '~5' means 5 units more than the base value,
|
||||
* while '~ -3' means 3 units less than the base value.
|
||||
*
|
||||
* @param value the relative offset value
|
||||
* @return a PositionNumber representing the relative offset
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber ofRelative(double value) {
|
||||
return new Relative(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a PositionNumber from a string. The string can represent either an absolute number (e.g., "10.5")
|
||||
* or a relative number prefixed with '~' (e.g., "~5" or "~ -3"). If the string is "~" with no number,
|
||||
* it is treated as a relative value of 0.
|
||||
*
|
||||
* @param string the string to parse
|
||||
* @return a PositionNumber representing the parsed value.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber fromString(String string) throws PositionParseException {
|
||||
if (string.startsWith("~")) {
|
||||
if (string.length() == 1) {
|
||||
return new Relative(0);
|
||||
}
|
||||
return new Relative(tryParseDouble(string.substring(1)));
|
||||
}
|
||||
return new Absolute(tryParseDouble(string));
|
||||
}
|
||||
|
||||
private static double tryParseDouble(String str) throws PositionParseException {
|
||||
return Try.of(() -> Double.parseDouble(str))
|
||||
.getOrElseThrow(throwable -> new PositionParseException(
|
||||
Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
|
||||
replace("{number}").with(str))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the effective value based on the base value.
|
||||
* For absolute PositionNumbers, this returns the stored value.
|
||||
* For relative PositionNumbers, this returns base + stored value.
|
||||
*
|
||||
* @param base the base value to use for relative calculations
|
||||
* @return the calculated effective value
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
double getValue(double base);
|
||||
|
||||
/**
|
||||
* Checks if this PositionNumber is relative.
|
||||
*
|
||||
* @return true if this is a relative PositionNumber, false if absolute
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
boolean isRelative();
|
||||
|
||||
/**
|
||||
* Checks if this PositionNumber is absolute.
|
||||
*
|
||||
* @return true if this is an absolute PositionNumber, false if relative
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
boolean isAbsolute();
|
||||
|
||||
/**
|
||||
* Gets the raw stored value without any base adjustment.
|
||||
*
|
||||
* @return the raw value (for relative, this is the offset; for absolute, this is the fixed value)
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
double getRawValue();
|
||||
|
||||
final class Relative implements PositionNumber {
|
||||
|
||||
private final double value;
|
||||
|
||||
Relative(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue(double base) {
|
||||
return base + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelative() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbsolute() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "~" + value;
|
||||
}
|
||||
}
|
||||
|
||||
final class Absolute implements PositionNumber {
|
||||
|
||||
private final double value;
|
||||
|
||||
Absolute(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue(double base) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelative() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbsolute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Double.toString(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents an x, y, z position in 3D space, with support for absolute and relative coordinates.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class VectorPosition {
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition with absolute coordinates.
|
||||
* @param x The absolute x coordinate.
|
||||
* @param y The absolute y coordinate.
|
||||
* @param z The absolute z coordinate.
|
||||
* @return A new VectorPosition instance with the specified absolute coordinates.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition ofAbsolute(double x, double y, double z) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(x),
|
||||
PositionNumber.ofAbsolute(y),
|
||||
PositionNumber.ofAbsolute(z)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition from a Bukkit Vector, using absolute coordinates.
|
||||
*
|
||||
* @param vector The Bukkit Vector to convert.
|
||||
* @return A new VectorPosition instance representing the given vector.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition ofVector(Vector vector) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(vector.getX()),
|
||||
PositionNumber.ofAbsolute(vector.getY()),
|
||||
PositionNumber.ofAbsolute(vector.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition from a Bukkit Location, using absolute coordinates.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new VectorPosition instance representing the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
public static VectorPosition ofLocation(Location location) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(location.getX()),
|
||||
PositionNumber.ofAbsolute(location.getY()),
|
||||
PositionNumber.ofAbsolute(location.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a VectorPosition from a string representation.
|
||||
* The expected format is "<x>,<y>,<z>" for absolute or relative coordinates.
|
||||
* <br>
|
||||
* Relative coordinates can be specified using the '~' prefix, e.g., "~10,~,~-10".
|
||||
*
|
||||
* @param coordStr The string representation of the coordinates.
|
||||
* @return A new VectorPosition instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition fromString(String coordStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COMMA.split(coordStr);
|
||||
if (parts.length != 3) {
|
||||
throw new PositionParseException(Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
|
||||
replace("{format}").with(coordStr)));
|
||||
}
|
||||
return new VectorPosition(
|
||||
PositionNumber.fromString(parts[0]),
|
||||
PositionNumber.fromString(parts[1]),
|
||||
PositionNumber.fromString(parts[2])
|
||||
);
|
||||
}
|
||||
|
||||
private final PositionNumber x;
|
||||
private final PositionNumber y;
|
||||
private final PositionNumber z;
|
||||
|
||||
/**
|
||||
* Creates a new VectorPosition with the specified PositionNumbers for x, y, and z.
|
||||
*
|
||||
* @param x The PositionNumber for the x coordinate.
|
||||
* @param y The PositionNumber for the y coordinate.
|
||||
* @param z The PositionNumber for the z coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public VectorPosition(PositionNumber x, PositionNumber y, PositionNumber z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the x coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the x coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the y coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the y coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the z coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the z coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the given Bukkit Vector in place by applying this VectorPosition's coordinates.
|
||||
* Relative coordinates will adjust the existing values, while absolute coordinates will set them directly.
|
||||
*
|
||||
* @param base The Bukkit Vector to augment.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitVector(Vector base) {
|
||||
base.setX(x.getValue(base.getX()));
|
||||
base.setY(y.getValue(base.getY()));
|
||||
base.setZ(z.getValue(base.getZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the given Bukkit Location in place by applying this VectorPosition's coordinates.
|
||||
* Relative coordinates will adjust the existing values, while absolute coordinates will set them directly.
|
||||
*
|
||||
* @param location The Bukkit Location to augment.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location location) {
|
||||
location.setX(x.getValue(location.getX()));
|
||||
location.setY(y.getValue(location.getY()));
|
||||
location.setZ(z.getValue(location.getZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this VectorPosition to a new Bukkit Vector based on a given base Vector.
|
||||
* This does not modify the base Vector, but returns a new Vector instance.
|
||||
*
|
||||
* @param base The base Bukkit Vector to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Vector representing this VectorPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Vector toBukkitVector(Vector base) {
|
||||
return new Vector(
|
||||
x.getValue(base.getX()),
|
||||
y.getValue(base.getY()),
|
||||
z.getValue(base.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this VectorPosition to a new Bukkit Location based on a given base Location.
|
||||
* This does not modify the base Location, but returns a new Location instance.
|
||||
*
|
||||
* @param base The base Bukkit Location to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Location representing this VectorPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Location toBukkitLocation(Location base) {
|
||||
return new Location(
|
||||
base.getWorld(),
|
||||
x.getValue(base.getX()),
|
||||
y.getValue(base.getY()),
|
||||
z.getValue(base.getZ()),
|
||||
base.getYaw(),
|
||||
base.getPitch()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return x + "," + y + "," + z;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -417,7 +418,7 @@ final class WorldConfig {
|
||||
|
||||
if (alias.get().isEmpty()) return;
|
||||
|
||||
Try.of(() -> Enum.valueOf(EnglishChatColor.class, color.toUpperCase()))
|
||||
Try.of(() -> Enum.valueOf(EnglishChatColor.class, color.toUpperCase(Locale.ENGLISH)))
|
||||
.map(c -> c.color)
|
||||
.onSuccess(c -> {
|
||||
if (c != ChatColor.WHITE) {
|
||||
@@ -425,7 +426,7 @@ final class WorldConfig {
|
||||
}
|
||||
});
|
||||
|
||||
Try.of(() -> Enum.valueOf(EnglishChatStyle.class, style.toUpperCase()))
|
||||
Try.of(() -> Enum.valueOf(EnglishChatStyle.class, style.toUpperCase(Locale.ENGLISH)))
|
||||
.map(c -> c.color)
|
||||
.onSuccess(s -> {
|
||||
if (s != null) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldPropertyChangedEvent;
|
||||
import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
@@ -37,11 +38,13 @@ final class WorldConfigNodes {
|
||||
private final NodeGroup nodes = new NodeGroup();
|
||||
private WorldManager worldManager;
|
||||
private EnforcementHandler enforcementHandler;
|
||||
private CoreConfig config;
|
||||
private MultiverseWorld world = null;
|
||||
|
||||
WorldConfigNodes(@NotNull MultiverseCore multiverseCore) {
|
||||
this.worldManager = multiverseCore.getServiceLocator().getService(WorldManager.class);
|
||||
this.enforcementHandler = multiverseCore.getServiceLocator().getService(EnforcementHandler.class);
|
||||
this.config = multiverseCore.getServiceLocator().getService(CoreConfig.class);
|
||||
}
|
||||
|
||||
MultiverseWorld getWorld() {
|
||||
@@ -238,13 +241,13 @@ final class WorldConfigNodes {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
if (newValue == null || newValue instanceof NullSpawnLocation) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
bukkitWorld.setSpawnLocation(newValue);
|
||||
newValue.setWorld(bukkitWorld);
|
||||
bukkitWorld.setSpawnLocation(newValue);
|
||||
});
|
||||
}));
|
||||
|
||||
final ConfigNode<EntitySpawnConfig> enititySpawnConfig = node(ConfigNode.builder("spawning", EntitySpawnConfig.class)
|
||||
.defaultValue(() -> EntitySpawnConfig.fromSection(new MemoryConfiguration()))
|
||||
.defaultValue(() -> EntitySpawnConfig.fromSection(config, new MemoryConfiguration()))
|
||||
.hidden()
|
||||
.serializer(new NodeSerializer<>() {
|
||||
@Override
|
||||
@@ -252,7 +255,7 @@ final class WorldConfigNodes {
|
||||
ConfigurationSection spawnSection = (object instanceof ConfigurationSection section)
|
||||
? section
|
||||
: new MemoryConfiguration();
|
||||
return EntitySpawnConfig.fromSection(spawnSection);
|
||||
return EntitySpawnConfig.fromSection(config, spawnSection);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -370,12 +371,20 @@ public final class WorldManager {
|
||||
private LoadedMultiverseWorld newLoadedMultiverseWorld(
|
||||
@NotNull World world, @Nullable String generator, @Nullable String biome, boolean adjustSpawn) {
|
||||
WorldConfig worldConfig = worldsConfigManager.addWorldConfig(world.getName());
|
||||
|
||||
// Properties from multiverse input
|
||||
worldConfig.setAdjustSpawn(adjustSpawn);
|
||||
worldConfig.setGenerator(generator == null ? "" : generator);
|
||||
worldConfig.setBiome(biome == null ? "" : biome);
|
||||
|
||||
// Properties from the bukkit world
|
||||
worldConfig.setDifficulty(world.getDifficulty());
|
||||
worldConfig.setKeepSpawnInMemory(world.getKeepSpawnInMemory());
|
||||
worldConfig.setScale(getCoordinateScale(world));
|
||||
|
||||
worldConfig.save();
|
||||
|
||||
MultiverseWorld mvWorld = newMultiverseWorld(world.getName(), worldConfig);
|
||||
newMultiverseWorld(world.getName(), worldConfig);
|
||||
LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld(
|
||||
world,
|
||||
worldConfig,
|
||||
@@ -384,20 +393,21 @@ public final class WorldManager {
|
||||
locationManipulation,
|
||||
entityPurger
|
||||
);
|
||||
setDefaultEnvironmentScale(mvWorld);
|
||||
loadedWorldsMap.put(loadedWorld.getName(), loadedWorld);
|
||||
saveWorldsConfig();
|
||||
pluginManager.callEvent(new MVWorldLoadedEvent(loadedWorld));
|
||||
return loadedWorld;
|
||||
}
|
||||
|
||||
private void setDefaultEnvironmentScale(MultiverseWorld world) {
|
||||
double scale = switch (world.getEnvironment()) {
|
||||
case NETHER -> 8.0;
|
||||
case THE_END -> 16.0;
|
||||
default -> 1.0;
|
||||
private double getCoordinateScale(World world) {
|
||||
if (PaperLib.isPaper()) {
|
||||
return world.getCoordinateScale();
|
||||
}
|
||||
return switch (world.getEnvironment()) {
|
||||
case NORMAL -> 1;
|
||||
case NETHER -> 8;
|
||||
default -> 1;
|
||||
};
|
||||
world.setScale(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -649,12 +659,18 @@ public final class WorldManager {
|
||||
*/
|
||||
public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull DeleteWorldOptions options) {
|
||||
return getLoadedWorld(options.world()).fold(
|
||||
() -> loadWorld(LoadWorldOptions.world(options.world()))
|
||||
.transform(DeleteFailureReason.LOAD_FAILED)
|
||||
.mapAttempt(world -> doDeleteWorld(world, options)),
|
||||
() -> loadThenDeleteWorld(options),
|
||||
world -> doDeleteWorld(world, options));
|
||||
}
|
||||
|
||||
private Attempt<String, DeleteFailureReason> loadThenDeleteWorld(@NotNull DeleteWorldOptions options) {
|
||||
return loadWorld(LoadWorldOptions.world(options.world()))
|
||||
.fold(
|
||||
ignore -> worldActionResult(DeleteFailureReason.LOAD_FAILED, options.world().getName()),
|
||||
loadedWorld -> doDeleteWorld(loadedWorld, options)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing multiverse world entirely. Warning: This will delete all world files.
|
||||
*
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
/**
|
||||
* Manages the worlds.yml file.
|
||||
@@ -81,8 +82,9 @@ final class WorldsConfigManager {
|
||||
}
|
||||
|
||||
// Copy old config file to `worlds.yml.old`
|
||||
Path oldWorldConfig = worldConfigFile.toPath().getParent().resolve(CONFIG_FILENAME + ".old");
|
||||
Files.copy(worldConfigFile.toPath(), oldWorldConfig, COPY_ATTRIBUTES);
|
||||
Path oldWorldConfig = worldConfigFile.toPath().getParent()
|
||||
.resolve(CONFIG_FILENAME + ".old." + System.currentTimeMillis());
|
||||
Files.copy(worldConfigFile.toPath(), oldWorldConfig, COPY_ATTRIBUTES, REPLACE_EXISTING);
|
||||
|
||||
return configData.replace("==: MVWorld", "w@: world")
|
||||
.replace("==: MVSpawnSettings", "")
|
||||
|
||||
+8
-2
@@ -1,5 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.world.biomeprovider;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -7,6 +9,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A parser for {@link SingleBiomeProvider}
|
||||
@@ -17,13 +20,16 @@ final class SingleBiomeProviderParser implements BiomeProviderParser {
|
||||
|
||||
@Override
|
||||
public BiomeProvider parseBiomeProvider(@NotNull String worldName, @NotNull String params) {
|
||||
return new SingleBiomeProvider(Biome.valueOf(params.toUpperCase()));
|
||||
NamespacedKey biomeKey = NamespacedKey.fromString(params.toLowerCase(Locale.ENGLISH));
|
||||
return new SingleBiomeProvider(Registry.BIOME.get(biomeKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> suggestParams(@NotNull String currentInput) {
|
||||
if (biomes == null) {
|
||||
biomes = Arrays.stream(Biome.values()).map(biome -> biome.toString().toLowerCase()).toList();
|
||||
biomes = Registry.BIOME.stream()
|
||||
.map(biome -> biome.getKey().getKey().toLowerCase(Locale.ENGLISH))
|
||||
.toList();
|
||||
}
|
||||
return biomes;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
package org.mvplugins.multiverse.core.world.entity;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public final class EntitySpawnConfig {
|
||||
|
||||
private final CoreConfig config;
|
||||
private final Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig;
|
||||
|
||||
EntitySpawnConfig(Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig) {
|
||||
EntitySpawnConfig(CoreConfig config, Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig) {
|
||||
this.config = config;
|
||||
this.spawnCategoriesConfig = spawnCategoriesConfig;
|
||||
}
|
||||
|
||||
public SpawnCategoryConfig getSpawnCategoryConfig(SpawnCategory spawnCategory) {
|
||||
return spawnCategoriesConfig.computeIfAbsent(spawnCategory,
|
||||
computeSpawnCategory -> new SpawnCategoryConfig(computeSpawnCategory, new MemoryConfiguration()));
|
||||
computeSpawnCategory -> new SpawnCategoryConfig(
|
||||
config,
|
||||
computeSpawnCategory,
|
||||
new MemoryConfiguration()
|
||||
));
|
||||
}
|
||||
|
||||
public boolean shouldAllowSpawn(Entity entity) {
|
||||
@@ -46,23 +53,23 @@ public final class EntitySpawnConfig {
|
||||
public ConfigurationSection toSection() {
|
||||
MemoryConfiguration section = new MemoryConfiguration();
|
||||
spawnCategoriesConfig.forEach((spawnCategory, spawnCategoryConfig) -> {
|
||||
section.set(spawnCategory.toString().toLowerCase(), spawnCategoryConfig.saveSection());
|
||||
section.set(spawnCategory.toString().toLowerCase(Locale.ENGLISH), spawnCategoryConfig.saveSection());
|
||||
});
|
||||
return section;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static EntitySpawnConfig fromSection(ConfigurationSection section) {
|
||||
Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig = new HashMap<>();
|
||||
public static EntitySpawnConfig fromSection(CoreConfig config, ConfigurationSection section) {
|
||||
Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig = new LinkedHashMap<>();
|
||||
section.getValues(false).forEach((key, value) -> {
|
||||
if (!(value instanceof ConfigurationSection sectionPart)) {
|
||||
Logging.warning("Invalid spawn category config for " + key + ": " + value);
|
||||
return;
|
||||
}
|
||||
SpawnCategory spawnCategory = SpawnCategory.valueOf(key.toUpperCase());
|
||||
spawnCategoriesConfig.put(spawnCategory, new SpawnCategoryConfig(spawnCategory, sectionPart));
|
||||
SpawnCategory spawnCategory = SpawnCategory.valueOf(key.toUpperCase(Locale.ENGLISH));
|
||||
spawnCategoriesConfig.put(spawnCategory, new SpawnCategoryConfig(config, spawnCategory, sectionPart));
|
||||
});
|
||||
return new EntitySpawnConfig(spawnCategoriesConfig);
|
||||
return new EntitySpawnConfig(config, spawnCategoriesConfig);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.handle.MemoryConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
@@ -21,6 +22,7 @@ import java.util.List;
|
||||
|
||||
public final class SpawnCategoryConfig {
|
||||
|
||||
private final CoreConfig config;
|
||||
private final SpawnCategory spawnCategory;
|
||||
private final MemoryConfigurationHandle handle;
|
||||
private final StringPropertyHandle stringPropertyHandle;
|
||||
@@ -28,7 +30,8 @@ public final class SpawnCategoryConfig {
|
||||
|
||||
private MultiverseWorld world;
|
||||
|
||||
SpawnCategoryConfig(SpawnCategory spawnCategory, ConfigurationSection section) {
|
||||
SpawnCategoryConfig(CoreConfig config, SpawnCategory spawnCategory, ConfigurationSection section) {
|
||||
this.config = config;
|
||||
this.spawnCategory = spawnCategory;
|
||||
this.nodes = new Nodes();
|
||||
this.handle = MemoryConfigurationHandle.builder(section, nodes.nodes)
|
||||
@@ -57,23 +60,39 @@ public final class SpawnCategoryConfig {
|
||||
return;
|
||||
}
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
if (!isSpawn()) {
|
||||
if (getExceptions().isEmpty()) {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: 0", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, 0);
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: -1", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, -1);
|
||||
}
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: %d", world.getName(), spawnCategory, getTickRate());
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, getTickRate());
|
||||
}
|
||||
Logging.finer("World %s %s setSpawnLimit: %d", world.getName(), spawnCategory, getSpawnLimit());
|
||||
bukkitWorld.setSpawnLimit(spawnCategory, getSpawnLimit());
|
||||
applyTickPerSpawns(bukkitWorld);
|
||||
applySpawnLimit(bukkitWorld);
|
||||
});
|
||||
}
|
||||
|
||||
private void applyTickPerSpawns(World bukkitWorld) {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
Logging.finer("World %s %s skipping setTicksPerSpawns due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
if (!isSpawn()) {
|
||||
if (getExceptions().isEmpty()) {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: 0", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, 0);
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: -1", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, -1);
|
||||
}
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: %d", world.getName(), spawnCategory, getTickRate());
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, getTickRate());
|
||||
}
|
||||
}
|
||||
|
||||
private void applySpawnLimit(World bukkitWorld) {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
Logging.finer("Skipping World %s %s setSpawnLimit due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
Logging.finer("World %s %s setSpawnLimit: %d", world.getName(), spawnCategory, getSpawnLimit());
|
||||
bukkitWorld.setSpawnLimit(spawnCategory, getSpawnLimit());
|
||||
}
|
||||
|
||||
public StringPropertyHandle getStringPropertyHandle() {
|
||||
return stringPropertyHandle;
|
||||
}
|
||||
|
||||
+30
-12
@@ -27,6 +27,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.utils.FileUtils;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
@@ -38,15 +39,17 @@ import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
public final class GeneratorProvider implements Listener {
|
||||
private final Map<String, String> defaultGenerators;
|
||||
private final Map<String, GeneratorPlugin> generatorPlugins;
|
||||
private final CoreConfig coreConfig;
|
||||
private final FileUtils fileUtils;
|
||||
|
||||
@Inject
|
||||
GeneratorProvider(@NotNull MultiverseCore multiverseCore, @NotNull FileUtils fileUtils) {
|
||||
GeneratorProvider(@NotNull MultiverseCore plugin, @NotNull CoreConfig coreConfig, @NotNull FileUtils fileUtils) {
|
||||
this.coreConfig = coreConfig;
|
||||
this.fileUtils = fileUtils;
|
||||
defaultGenerators = new HashMap<>();
|
||||
generatorPlugins = new HashMap<>();
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(this, multiverseCore);
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
loadDefaultWorldGenerators();
|
||||
loadPluginGenerators();
|
||||
}
|
||||
@@ -73,11 +76,14 @@ public final class GeneratorProvider implements Listener {
|
||||
* Find generator plugins from plugins loaded and register them.
|
||||
*/
|
||||
private void loadPluginGenerators() {
|
||||
Arrays.stream(Bukkit.getPluginManager().getPlugins()).forEach(plugin -> {
|
||||
if (!coreConfig.getAutoDetectGeneratorPlugins()) {
|
||||
return;
|
||||
}
|
||||
for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
|
||||
if (testIsGeneratorPlugin(plugin)) {
|
||||
registerGeneratorPlugin(new SimpleGeneratorPlugin(plugin.getName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,14 +94,23 @@ public final class GeneratorProvider implements Listener {
|
||||
*/
|
||||
private boolean testIsGeneratorPlugin(Plugin plugin) {
|
||||
String worldName = Bukkit.getWorlds().stream().findFirst().map(World::getName).orElse("world");
|
||||
return Try.of(() -> plugin.getDefaultWorldGenerator(worldName, "") != null)
|
||||
.recover(IllegalArgumentException.class, true)
|
||||
.recover(throwable -> {
|
||||
Logging.warning("Plugin %s threw an exception when testing if it is a generator plugin!",
|
||||
plugin.getName());
|
||||
throwable.printStackTrace();
|
||||
return false;
|
||||
}).getOrElse(false);
|
||||
try {
|
||||
return plugin.getDefaultWorldGenerator(worldName, "") != null;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Some plugins throw this if they don't support world generation
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
Logging.warning("Plugin %s threw an exception when testing if it is a generator plugin! ",
|
||||
plugin.getName());
|
||||
Logging.warning("Error by plugin %s: %s", plugin.getName(), t.getMessage());
|
||||
Logging.warning("This is NOT a bug in Multiverse. Do NOT report this to Multiverse support. " +
|
||||
"Enable debug mode with `/mv debug 1` to see the full stack trace.");
|
||||
if (coreConfig.getGlobalDebug() >= 1) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
// Assume it's a generator plugin since it tried to do something, most likely the id is wrong.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,6 +221,9 @@ public final class GeneratorProvider implements Listener {
|
||||
*/
|
||||
@EventHandler
|
||||
private void onPluginEnable(PluginEnableEvent event) {
|
||||
if (!coreConfig.getAutoDetectGeneratorPlugins()) {
|
||||
return;
|
||||
}
|
||||
if (!testIsGeneratorPlugin(event.getPlugin())) {
|
||||
Logging.finest("Plugin %s is not a generator plugin.", event.getPlugin().getName());
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -130,7 +131,7 @@ public final class WorldNameChecker {
|
||||
* @return True if it looks like a world, else false.
|
||||
*/
|
||||
private boolean folderHasDat(@NotNull File worldFolder) {
|
||||
File[] files = worldFolder.listFiles((file, name) -> name.toLowerCase().endsWith(".dat"));
|
||||
File[] files = worldFolder.listFiles((file, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(".dat"));
|
||||
return files != null && files.length > 0;
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -22,13 +22,13 @@ import java.util.Objects;
|
||||
* This is useful to store location with world that may not be loaded yet or have been unloaded at some point.
|
||||
*/
|
||||
@SerializableAs("UnloadedWorldLocation")
|
||||
public final class UnloadedWorldLocation extends Location {
|
||||
public class UnloadedWorldLocation extends Location {
|
||||
|
||||
public static UnloadedWorldLocation fromLocation(@NotNull Location location) {
|
||||
return new UnloadedWorldLocation(location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
|
||||
}
|
||||
|
||||
private String worldName;
|
||||
private @Nullable String worldName;
|
||||
|
||||
public UnloadedWorldLocation(@Nullable String worldName, double x, double y, double z) {
|
||||
super(null, x, y, z);
|
||||
@@ -67,7 +67,7 @@ public final class UnloadedWorldLocation extends Location {
|
||||
this.worldName = worldName;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
public @Nullable String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
@@ -160,10 +160,10 @@ public final class UnloadedWorldLocation extends Location {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 19 * hash + worldName.hashCode();
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getX()) ^ (Double.doubleToLongBits(this.getX()) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getY()) ^ (Double.doubleToLongBits(this.getY()) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getZ()) ^ (Double.doubleToLongBits(this.getZ()) >>> 32));
|
||||
hash = 19 * hash + String.valueOf(worldName).hashCode();
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getX()));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getY()));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getZ()));
|
||||
hash = 19 * hash + Float.floatToIntBits(this.getPitch());
|
||||
hash = 19 * hash + Float.floatToIntBits(this.getYaw());
|
||||
return hash;
|
||||
@@ -171,7 +171,7 @@ public final class UnloadedWorldLocation extends Location {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Location{" +
|
||||
return "UnloadedWorldLocation{" +
|
||||
"world=" + worldName +
|
||||
",x=" + getX() +
|
||||
",y=" + getY() +
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -255,7 +255,7 @@ mv-core.destination.cannon.failurereason.invalidformat=&cCannon destination form
|
||||
mv-core.destination.exact.failurereason.invalidformat=&cExact destination format is: &6e:worldname:x,y,z:pitch:yaw
|
||||
mv-core.destination.player.failurereason.playernotfound=&cPlayer '&6{player}&c' does not exist or is not online!
|
||||
mv-core.destination.shared.failurereason.invalidcoordinatesformat=You must specify a valid coordinate in this format: &6{x},{y},{z}
|
||||
mv-core.destination.shared.failurereason.invalidnumberformat=Value is not a valid number. {error}.
|
||||
mv-core.destination.shared.failurereason.invalidnumberformat=&cInvalid destination input. {error}
|
||||
mv-core.destination.shared.failurereason.worldnotfound=&cWorld '&6{world}&c' does not exist or is not loaded!
|
||||
mv-core.destination.parse.failurereason.invaliddestinationid=Invalid destination id: {id}. Valid destination ids are: {ids}
|
||||
|
||||
@@ -281,8 +281,8 @@ mv-core.createworld.worldexistunloaded=World '{world}' already exists, but it's
|
||||
mv-core.createworld.worldexistloaded=World '{world}' already exists!
|
||||
|
||||
mv-core.deleteworld.worldnonexistent=World '{world}' not found!
|
||||
mv-core.deleteworld.loadfailed=Unable to load world '{world}', does the world folder exist?&f You can run '&a/mv remove {world}&f' to remove it from Multiverse, or attempt to load again with '&a/mv load {world}&f'.
|
||||
mv-core.deleteworld.worldfoldernotfound=World '{world}' folder not found!
|
||||
mv-core.deleteworld.loadfailed=Unable to load world '{world}', is the world folder deleted or corrupted?&f You can run '&a/mv remove {world}&f' to remove it from Multiverse, or attempt to load again with '&a/mv load {world}&f'.
|
||||
mv-core.deleteworld.worldfoldernotfound=&cUnable to locate '{world}' folder! You may '&a/mv remove {world}&c' to remove it from Multiverse, and manually delete the folder if it exists.
|
||||
mv-core.deleteworld.failedtodeletefolder=Failed to delete world folder '{world}': {error}\n&fSee console for more details.
|
||||
|
||||
mv-core.importworld.invalidworldname=World '{world}' contains invalid characters!
|
||||
@@ -327,6 +327,11 @@ mv-core.exception.multiverseworld.unloaddefaultworld=&cYou can't unload the defa
|
||||
mv-core.exception.multiverseworld.unloadplayersinworld=&cThere are still &6{count}&c player(s) in the world! Use '&6--remove-players&c' flag to your command to teleport all players out of the world.
|
||||
mv-core.exception.multiverseworld.unloaderror=&cAn unknown error occurred while unloading world: &6{world}&c.\n&cSee console for more details.
|
||||
|
||||
# multiverse position parse exception
|
||||
mv-core.exception.positionparse.invaliddirection=&cInvalid direction string format: {format}. Expected format: <pitch>:<yaw>
|
||||
mv-core.exception.positionparse.invalidcoordinates=&cInvalid coordinates format: {format}. Expected format: <x>,<y>,<z>
|
||||
mv-core.exception.positionparse.invalidnumber=&cInvalid number format: {number}. Expects a numeric value.
|
||||
|
||||
# generic
|
||||
mv-core.generic.success=Success!
|
||||
mv-core.generic.failure=Failed!
|
||||
|
||||
@@ -69,6 +69,22 @@ class DestinationTest : TestWithMockBukkit() {
|
||||
assertEquals("e:world:1.2,2.0,3.0:10.5:9.5", destination.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Exact destination instance with relative position`() {
|
||||
val destination = destinationsProvider.parseDestination("e:world:~1.5,2,~-3:10.5:~9.5").orNull
|
||||
assertTrue(destination is ExactDestinationInstance)
|
||||
val expectedLocation = Location(
|
||||
world.bukkitWorld.orNull,
|
||||
player.location.x + 1.5,
|
||||
2.0,
|
||||
player.location.z - 3.0,
|
||||
player.location.yaw + 9.5F,
|
||||
10.5F
|
||||
)
|
||||
assertLocationEquals(expectedLocation, destination.getLocation(player).orNull)
|
||||
assertEquals("e:world:~1.5,2.0,~-3.0:10.5:~9.5", destination.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Exact destination instance from location`() {
|
||||
val exactDestination = serviceLocator.getActiveService(ExactDestination::class.java).takeIf { it != null } ?: run {
|
||||
@@ -123,6 +139,7 @@ class DestinationTest : TestWithMockBukkit() {
|
||||
assertTrue(destinationsProvider.parseDestination("b:invalid-bed").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("ca:invalid-cannon").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("e:world:1,2,x").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("e:world:1,2,3:").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("pl:invalid-player").isFailure)
|
||||
assertTrue(destinationsProvider.parseDestination("w:invalid-world").isFailure)
|
||||
// todo: should we make invalid yaw for WorldDestination fail?
|
||||
|
||||
@@ -4,6 +4,8 @@ world:
|
||||
enforce-access: false
|
||||
enforce-gamemode: true
|
||||
enforce-flight: true
|
||||
apply-entity-spawn-rate: true
|
||||
apply-entity-spawn-limit: true
|
||||
auto-purge-entities: false
|
||||
world-name-format:
|
||||
nether: '%overworld%_nether'
|
||||
@@ -52,6 +54,8 @@ event-priority:
|
||||
|
||||
misc:
|
||||
bukkit-yml-path: bukkit.yml
|
||||
server-properties-path: server.properties
|
||||
auto-detect-generator-plugins: true
|
||||
global-debug: 0
|
||||
debug-permissions: false
|
||||
silent-start: false
|
||||
|
||||
Reference in new issue
Block a user