mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 08:42:11 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
608cb8611c | ||
|
|
a965b9ce56 | ||
|
|
981d368058 | ||
|
|
7507099a4e | ||
|
|
29b58b1655 | ||
|
|
a1081c1b6b | ||
|
|
3152b62668 | ||
|
|
0082cc9852 | ||
|
|
f60b656bed | ||
|
|
da5f5de5cd | ||
|
|
e9aacecc2f | ||
|
|
fa3adbe605 | ||
|
|
e33b2f7025 | ||
|
|
a9cc577754 | ||
|
|
02344a31cf | ||
|
|
2f4871ef71 | ||
|
|
9750fad4b7 | ||
|
|
df77372d96 | ||
|
|
039af3c219 | ||
|
|
09ec4f87ef | ||
|
|
f996f22f4b | ||
|
|
b230c5ae9c | ||
|
|
2561107697 | ||
|
|
70babc8656 | ||
|
|
bd09d6c950 | ||
|
|
b94de763ca | ||
|
|
22ca57543f | ||
|
|
25c70170d9 | ||
|
|
9926041c2f | ||
|
|
1c99898d0d | ||
|
|
41e0261eee | ||
|
|
642f3ab1b8 | ||
|
|
57d1844756 | ||
|
|
73e1650fc3 | ||
|
|
800709ca95 | ||
|
|
935cbad21c | ||
|
|
0e2a27b298 | ||
|
|
baaa175423 | ||
|
|
e2b7a31191 | ||
|
|
afa2825bc7 | ||
|
|
21b4f6b087 | ||
|
|
a771652c8f | ||
|
|
e89b4f19ee | ||
|
|
eb8ef32a94 | ||
|
|
f9d783bc5c | ||
|
|
87dd4168cd | ||
|
|
2da6ece606 | ||
|
|
cbb3a7a518 | ||
|
|
57796d295b | ||
|
|
09314e8bce | ||
|
|
907a6dd5c0 | ||
|
|
a50bd807b6 | ||
|
|
02081b4654 | ||
|
|
156ec7911d | ||
|
|
2b95c62a84 | ||
|
|
455396713e | ||
|
|
8bc3a593d0 | ||
|
|
5177f290ae | ||
|
|
5118864bb8 | ||
|
|
654da03add | ||
|
|
61229356ad | ||
|
|
fa5aa41547 | ||
|
|
04f07657eb | ||
|
|
eea2af169f | ||
|
|
6d393723ff | ||
|
|
9137ef04d2 | ||
|
|
878c4bea3d | ||
|
|
021d86e21c | ||
|
|
3e9425c8f1 | ||
|
|
cc60b9c938 | ||
|
|
d3cc950a97 | ||
|
|
7c6b1d07b0 | ||
|
|
35b129701f | ||
|
|
d92fe17c4f | ||
|
|
d1660d7e81 |
No files matched your search
+8
-8
@@ -1,7 +1,7 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.0'
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.1'
|
||||
}
|
||||
|
||||
group = 'org.mvplugins.multiverse.core'
|
||||
@@ -25,9 +25,9 @@ repositories {
|
||||
}
|
||||
|
||||
configure(apiDependencies) {
|
||||
serverApiVersion = '1.19.4-R0.1-SNAPSHOT'
|
||||
serverApiVersion = '1.21.8-R0.1-SNAPSHOT'
|
||||
mockBukkitServerApiVersion = '1.21'
|
||||
mockBukkitVersion = '4.31.1'
|
||||
mockBukkitVersion = '4.69.2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -48,9 +48,9 @@ dependencies {
|
||||
}
|
||||
|
||||
// Utils
|
||||
shadowed 'io.vavr:vavr:0.10.4'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.0.3'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.0.3') {
|
||||
shadowed 'io.vavr:vavr:0.10.7'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.1.1'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.1.1') {
|
||||
exclude group: 'org.apache.maven', module: 'maven-core'
|
||||
}
|
||||
shadowed('com.dumptruckman.minecraft:Logging:1.1.1') {
|
||||
@@ -60,8 +60,8 @@ dependencies {
|
||||
shadowed('org.bstats:bstats-bukkit:3.1.0') {
|
||||
exclude group: 'org.bukkit', module: 'bukkit'
|
||||
}
|
||||
shadowed 'net.minidev:json-smart:2.4.9'
|
||||
shadowed 'org.jetbrains:annotations:22.0.0'
|
||||
shadowed 'net.minidev:json-smart:2.5.2'
|
||||
shadowed 'org.jetbrains:annotations:26.0.2'
|
||||
shadowed 'io.papermc:paperlib:1.0.8'
|
||||
|
||||
// Tests
|
||||
|
||||
@@ -87,7 +87,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
registerAsyncCompletion("commands", this::suggestCommands);
|
||||
registerAsyncCompletion("destinations", this::suggestDestinations);
|
||||
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
|
||||
registerStaticCompletion("environments", suggestEnums(World.Environment.class));
|
||||
registerStaticCompletion("environments", List.of("normal", "nether", "the_end")); // Don't tab complete the "custom" environment
|
||||
registerAsyncCompletion("flags", this::suggestFlags);
|
||||
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -68,7 +68,8 @@ class ImportCommand extends CoreCommand {
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.generator(parsedFlags.flagValue(flags.generator, String.class))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn)))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
|
||||
.onSuccess(newWorld -> {
|
||||
Logging.fine("World import success: " + newWorld);
|
||||
issuer.sendInfo(MVCorei18n.IMPORT_SUCCESS, Replace.WORLD.with(newWorld.getName()));
|
||||
@@ -105,6 +106,10 @@ class ImportCommand extends CoreCommand {
|
||||
private final CommandValueFlag<String> biome = flag(CommandValueFlag.builder("--biome", String.class)
|
||||
.addAlias("-b")
|
||||
.build());
|
||||
|
||||
private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check")
|
||||
.addAlias("-f")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -3,9 +3,8 @@ package org.mvplugins.multiverse.core.commands;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Conditions;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Single;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -15,19 +14,26 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
|
||||
@Service
|
||||
class LoadCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final LoadCommand.Flags flags;
|
||||
|
||||
@Inject
|
||||
LoadCommand(@NotNull WorldManager worldManager) {
|
||||
LoadCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) {
|
||||
this.worldManager = worldManager;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Subcommand("load")
|
||||
@@ -38,13 +44,19 @@ class LoadCommand extends CoreCommand {
|
||||
void onLoadCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Single
|
||||
@Conditions("worldname:scope=unloaded")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.load.world.description}")
|
||||
String worldName) {
|
||||
issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(worldName));
|
||||
worldManager.loadWorld(worldName)
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(world.getName()));
|
||||
worldManager.loadWorld(LoadWorldOptions.world(world)
|
||||
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
|
||||
.onSuccess(newWorld -> {
|
||||
Logging.fine("World load success: " + newWorld);
|
||||
issuer.sendInfo(MVCorei18n.LOAD_SUCCESS, Replace.WORLD.with(newWorld.getName()));
|
||||
@@ -54,17 +66,32 @@ class LoadCommand extends CoreCommand {
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvunload";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check")
|
||||
.addAlias("-f")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class LegacyAlias extends LoadCommand implements LegacyAliasCommand {
|
||||
@Inject
|
||||
LegacyAlias(@NotNull WorldManager worldManager) {
|
||||
super(worldManager);
|
||||
LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) {
|
||||
super(worldManager, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvload")
|
||||
void onLoadCommand(MVCommandIssuer issuer, String worldName) {
|
||||
super.onLoadCommand(issuer, worldName);
|
||||
void onLoadCommand(MVCommandIssuer issuer, MultiverseWorld world, String[] flagArray) {
|
||||
super.onLoadCommand(issuer, world, flagArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
@@ -23,19 +25,20 @@ import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
|
||||
|
||||
@Service
|
||||
class RemoveCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
private final RemovePlayerFlags flags;
|
||||
private final Flags flags;
|
||||
|
||||
@Inject
|
||||
RemoveCommand(
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
@NotNull RemovePlayerFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
this.worldManager = worldManager;
|
||||
this.playerWorldTeleporter = playerWorldTeleporter;
|
||||
@@ -44,7 +47,7 @@ class RemoveCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("remove")
|
||||
@CommandPermission("multiverse.core.remove")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.remove.description}")
|
||||
void onRemoveCommand(
|
||||
@@ -64,12 +67,14 @@ class RemoveCommand extends CoreCommand {
|
||||
? worldManager.getLoadedWorld(world).map(playerWorldTeleporter::removeFromWorld).getOrElse(AsyncAttemptsAggregate::emptySuccess)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> doWorldRemoving(issuer, world))
|
||||
future.onSuccess(() -> doWorldRemoving(issuer, world, parsedFlags))
|
||||
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
|
||||
}
|
||||
|
||||
private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world) {
|
||||
worldManager.removeWorld(world)
|
||||
private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
worldManager.removeWorld(RemoveWorldOptions.world(world)
|
||||
.saveBukkitWorld(!parsedFlags.hasFlag(flags.noSave))
|
||||
.unloadBukkitWorld(!parsedFlags.hasFlag(flags.noUnloadBukkitWorld)))
|
||||
.onSuccess(removedWorldName -> {
|
||||
Logging.fine("World remove success: " + removedWorldName);
|
||||
issuer.sendInfo(MVCorei18n.REMOVE_SUCCESS, Replace.WORLD.with(removedWorldName));
|
||||
@@ -79,13 +84,32 @@ class RemoveCommand extends CoreCommand {
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvremove";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world")
|
||||
.addAlias("-b")
|
||||
.build());
|
||||
|
||||
private final CommandFlag noSave = flag(CommandFlag.builder("--no-save")
|
||||
.addAlias("-n")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class LegacyAlias extends RemoveCommand implements LegacyAliasCommand {
|
||||
@Inject
|
||||
LegacyAlias(
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
RemovePlayerFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
super(worldManager, playerWorldTeleporter, flags);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
safetyTeleporter.to(mvWorld.getSpawnLocation())
|
||||
.by(issuer)
|
||||
.checkSafety(checkSafety)
|
||||
.teleport(entity)
|
||||
.teleportSingle(entity)
|
||||
.onSuccess(() -> issuer.sendInfo(MVCorei18n.SPAWN_SUCCESS,
|
||||
Replace.PLAYER.with(entity.equals(issuer.getPlayer())
|
||||
? Message.of(MVCorei18n.GENERIC_YOU)
|
||||
@@ -119,7 +119,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
? Message.of(MVCorei18n.GENERIC_YOU)
|
||||
: Message.of(entity.getName())),
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.REASON.with(failure.getFailureMessage())));
|
||||
Replace.REASON.with(failure.getFirst().getFailureMessage())));
|
||||
}
|
||||
|
||||
private void handleMultiTeleport(MVCommandIssuer issuer, LoadedMultiverseWorld mvWorld,
|
||||
|
||||
@@ -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}
|
||||
*/
|
||||
|
||||
@@ -137,6 +137,24 @@ final class CoreConfigNodes {
|
||||
.name("enforce-flight")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnRate = node(ConfigNode.builder("world.apply-entity-spawn-rate", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `tick-rate` config in worlds.yml.")
|
||||
.comment("If disabled, the `tick-rate` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity spawn rate per world.")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-rate")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnLimit = node(ConfigNode.builder("world.apply-entity-spawn-limit", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `spawn-limit` config when a world is loaded.")
|
||||
.comment("If disabled, the `spawn-limit` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity limits per world.")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-limit")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoPurgeEntities = node(ConfigNode.builder("world.auto-purge-entities", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will purge entities on world load based world's entity spawn config.")
|
||||
@@ -494,6 +512,23 @@ final class CoreConfigNodes {
|
||||
.name("bukkit-yml-path")
|
||||
.build());
|
||||
|
||||
final ConfigNode<String> serverPropertiesPath = node(ConfigNode.builder("misc.server-properties-path", String.class)
|
||||
.comment("")
|
||||
.comment("Change this if you use a custom path for the server.properties file with `--config` startup flag.")
|
||||
.comment("Note: this config option needs a server restart to take effect.")
|
||||
.defaultValue("server.properties")
|
||||
.name("server-properties-path")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoDetectGeneratorPlugins = node(ConfigNode.builder("misc.auto-detect-generator-plugins", Boolean.class)
|
||||
.comment("")
|
||||
.comment("When enabled, Multiverse will attempt to automatically detect world generator plugins installed on your server.")
|
||||
.comment("This option only affects tab-completion within `/mv create` command and output of `/mv generators` command.")
|
||||
.comment("Disabling this will not have any affect on the usages of world generator plugins itself.")
|
||||
.defaultValue(true)
|
||||
.name("auto-detect-generator-plugins")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> globalDebug = node(ConfigNode.builder("misc.global-debug", Integer.class)
|
||||
.comment("")
|
||||
.comment("This is our debug flag to help identify issues with Multiverse.")
|
||||
@@ -518,6 +553,7 @@ final class CoreConfigNodes {
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> debugPermissions = node(ConfigNode.builder("misc.debug-permissions", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether console will log every permission check done by all multiverse plugins.")
|
||||
.comment("This will only work if the above 'global-debug' is set to 1 or more.")
|
||||
.defaultValue(false)
|
||||
|
||||
+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();
|
||||
}
|
||||
}
|
||||
@@ -69,9 +69,12 @@ public class RegexContentFilter implements ContentFilter {
|
||||
if (!hasValidRegex()) {
|
||||
return false;
|
||||
}
|
||||
String text = ChatTextFormatter.removeColor(String.valueOf(value)).toLowerCase();
|
||||
String text = ChatTextFormatter.removeColor(String.valueOf(value));
|
||||
if (text == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return regexPattern.matcher(text).find();
|
||||
return regexPattern.matcher(text.toLowerCase()).find();
|
||||
} catch (PatternSyntaxException ignored) {
|
||||
Logging.warning("Error parsing regex '%s' for input '%s'", regexString, text);
|
||||
return false;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.mvplugins.multiverse.core.exceptions.utils.position;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
public class PositionParseException extends MultiverseException {
|
||||
public PositionParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -88,8 +88,9 @@ final class MVChatListener implements CoreListener {
|
||||
String prefixChatFormat = config.getPrefixChatFormat();
|
||||
prefixChatFormat = prefixChatFormat.replace("%world%", worldName).replace("%chat%", chat);
|
||||
prefixChatFormat = ChatTextFormatter.colorize(prefixChatFormat);
|
||||
|
||||
event.setFormat(prefixChatFormat);
|
||||
if (prefixChatFormat != null) {
|
||||
event.setFormat(prefixChatFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private String getWorldName(Player player) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventPriorityKe
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportQueue;
|
||||
import org.mvplugins.multiverse.core.utils.result.ResultChain;
|
||||
@@ -70,6 +71,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
private final DestinationsProvider destinationsProvider;
|
||||
private final EnforcementHandler enforcementHandler;
|
||||
private final DimensionFinder dimensionFinder;
|
||||
private final CorePermissionsChecker corePermissionsChecker;
|
||||
|
||||
private final Map<String, String> playerWorld = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -86,7 +88,8 @@ final class MVPlayerListener implements CoreListener {
|
||||
Provider<MVCommandManager> commandManagerProvider,
|
||||
DestinationsProvider destinationsProvider,
|
||||
EnforcementHandler enforcementHandler,
|
||||
DimensionFinder dimensionFinder) {
|
||||
DimensionFinder dimensionFinder,
|
||||
CorePermissionsChecker corePermissionsChecker) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.worldManagerProvider = worldManagerProvider;
|
||||
@@ -99,6 +102,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.enforcementHandler = enforcementHandler;
|
||||
this.dimensionFinder = dimensionFinder;
|
||||
this.corePermissionsChecker = corePermissionsChecker;
|
||||
}
|
||||
|
||||
private WorldManager getWorldManager() {
|
||||
@@ -244,6 +248,10 @@ final class MVPlayerListener implements CoreListener {
|
||||
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())
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.reasons.LoadFailureReason;
|
||||
import org.mvplugins.multiverse.core.world.reasons.UnloadFailureReason;
|
||||
@@ -68,7 +69,7 @@ final class MVWorldListener implements CoreListener {
|
||||
worldManager.getUnloadedWorld(event.getWorld().getName())
|
||||
.peek(world -> {
|
||||
Logging.fine("Loading world: " + world.getName());
|
||||
worldManager.loadWorld(world).onFailure(failure -> {
|
||||
worldManager.loadWorld(LoadWorldOptions.world(world)).onFailure(failure -> {
|
||||
if (failure.getFailureReason() != LoadFailureReason.WORLD_ALREADY_LOADING) {
|
||||
Logging.severe("Failed to load world: " + failure);
|
||||
}
|
||||
|
||||
@@ -308,11 +308,13 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
IMPORTWORLD_WORLDEXISTUNLOADED,
|
||||
IMPORTWORLD_WORLDEXISTLOADED,
|
||||
IMPORTWORLD_WORLDFOLDERINVALID,
|
||||
IMPORTWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
|
||||
LOADWORLD_WORLDALREADYLOADING,
|
||||
LOADWORLD_WORLDNONEXISTENT,
|
||||
LOADWORLD_WORLDEXISTFOLDER,
|
||||
LOADWORLD_WORLDEXISTLOADED,
|
||||
LOADWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
|
||||
REMOVEWORLD_WORLDNONEXISTENT,
|
||||
|
||||
@@ -344,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,
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.permissions;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
@@ -15,6 +16,11 @@ import static org.mvplugins.multiverse.core.permissions.PermissionUtils.concatPe
|
||||
|
||||
@Service
|
||||
public final class CorePermissions {
|
||||
/**
|
||||
* Permission to bypass the join location.
|
||||
*/
|
||||
static final String JOINLOCATION_BYPASS = "mv.bypass.joinlocation";
|
||||
|
||||
/**
|
||||
* Permission to access a world.
|
||||
*/
|
||||
@@ -52,6 +58,12 @@ public final class CorePermissions {
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void registerBasePermissions() {
|
||||
pluginManager.addPermission(new Permission(JOINLOCATION_BYPASS, PermissionDefault.FALSE));
|
||||
Logging.fine("Successfully registered base permissions");
|
||||
}
|
||||
|
||||
public Try<Void> addWorldPermissions(@NotNull MultiverseWorld world) {
|
||||
return Try.run(() -> {
|
||||
pluginManager.addPermission(new Permission(
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.permissions;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -50,6 +51,19 @@ public final class CorePermissionsChecker {
|
||||
this.worldManager = worldManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the sender has permission to bypass the join location restriction.
|
||||
*
|
||||
* @param sender The command sender.
|
||||
* @return True if the sender has bypass permission, false otherwise.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean hasJoinLocationBypassPermission(@NotNull CommandSender sender) {
|
||||
return hasPermission(sender, CorePermissions.JOINLOCATION_BYPASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the sender has permission to access the specified world.
|
||||
*
|
||||
|
||||
@@ -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,75 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ExactStringMatcher is a StringMatcher that matches strings against a set of exact values.
|
||||
* It can be initialized with a single string, a collection of strings, or can have exact matches added later.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class ExactStringMatcher implements StringMatcher {
|
||||
private final Set<String> exactMatches;
|
||||
|
||||
/**
|
||||
* Creates a new ExactStringMatcher with no initial matches. Use {@link #addExactMatch(String)} to add matches later.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public ExactStringMatcher() {
|
||||
this.exactMatches = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ExactStringMatcher with a single exact match.
|
||||
*
|
||||
* @param exactMatch the exact string to match against
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public ExactStringMatcher(@NotNull String exactMatch) {
|
||||
this.exactMatches = new HashSet<>();
|
||||
this.exactMatches.add(exactMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ExactStringMatcher with multiple exact matches.
|
||||
*
|
||||
* @param exactMatches the collection of exact strings to match against
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public ExactStringMatcher(@NotNull Collection<String> exactMatches) {
|
||||
this.exactMatches = new HashSet<>(exactMatches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an exact match string to this matcher.
|
||||
*
|
||||
* @param value the exact string to add to the matcher
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public void addExactMatch(@NotNull String value) {
|
||||
this.exactMatches.add(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
return exactMatches.contains(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MatcherGroup is a collection of StringMatchers that can match against a string.
|
||||
* It works with all format supported by {@link StringMatcher#fromString(String)}.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class MatcherGroup implements StringMatcher {
|
||||
|
||||
private final ExactStringMatcher exactMatcher;
|
||||
private final List<StringMatcher> stringMatchers;
|
||||
|
||||
/**
|
||||
* Creates a new empty MatcherGroup.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MatcherGroup() {
|
||||
this.exactMatcher = new ExactStringMatcher();
|
||||
this.stringMatchers = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MatcherGroup with multiple strings to be parsed into matchers.
|
||||
*
|
||||
* @param matchStrings the collection of match strings
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MatcherGroup(@NotNull Collection<String> matchStrings) {
|
||||
this();
|
||||
for (String matchString : matchStrings) {
|
||||
addMatcher(matchString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MatcherGroup with a single exact match.
|
||||
*
|
||||
* @param matchString the single match string
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public void addMatcher(@Nullable String matchString) {
|
||||
if (matchString == null || matchString.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (isExact(matchString)) {
|
||||
exactMatcher.addExactMatch(matchString);
|
||||
} else {
|
||||
stringMatchers.add(StringMatcher.fromString(matchString));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExact(@NotNull String matcherString) {
|
||||
return !matcherString.contains("*") && !matcherString.startsWith("r=");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an existing matcher to the group.
|
||||
*
|
||||
* @param matcher the StringMatcher to add
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public void addMatcher(@NotNull StringMatcher matcher) {
|
||||
stringMatchers.add(matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
if (exactMatcher.matches(value)) {
|
||||
return true;
|
||||
}
|
||||
for (StringMatcher matcher : stringMatchers) {
|
||||
if (matcher.matches(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MatcherGroup{" + "exactMatcher=" + exactMatcher +
|
||||
", stringMatchers=" + stringMatchers +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* RegexStringMatcher is a StringMatcher that matches strings against a regex pattern.
|
||||
* It can be initialized with a regex string, which can optionally start with 'r=' to indicate it's a regex.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class RegexStringMatcher implements StringMatcher {
|
||||
private final @NotNull String regexString;
|
||||
private final @Nullable Pattern regexPattern;
|
||||
|
||||
/**
|
||||
* Creates a new RegexStringMatcher with a regex string. 'r=' prefix will be stripped if present.
|
||||
*
|
||||
* @param regexString the regex string to match against. If it starts with 'r=', that part will be ignored.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public RegexStringMatcher(@NotNull String regexString) {
|
||||
this.regexString = regexString;
|
||||
this.regexPattern = compileRegex(regexString);
|
||||
}
|
||||
|
||||
private Pattern compileRegex(String regexString) {
|
||||
if (regexString.startsWith("r=")) {
|
||||
regexString = regexString.substring(2);
|
||||
}
|
||||
|
||||
String finalRegexString = regexString;
|
||||
return Try.of(() -> Pattern.compile(finalRegexString))
|
||||
.onFailure(ex -> Logging.warning("Failed to compile regex '%s': %s",
|
||||
finalRegexString, ex.getMessage()))
|
||||
.getOrNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
if (regexPattern == null || value == null) {
|
||||
return false;
|
||||
}
|
||||
return regexPattern.matcher(value).matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* StringMatcher is an interface for matching strings against various patterns.
|
||||
* It currently built-in supports exact matches, wildcard matches, and regex matches.
|
||||
* <br />
|
||||
* Implement this interface to create your own custom string matchers.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public interface StringMatcher {
|
||||
|
||||
/**
|
||||
* Creates a StringMatcher from a string representation. When the string starts with "r=", it is treated as a regex.
|
||||
* If the string contains a '*', it is treated as a wildcard match.
|
||||
*
|
||||
* @param matcherString the string to be parsed into a matcher.
|
||||
* @return a StringMatcher instance based on the provided string.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
static @NotNull StringMatcher fromString(@NotNull String matcherString) {
|
||||
if (matcherString.startsWith("r=")) {
|
||||
return new RegexStringMatcher(matcherString);
|
||||
} else if (matcherString.contains("*")) {
|
||||
return new WildcardStringMatcher(matcherString);
|
||||
} else {
|
||||
return new ExactStringMatcher(matcherString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given value matches the pattern defined by this StringMatcher.
|
||||
*
|
||||
* @param value the string to match against the pattern.
|
||||
* @return true if the value matches, false otherwise.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
boolean matches(@Nullable String value);
|
||||
|
||||
/**
|
||||
* Filters a list of strings, returning only those that match the pattern defined by this StringMatcher.
|
||||
* This method is a convenience for applying the matcher to a collection of strings.
|
||||
*
|
||||
* @param values the list of strings to filter.
|
||||
* @return A list of strings that match the pattern defined by this StringMatcher.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
default @NotNull List<String> filter(@NotNull List<String> values) {
|
||||
return values.stream()
|
||||
.filter(this::matches)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.mvplugins.multiverse.core.utils.matcher;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* WildcardStringMatcher is a StringMatcher that matches strings against a wildcard pattern.
|
||||
* It supports '*' as a wildcard character, which can match any sequence of characters.
|
||||
* <br />
|
||||
* For example, the wildcard "foo*bar" will match any string that starts with "foo" and ends with "bar",
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public class WildcardStringMatcher implements StringMatcher {
|
||||
|
||||
private final String wildcard;
|
||||
private final Pattern pattern;
|
||||
|
||||
/**
|
||||
* Creates a new WildcardStringMatcher with a wildcard string.
|
||||
* The wildcard string can contain '*' characters, which will be replaced with a regex equivalent.
|
||||
*
|
||||
* @param wildcard the wildcard string to match against.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public WildcardStringMatcher(@NotNull String wildcard) {
|
||||
this.wildcard = wildcard;
|
||||
this.pattern = Try.of(() -> Pattern.compile(("\\Q" + wildcard + "\\E").replace("*", "\\E.*\\Q")))
|
||||
.onFailure(ex -> Logging.warning("Failed to compile wildcard '%s': %s",
|
||||
wildcard, ex.getMessage()))
|
||||
.getOrNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(@Nullable String value) {
|
||||
if (pattern == null || value == null) {
|
||||
return false;
|
||||
}
|
||||
return pattern.matcher(value).matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
/**
|
||||
* Represents a position for an entity in 3D space, including both coordinates and facing direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class EntityPosition {
|
||||
|
||||
/**
|
||||
* Creates an EntityPosition with absolute coordinates and direction.
|
||||
*
|
||||
* @param x The absolute X coordinate.
|
||||
* @param y The absolute Y coordinate.
|
||||
* @param z The absolute Z coordinate.
|
||||
* @param pitch The absolute pitch (vertical angle).
|
||||
* @param yaw The absolute yaw (horizontal angle).
|
||||
* @return A new EntityPosition instance with the specified absolute coordinates and direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition ofAbsolute(double x, double y, double z, double pitch, double yaw) {
|
||||
return new EntityPosition(
|
||||
VectorPosition.ofAbsolute(x, y, z),
|
||||
FaceDirection.ofAbsolute(pitch, yaw)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EntityPosition from a Bukkit Location with absolute coordinates and direction.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new EntityPosition instance representing the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition ofLocation(Location location) {
|
||||
return new EntityPosition(VectorPosition.ofLocation(location), FaceDirection.ofLocation(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an EntityPosition from a string representation.
|
||||
* The expected format is "<x>,<y>,<z>:<pitch>:<yaw>" for absolute coordinates and direction,
|
||||
* or "<x>,<y>,<z>" for absolute coordinates with default direction (0 pitch, 0 yaw).
|
||||
* <br>
|
||||
* Relative coordinates and direction can be specified using the '~' prefix, e.g., "~10,~,~-10:0:90".
|
||||
*
|
||||
* @param positionStr The string representation of the position.
|
||||
* @return A new EntityPosition instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static EntityPosition fromString(String positionStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COLON.split(positionStr, 2);
|
||||
return parts.length == 2
|
||||
? new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.fromString(parts[1]))
|
||||
: new EntityPosition(VectorPosition.fromString(parts[0]), FaceDirection.ofAbsolute(0, 0));
|
||||
}
|
||||
|
||||
private final VectorPosition vector;
|
||||
private final FaceDirection direction;
|
||||
|
||||
/**
|
||||
* Creates a new EntityPosition with the specified vector and direction.
|
||||
*
|
||||
* @param vector The vector position (coordinates).
|
||||
* @param direction The facing direction (pitch and yaw).
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public EntityPosition(VectorPosition vector, FaceDirection direction) {
|
||||
this.vector = vector;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vector position (coordinates).
|
||||
*
|
||||
* @return The vector position.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public VectorPosition getVector() {
|
||||
return vector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the facing direction (pitch and yaw).
|
||||
*
|
||||
* @return The facing direction.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public FaceDirection getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments a given Bukkit Location by applying the relative components of this EntityPosition.
|
||||
* This modifies the provided Location in place.
|
||||
*
|
||||
* @param base The base Bukkit Location to augment and offset for relative positioning as required.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location base) {
|
||||
vector.augmentBukkitLocation(base);
|
||||
direction.augmentBukkitLocation(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this EntityPosition to a new Bukkit Location based on a given base Location.
|
||||
* This does not modify the base Location, but returns a new Location instance.
|
||||
*
|
||||
* @param base The base Bukkit Location to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Location representing this EntityPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Location toBukkitLocation(Location base) {
|
||||
return new Location(
|
||||
base.getWorld(),
|
||||
vector.getX().getValue(base.getX()),
|
||||
vector.getY().getValue(base.getY()),
|
||||
vector.getZ().getValue(base.getZ()),
|
||||
(float) direction.getYaw().getValue(base.getYaw()),
|
||||
(float) direction.getPitch().getValue(base.getPitch())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return vector + ":" + direction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents a direction to face, defined by pitch and yaw.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class FaceDirection {
|
||||
|
||||
/**
|
||||
* Creates a FaceDirection with absolute pitch and yaw.
|
||||
*
|
||||
* @param pitch The absolute pitch (vertical angle).
|
||||
* @param yaw The absolute yaw (horizontal angle).
|
||||
* @return A new FaceDirection instance with the specified absolute pitch and yaw.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection ofAbsolute(double pitch, double yaw) {
|
||||
return new FaceDirection(
|
||||
PositionNumber.ofAbsolute(pitch),
|
||||
PositionNumber.ofAbsolute(yaw)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pitch and yaw from a Bukkit Location to create an FaceDirection with absolute values.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new FaceDirection instance representing the direction of the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection ofLocation(org.bukkit.Location location) {
|
||||
return new FaceDirection(
|
||||
PositionNumber.ofAbsolute(location.getPitch()),
|
||||
PositionNumber.ofAbsolute(location.getYaw())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a FaceDirection from a string representation.
|
||||
* The expected format is "<pitch>:<yaw>" for absolute pitch and yaw.
|
||||
* <br>
|
||||
* Relative pitch and yaw can be specified using the '~' prefix, e.g., "~:~5".
|
||||
*
|
||||
* @param directionStr The string representation of the direction.
|
||||
* @return A new FaceDirection instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static FaceDirection fromString(String directionStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COLON.split(directionStr, 2);
|
||||
if (parts.length != 2) {
|
||||
throw new PositionParseException(Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDDIRECTION,
|
||||
replace("{format}").with(directionStr)));
|
||||
}
|
||||
//TODO: Add support for compass directions (N, S, E, W, NE, NW, SE, SW) for yaw
|
||||
PositionNumber pitch = PositionNumber.fromString(parts[0]);
|
||||
PositionNumber yaw = PositionNumber.fromString(parts[1]);
|
||||
return new FaceDirection(pitch, yaw);
|
||||
}
|
||||
|
||||
private final PositionNumber pitch;
|
||||
private final PositionNumber yaw;
|
||||
|
||||
/**
|
||||
* Creates a new FaceDirection with the specified pitch and yaw.
|
||||
*
|
||||
* @param pitch The pitch (vertical angle).
|
||||
* @param yaw The yaw (horizontal angle).
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public FaceDirection(PositionNumber pitch, PositionNumber yaw) {
|
||||
this.pitch = pitch;
|
||||
this.yaw = yaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pitch component of this FaceDirection.
|
||||
*
|
||||
* @return The pitch number representation.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getPitch() {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the yaw component of this FaceDirection.
|
||||
*
|
||||
* @return The yaw number representation.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getYaw() {
|
||||
return yaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this FaceDirection to a given Bukkit Location, modifying its pitch and yaw.
|
||||
* <br>
|
||||
* Relative pitch and yaw will be offset based on the current values in the Location.
|
||||
*
|
||||
* @param base The Bukkit Location to modify.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location base) {
|
||||
base.setPitch((float) pitch.getValue(base.getPitch()));
|
||||
base.setYaw((float) yaw.getValue(base.getYaw()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pitch + ":" + yaw;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents a number that can be either absolute or relative (prefixed with '~').
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public sealed interface PositionNumber permits PositionNumber.Relative, PositionNumber.Absolute {
|
||||
|
||||
/**
|
||||
* Creates an absolute PositionNumber. Absolute numbers represent fixed coordinates that will not be adjusted
|
||||
* based on any base value.
|
||||
*
|
||||
* @param value the absolute value
|
||||
* @return a PositionNumber representing the absolute value
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber ofAbsolute(double value) {
|
||||
return new Absolute(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a relative PositionNumber. Relative numbers are prefixed with '~' and represent an offset
|
||||
* from a base value. For example, a relative PositionNumber of '~5' means 5 units more than the base value,
|
||||
* while '~ -3' means 3 units less than the base value.
|
||||
*
|
||||
* @param value the relative offset value
|
||||
* @return a PositionNumber representing the relative offset
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber ofRelative(double value) {
|
||||
return new Relative(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a PositionNumber from a string. The string can represent either an absolute number (e.g., "10.5")
|
||||
* or a relative number prefixed with '~' (e.g., "~5" or "~ -3"). If the string is "~" with no number,
|
||||
* it is treated as a relative value of 0.
|
||||
*
|
||||
* @param string the string to parse
|
||||
* @return a PositionNumber representing the parsed value.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
static PositionNumber fromString(String string) throws PositionParseException {
|
||||
if (string.startsWith("~")) {
|
||||
if (string.length() == 1) {
|
||||
return new Relative(0);
|
||||
}
|
||||
return new Relative(tryParseDouble(string.substring(1)));
|
||||
}
|
||||
return new Absolute(tryParseDouble(string));
|
||||
}
|
||||
|
||||
private static double tryParseDouble(String str) throws PositionParseException {
|
||||
return Try.of(() -> Double.parseDouble(str))
|
||||
.getOrElseThrow(throwable -> new PositionParseException(
|
||||
Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
|
||||
replace("{number}").with(str))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the effective value based on the base value.
|
||||
* For absolute PositionNumbers, this returns the stored value.
|
||||
* For relative PositionNumbers, this returns base + stored value.
|
||||
*
|
||||
* @param base the base value to use for relative calculations
|
||||
* @return the calculated effective value
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
double getValue(double base);
|
||||
|
||||
/**
|
||||
* Checks if this PositionNumber is relative.
|
||||
*
|
||||
* @return true if this is a relative PositionNumber, false if absolute
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
boolean isRelative();
|
||||
|
||||
/**
|
||||
* Checks if this PositionNumber is absolute.
|
||||
*
|
||||
* @return true if this is an absolute PositionNumber, false if relative
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
boolean isAbsolute();
|
||||
|
||||
/**
|
||||
* Gets the raw stored value without any base adjustment.
|
||||
*
|
||||
* @return the raw value (for relative, this is the offset; for absolute, this is the fixed value)
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
double getRawValue();
|
||||
|
||||
final class Relative implements PositionNumber {
|
||||
|
||||
private final double value;
|
||||
|
||||
Relative(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue(double base) {
|
||||
return base + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelative() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbsolute() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "~" + value;
|
||||
}
|
||||
}
|
||||
|
||||
final class Absolute implements PositionNumber {
|
||||
|
||||
private final double value;
|
||||
|
||||
Absolute(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue(double base) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelative() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbsolute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Double.toString(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package org.mvplugins.multiverse.core.utils.position;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents an x, y, z position in 3D space, with support for absolute and relative coordinates.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public class VectorPosition {
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition with absolute coordinates.
|
||||
* @param x The absolute x coordinate.
|
||||
* @param y The absolute y coordinate.
|
||||
* @param z The absolute z coordinate.
|
||||
* @return A new VectorPosition instance with the specified absolute coordinates.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition ofAbsolute(double x, double y, double z) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(x),
|
||||
PositionNumber.ofAbsolute(y),
|
||||
PositionNumber.ofAbsolute(z)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition from a Bukkit Vector, using absolute coordinates.
|
||||
*
|
||||
* @param vector The Bukkit Vector to convert.
|
||||
* @return A new VectorPosition instance representing the given vector.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition ofVector(Vector vector) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(vector.getX()),
|
||||
PositionNumber.ofAbsolute(vector.getY()),
|
||||
PositionNumber.ofAbsolute(vector.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VectorPosition from a Bukkit Location, using absolute coordinates.
|
||||
*
|
||||
* @param location The Bukkit Location to convert.
|
||||
* @return A new VectorPosition instance representing the given location.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
public static VectorPosition ofLocation(Location location) {
|
||||
return new VectorPosition(
|
||||
PositionNumber.ofAbsolute(location.getX()),
|
||||
PositionNumber.ofAbsolute(location.getY()),
|
||||
PositionNumber.ofAbsolute(location.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a VectorPosition from a string representation.
|
||||
* The expected format is "<x>,<y>,<z>" for absolute or relative coordinates.
|
||||
* <br>
|
||||
* Relative coordinates can be specified using the '~' prefix, e.g., "~10,~,~-10".
|
||||
*
|
||||
* @param coordStr The string representation of the coordinates.
|
||||
* @return A new VectorPosition instance parsed from the string.
|
||||
* @throws PositionParseException If the string format is invalid.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public static VectorPosition fromString(String coordStr) throws PositionParseException {
|
||||
String[] parts = REPatterns.COMMA.split(coordStr);
|
||||
if (parts.length != 3) {
|
||||
throw new PositionParseException(Message.of(MVCorei18n.EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
|
||||
replace("{format}").with(coordStr)));
|
||||
}
|
||||
return new VectorPosition(
|
||||
PositionNumber.fromString(parts[0]),
|
||||
PositionNumber.fromString(parts[1]),
|
||||
PositionNumber.fromString(parts[2])
|
||||
);
|
||||
}
|
||||
|
||||
private final PositionNumber x;
|
||||
private final PositionNumber y;
|
||||
private final PositionNumber z;
|
||||
|
||||
/**
|
||||
* Creates a new VectorPosition with the specified PositionNumbers for x, y, and z.
|
||||
*
|
||||
* @param x The PositionNumber for the x coordinate.
|
||||
* @param y The PositionNumber for the y coordinate.
|
||||
* @param z The PositionNumber for the z coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public VectorPosition(PositionNumber x, PositionNumber y, PositionNumber z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the x coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the x coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the y coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the y coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PositionNumber for the z coordinate.
|
||||
*
|
||||
* @return The PositionNumber representing the z coordinate.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public PositionNumber getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the given Bukkit Vector in place by applying this VectorPosition's coordinates.
|
||||
* Relative coordinates will adjust the existing values, while absolute coordinates will set them directly.
|
||||
*
|
||||
* @param base The Bukkit Vector to augment.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitVector(Vector base) {
|
||||
base.setX(x.getValue(base.getX()));
|
||||
base.setY(y.getValue(base.getY()));
|
||||
base.setZ(z.getValue(base.getZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the given Bukkit Location in place by applying this VectorPosition's coordinates.
|
||||
* Relative coordinates will adjust the existing values, while absolute coordinates will set them directly.
|
||||
*
|
||||
* @param location The Bukkit Location to augment.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public void augmentBukkitLocation(Location location) {
|
||||
location.setX(x.getValue(location.getX()));
|
||||
location.setY(y.getValue(location.getY()));
|
||||
location.setZ(z.getValue(location.getZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this VectorPosition to a new Bukkit Vector based on a given base Vector.
|
||||
* This does not modify the base Vector, but returns a new Vector instance.
|
||||
*
|
||||
* @param base The base Bukkit Vector to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Vector representing this VectorPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Vector toBukkitVector(Vector base) {
|
||||
return new Vector(
|
||||
x.getValue(base.getX()),
|
||||
y.getValue(base.getY()),
|
||||
z.getValue(base.getZ())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this VectorPosition to a new Bukkit Location based on a given base Location.
|
||||
* This does not modify the base Location, but returns a new Location instance.
|
||||
*
|
||||
* @param base The base Bukkit Location to use as a reference for relative positioning as required.
|
||||
* @return A new Bukkit Location representing this VectorPosition.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Location toBukkitLocation(Location base) {
|
||||
return new Location(
|
||||
base.getWorld(),
|
||||
x.getValue(base.getX()),
|
||||
y.getValue(base.getY()),
|
||||
z.getValue(base.getZ()),
|
||||
base.getYaw(),
|
||||
base.getPitch()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return x + "," + y + "," + z;
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(mapper.apply(get()));
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(mapper.get());
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return mapper.apply(get());
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return mapper.get();
|
||||
} else {
|
||||
return new Failure<>(getFailureReason(), getFailureMessage());
|
||||
return new Failure<>((Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
if (this instanceof Success) {
|
||||
return new Success<>(get());
|
||||
} else {
|
||||
return new Failure<>(failureReason, getFailureMessage());
|
||||
return new Failure<>(failureReason, getFailureMessage(), (Failure<T, F>) this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,10 +415,20 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
final class Failure<T, F extends FailureReason> implements Attempt<T, F> {
|
||||
private final F failureReason;
|
||||
private final Message message;
|
||||
private final Failure<?, ?> causeBy;
|
||||
|
||||
Failure(F failureReason, Message message) {
|
||||
this(failureReason, message, null);
|
||||
}
|
||||
|
||||
Failure(Failure<?, F> failure) {
|
||||
this(failure.failureReason, failure.message, failure.causeBy);
|
||||
}
|
||||
|
||||
Failure(F failureReason, Message message, Failure<?, ?> causeBy) {
|
||||
this.failureReason = failureReason;
|
||||
this.message = message;
|
||||
this.causeBy = causeBy;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -465,6 +475,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
public String toString() {
|
||||
return "Failure{"
|
||||
+ "reason=" + failureReason
|
||||
+ (causeBy != null ? ", causeBy=" + causeBy : "")
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,21 @@ package org.mvplugins.multiverse.core.utils.result;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
|
||||
/**
|
||||
* Represents a failure reason for an {@link Attempt}.
|
||||
*/
|
||||
public interface FailureReason extends MessageKeyProvider {
|
||||
/**
|
||||
* A generic failure reason that can be used when no specific reason is applicable.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
FailureReason GENERIC = new FailureReason() { };
|
||||
|
||||
default MessageKey getMessageKey() {
|
||||
return MVCorei18n.GENERIC_FAILURE.getMessageKey();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,21 @@ package org.mvplugins.multiverse.core.utils.result;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
|
||||
/**
|
||||
* Represents a success reason for an {@link Attempt}.
|
||||
*/
|
||||
public interface SuccessReason extends MessageKeyProvider {
|
||||
/**
|
||||
* A generic success reason that can be used when no specific reason is applicable.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
SuccessReason GENERIC = new SuccessReason() { };
|
||||
|
||||
default MessageKey getMessageKey() {
|
||||
return MVCorei18n.GENERIC_SUCCESS.getMessageKey();
|
||||
}
|
||||
|
||||
+27
-19
@@ -4,37 +4,45 @@ import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
final class AdventureTextFormatter implements TextFormatter {
|
||||
@Override
|
||||
public void sendFormattedMessage(CommandSender sender, String message) {
|
||||
sender.sendMessage(colorize(message));
|
||||
public void sendFormattedMessage(@NotNull CommandSender sender, @Nullable String message) {
|
||||
String colorizedMessage = colorize(message);
|
||||
if (colorizedMessage == null) {
|
||||
return; // Avoid sending null messages
|
||||
}
|
||||
sender.sendMessage(colorizedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String removeColor(String message) {
|
||||
TextComponent sectionComponent = LegacyComponentSerializer.legacySection().deserialize(colorize(message));
|
||||
return PlainTextComponentSerializer.plainText().serialize(sectionComponent);
|
||||
}
|
||||
|
||||
public String removeAmpColor(String message) {
|
||||
return PlainTextComponentSerializer.plainText().serialize(toAmpComponent(message));
|
||||
}
|
||||
|
||||
public String removeSectionColor(String message) {
|
||||
return PlainTextComponentSerializer.plainText().serialize(toSectionComponent(message));
|
||||
public @Nullable String removeColor(@Nullable String message) {
|
||||
TextComponent sectionComponent = LegacyComponentSerializer.legacySection().deserializeOrNull(colorize(message));
|
||||
return PlainTextComponentSerializer.plainText().serializeOrNull(sectionComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String colorize(String message) {
|
||||
return LegacyComponentSerializer.legacySection().serialize(toAmpComponent(message));
|
||||
public @Nullable String removeAmpColor(@Nullable String message) {
|
||||
return PlainTextComponentSerializer.plainText().serializeOrNull(toAmpComponent(message));
|
||||
}
|
||||
|
||||
private TextComponent toAmpComponent(String message) {
|
||||
return LegacyComponentSerializer.legacyAmpersand().deserialize(message);
|
||||
@Override
|
||||
public @Nullable String removeSectionColor(@Nullable String message) {
|
||||
return PlainTextComponentSerializer.plainText().serializeOrNull(toSectionComponent(message));
|
||||
}
|
||||
|
||||
private TextComponent toSectionComponent(String message) {
|
||||
return LegacyComponentSerializer.legacySection().deserialize(message);
|
||||
@Override
|
||||
public @Nullable String colorize(@Nullable String message) {
|
||||
return LegacyComponentSerializer.legacySection().serializeOrNull(toAmpComponent(message));
|
||||
}
|
||||
|
||||
private @Nullable TextComponent toAmpComponent(@Nullable String message) {
|
||||
return LegacyComponentSerializer.legacyAmpersand().deserializeOrNull(message);
|
||||
}
|
||||
|
||||
private @Nullable TextComponent toSectionComponent(@Nullable String message) {
|
||||
return LegacyComponentSerializer.legacySection().deserializeOrNull(message);
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,36 @@ package org.mvplugins.multiverse.core.utils.text;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
final class ChatColorTextFormatter implements TextFormatter {
|
||||
@Override
|
||||
public void sendFormattedMessage(CommandSender sender, String message) {
|
||||
public void sendFormattedMessage(@NotNull CommandSender sender, String message) {
|
||||
sender.sendMessage(colorize(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String removeColor(String message) {
|
||||
public @Nullable String removeColor(@Nullable String message) {
|
||||
return ChatColor.stripColor(colorize(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String removeAmpColor(String message) {
|
||||
public @Nullable String removeAmpColor(@Nullable String message) {
|
||||
return removeColor(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String removeSectionColor(String message) {
|
||||
public @Nullable String removeSectionColor(@Nullable String message) {
|
||||
return ChatColor.stripColor(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String colorize(String message) {
|
||||
public @Nullable String colorize(@Nullable String message) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
return ChatColor.translateAlternateColorCodes('&', message);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package org.mvplugins.multiverse.core.utils.text;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
/**
|
||||
@@ -16,7 +18,8 @@ public final class ChatTextFormatter {
|
||||
private static final TextFormatter wrapper;
|
||||
|
||||
static {
|
||||
if (ReflectHelper.hasClass("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer")) {
|
||||
if (ReflectHelper.hasClass("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer")
|
||||
&& ReflectHelper.hasClass("net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer")) {
|
||||
wrapper = new AdventureTextFormatter();
|
||||
} else {
|
||||
wrapper = new ChatColorTextFormatter();
|
||||
@@ -32,7 +35,7 @@ public final class ChatTextFormatter {
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static void sendFormattedMessage(CommandSender sender, String message) {
|
||||
public static void sendFormattedMessage(@NotNull CommandSender sender, @Nullable String message) {
|
||||
wrapper.sendFormattedMessage(sender, message);
|
||||
}
|
||||
|
||||
@@ -44,7 +47,7 @@ public final class ChatTextFormatter {
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
public static String removeColor(String message) {
|
||||
public static @Nullable String removeColor(@Nullable String message) {
|
||||
return wrapper.removeColor(message);
|
||||
}
|
||||
|
||||
@@ -57,7 +60,7 @@ public final class ChatTextFormatter {
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String removeAmpColor(String message) {
|
||||
public static @Nullable String removeAmpColor(@Nullable String message) {
|
||||
return wrapper.removeAmpColor(message);
|
||||
}
|
||||
|
||||
@@ -70,7 +73,7 @@ public final class ChatTextFormatter {
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String removeSectionColor(String message) {
|
||||
public static @Nullable String removeSectionColor(@Nullable String message) {
|
||||
return wrapper.removeSectionColor(message);
|
||||
}
|
||||
|
||||
@@ -83,7 +86,7 @@ public final class ChatTextFormatter {
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String colorize(String message) {
|
||||
public static @Nullable String colorize(@Nullable String message) {
|
||||
return wrapper.colorize(message);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
package org.mvplugins.multiverse.core.utils.text;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
interface TextFormatter {
|
||||
void sendFormattedMessage(CommandSender sender, String message);
|
||||
void sendFormattedMessage(@NotNull CommandSender sender, @Nullable String message);
|
||||
|
||||
String removeColor(String message);
|
||||
@Nullable String removeColor(@Nullable String message);
|
||||
|
||||
String removeAmpColor(String message);
|
||||
@Nullable String removeAmpColor(@Nullable String message);
|
||||
|
||||
String removeSectionColor(String message);
|
||||
@Nullable String removeSectionColor(@Nullable String message);
|
||||
|
||||
String colorize(String message);
|
||||
@Nullable String colorize(@Nullable String message);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldPropertyChangedEvent;
|
||||
import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
@@ -37,11 +38,13 @@ final class WorldConfigNodes {
|
||||
private final NodeGroup nodes = new NodeGroup();
|
||||
private WorldManager worldManager;
|
||||
private EnforcementHandler enforcementHandler;
|
||||
private CoreConfig config;
|
||||
private MultiverseWorld world = null;
|
||||
|
||||
WorldConfigNodes(@NotNull MultiverseCore multiverseCore) {
|
||||
this.worldManager = multiverseCore.getServiceLocator().getService(WorldManager.class);
|
||||
this.enforcementHandler = multiverseCore.getServiceLocator().getService(EnforcementHandler.class);
|
||||
this.config = multiverseCore.getServiceLocator().getService(CoreConfig.class);
|
||||
}
|
||||
|
||||
MultiverseWorld getWorld() {
|
||||
@@ -238,13 +241,13 @@ final class WorldConfigNodes {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
if (newValue == null || newValue instanceof NullSpawnLocation) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
bukkitWorld.setSpawnLocation(newValue.getBlockX(), newValue.getBlockY(), newValue.getBlockZ());
|
||||
bukkitWorld.setSpawnLocation(newValue);
|
||||
newValue.setWorld(bukkitWorld);
|
||||
});
|
||||
}));
|
||||
|
||||
final ConfigNode<EntitySpawnConfig> enititySpawnConfig = node(ConfigNode.builder("spawning", EntitySpawnConfig.class)
|
||||
.defaultValue(() -> EntitySpawnConfig.fromSection(new MemoryConfiguration()))
|
||||
.defaultValue(() -> EntitySpawnConfig.fromSection(config, new MemoryConfiguration()))
|
||||
.hidden()
|
||||
.serializer(new NodeSerializer<>() {
|
||||
@Override
|
||||
@@ -252,7 +255,7 @@ final class WorldConfigNodes {
|
||||
ConfigurationSection spawnSection = (object instanceof ConfigurationSection section)
|
||||
? section
|
||||
: new MemoryConfiguration();
|
||||
return EntitySpawnConfig.fromSection(spawnSection);
|
||||
return EntitySpawnConfig.fromSection(config, spawnSection);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -19,12 +20,12 @@ import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -45,6 +46,7 @@ import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissions;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
import org.mvplugins.multiverse.core.utils.ServerProperties;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
@@ -62,7 +64,9 @@ import org.mvplugins.multiverse.core.world.options.CreateWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.ImportWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.KeepWorldSettingsOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.RegenWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.reasons.CloneFailureReason;
|
||||
import org.mvplugins.multiverse.core.world.reasons.CreateFailureReason;
|
||||
@@ -140,6 +144,7 @@ public final class WorldManager {
|
||||
*
|
||||
* @return The result of the load.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public Try<Void> initAllWorlds() {
|
||||
return updateWorldsFromConfig().andThenTry(() -> {
|
||||
importExistingWorlds();
|
||||
@@ -167,7 +172,11 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private void removeWorldsNotInConfigs(Collection<String> removedWorlds) {
|
||||
removedWorlds.forEach(worldName -> removeWorld(worldName)
|
||||
removedWorlds.forEach(worldName -> getWorld(worldName)
|
||||
.fold(
|
||||
() -> Attempt.failure(FailureReason.GENERIC, Message.of("world already removed")),
|
||||
world -> removeWorld(RemoveWorldOptions.world(world))
|
||||
)
|
||||
.onFailure(failure -> Logging.severe("Failed to unload world %s: %s", worldName, failure))
|
||||
.onSuccess(success -> Logging.fine("Unloaded world %s as it was removed from config", worldName)));
|
||||
}
|
||||
@@ -218,8 +227,9 @@ public final class WorldManager {
|
||||
if (isLoadedWorld(world) || !world.isAutoLoad()) {
|
||||
return;
|
||||
}
|
||||
loadWorld(world).onFailure(failure -> Logging.severe("Failed to load world %s: %s",
|
||||
world.getName(), failure));
|
||||
loadWorld(LoadWorldOptions.world(world))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to load world %s: %s", world.getName(), failure));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -278,23 +288,30 @@ public final class WorldManager {
|
||||
* @param options The options for customizing the import of an existing world folder.
|
||||
* @return The result of the import.
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, ImportFailureReason> importWorld(
|
||||
ImportWorldOptions options) {
|
||||
return validateImportWorldOptions(options).mapAttempt(this::doImportWorld);
|
||||
}
|
||||
|
||||
private Attempt<ImportWorldOptions, ImportFailureReason> validateImportWorldOptions(
|
||||
ImportWorldOptions options) {
|
||||
public Attempt<LoadedMultiverseWorld, ImportFailureReason> importWorld(ImportWorldOptions options) {
|
||||
String worldName = options.worldName();
|
||||
if (!worldNameChecker.isValidWorldName(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.INVALID_WORLDNAME, worldName);
|
||||
} else if (!worldNameChecker.isValidWorldFolder(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, worldName);
|
||||
} else if (isLoadedWorld(worldName)) {
|
||||
if (isLoadedWorld(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_EXIST_LOADED, worldName);
|
||||
} else if (isWorld(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_EXIST_UNLOADED, worldName);
|
||||
}
|
||||
|
||||
World bukkitWorld = Bukkit.getWorld(worldName);
|
||||
if (bukkitWorld != null) {
|
||||
// World is already loaded, maybe by another plugin
|
||||
return doImportBukkitWorld(options, bukkitWorld);
|
||||
}
|
||||
|
||||
return validateImportWorldOptions(options).mapAttempt(this::doImportWorld);
|
||||
}
|
||||
|
||||
private Attempt<ImportWorldOptions, ImportFailureReason> validateImportWorldOptions(ImportWorldOptions options) {
|
||||
String worldName = options.worldName();
|
||||
if (!worldNameChecker.isValidWorldName(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.INVALID_WORLDNAME, worldName);
|
||||
} else if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, worldName);
|
||||
}
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
@@ -319,6 +336,23 @@ public final class WorldManager {
|
||||
});
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportBukkitWorld(ImportWorldOptions options, World bukkitWorld) {
|
||||
if (options.environment() != bukkitWorld.getEnvironment()) {
|
||||
return Attempt.failure(ImportFailureReason.BUKKIT_ENVIRONMENT_MISMATCH,
|
||||
Replace.WORLD.with(bukkitWorld.getName()),
|
||||
replace("{bukkitEnvironment}").with(bukkitWorld.getEnvironment().name()),
|
||||
replace("{mvEnvironment}").with(options.environment().name()));
|
||||
}
|
||||
|
||||
LoadedMultiverseWorld loadedWorld = newLoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
generatorProvider.parseGeneratorString(options.worldName(), options.generator()),
|
||||
options.biome(),
|
||||
options.useSpawnAdjust());
|
||||
pluginManager.callEvent(new MVWorldImportedEvent(loadedWorld));
|
||||
return Attempt.success(loadedWorld);
|
||||
}
|
||||
|
||||
private MultiverseWorld newMultiverseWorld(String worldName, WorldConfig worldConfig) {
|
||||
MultiverseWorld mvWorld = new MultiverseWorld(worldName, worldConfig, config);
|
||||
worldsMap.put(mvWorld.getName(), mvWorld);
|
||||
@@ -371,10 +405,14 @@ public final class WorldManager {
|
||||
*
|
||||
* @param worldName The name of the world to load.
|
||||
* @return The result of the load.
|
||||
*
|
||||
* @deprecated Use {@link #loadWorld(LoadWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<LoadedMultiverseWorld, LoadFailureReason> loadWorld(@NotNull String worldName) {
|
||||
return getWorld(worldName)
|
||||
.map(this::loadWorld)
|
||||
.map(world -> loadWorld(LoadWorldOptions.world(world)))
|
||||
.getOrElse(() -> worldNameChecker.isValidWorldFolder(worldName)
|
||||
? worldActionResult(LoadFailureReason.WORLD_EXIST_FOLDER, worldName)
|
||||
: worldActionResult(LoadFailureReason.WORLD_NON_EXISTENT, worldName));
|
||||
@@ -385,12 +423,30 @@ public final class WorldManager {
|
||||
*
|
||||
* @param world The world to load.
|
||||
* @return The result of the load.
|
||||
*
|
||||
* @deprecated Use {@link #loadWorld(LoadWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<LoadedMultiverseWorld, LoadFailureReason> loadWorld(@NotNull MultiverseWorld world) {
|
||||
return validateWorldToLoad(world).mapAttempt(this::doLoadWorld);
|
||||
return loadWorld(LoadWorldOptions.world(world));
|
||||
}
|
||||
|
||||
private Attempt<MultiverseWorld, LoadFailureReason> validateWorldToLoad(@NotNull MultiverseWorld mvWorld) {
|
||||
/**
|
||||
* Loads an existing world in config.
|
||||
*
|
||||
* @param options The options for customizing the loading of a world.
|
||||
* @return The result of the load.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public Attempt<LoadedMultiverseWorld, LoadFailureReason> loadWorld(@NotNull LoadWorldOptions options) {
|
||||
return validateWorldToLoad(options).mapAttempt(this::doLoadWorld);
|
||||
}
|
||||
|
||||
private Attempt<LoadWorldOptions, LoadFailureReason> validateWorldToLoad(@NotNull LoadWorldOptions options) {
|
||||
MultiverseWorld mvWorld = options.world();
|
||||
if (loadTracker.contains(mvWorld.getName())) {
|
||||
// This is to prevent recursive calls by WorldLoadEvent
|
||||
Logging.fine("World already loading: " + mvWorld.getName());
|
||||
@@ -399,15 +455,19 @@ public final class WorldManager {
|
||||
Logging.severe("World already loaded: " + mvWorld.getName());
|
||||
return worldActionResult(LoadFailureReason.WORLD_EXIST_LOADED, mvWorld.getName());
|
||||
}
|
||||
return worldActionResult(mvWorld);
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, LoadFailureReason> doLoadWorld(@NotNull MultiverseWorld mvWorld) {
|
||||
private Attempt<LoadedMultiverseWorld, LoadFailureReason> doLoadWorld(@NotNull LoadWorldOptions options) {
|
||||
MultiverseWorld mvWorld = options.world();
|
||||
|
||||
World bukkitWorld = Bukkit.getWorld(mvWorld.getName());
|
||||
if (bukkitWorld != null) {
|
||||
// World already loaded, maybe by another plugin
|
||||
Logging.finer("World already loaded in bukkit: " + mvWorld.getName());
|
||||
return newLoadedMultiverseWorld(mvWorld, bukkitWorld);
|
||||
return doLoadBukkitWorld(bukkitWorld, mvWorld);
|
||||
}
|
||||
|
||||
if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(mvWorld.getName())) {
|
||||
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
|
||||
}
|
||||
|
||||
WorldCreator worldCreator = WorldCreator.name(mvWorld.getName())
|
||||
@@ -420,6 +480,18 @@ public final class WorldManager {
|
||||
.mapAttempt(newBukkitWorld -> newLoadedMultiverseWorld(mvWorld, newBukkitWorld));
|
||||
}
|
||||
|
||||
private @NotNull Attempt<LoadedMultiverseWorld, LoadFailureReason> doLoadBukkitWorld(World bukkitWorld, MultiverseWorld mvWorld) {
|
||||
if (bukkitWorld.getEnvironment() != mvWorld.getEnvironment()) {
|
||||
return Attempt.failure(LoadFailureReason.BUKKIT_ENVIRONMENT_MISMATCH,
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
replace("{bukkitEnvironment}").with(bukkitWorld.getEnvironment().name()),
|
||||
replace("{mvEnvironment}").with(mvWorld.getEnvironment().name()));
|
||||
}
|
||||
// World already loaded, maybe by another plugin
|
||||
Logging.finer("World already loaded in bukkit: " + mvWorld.getName());
|
||||
return newLoadedMultiverseWorld(mvWorld, bukkitWorld);
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, LoadFailureReason> newLoadedMultiverseWorld(MultiverseWorld mvWorld, World bukkitWorld) {
|
||||
WorldConfig worldConfig = worldsConfigManager.getWorldConfig(mvWorld.getName()).get(); //TODO: null check here, but logically it should never be null.
|
||||
LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld(
|
||||
@@ -481,11 +553,14 @@ public final class WorldManager {
|
||||
*
|
||||
* @param worldName The name of the world to remove.
|
||||
* @return The result of the remove.
|
||||
*
|
||||
* @deprecated Get the {@link MultiverseWorld} yourself and use {@link #removeWorld(RemoveWorldOptions)} instead.
|
||||
*/
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(
|
||||
@NotNull String worldName) {
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull String worldName) {
|
||||
return getWorld(worldName)
|
||||
.map(this::removeWorld)
|
||||
.map(world -> removeWorld(RemoveWorldOptions.world(world)))
|
||||
.getOrElse(() -> worldActionResult(RemoveFailureReason.WORLD_NON_EXISTENT, worldName));
|
||||
}
|
||||
|
||||
@@ -495,11 +570,13 @@ public final class WorldManager {
|
||||
*
|
||||
* @param world The multiverse world to remove.
|
||||
* @return The result of the remove.
|
||||
*
|
||||
* @deprecated Use {@link #removeWorld(RemoveWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull MultiverseWorld world) {
|
||||
return getLoadedWorld(world).fold(
|
||||
() -> removeWorldFromConfig(world),
|
||||
this::removeWorld);
|
||||
return removeWorld(RemoveWorldOptions.world(world));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,9 +585,40 @@ public final class WorldManager {
|
||||
*
|
||||
* @param loadedWorld The multiverse world to remove.
|
||||
* @return The result of the remove.
|
||||
*
|
||||
* @deprecated Use {@link #removeWorld(RemoveWorldOptions)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.2", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull LoadedMultiverseWorld loadedWorld) {
|
||||
return unloadWorld(UnloadWorldOptions.world(loadedWorld))
|
||||
return removeWorld(RemoveWorldOptions.world(loadedWorld));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing multiverse world. It will be deleted from the worlds config and will no longer be known
|
||||
* to Multiverse. World files will not be deleted.
|
||||
*
|
||||
* @param options The options for customizing the removal of a world.
|
||||
* @return The result of the remove action.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public Attempt<String, RemoveFailureReason> removeWorld(@NotNull RemoveWorldOptions options) {
|
||||
MultiverseWorld world = options.world();
|
||||
return getLoadedWorld(world).fold(
|
||||
() -> removeWorldFromConfig(world),
|
||||
loadedWorld -> unloadBeforeRemoveWorld(loadedWorld, options)
|
||||
);
|
||||
}
|
||||
|
||||
private Attempt<String, RemoveFailureReason> unloadBeforeRemoveWorld(@NotNull LoadedMultiverseWorld loadedWorld,
|
||||
@NotNull RemoveWorldOptions options) {
|
||||
UnloadWorldOptions unloadWorldOptions = UnloadWorldOptions.world(loadedWorld)
|
||||
.saveBukkitWorld(options.saveBukkitWorld())
|
||||
.unloadBukkitWorld(options.unloadBukkitWorld());
|
||||
|
||||
return unloadWorld(unloadWorldOptions)
|
||||
.transform(RemoveFailureReason.UNLOAD_FAILED)
|
||||
.mapAttempt(this::removeWorldFromConfig);
|
||||
}
|
||||
@@ -541,12 +649,18 @@ public final class WorldManager {
|
||||
*/
|
||||
public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull DeleteWorldOptions options) {
|
||||
return getLoadedWorld(options.world()).fold(
|
||||
() -> loadWorld(options.world())
|
||||
.transform(DeleteFailureReason.LOAD_FAILED)
|
||||
.mapAttempt(world -> doDeleteWorld(world, options)),
|
||||
() -> loadThenDeleteWorld(options),
|
||||
world -> doDeleteWorld(world, options));
|
||||
}
|
||||
|
||||
private Attempt<String, DeleteFailureReason> loadThenDeleteWorld(@NotNull DeleteWorldOptions options) {
|
||||
return loadWorld(LoadWorldOptions.world(options.world()))
|
||||
.fold(
|
||||
ignore -> worldActionResult(DeleteFailureReason.LOAD_FAILED, options.world().getName()),
|
||||
loadedWorld -> doDeleteWorld(loadedWorld, options)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing multiverse world entirely. Warning: This will delete all world files.
|
||||
*
|
||||
@@ -564,7 +678,11 @@ public final class WorldManager {
|
||||
? Attempt.failure(DeleteFailureReason.EVENT_CANCELLED)
|
||||
: Attempt.success(null);
|
||||
})
|
||||
.mapAttempt(() -> removeWorld(world).transform(DeleteFailureReason.REMOVE_FAILED))
|
||||
.mapAttempt(() -> removeWorld(RemoveWorldOptions
|
||||
.world(world)
|
||||
.unloadBukkitWorld(true)
|
||||
.saveBukkitWorld(false)
|
||||
).transform(DeleteFailureReason.REMOVE_FAILED))
|
||||
.mapAttempt(() -> fileUtils.deleteFolder(worldFolder.get(), options.keepFiles()).fold(
|
||||
exception -> worldActionResult(DeleteFailureReason.FAILED_TO_DELETE_FOLDER,
|
||||
world.getName(), exception),
|
||||
@@ -632,7 +750,7 @@ public final class WorldManager {
|
||||
private Attempt<CloneWorldOptions, CloneFailureReason> cloneWorldCopyFolder(@NotNull CloneWorldOptions options) {
|
||||
if (options.saveBukkitWorld()) {
|
||||
Logging.finer("Saving bukkit world before cloning: " + options.world().getName());
|
||||
options.world().getBukkitWorld().peek(World::save);
|
||||
options.world().getBukkitWorld().peek(this::saveWorldWithFlush);
|
||||
}
|
||||
File worldFolder = options.world().getBukkitWorld().map(World::getWorldFolder).get();
|
||||
File newWorldFolder = new File(Bukkit.getWorldContainer(), options.newWorldName());
|
||||
@@ -642,6 +760,17 @@ public final class WorldManager {
|
||||
success -> worldActionResult(options));
|
||||
}
|
||||
|
||||
// This method is only available since 1.21
|
||||
private final Method saveWithFlush = ReflectHelper.getMethod(World.class, "save", boolean.class);
|
||||
private void saveWorldWithFlush(World world) {
|
||||
if (saveWithFlush != null) {
|
||||
Logging.fine("Using world save method with flush...");
|
||||
ReflectHelper.invokeMethod(world, saveWithFlush, true);
|
||||
} else {
|
||||
world.save();
|
||||
}
|
||||
}
|
||||
|
||||
private void cloneWorldTransferData(@NotNull CloneWorldOptions options, @NotNull LoadedMultiverseWorld newWorld) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = transferData(options, options.world());
|
||||
dataTransfer.pasteAllTo(newWorld);
|
||||
@@ -901,6 +1030,9 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private Option<MultiverseWorld> getUnloadedWorldByAlias(@Nullable String alias) {
|
||||
if (alias == null || alias.isEmpty()) {
|
||||
return Option.none();
|
||||
}
|
||||
String colourlessAlias = ChatTextFormatter.removeColor(alias);
|
||||
return Option.ofOptional(worldsMap.values().stream()
|
||||
.filter(world -> !world.isLoaded())
|
||||
@@ -936,7 +1068,7 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable World world) {
|
||||
return world == null ? Option.none() : Option.of((LoadedMultiverseWorld) loadedWorldsMap.get(world.getName()));
|
||||
return world == null ? Option.none() : Option.of(loadedWorldsMap.get(world.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -946,7 +1078,7 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable MultiverseWorld world) {
|
||||
return world == null ? Option.none() : Option.of((LoadedMultiverseWorld) loadedWorldsMap.get(world.getName()));
|
||||
return world == null ? Option.none() : Option.of(loadedWorldsMap.get(world.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -956,7 +1088,7 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable String worldName) {
|
||||
return Option.of((LoadedMultiverseWorld) loadedWorldsMap.get(worldName));
|
||||
return Option.of(loadedWorldsMap.get(worldName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -971,9 +1103,12 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private Option<LoadedMultiverseWorld> getLoadedWorldByAlias(@Nullable String alias) {
|
||||
if (alias == null || alias.isEmpty()) {
|
||||
return Option.none();
|
||||
}
|
||||
return Option.ofOptional(loadedWorldsMap.values().stream()
|
||||
.filter(world -> world.getColourlessAlias().equalsIgnoreCase(ChatColor.stripColor(alias)))
|
||||
.map(world -> (LoadedMultiverseWorld) world)
|
||||
.filter(world -> world.getColourlessAlias()
|
||||
.equalsIgnoreCase(ChatTextFormatter.removeColor(alias)))
|
||||
.findFirst());
|
||||
}
|
||||
|
||||
@@ -984,7 +1119,6 @@ public final class WorldManager {
|
||||
*/
|
||||
public Collection<LoadedMultiverseWorld> getLoadedWorlds() {
|
||||
return loadedWorldsMap.values().stream()
|
||||
.map(world -> (LoadedMultiverseWorld) world)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
/**
|
||||
* Manages the worlds.yml file.
|
||||
@@ -81,10 +82,11 @@ final class WorldsConfigManager {
|
||||
}
|
||||
|
||||
// Copy old config file to `worlds.yml.old`
|
||||
Path oldWorldConfig = worldConfigFile.toPath().getParent().resolve(CONFIG_FILENAME + ".old");
|
||||
Files.copy(worldConfigFile.toPath(), oldWorldConfig, COPY_ATTRIBUTES);
|
||||
Path oldWorldConfig = worldConfigFile.toPath().getParent()
|
||||
.resolve(CONFIG_FILENAME + ".old." + System.currentTimeMillis());
|
||||
Files.copy(worldConfigFile.toPath(), oldWorldConfig, COPY_ATTRIBUTES, REPLACE_EXISTING);
|
||||
|
||||
return configData.replace("==: MVWorld", "")
|
||||
return configData.replace("==: MVWorld", "w@: world")
|
||||
.replace("==: MVSpawnSettings", "")
|
||||
.replace("==: MVSpawnSubSettings", "")
|
||||
.replace("==: MVEntryFee", "");
|
||||
@@ -92,16 +94,26 @@ final class WorldsConfigManager {
|
||||
.andThenTry(configData -> Files.writeString(worldConfigFile.toPath(), configData))
|
||||
.andThenTry(() -> {
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(worldConfigFile);
|
||||
List<ConfigurationSection> worlds = config.getConfigurationSection("worlds")
|
||||
.getKeys(false)
|
||||
.stream()
|
||||
.map(worldName -> config.getConfigurationSection("worlds." + worldName))
|
||||
.toList();
|
||||
|
||||
ConfigurationSection worldsSection = config.getConfigurationSection("worlds");
|
||||
if (worldsSection == null) {
|
||||
worldsSection = config.createSection("worlds");
|
||||
}
|
||||
|
||||
List<String> worldNames = getOldConfigWorldNames(worldsSection);
|
||||
|
||||
Map<String, ConfigurationSection> worldConfigMap = new HashMap<>();
|
||||
for (String worldName : worldNames) {
|
||||
ConfigurationSection worldSection = worldsSection.getConfigurationSection(worldName);
|
||||
if (worldSection != null) {
|
||||
worldConfigMap.put(worldName, worldSection);
|
||||
}
|
||||
}
|
||||
|
||||
config.set("worlds", null);
|
||||
|
||||
for (ConfigurationSection world : worlds) {
|
||||
config.createSection(world.getName(), world.getValues(true));
|
||||
for (Map.Entry<String, ConfigurationSection> entry : worldConfigMap.entrySet()) {
|
||||
config.set(encodeWorldName(entry.getKey()), entry.getValue());
|
||||
}
|
||||
config.save(worldConfigFile);
|
||||
})
|
||||
@@ -115,6 +127,35 @@ final class WorldsConfigManager {
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull List<String> getOldConfigWorldNames(ConfigurationSection worldsSection) {
|
||||
List<String> worldNames = new ArrayList<>();
|
||||
recursiveGetOldConfigWorldNames(worldsSection, worldNames);
|
||||
return worldNames;
|
||||
}
|
||||
|
||||
private void recursiveGetOldConfigWorldNames(ConfigurationSection section, List<String> worldNames) {
|
||||
Set<String> keys = section.getKeys(false);
|
||||
if (keys.isEmpty()) {
|
||||
// No keys in this section, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (keys.contains("w@")) {
|
||||
// this is the world data section already, get path without the "worlds." prefix
|
||||
worldNames.add(section.getCurrentPath().substring(7));
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : keys) {
|
||||
ConfigurationSection dataSection = section.getConfigurationSection(key);
|
||||
if (dataSection == null) {
|
||||
// Something is wrong with the config, skip this key
|
||||
continue;
|
||||
}
|
||||
recursiveGetOldConfigWorldNames(dataSection, worldNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the worlds.yml file and creates a WorldConfig for each world in the file if it doesn't already exist.
|
||||
*
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
package org.mvplugins.multiverse.core.world.entity;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class EntitySpawnConfig {
|
||||
|
||||
private final CoreConfig config;
|
||||
private final Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig;
|
||||
|
||||
EntitySpawnConfig(Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig) {
|
||||
EntitySpawnConfig(CoreConfig config, Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig) {
|
||||
this.config = config;
|
||||
this.spawnCategoriesConfig = spawnCategoriesConfig;
|
||||
}
|
||||
|
||||
public SpawnCategoryConfig getSpawnCategoryConfig(SpawnCategory spawnCategory) {
|
||||
return spawnCategoriesConfig.computeIfAbsent(spawnCategory,
|
||||
computeSpawnCategory -> new SpawnCategoryConfig(computeSpawnCategory, new MemoryConfiguration()));
|
||||
computeSpawnCategory -> new SpawnCategoryConfig(
|
||||
config,
|
||||
computeSpawnCategory,
|
||||
new MemoryConfiguration()
|
||||
));
|
||||
}
|
||||
|
||||
public boolean shouldAllowSpawn(Entity entity) {
|
||||
@@ -52,17 +58,17 @@ public final class EntitySpawnConfig {
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static EntitySpawnConfig fromSection(ConfigurationSection section) {
|
||||
Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig = new HashMap<>();
|
||||
public static EntitySpawnConfig fromSection(CoreConfig config, ConfigurationSection section) {
|
||||
Map<SpawnCategory, SpawnCategoryConfig> spawnCategoriesConfig = new LinkedHashMap<>();
|
||||
section.getValues(false).forEach((key, value) -> {
|
||||
if (!(value instanceof ConfigurationSection sectionPart)) {
|
||||
Logging.warning("Invalid spawn category config for " + key + ": " + value);
|
||||
return;
|
||||
}
|
||||
SpawnCategory spawnCategory = SpawnCategory.valueOf(key.toUpperCase());
|
||||
spawnCategoriesConfig.put(spawnCategory, new SpawnCategoryConfig(spawnCategory, sectionPart));
|
||||
spawnCategoriesConfig.put(spawnCategory, new SpawnCategoryConfig(config, spawnCategory, sectionPart));
|
||||
});
|
||||
return new EntitySpawnConfig(spawnCategoriesConfig);
|
||||
return new EntitySpawnConfig(config, spawnCategoriesConfig);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.handle.MemoryConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
@@ -21,6 +22,7 @@ import java.util.List;
|
||||
|
||||
public final class SpawnCategoryConfig {
|
||||
|
||||
private final CoreConfig config;
|
||||
private final SpawnCategory spawnCategory;
|
||||
private final MemoryConfigurationHandle handle;
|
||||
private final StringPropertyHandle stringPropertyHandle;
|
||||
@@ -28,7 +30,8 @@ public final class SpawnCategoryConfig {
|
||||
|
||||
private MultiverseWorld world;
|
||||
|
||||
SpawnCategoryConfig(SpawnCategory spawnCategory, ConfigurationSection section) {
|
||||
SpawnCategoryConfig(CoreConfig config, SpawnCategory spawnCategory, ConfigurationSection section) {
|
||||
this.config = config;
|
||||
this.spawnCategory = spawnCategory;
|
||||
this.nodes = new Nodes();
|
||||
this.handle = MemoryConfigurationHandle.builder(section, nodes.nodes)
|
||||
@@ -57,23 +60,39 @@ public final class SpawnCategoryConfig {
|
||||
return;
|
||||
}
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
if (!isSpawn()) {
|
||||
if (getExceptions().isEmpty()) {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: 0", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, 0);
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: -1", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, -1);
|
||||
}
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: %d", world.getName(), spawnCategory, getTickRate());
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, getTickRate());
|
||||
}
|
||||
Logging.finer("World %s %s setSpawnLimit: %d", world.getName(), spawnCategory, getSpawnLimit());
|
||||
bukkitWorld.setSpawnLimit(spawnCategory, getSpawnLimit());
|
||||
applyTickPerSpawns(bukkitWorld);
|
||||
applySpawnLimit(bukkitWorld);
|
||||
});
|
||||
}
|
||||
|
||||
private void applyTickPerSpawns(World bukkitWorld) {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
Logging.finer("World %s %s skipping setTicksPerSpawns due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
if (!isSpawn()) {
|
||||
if (getExceptions().isEmpty()) {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: 0", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, 0);
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: -1", world.getName(), spawnCategory);
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, -1);
|
||||
}
|
||||
} else {
|
||||
Logging.finer("World %s %s setTicksPerSpawns: %d", world.getName(), spawnCategory, getTickRate());
|
||||
bukkitWorld.setTicksPerSpawns(spawnCategory, getTickRate());
|
||||
}
|
||||
}
|
||||
|
||||
private void applySpawnLimit(World bukkitWorld) {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
Logging.finer("Skipping World %s %s setSpawnLimit due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
Logging.finer("World %s %s setSpawnLimit: %d", world.getName(), spawnCategory, getSpawnLimit());
|
||||
bukkitWorld.setSpawnLimit(spawnCategory, getSpawnLimit());
|
||||
}
|
||||
|
||||
public StringPropertyHandle getStringPropertyHandle() {
|
||||
return stringPropertyHandle;
|
||||
}
|
||||
|
||||
+26
-12
@@ -27,6 +27,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.utils.FileUtils;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
@@ -38,15 +39,17 @@ import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
public final class GeneratorProvider implements Listener {
|
||||
private final Map<String, String> defaultGenerators;
|
||||
private final Map<String, GeneratorPlugin> generatorPlugins;
|
||||
private final CoreConfig coreConfig;
|
||||
private final FileUtils fileUtils;
|
||||
|
||||
@Inject
|
||||
GeneratorProvider(@NotNull MultiverseCore multiverseCore, @NotNull FileUtils fileUtils) {
|
||||
GeneratorProvider(@NotNull MultiverseCore plugin, @NotNull CoreConfig coreConfig, @NotNull FileUtils fileUtils) {
|
||||
this.coreConfig = coreConfig;
|
||||
this.fileUtils = fileUtils;
|
||||
defaultGenerators = new HashMap<>();
|
||||
generatorPlugins = new HashMap<>();
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(this, multiverseCore);
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
loadDefaultWorldGenerators();
|
||||
loadPluginGenerators();
|
||||
}
|
||||
@@ -73,11 +76,14 @@ public final class GeneratorProvider implements Listener {
|
||||
* Find generator plugins from plugins loaded and register them.
|
||||
*/
|
||||
private void loadPluginGenerators() {
|
||||
Arrays.stream(Bukkit.getPluginManager().getPlugins()).forEach(plugin -> {
|
||||
if (!coreConfig.getAutoDetectGeneratorPlugins()) {
|
||||
return;
|
||||
}
|
||||
for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
|
||||
if (testIsGeneratorPlugin(plugin)) {
|
||||
registerGeneratorPlugin(new SimpleGeneratorPlugin(plugin.getName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,14 +94,19 @@ public final class GeneratorProvider implements Listener {
|
||||
*/
|
||||
private boolean testIsGeneratorPlugin(Plugin plugin) {
|
||||
String worldName = Bukkit.getWorlds().stream().findFirst().map(World::getName).orElse("world");
|
||||
return Try.of(() -> plugin.getDefaultWorldGenerator(worldName, "") != null)
|
||||
.recover(IllegalArgumentException.class, true)
|
||||
.recover(throwable -> {
|
||||
Logging.warning("Plugin %s threw an exception when testing if it is a generator plugin!",
|
||||
plugin.getName());
|
||||
throwable.printStackTrace();
|
||||
return false;
|
||||
}).getOrElse(false);
|
||||
try {
|
||||
return plugin.getDefaultWorldGenerator(worldName, null) != null;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Some plugins throw this if they don't support world generation
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
Logging.warning("Plugin %s threw an exception when testing if it is a generator plugin! ",
|
||||
plugin.getName());
|
||||
Logging.warning("This is NOT a bug in Multiverse. Do NOT report this to Multiverse support.");
|
||||
t.printStackTrace();
|
||||
// Assume it's a generator plugin since it tried to do something, most likely the id is wrong.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,6 +217,9 @@ public final class GeneratorProvider implements Listener {
|
||||
*/
|
||||
@EventHandler
|
||||
private void onPluginEnable(PluginEnableEvent event) {
|
||||
if (!coreConfig.getAutoDetectGeneratorPlugins()) {
|
||||
return;
|
||||
}
|
||||
if (!testIsGeneratorPlugin(event.getPlugin())) {
|
||||
Logging.finest("Plugin %s is not a generator plugin.", event.getPlugin().getName());
|
||||
return;
|
||||
|
||||
+8
-8
@@ -22,13 +22,13 @@ import java.util.Objects;
|
||||
* This is useful to store location with world that may not be loaded yet or have been unloaded at some point.
|
||||
*/
|
||||
@SerializableAs("UnloadedWorldLocation")
|
||||
public final class UnloadedWorldLocation extends Location {
|
||||
public class UnloadedWorldLocation extends Location {
|
||||
|
||||
public static UnloadedWorldLocation fromLocation(@NotNull Location location) {
|
||||
return new UnloadedWorldLocation(location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
|
||||
}
|
||||
|
||||
private String worldName;
|
||||
private @Nullable String worldName;
|
||||
|
||||
public UnloadedWorldLocation(@Nullable String worldName, double x, double y, double z) {
|
||||
super(null, x, y, z);
|
||||
@@ -67,7 +67,7 @@ public final class UnloadedWorldLocation extends Location {
|
||||
this.worldName = worldName;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
public @Nullable String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
@@ -160,10 +160,10 @@ public final class UnloadedWorldLocation extends Location {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 19 * hash + worldName.hashCode();
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getX()) ^ (Double.doubleToLongBits(this.getX()) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getY()) ^ (Double.doubleToLongBits(this.getY()) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.getZ()) ^ (Double.doubleToLongBits(this.getZ()) >>> 32));
|
||||
hash = 19 * hash + String.valueOf(worldName).hashCode();
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getX()));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getY()));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.getZ()));
|
||||
hash = 19 * hash + Float.floatToIntBits(this.getPitch());
|
||||
hash = 19 * hash + Float.floatToIntBits(this.getYaw());
|
||||
return hash;
|
||||
@@ -171,7 +171,7 @@ public final class UnloadedWorldLocation extends Location {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Location{" +
|
||||
return "UnloadedWorldLocation{" +
|
||||
"world=" + worldName +
|
||||
",x=" + getX() +
|
||||
",y=" + getY() +
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -24,6 +25,7 @@ public final class ImportWorldOptions {
|
||||
private World.Environment environment = World.Environment.NORMAL;
|
||||
private String generator = null;
|
||||
private boolean useSpawnAdjust = true;
|
||||
private boolean doFolderCheck = true;
|
||||
|
||||
ImportWorldOptions(String worldName) {
|
||||
this.worldName = worldName;
|
||||
@@ -119,4 +121,30 @@ public final class ImportWorldOptions {
|
||||
public boolean useSpawnAdjust() {
|
||||
return useSpawnAdjust;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to ensure the world folder is a valid world before importing it.
|
||||
*
|
||||
* @param doFolderCheckInput Whether to do the folder check
|
||||
* @return This {@link ImportWorldOptions} instance
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public @NotNull ImportWorldOptions doFolderCheck(boolean doFolderCheckInput) {
|
||||
this.doFolderCheck = doFolderCheckInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to ensure the world folder is a valid world before importing it.
|
||||
*
|
||||
* @return Whether to do the folder check
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean doFolderCheck() {
|
||||
return doFolderCheck;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Options for customizing the loading of a world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public final class LoadWorldOptions {
|
||||
|
||||
/**
|
||||
* Creates a new {@link LoadWorldOptions} instance with the given world.
|
||||
*
|
||||
* @param world The world to load.
|
||||
* @return A new {@link LoadWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public static LoadWorldOptions world(MultiverseWorld world) {
|
||||
return new LoadWorldOptions(world);
|
||||
}
|
||||
|
||||
private final MultiverseWorld world;
|
||||
private boolean doFolderCheck = true;
|
||||
|
||||
LoadWorldOptions(MultiverseWorld world) {
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world to load.
|
||||
*
|
||||
* @return The world to load.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MultiverseWorld world() {
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to check if the world folder is valid before loading the world.
|
||||
* <br />
|
||||
* This helps to prevent deleted world folders from being re-created.
|
||||
*
|
||||
* @return Whether to check if the world folder is valid before loading the world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean doFolderCheck() {
|
||||
return doFolderCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to check if the world folder is valid before loading the world.
|
||||
*
|
||||
* @return This {@link LoadWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public LoadWorldOptions doFolderCheck(boolean doFolderCheck) {
|
||||
this.doFolderCheck = doFolderCheck;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Options for customizing the removal of a world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public final class RemoveWorldOptions {
|
||||
|
||||
/**
|
||||
* Creates a new {@link RemoveWorldOptions} instance with the given world. The world may be a loaded or unloaded.
|
||||
*
|
||||
* @param world The world to remove.
|
||||
* @return A new {@link RemoveWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public static RemoveWorldOptions world(MultiverseWorld world) {
|
||||
return new RemoveWorldOptions(world);
|
||||
}
|
||||
|
||||
private final MultiverseWorld world;
|
||||
private boolean saveBukkitWorld = true;
|
||||
private boolean unloadBukkitWorld = true;
|
||||
|
||||
private RemoveWorldOptions(MultiverseWorld world) {
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world to remove. The world may be a loaded or unloaded.
|
||||
*
|
||||
* @return The world to remove.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public MultiverseWorld world() {
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to save the Bukkit world before removing it.
|
||||
* <br />
|
||||
* This option only applies when {@link #unloadBukkitWorld()} is true.
|
||||
*
|
||||
* @param saveBukkitWorldInput Whether to save the Bukkit world.
|
||||
* @return This {@link RemoveWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public RemoveWorldOptions saveBukkitWorld(boolean saveBukkitWorldInput) {
|
||||
this.saveBukkitWorld = saveBukkitWorldInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to save the Bukkit world before removing it. By default, this is true.
|
||||
* <br />
|
||||
* This option only applies when {@link #unloadBukkitWorld()} is true.
|
||||
*
|
||||
* @return Whether to save the Bukkit world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean saveBukkitWorld() {
|
||||
return saveBukkitWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to unload the Bukkit world before removing it. This option is usually only used if the world is
|
||||
* managed by another plugin and you want to untrack it from Multiverse without unloading from the server.
|
||||
*
|
||||
* @param unloadBukkitWorldInput Whether to unload the Bukkit world.
|
||||
* @return This {@link RemoveWorldOptions} instance.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public RemoveWorldOptions unloadBukkitWorld(boolean unloadBukkitWorldInput) {
|
||||
this.unloadBukkitWorld = unloadBukkitWorldInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to unload the Bukkit world before removing it. By default, this is true.
|
||||
*
|
||||
* @return Whether to unload the Bukkit world.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean unloadBukkitWorld() {
|
||||
return unloadBukkitWorld;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.world.reasons;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
@@ -30,6 +31,14 @@ public enum ImportFailureReason implements FailureReason {
|
||||
*/
|
||||
WORLD_EXIST_LOADED(MVCorei18n.IMPORTWORLD_WORLDEXISTLOADED),
|
||||
|
||||
/**
|
||||
* The import environment input does not match the loaded Bukkit world's environment.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
BUKKIT_ENVIRONMENT_MISMATCH(MVCorei18n.IMPORTWORLD_BUKKITENVIRONMENTMISMATCH),
|
||||
|
||||
/**
|
||||
* Bukkit API failed to create the world.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.world.reasons;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
@@ -25,11 +26,27 @@ public enum LoadFailureReason implements FailureReason {
|
||||
*/
|
||||
WORLD_EXIST_FOLDER(MVCorei18n.LOADWORLD_WORLDEXISTFOLDER),
|
||||
|
||||
/**
|
||||
* The world folder is invalid.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
WORLD_FOLDER_INVALID(MVCorei18n.IMPORTWORLD_WORLDFOLDERINVALID),
|
||||
|
||||
/**
|
||||
* The world is already loaded.
|
||||
*/
|
||||
WORLD_EXIST_LOADED(MVCorei18n.LOADWORLD_WORLDEXISTLOADED),
|
||||
|
||||
/**
|
||||
* The mv world's environment does not match the loaded Bukkit world's environment.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
BUKKIT_ENVIRONMENT_MISMATCH(MVCorei18n.LOADWORLD_BUKKITENVIRONMENTMISMATCH),
|
||||
|
||||
/**
|
||||
* Bukkit API failed to create the world.
|
||||
*/
|
||||
|
||||
@@ -3,8 +3,10 @@ package org.mvplugins.multiverse.core.world.reasons;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
/**
|
||||
* Result of a world removal operation.
|
||||
@@ -12,7 +14,11 @@ import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
public enum RemoveFailureReason implements FailureReason {
|
||||
/**
|
||||
* The world does not exist.
|
||||
*
|
||||
* @deprecated No longer in use as {@link WorldManager#removeWorld(String)} is now deprecated.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.2")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
WORLD_NON_EXISTENT(MVCorei18n.REMOVEWORLD_WORLDNONEXISTENT),
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,19 +281,21 @@ mv-core.createworld.worldexistunloaded=World '{world}' already exists, but it's
|
||||
mv-core.createworld.worldexistloaded=World '{world}' already exists!
|
||||
|
||||
mv-core.deleteworld.worldnonexistent=World '{world}' not found!
|
||||
mv-core.deleteworld.loadfailed=Unable to load world '{world}', does the world folder exist?&f You can run '&a/mv remove {world}&f' to remove it from Multiverse, or attempt to load again with '&a/mv load {world}&f'.
|
||||
mv-core.deleteworld.worldfoldernotfound=World '{world}' folder not found!
|
||||
mv-core.deleteworld.loadfailed=Unable to load world '{world}', is the world folder deleted or corrupted?&f You can run '&a/mv remove {world}&f' to remove it from Multiverse, or attempt to load again with '&a/mv load {world}&f'.
|
||||
mv-core.deleteworld.worldfoldernotfound=&cUnable to locate '{world}' folder! You may '&a/mv remove {world}&c' to remove it from Multiverse, and manually delete the folder if it exists.
|
||||
mv-core.deleteworld.failedtodeletefolder=Failed to delete world folder '{world}': {error}\n&fSee console for more details.
|
||||
|
||||
mv-core.importworld.invalidworldname=World '{world}' contains invalid characters!
|
||||
mv-core.importworld.worldexistunloaded=World '{world}' already exists, but it's not loaded!&f Type '&a/mv load {world}&f' if you wish to load it.
|
||||
mv-core.importworld.worldexistloaded=World '{world}' already exists!
|
||||
mv-core.importworld.worldfolderinvalid=World '{world}' folder contents does not seem to be a valid world!
|
||||
mv-core.importworld.worldfolderinvalid=World '{world}' folder contents does not seem to be a valid world!\n&cIf the server software does something different with world folders, or you are very certain the world is valid, use '&3--skip-folder-check&c' flag.
|
||||
mv-core.importworld.bukkitenvironmentmismatch=Environment mismatch detected!&f The world '{world}' is already loaded with environment '{bukkitEnvironment}', but Multiverse is trying to import it with environment '{mvEnvironment}'.
|
||||
|
||||
mv-core.loadworld.worldalreadyloading=World '{world}' is already loading! Please wait...
|
||||
mv-core.loadworld.worldnonexistent=World '{world}' not found! Use '&a/mv create {world} <environment>&f' to create it.
|
||||
mv-core.loadworld.worldexistfolder=World '{world}' exists in server folders, but it's not known to Multiverse!&f Type '&a/mv import {world} <environment>&f' if you wish to import it.
|
||||
mv-core.loadworld.worldexistloaded=World '{world}' is already loaded!
|
||||
mv-core.loadworld.bukkitenvironmentmismatch=Environment mismatch detected!&f The world '{world}' is already loaded with environment '{bukkitEnvironment}', but Multiverse is trying to load it with environment '{mvEnvironment}'.
|
||||
|
||||
mv-core.removeworld.worldnonexistent=World '{world}' not found!
|
||||
|
||||
@@ -325,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?
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.mvplugins.multiverse.core.utils
|
||||
|
||||
import org.mvplugins.multiverse.core.utils.matcher.ExactStringMatcher
|
||||
import org.mvplugins.multiverse.core.utils.matcher.RegexStringMatcher
|
||||
import org.mvplugins.multiverse.core.utils.matcher.WildcardStringMatcher
|
||||
import kotlin.test.Test
|
||||
|
||||
class StringMatcherTest {
|
||||
|
||||
@Test
|
||||
fun `exact string matcher - single string`() {
|
||||
val matcher = ExactStringMatcher("test")
|
||||
assert(matcher.matches("test"))
|
||||
assert(!matcher.matches("Test"))
|
||||
assert(!matcher.matches("testing"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exact string matcher - multiple strings`() {
|
||||
val matcher = ExactStringMatcher(setOf("test", "example", "sample"));
|
||||
assert(matcher.matches("test"))
|
||||
assert(matcher.matches("example"))
|
||||
assert(matcher.matches("sample"))
|
||||
assert(!matcher.matches("Test"))
|
||||
assert(!matcher.matches("testing"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `wildcard string matcher - single`() {
|
||||
val matcher = WildcardStringMatcher("test*")
|
||||
assert(matcher.matches("test"))
|
||||
assert(matcher.matches("test123"))
|
||||
assert(matcher.matches("testing"))
|
||||
assert(!matcher.matches("TEST"))
|
||||
assert(!matcher.matches("nottest123"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `wildcard string matcher - multiple`() {
|
||||
val matcher = WildcardStringMatcher("test-*_*-world")
|
||||
assert(matcher.matches("test-123_pl-world"))
|
||||
assert(matcher.matches("test-abc_xyz-world"))
|
||||
assert(matcher.matches("test-hello_world-world"))
|
||||
assert(!matcher.matches("test-hello-world"))
|
||||
assert(!matcher.matches("test-hello_world-world!"))
|
||||
assert(!matcher.matches("TEST-123_PL-WORLD"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `regex string matcher`() {
|
||||
val matcher = RegexStringMatcher("r=^test[0-9]+$")
|
||||
assert(matcher.matches("test123"))
|
||||
assert(matcher.matches("test456"))
|
||||
assert(!matcher.matches("test"))
|
||||
assert(!matcher.matches("TEST123"))
|
||||
assert(!matcher.matches("testing"))
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class WorldConfigMangerTest : TestWithMockBukkit() {
|
||||
assertEquals(MVEconomist.VAULT_ECONOMY_MATERIAL, endWorldConfig.entryFeeCurrency)
|
||||
assertEquals(0.0, endWorldConfig.entryFeeAmount)
|
||||
|
||||
val worldConfig = worldConfigManager.getWorldConfig("world").orNull
|
||||
val worldConfig = worldConfigManager.getWorldConfig("world.a.b").orNull
|
||||
assertNotNull(worldConfig)
|
||||
|
||||
assertEquals(-5176596003035866649, worldConfig.seed)
|
||||
@@ -58,6 +58,9 @@ class WorldConfigMangerTest : TestWithMockBukkit() {
|
||||
assertEquals(Material.DIRT, worldConfig.entryFeeCurrency)
|
||||
assertEquals(5.0, worldConfig.entryFeeAmount)
|
||||
|
||||
val world2Config = worldConfigManager.getWorldConfig("world.a.c").orNull
|
||||
assertNotNull(world2Config)
|
||||
|
||||
assertConfigEquals("/worlds/migrated_worlds.yml", "worlds.yml")
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ import org.mvplugins.multiverse.core.event.world.*
|
||||
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.RegenWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions
|
||||
import org.mvplugins.multiverse.core.world.reasons.CloneFailureReason
|
||||
import org.mvplugins.multiverse.core.world.reasons.CreateFailureReason
|
||||
@@ -102,7 +104,7 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `Remove world`() {
|
||||
assertTrue(worldManager.removeWorld(world).isSuccess)
|
||||
assertTrue(worldManager.removeWorld(RemoveWorldOptions.world(world)).isSuccess)
|
||||
assertFalse(worldManager.getWorld("world").isDefined)
|
||||
assertFalse(worldManager.getLoadedWorld("world").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("world").isDefined)
|
||||
@@ -132,13 +134,23 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
assertTrue(worldManager.getWorld("world2").isDefined)
|
||||
assertTrue(worldManager.getUnloadedWorld("world2").isDefined)
|
||||
|
||||
assertTrue(worldManager.loadWorld("world2").isSuccess)
|
||||
assertTrue(worldManager.loadWorld(LoadWorldOptions.world(world2)).isSuccess)
|
||||
assertTrue(world2.isLoaded)
|
||||
assertTrue(worldManager.getLoadedWorld("world2").flatMap{ w -> w.bukkitWorld }.isDefined)
|
||||
assertTrue(worldManager.getLoadedWorld("world2").isDefined)
|
||||
assertFalse(worldManager.getUnloadedWorld("world2").isDefined)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load world failed - invalid world folder`() {
|
||||
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
|
||||
File(Bukkit.getWorldContainer(), "world2/").deleteRecursively()
|
||||
assertEquals(
|
||||
LoadFailureReason.WORLD_FOLDER_INVALID,
|
||||
worldManager.loadWorld(LoadWorldOptions.world(world2)).failureReason
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load world failed - non-existent world`() {
|
||||
assertEquals(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,7 +46,7 @@ world_the_end:
|
||||
tick-rate: -1
|
||||
world-blacklist: []
|
||||
version: 1.2
|
||||
world:
|
||||
world[dot]a[dot]b:
|
||||
adjust-spawn: true
|
||||
alias: ''
|
||||
allow-advancement-grant: true
|
||||
@@ -95,3 +95,52 @@ world:
|
||||
world-blacklist:
|
||||
- test
|
||||
version: 1.2
|
||||
world[dot]a[dot]c:
|
||||
adjust-spawn: true
|
||||
alias: ''
|
||||
allow-advancement-grant: true
|
||||
allow-flight: false
|
||||
allow-weather: true
|
||||
anchor-respawn: true
|
||||
auto-heal: true
|
||||
auto-load: true
|
||||
bed-respawn: true
|
||||
difficulty: normal
|
||||
entry-fee:
|
||||
enabled: true
|
||||
amount: 5.0
|
||||
currency: DIRT
|
||||
environment: normal
|
||||
gamemode: survival
|
||||
biome: ''
|
||||
generator: ''
|
||||
hidden: false
|
||||
hunger: true
|
||||
keep-spawn-in-memory: true
|
||||
player-limit: -1
|
||||
portal-form: all
|
||||
pvp: true
|
||||
respawn-world: ''
|
||||
scale: 1.0
|
||||
seed: -5176596003035866649
|
||||
spawn-location:
|
||||
==: MVSpawnLocation
|
||||
x: -64.0
|
||||
y: 64.0
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
spawning:
|
||||
animal:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
monster:
|
||||
exceptions: [ ]
|
||||
spawn: true
|
||||
spawn-limit: -1
|
||||
tick-rate: -1
|
||||
world-blacklist:
|
||||
- test
|
||||
version: 1.2
|
||||
@@ -46,7 +46,7 @@ worlds:
|
||||
generator: 'null'
|
||||
playerLimit: '-1'
|
||||
allowFlight: 'true'
|
||||
world:
|
||||
world.a.b:
|
||||
==: MVWorld
|
||||
hidden: 'false'
|
||||
alias: ''
|
||||
@@ -95,3 +95,52 @@ worlds:
|
||||
generator: 'null'
|
||||
playerLimit: '-1'
|
||||
allowFlight: 'false'
|
||||
world.a.c:
|
||||
==: MVWorld
|
||||
hidden: 'false'
|
||||
alias: ''
|
||||
color: WHITE
|
||||
style: NORMAL
|
||||
pvp: 'true'
|
||||
scale: '1.0'
|
||||
respawnWorld: ''
|
||||
allowWeather: 'true'
|
||||
difficulty: NORMAL
|
||||
spawning:
|
||||
==: MVSpawnSettings
|
||||
animals:
|
||||
==: MVSpawnSubSettings
|
||||
spawn: 'true'
|
||||
spawnrate: '-1'
|
||||
exceptions: []
|
||||
monsters:
|
||||
==: MVSpawnSubSettings
|
||||
spawn: 'true'
|
||||
spawnrate: '-1'
|
||||
exceptions: []
|
||||
entryfee:
|
||||
==: MVEntryFee
|
||||
amount: '5.0'
|
||||
currency: 'DIRT'
|
||||
hunger: 'true'
|
||||
autoHeal: 'true'
|
||||
adjustSpawn: 'true'
|
||||
portalForm: ALL
|
||||
gameMode: SURVIVAL
|
||||
keepSpawnInMemory: 'true'
|
||||
spawnLocation:
|
||||
==: MVSpawnLocation
|
||||
x: -64.0
|
||||
y: 64.0
|
||||
z: 48.0
|
||||
pitch: 0.0
|
||||
yaw: 0.0
|
||||
autoLoad: 'true'
|
||||
bedRespawn: 'true'
|
||||
worldBlacklist:
|
||||
- test
|
||||
environment: NORMAL
|
||||
seed: '-5176596003035866649'
|
||||
generator: 'null'
|
||||
playerLimit: '-1'
|
||||
allowFlight: 'false'
|
||||
Reference in new issue
Block a user