mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 16:50:03 -05:00
Compare commits
54
Commits
5.7.2-pre.2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34185b90a9 | ||
|
|
5d57bbfda9 | ||
|
|
a78459ab77 | ||
|
|
0ad795b27f | ||
|
|
2765cf6f3e | ||
|
|
7a9b21f9a0 | ||
|
|
f55ceda42c | ||
|
|
893c69a6e1 | ||
|
|
6bb93766d4 | ||
|
|
fe49efe276 | ||
|
|
8de11b59a1 | ||
|
|
744450cbbb | ||
|
|
ecf107563d | ||
|
|
f7ea31d949 | ||
|
|
1bf4b99ecf | ||
|
|
4de4e5a68e | ||
|
|
f6a8a6b3b4 | ||
|
|
d485238c56 | ||
|
|
5234445746 | ||
|
|
0b626d9eaf | ||
|
|
2ff321e475 | ||
|
|
58dbe55be4 | ||
|
|
a2045b52bd | ||
|
|
396ece4d27 | ||
|
|
e25ac0e531 | ||
|
|
7cf53ee7a8 | ||
|
|
4a46c13754 | ||
|
|
5449ade6f3 | ||
|
|
a9f8deeb01 | ||
|
|
1ce7eb6d3e | ||
|
|
d11d664020 | ||
|
|
abd86f81e5 | ||
|
|
0faec79250 | ||
|
|
9264f5e2ce | ||
|
|
892ce24845 | ||
|
|
091eec96b9 | ||
|
|
37ecceb179 | ||
|
|
7697065519 | ||
|
|
ad7b9638f1 | ||
|
|
37ba5ecc04 | ||
|
|
2445464ab8 | ||
|
|
7fad9aa2e4 | ||
|
|
a8c0b221cd | ||
|
|
826fe4c6d0 | ||
|
|
28240c3649 | ||
|
|
c8e4c3239a | ||
|
|
b23735dc72 | ||
|
|
97cbb4576d | ||
|
|
1ccfa2b24b | ||
|
|
d50cd6cce5 | ||
|
|
b500956555 | ||
|
|
de80667bb4 | ||
|
|
68bbb6ab90 | ||
|
|
16738f181a |
No files matched your search
@@ -0,0 +1,77 @@
|
||||
name: 'Generic: Artifact Comment'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
plugin_name:
|
||||
description: 'Plugin name used by Generic: Test'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
comment-artifact:
|
||||
if: >-
|
||||
github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
# Run separately so fork builds can post comments without a write token.
|
||||
# Do not check out or execute code from the pull request in this workflow.
|
||||
- name: Find pull request and artifact
|
||||
id: artifact
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PLUGIN_NAME: ${{ inputs.plugin_name }}
|
||||
with:
|
||||
script: |
|
||||
const run = context.payload.workflow_run;
|
||||
// Fork runs can have an empty pull_requests array in the event.
|
||||
const pulls = await github.paginate(github.rest.pulls.list, {
|
||||
...context.repo,
|
||||
state: 'open',
|
||||
head: `${run.head_repository.owner.login}:${run.head_branch}`,
|
||||
per_page: 100,
|
||||
});
|
||||
const pull = pulls.find(pr =>
|
||||
pr.head.repo?.id === run.head_repository.id &&
|
||||
pr.head.ref === run.head_branch &&
|
||||
pr.head.sha === run.head_sha
|
||||
);
|
||||
if (!pull) {
|
||||
core.info('No open pull request matches this build; skipping comment.');
|
||||
return;
|
||||
}
|
||||
|
||||
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
|
||||
...context.repo,
|
||||
run_id: run.id,
|
||||
per_page: 100,
|
||||
});
|
||||
const artifact = artifacts.find(item =>
|
||||
item.name === `${process.env.PLUGIN_NAME}-pr${pull.number}` && !item.expired
|
||||
);
|
||||
if (!artifact) {
|
||||
core.info('No build artifact is available; skipping comment.');
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput('number', pull.number);
|
||||
core.setOutput('url', `${run.html_url}/artifacts/${artifact.id}`);
|
||||
|
||||
- name: Comment artifact
|
||||
if: steps.artifact.outputs.number != ''
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
with:
|
||||
number: ${{ steps.artifact.outputs.number }}
|
||||
header: build-artifact-${{ inputs.plugin_name }}
|
||||
message: |
|
||||
### ✅ `${{ inputs.plugin_name }}` test build ready
|
||||
|
||||
Last updated for: [${{ github.event.workflow_run.head_sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }})
|
||||
**Download:** [${{ inputs.plugin_name }}-pr${{ steps.artifact.outputs.number }}.zip](${{ steps.artifact.outputs.url }})
|
||||
|
||||
View [CI build and checks](${{ github.event.workflow_run.html_url }}) for more details.
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
files: '["${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar"]'
|
||||
name: ${{ steps.release-info.outputs.tag_name }}
|
||||
changelog: ${{ steps.release-artifact.outputs.body }}
|
||||
game_versions: 26.2, 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
game_versions: 26.3, 26.2, 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
version_type: ${{ steps.parse-release-type.outputs.release_type }}
|
||||
loaders: bukkit, spigot, paper, purpur
|
||||
dependencies: ${{ inputs.modrinth_dependencies }}
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
changelog: ${{ steps.release-artifact.outputs.body }}
|
||||
changelog_type: markdown
|
||||
display_name: ${{ steps.release-info.outputs.tag_name }}
|
||||
game_versions: 26.2, 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
game_versions: 26.3, 26.2, 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
|
||||
release_type: ${{ steps.parse-release-type.outputs.release_type }}
|
||||
project_relations: ${{ inputs.dbo_project_relations }}
|
||||
file_path: ${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar
|
||||
@@ -136,5 +136,5 @@ jobs:
|
||||
channel: ${{ steps.parse-release-type.outputs.release_type }}
|
||||
files: '[{"path": "${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar", "platforms": ["PAPER"]}]'
|
||||
description: ${{ steps.release-artifact.outputs.body }}
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-26.2"]}'
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-26.3"]}'
|
||||
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
|
||||
@@ -36,13 +36,3 @@ jobs:
|
||||
with:
|
||||
name: ${{ inputs.plugin_name }}-pr${{ github.event.pull_request.number }}
|
||||
path: build/libs/${{ inputs.plugin_name }}-pr${{ github.event.pull_request.number }}.jar
|
||||
|
||||
comment-artifact:
|
||||
needs: test-and-artifact
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: benwoo1110/artifact-comment-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,15 @@
|
||||
name: 'PR: Artifact Comment'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['PR: Test']
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
comment-artifact:
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: write
|
||||
uses: ./.github/workflows/generic.artifact_comment.yml
|
||||
with:
|
||||
plugin_name: multiverse-core
|
||||
@@ -46,3 +46,6 @@ debug.txt
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
|
||||
# Kotlin generated files
|
||||
/.kotlin/
|
||||
+10
-10
@@ -9,13 +9,13 @@ description = 'Multiverse-Core'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = 'aikar repo'
|
||||
url = uri('https://repo.aikar.co/content/groups/aikar/')
|
||||
name = 'jitpack repo'
|
||||
url = uri('https://jitpack.io')
|
||||
}
|
||||
|
||||
maven {
|
||||
name = 'glaremasters repo'
|
||||
url = 'https://repo.glaremasters.me/repository/towny/'
|
||||
name = 'aikar repo'
|
||||
url = uri('https://repo.aikar.co/content/groups/aikar/')
|
||||
}
|
||||
|
||||
maven {
|
||||
@@ -37,18 +37,18 @@ dependencies {
|
||||
}
|
||||
|
||||
// PlaceholderAPI
|
||||
externalPlugin 'me.clip:placeholderapi:2.11.6'
|
||||
externalPlugin 'me.clip:placeholderapi:2.12.3'
|
||||
|
||||
// Command Framework
|
||||
shadowed 'co.aikar:acf-paper:0.5.1-SNAPSHOT'
|
||||
|
||||
// Config
|
||||
shadowed('io.github.townyadvanced.commentedconfiguration:CommentedConfiguration:1.0.1') {
|
||||
shadowed('com.github.TownyAdvanced:CommentedConfiguration:1.0.3') {
|
||||
exclude group: 'org.spigotmc', module: 'spigot-api'
|
||||
}
|
||||
|
||||
// Utils
|
||||
shadowed 'io.vavr:vavr:0.10.7'
|
||||
shadowed 'io.vavr:vavr:1.0.1'
|
||||
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'
|
||||
@@ -57,11 +57,11 @@ dependencies {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
shadowed 'de.themoep.idconverter:mappings:1.2-SNAPSHOT'
|
||||
shadowed('org.bstats:bstats-bukkit:3.1.0') {
|
||||
shadowed('org.bstats:bstats-bukkit:3.2.1') {
|
||||
exclude group: 'org.bukkit', module: 'bukkit'
|
||||
}
|
||||
shadowed 'net.minidev:json-smart:2.5.2'
|
||||
shadowed 'org.jetbrains:annotations:26.0.2'
|
||||
shadowed 'net.minidev:json-smart:2.6.0'
|
||||
shadowed 'org.jetbrains:annotations:26.1.0'
|
||||
shadowed 'io.papermc:paperlib:1.0.8'
|
||||
|
||||
// Tests
|
||||
|
||||
@@ -102,11 +102,12 @@ public class MultiverseCore extends MultiverseModule {
|
||||
SpawnCategoryMapper.buildSpawnCategoryMap();
|
||||
|
||||
// Initialize the worlds
|
||||
worldManagerProvider.get().initAllWorlds().andThenTry(() -> {
|
||||
Try.run(() -> {
|
||||
setUpLocales();
|
||||
worldManagerProvider.get().initAllWorlds();
|
||||
loadEconomist(); // Setup economy here so vault is loaded
|
||||
loadAnchors();
|
||||
registerDynamicListeners(CoreListener.class);
|
||||
setUpLocales();
|
||||
registerCommands(CoreCommand.class);
|
||||
registerDestinations();
|
||||
setupMetrics();
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package org.mvplugins.multiverse.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
@@ -16,18 +24,13 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Provides access to the MultiverseCore API.
|
||||
*/
|
||||
public final class MultiverseCoreApi {
|
||||
|
||||
private static MultiverseCoreApi instance;
|
||||
private static final List<Consumer<MultiverseCoreApi>> whenLoadedCallbacks = new ArrayList<>();
|
||||
private static final List<Consumer<MultiverseCoreApi>> WHEN_LOADED_CALLBACKS = new ArrayList<>();
|
||||
|
||||
static void init(@NotNull MultiverseCore multiverseCore) {
|
||||
if (instance != null) {
|
||||
@@ -36,11 +39,24 @@ public final class MultiverseCoreApi {
|
||||
instance = new MultiverseCoreApi(multiverseCore.getServiceLocator());
|
||||
Bukkit.getServicesManager().register(MultiverseCoreApi.class, instance, multiverseCore, ServicePriority.Normal);
|
||||
|
||||
whenLoadedCallbacks.forEach(c -> c.accept(instance));
|
||||
whenLoadedCallbacks.clear();
|
||||
List<Consumer<MultiverseCoreApi>> callbacks = List.copyOf(WHEN_LOADED_CALLBACKS);
|
||||
WHEN_LOADED_CALLBACKS.clear();
|
||||
MultiverseCoreApi loadedApi = instance;
|
||||
callbacks.forEach(callback -> runLoadCallback(callback, loadedApi));
|
||||
}
|
||||
|
||||
private static void runLoadCallback(
|
||||
@NotNull Consumer<MultiverseCoreApi> callback,
|
||||
@NotNull MultiverseCoreApi loadedApi) {
|
||||
Try.run(() -> callback.accept(loadedApi))
|
||||
.onFailure(exception -> Logging.warning(
|
||||
"A Multiverse-Core API load callback failed: %s", exception.getMessage()));
|
||||
}
|
||||
|
||||
static void shutdown() {
|
||||
if (instance == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getServicesManager().unregister(instance);
|
||||
instance = null;
|
||||
}
|
||||
@@ -62,7 +78,7 @@ public final class MultiverseCoreApi {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance);
|
||||
} else {
|
||||
whenLoadedCallbacks.add(consumer);
|
||||
WHEN_LOADED_CALLBACKS.add(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,8 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
@Override
|
||||
public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) {
|
||||
// Split string in to an Array with underscores
|
||||
List<String> paramsArray = Lists.newArrayList(REPatterns.UNDERSCORE.split(params));
|
||||
List<String> paramsArray = Lists.newArrayList(
|
||||
StringFormatter.parseQuotesInArgs(REPatterns.UNDERSCORE.split(params), "_"));
|
||||
|
||||
// No placeholder defined
|
||||
if (paramsArray.isEmpty()) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import co.aikar.commands.LogLevel;
|
||||
import co.aikar.commands.PaperCommandManager;
|
||||
import co.aikar.commands.RootCommand;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -24,6 +26,7 @@ import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizedMessage;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.WorldNameChecker;
|
||||
|
||||
@@ -68,6 +71,16 @@ public class MVCommandManager extends PaperCommandManager {
|
||||
this.setDefaultExceptionHandler(new MVDefaultExceptionHandler());
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
LocalizedMessage.setDefaultLocalesManager(getLocales());
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void preDestroy() {
|
||||
LocalizedMessage.setDefaultLocalesManager(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a list of commands and handles {@link LegacyAliasCommand} based on config option.
|
||||
* @param commands The commands to register
|
||||
|
||||
@@ -61,7 +61,7 @@ class CheckCommand extends CoreCommand {
|
||||
replace("{location}").with(destination.getLocation(player)
|
||||
.map(locationManipulation::locationToString)
|
||||
.map(Message::of)
|
||||
.getOrElse(() -> Message.of(MVCorei18n.GENERIC_NULL, "Null!"))));
|
||||
.getOrElse(() -> Message.of(MVCorei18n.GENERIC_NULL))));
|
||||
|
||||
// TODO: Show permission required for this particular destination
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class CloneCommand extends CoreCommand {
|
||||
@Subcommand("clone")
|
||||
@CommandPermission("multiverse.core.clone")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @empty @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> <new-world-name> [--reset-world-config --reset-gamerules --reset-world-border]")
|
||||
@Syntax("<world> <new-world-name> [--reset-world-config --reset-gamerules --reset-world-border --no-save]")
|
||||
@Description("{@@mv-core.clone.description}")
|
||||
void onCloneCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -51,11 +51,11 @@ class CloneCommand extends CoreCommand {
|
||||
MultiverseWorld world,
|
||||
|
||||
@Syntax("<new-world-name>")
|
||||
@Description("{@@mv-core.clone.newWorld.description}")
|
||||
@Description("{@@mv-core.clone.newworld.description}")
|
||||
String newWorldName,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--reset-world-config --reset-gamerules --reset-world-border]")
|
||||
@Syntax("[--reset-world-config --reset-gamerules --reset-world-border --no-save]")
|
||||
@Description("{@@mv-core.regen.other.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
@@ -56,7 +56,7 @@ class CreateCommand extends CoreCommand {
|
||||
@CommandPermission("multiverse.core.create")
|
||||
@CommandCompletion("@empty @environments @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> " +
|
||||
"--adjust-spawn --no-structures --generate-bonus-chest --force-spawn-position <x,y,z:pitch:yaw> " +
|
||||
"--no-adjust-spawn --no-structures --generate-bonus-chest --force-spawn-position <x,y,z:pitch:yaw> " +
|
||||
"--biome <biome> --generator-settings <json-settings> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.description}")
|
||||
void onCreateCommand(
|
||||
@@ -71,7 +71,7 @@ class CreateCommand extends CoreCommand {
|
||||
World.Environment environment,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn " +
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --no-adjust-spawn " +
|
||||
"--no-structures --generate-bonus-chest --force-spawn-position <x,y,z:pitch:yaw> --biome <biome> " +
|
||||
"--generator-settings <json-settings> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.flags.description}")
|
||||
|
||||
@@ -79,7 +79,7 @@ class DeleteCommand extends CoreCommand {
|
||||
commandQueueManager.addToQueue(CommandQueuePayload
|
||||
.issuer(issuer)
|
||||
.action(() -> runDeleteCommand(issuer, world, parsedFlags))
|
||||
.prompt(Message.of(MVCorei18n.DELETE_PROMPT, "",
|
||||
.prompt(Message.of(MVCorei18n.DELETE_PROMPT,
|
||||
Replace.WORLD.with(world.getName()))));
|
||||
}
|
||||
|
||||
|
||||
@@ -54,21 +54,21 @@ class GameruleCommand extends CoreCommand {
|
||||
@Subcommand("gamerule|rule set")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules @gamerulesvalues @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> <Gamerule value> [World or *]")
|
||||
@Syntax("<gamerule> <value> [worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.set.description}")
|
||||
void onGameruleSetCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<Gamerule>")
|
||||
@Syntax("<gamerule>")
|
||||
@Description("{@@mv-core.gamerule.set.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Syntax("<Value>")
|
||||
@Syntax("<value>")
|
||||
@Description("{@@mv-core.gamerule.set.value.description}")
|
||||
GameRuleValue gameRuleValue,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=2")
|
||||
@Syntax("[World or *]")
|
||||
@Syntax("[worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.set.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
Object value = gameRuleValue.value();
|
||||
@@ -105,17 +105,17 @@ class GameruleCommand extends CoreCommand {
|
||||
@Subcommand("gamerule|rule reset")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> [World or *]")
|
||||
@Syntax("<gamerule> [worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.reset.description}")
|
||||
void onGameruleResetCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<Gamerule>")
|
||||
@Syntax("<gamerule>")
|
||||
@Description("{@@mv-core.gamerule.reset.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[World or *]")
|
||||
@Syntax("[worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.reset.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
AtomicBoolean success = new AtomicBoolean(true);
|
||||
@@ -153,7 +153,7 @@ class GameruleCommand extends CoreCommand {
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("<world>")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.gamerule.list.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@ class ImportCommand extends CoreCommand {
|
||||
@Subcommand("import")
|
||||
@CommandPermission("multiverse.core.import")
|
||||
@CommandCompletion("@mvworlds:scope=potential @environments @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<name> <environment> [--generator <generator[:id]> --adjust-spawn --biome <biome>]")
|
||||
@Syntax("<name> <environment> [--generator <generator[:id]> --no-adjust-spawn --biome <biome> " +
|
||||
"--skip-folder-check --generator-settings <json-settings>]")
|
||||
@Description("{@@mv-core.import.description}")
|
||||
void onImportCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -53,12 +54,13 @@ class ImportCommand extends CoreCommand {
|
||||
@Description("{@@mv-core.import.name.description}")
|
||||
String worldName,
|
||||
|
||||
@Syntax("<env>")
|
||||
@Syntax("<environment>")
|
||||
@Description("{@@mv-core.import.env.description}")
|
||||
World.Environment environment,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--generator <generator[:id]> --adjust-spawn --biome <biome>]")
|
||||
@Syntax("[--generator <generator[:id]> --no-adjust-spawn --biome <biome> " +
|
||||
"--skip-folder-check --generator-settings <json-settings>]")
|
||||
@Description("{@@mv-core.import.other.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
@@ -54,12 +54,12 @@ class ListCommand extends CoreCommand {
|
||||
@Subcommand("list")
|
||||
@CommandPermission("multiverse.core.list.worlds")
|
||||
@CommandCompletion("@flags:groupName=" + Flags.NAME)
|
||||
@Syntax("--filter [filter] --page [page] --raw")
|
||||
@Syntax("[--filter <filter>] [--page <page>] [--raw]")
|
||||
@Description("{{@mv-core.list.description}}")
|
||||
public void onListCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("[--filter <filter>] [--page <page>]")
|
||||
@Syntax("[--filter <filter>] [--page <page>] [--raw]")
|
||||
@Description("Filters the list of worlds by the given regex and displays the given page.")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
@@ -39,7 +39,7 @@ class LoadCommand extends CoreCommand {
|
||||
@Subcommand("load")
|
||||
@CommandPermission("multiverse.core.load")
|
||||
@CommandCompletion("@mvworlds:scope=unloaded @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Syntax("<world> [--skip-folder-check]")
|
||||
@Description("{@@mv-core.load.description}")
|
||||
void onLoadCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@@ -70,7 +70,7 @@ class RegenCommand extends CoreCommand {
|
||||
@CommandPermission("multiverse.core.regen")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> [--seed [seed]] [--reset-world-config] [--reset-gamerules] [--reset-world-border] " +
|
||||
"[--remove-players [destination]]")
|
||||
"[--keep-files <files>] [--remove-players [destination]]")
|
||||
@Description("{@@mv-core.regen.description}")
|
||||
void onRegenCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -81,7 +81,7 @@ class RegenCommand extends CoreCommand {
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed [seed]] [--reset-world-config] [--reset-gamerules] [--reset-world-border] " +
|
||||
"[--remove-players [destination]]")
|
||||
"[--keep-files <files>] [--remove-players [destination]]")
|
||||
@Description("{@@mv-core.regen.other.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -89,7 +89,7 @@ class RegenCommand extends CoreCommand {
|
||||
commandQueueManager.addToQueue(CommandQueuePayload
|
||||
.issuer(issuer)
|
||||
.action(() -> runRegenCommand(issuer, world, parsedFlags))
|
||||
.prompt(Message.of(MVCorei18n.REGEN_PROMPT, "",
|
||||
.prompt(Message.of(MVCorei18n.REGEN_PROMPT,
|
||||
Replace.WORLD.with(world.getName()))));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,12 @@ class SetSpawnCommand extends CoreCommand {
|
||||
@Subcommand("setspawn")
|
||||
@CommandPermission("multiverse.core.spawn.set")
|
||||
@CommandCompletion("@flags:groupName=" + UnsafeFlags.NAME + " @flags:resolveUntil=arg1,groupName=" + UnsafeFlags.NAME)
|
||||
@Syntax("[worldname:x,y,z[,pitch,yaw]] [--unsafe]")
|
||||
@Syntax("[worldname:x,y,z[,yaw,pitch]] [--unsafe]")
|
||||
@Description("{@@mv-core.setspawn.description}")
|
||||
void onSetSpawnCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("[worldname:x,y,z[,pitch,yaw]]")
|
||||
@Syntax("[worldname:x,y,z[,yaw,pitch]]")
|
||||
@Description("{@@mv-core.setspawn.location.description}")
|
||||
PlayerLocation playerLocation,
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
@CommandPermission("@mvspawn")
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvspawnother|@flags:resolveUntil=arg1,groupName=" + UnsafeFlags.NAME
|
||||
+ " @flags:groupName=" + UnsafeFlags.NAME)
|
||||
@Syntax("[player]")
|
||||
@Syntax("[player] [--unsafe]")
|
||||
@Description("{@@mv-core.spawn.description}")
|
||||
void onSpawnTpCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@@ -59,7 +59,7 @@ final class TeleportCommand extends CoreCommand {
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvteleportother|@destinations:byIssuerForArg=arg1 "
|
||||
+ "@destinations:notByIssuerForArg=arg1|@flags:byIssuerForArg=arg1,groupName=" + Flags.NAME + " "
|
||||
+ "@flags:notByIssuerForArg=arg1,groupName=" + Flags.NAME)
|
||||
@Syntax("[player] <destination> [--unsafe]")
|
||||
@Syntax("[player] <destination> [--unsafe --silent]")
|
||||
@Description("{@@mv-core.teleport.description}")
|
||||
void onTeleportCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -74,7 +74,7 @@ final class TeleportCommand extends CoreCommand {
|
||||
DestinationInstance<?, ?> destination,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--unsafe]")
|
||||
@Syntax("[--unsafe --silent]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
Player[] players = playersValue.value();
|
||||
|
||||
@@ -52,7 +52,7 @@ class UnloadCommand extends CoreCommand {
|
||||
@Subcommand("unload")
|
||||
@CommandPermission("multiverse.core.unload")
|
||||
@CommandCompletion("@mvworlds @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> [--remove-players [destination]] [--no-save]")
|
||||
@Syntax("<world> [--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Description("{@@mv-core.unload.description}")
|
||||
void onUnloadCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -62,7 +62,7 @@ class UnloadCommand extends CoreCommand {
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players [destination]] [--no-save]")
|
||||
@Syntax("[--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Description("{@@mv-core.gamerules.description.page}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
@@ -50,10 +50,10 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
void onWorldBorderCenter(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("[x]")
|
||||
@Syntax("<x>")
|
||||
double x,
|
||||
|
||||
@Syntax("[z]")
|
||||
@Syntax("<z>")
|
||||
double z,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
|
||||
@@ -246,6 +246,16 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.endWorldNameFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public Try<Void> setWarnAliasConflicts(boolean warnAliasConflicts) {
|
||||
return configHandle.set(configNodes.warnAliasConflicts, warnAliasConflicts);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public boolean getWarnAliasConflicts() {
|
||||
return configHandle.get(configNodes.warnAliasConflicts);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -200,6 +200,15 @@ final class CoreConfigNodes {
|
||||
.stringParser(DimensionFormatNodeStringParser.INSTANCE)
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> warnAliasConflicts = node(ConfigNode.builder("world.warn-alias-conflicts", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will warn about alias duplicates when adding worlds or modifying aliases.")
|
||||
.comment("Although not enforced, it is highly recommended to not have multiple worlds with the same alias or")
|
||||
.comment("alias matching another world's name as it can cause confusion during world listing and selection in commands.")
|
||||
.defaultValue(true)
|
||||
.name("warn-alias-conflicts")
|
||||
.build());
|
||||
|
||||
private final ConfigHeaderNode teleportHeader = node(ConfigHeaderNode.builder("teleport")
|
||||
.comment("")
|
||||
.comment("")
|
||||
|
||||
@@ -492,6 +492,7 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
* @deprecated Use {@link #onLoadAndChange(NodeChangeCallback)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.4", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public @NotNull B onSetValue(@NotNull BiConsumer<T, T> onSetValue) {
|
||||
return onLoadAndChange(onSetValue::accept);
|
||||
}
|
||||
|
||||
@@ -96,12 +96,14 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_FORMAT);
|
||||
}
|
||||
|
||||
String worldName = items[0];
|
||||
String worldNameOrAlias = items[0];
|
||||
String positionStr = items[1];
|
||||
|
||||
World world = getLoadedMultiverseWorld(worldName).flatMap(LoadedMultiverseWorld::getBukkitWorld).getOrNull();
|
||||
World world = getLoadedMultiverseWorld(worldNameOrAlias)
|
||||
.flatMap(LoadedMultiverseWorld::getBukkitWorld)
|
||||
.getOrNull();
|
||||
if (world == null) {
|
||||
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldName));
|
||||
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldNameOrAlias));
|
||||
}
|
||||
|
||||
EntityPosition position;
|
||||
@@ -111,7 +113,7 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
|
||||
return Attempt.success(new ExactDestinationInstance(this, worldManager, worldName, position));
|
||||
return Attempt.success(new ExactDestinationInstance(this, worldManager, world.getName(), position));
|
||||
}
|
||||
|
||||
//TODO: Extract to a world finder class
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.mvplugins.multiverse.core.exceptions;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizableMessage;
|
||||
|
||||
+11
-6
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.exceptions.command;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizableMessage;
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizedMessage;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
@@ -18,16 +19,20 @@ public class MVInvalidCommandArgument extends InvalidCommandArgument {
|
||||
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static MVInvalidCommandArgument causeBy(Throwable throwable, boolean showSyntax) {
|
||||
return (throwable instanceof LocalizableMessage localizableMessage)
|
||||
? of(localizableMessage.getLocalizableMessage(), showSyntax)
|
||||
: new MVInvalidCommandArgument(throwable.getLocalizedMessage(), showSyntax);
|
||||
if (throwable instanceof LocalizableMessage localizableMessage) {
|
||||
Message message = localizableMessage.getLocalizableMessage();
|
||||
return message == null
|
||||
? new MVInvalidCommandArgument(throwable.getLocalizedMessage(), showSyntax)
|
||||
: of(message, showSyntax);
|
||||
}
|
||||
return new MVInvalidCommandArgument(throwable.getLocalizedMessage(), showSyntax);
|
||||
}
|
||||
|
||||
public static MVInvalidCommandArgument of(Message message) {
|
||||
public static MVInvalidCommandArgument of(@NotNull Message message) {
|
||||
return of(message, true);
|
||||
}
|
||||
|
||||
public static MVInvalidCommandArgument of(Message message, boolean showSyntax) {
|
||||
public static MVInvalidCommandArgument of(@NotNull Message message, boolean showSyntax) {
|
||||
return message instanceof LocalizedMessage
|
||||
? new MVInvalidCommandArgument((LocalizedMessage) message, showSyntax)
|
||||
: new MVInvalidCommandArgument(message, showSyntax);
|
||||
@@ -42,6 +47,6 @@ public class MVInvalidCommandArgument extends InvalidCommandArgument {
|
||||
}
|
||||
|
||||
private MVInvalidCommandArgument(LocalizedMessage message, boolean showSyntax) {
|
||||
super(message.getMessageKey(), showSyntax, message.getReplacements());
|
||||
super(message.getMessageKey(), showSyntax, message.getRawReplacements());
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package org.mvplugins.multiverse.core.listeners;
|
||||
import com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -19,14 +20,19 @@ import org.bukkit.event.entity.EntityRegainHealthEvent;
|
||||
import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason;
|
||||
import org.bukkit.event.entity.EntitySpawnEvent;
|
||||
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.EventRunnable;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventClass;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.EntityCompatibility;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Multiverse's Entity {@link Listener}.
|
||||
*/
|
||||
@@ -92,10 +98,7 @@ final class MVEntityListener implements CoreListener {
|
||||
@Override
|
||||
public void onEvent(PreCreatureSpawnEvent event) {
|
||||
// Always allow custom command and plugins to spawn creatures
|
||||
if (event.getReason() == SpawnReason.CUSTOM
|
||||
|| event.getReason() == SpawnReason.COMMAND
|
||||
|| event.getReason() == SpawnReason.BREEDING
|
||||
|| event.getReason() == SpawnReason.SPAWNER_EGG) {
|
||||
if (isAlwaysAllowedSpawnReason(event.getReason())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,16 +126,13 @@ final class MVEntityListener implements CoreListener {
|
||||
}
|
||||
|
||||
// Always allow custom command and plugins to spawn creatures
|
||||
if (event.getSpawnReason() == SpawnReason.CUSTOM
|
||||
|| event.getSpawnReason() == SpawnReason.COMMAND
|
||||
|| event.getSpawnReason() == SpawnReason.BREEDING
|
||||
|| event.getSpawnReason() == SpawnReason.SPAWNER_EGG) {
|
||||
if (isAlwaysAllowedSpawnReason(event.getSpawnReason())) {
|
||||
return;
|
||||
}
|
||||
|
||||
worldManager.getLoadedWorld(event.getEntity().getWorld())
|
||||
.peek(world -> {
|
||||
if (!world.getEntitySpawnConfig().shouldAllowSpawn(event.getEntity())) {
|
||||
if (!world.getEntitySpawnConfig().shouldAllowSpawn(event.getEntityType())) {
|
||||
Logging.finest("Cancelling Creature Spawn Event for: " + event.getEntity());
|
||||
event.setCancelled(true);
|
||||
}
|
||||
@@ -151,10 +151,34 @@ final class MVEntityListener implements CoreListener {
|
||||
|
||||
worldManager.getLoadedWorld(event.getEntity().getWorld())
|
||||
.peek(world -> {
|
||||
if (!world.getEntitySpawnConfig().shouldAllowSpawn(event.getEntity())) {
|
||||
if (!world.getEntitySpawnConfig().shouldAllowSpawn(event.getEntityType())) {
|
||||
Logging.finest("Cancelling Entity Spawn Event for: " + event.getEntity());
|
||||
event.setCancelled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventMethod
|
||||
void chunkLoad(ChunkLoadEvent event) {
|
||||
worldManager.getLoadedWorld(event.getWorld())
|
||||
.peek(world -> {
|
||||
long count = Arrays.stream(event.getChunk().getEntities())
|
||||
.filter(entity -> !(entity instanceof Player))
|
||||
.filter(entity -> !isAlwaysAllowedSpawnReason(EntityCompatibility.getEntitySpawnReason(entity)))
|
||||
.filter(entity -> !world.getEntitySpawnConfig().shouldAllowSpawn(entity))
|
||||
.peek(Entity::remove)
|
||||
.count();
|
||||
if (count > 0) {
|
||||
Logging.finest("Removed %d entities from chunk %d, %d in world %s due to spawn category settings.",
|
||||
count, event.getChunk().getX(), event.getChunk().getZ(), event.getWorld().getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isAlwaysAllowedSpawnReason(@Nullable SpawnReason reason) {
|
||||
return reason == SpawnReason.CUSTOM
|
||||
|| reason == SpawnReason.COMMAND
|
||||
|| reason == SpawnReason.BREEDING
|
||||
|| reason == SpawnReason.SPAWNER_EGG;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
CONFIG_STRING_PARSER_INVALIDDOUBLE,
|
||||
CONFIG_STRING_PARSER_INVALIDFLOAT,
|
||||
CONFIG_STRING_PARSER_INVALIDLONG,
|
||||
CONFIG_STRING_PARSER_INVALIDMATERIAL,
|
||||
|
||||
// core config
|
||||
CORE_CONFIG_CUSTOMPORTALSEARCHRADIUS_NONNEGATIVE,
|
||||
@@ -182,7 +183,7 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
|
||||
// /mv meta info
|
||||
META_INFO_DESCRIPTION,
|
||||
META_INFO_WORLD,
|
||||
META_INFO_WORLD_DESCRIPTION,
|
||||
META_INFO_HEADER,
|
||||
META_INFO_NOCONTENT,
|
||||
|
||||
@@ -370,6 +371,11 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
TELEPORTFAILUREREASON_TELEPORT_FAILED_EXCEPTION,
|
||||
TELEPORTFAILUREREASON_EVENT_CANCELLED,
|
||||
|
||||
// alias name conflict
|
||||
ALIASNAMECONFLICT_DETECTED,
|
||||
ALIASNAMECONFLICT_DUPLICATEALIAS,
|
||||
ALIASNAMECONFLICT_DUPLICATEWORLDNAME,
|
||||
|
||||
// world manager result
|
||||
CLONEWORLD_INVALIDWORLDNAME,
|
||||
CLONEWORLD_WORLDEXISTFOLDER,
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package org.mvplugins.multiverse.core.locale.message;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import co.aikar.commands.Locales;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class LocalizedMessage extends Message implements MessageKeyProvider {
|
||||
|
||||
private static @Nullable Locales locales;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setDefaultLocalesManager(@Nullable Locales locales) {
|
||||
LocalizedMessage.locales = locales;
|
||||
}
|
||||
|
||||
private final @NotNull MessageKeyProvider messageKeyProvider;
|
||||
|
||||
LocalizedMessage(
|
||||
@@ -22,11 +28,33 @@ public final class LocalizedMessage extends Message implements MessageKeyProvide
|
||||
this.messageKeyProvider = messageKeyProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MessageKey getMessageKey() {
|
||||
return messageKeyProvider.getMessageKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String[] getReplacements() {
|
||||
return locales == null ? super.getReplacements() : getReplacements(locales, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String formatted() {
|
||||
return locales == null ? super.formatted() : formatted(locales, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String formatted(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
String[] parsedReplacements = getReplacements(locales, commandIssuer);
|
||||
|
||||
@@ -6,9 +6,11 @@ import co.aikar.commands.ACFUtil;
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import co.aikar.commands.Locales;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
|
||||
/**
|
||||
* A message that can be formatted with replacements and localized.
|
||||
@@ -81,15 +83,45 @@ public sealed class Message permits LocalizedMessage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the replacements for this message.
|
||||
* Gets the replacements for this message. This is the raw, non-localized parsing of replacements.
|
||||
* <br/>
|
||||
* This array is guaranteed to be of even length and suitable for use with
|
||||
* {@link ACFUtil#replaceStrings(String, String...)}.
|
||||
*
|
||||
* @return The replacements
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public @NotNull String[] getRawReplacements() {
|
||||
return toReplacementsArray(replacements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the replacements for this message with localization support when the plugin has loaded and injected its
|
||||
* default {@link MVCommandManager#getLocales()} instance.
|
||||
* Otherwise, it will fall back to use {@link getRawReplacements()}.
|
||||
*
|
||||
* @return The replacements
|
||||
*/
|
||||
public @NotNull String[] getReplacements() {
|
||||
return toReplacementsArray(replacements);
|
||||
return getRawReplacements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the replacements for this message with localization support based on the command issuer's locale config.
|
||||
* <br/>
|
||||
* This array is guaranteed to be of even length and suitable for use with
|
||||
* {@link ACFUtil#replaceStrings(String, String...)}.
|
||||
*
|
||||
* @param issuer The command issuer the message is for
|
||||
* @return The replacements
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public @NotNull String[] getReplacements(@NotNull CommandIssuer issuer) {
|
||||
return getReplacements(issuer.getManager().getLocales(), issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,20 +148,36 @@ public sealed class Message permits LocalizedMessage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted message.
|
||||
* Gets the raw formatted message.
|
||||
* <br/>
|
||||
* This is the raw, non-localized message with replacements applied.
|
||||
* This method will never use the locale message key.
|
||||
*
|
||||
* @return The formatted message
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
public @NotNull String formatted() {
|
||||
String[] parsedReplacements = getReplacements();
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public @NotNull String rawFormatted() {
|
||||
String[] parsedReplacements = getRawReplacements();
|
||||
if (parsedReplacements.length == 0) {
|
||||
return raw();
|
||||
}
|
||||
return ACFUtil.replaceStrings(message, parsedReplacements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted message.
|
||||
* <br/>
|
||||
* This is the localized message with replacements applied when the plugin has loaded and injected its default
|
||||
* {@link MVCommandManager#getLocales()} instance. Otherwise, it will fall back to use {@link rawFormatted()}.
|
||||
*
|
||||
* @return The formatted message
|
||||
*/
|
||||
public @NotNull String formatted() {
|
||||
return rawFormatted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted message from localization data.
|
||||
* <br/>
|
||||
@@ -146,9 +194,9 @@ public sealed class Message permits LocalizedMessage {
|
||||
* Gets the formatted message from localization data.
|
||||
* <br/>
|
||||
* This is the localized message with replacements applied. The message is localized using the locale of the given
|
||||
* command issuer, if not null.
|
||||
* command issuer.
|
||||
*
|
||||
* @param commandIssuer The command issuer the message is for, or null for the console (default locale)
|
||||
* @param commandIssuer The command issuer the message is for.
|
||||
* @return The formatted, localized message
|
||||
*/
|
||||
public @NotNull String formatted(@NotNull CommandIssuer commandIssuer) {
|
||||
@@ -178,7 +226,7 @@ public sealed class Message permits LocalizedMessage {
|
||||
int i = 0;
|
||||
for (MessageReplacement replacement : replacements) {
|
||||
replacementsArray[i++] = replacement.getKey();
|
||||
replacementsArray[i++] = replacement.getReplacement().fold(s -> s, Message::formatted);
|
||||
replacementsArray[i++] = replacement.getReplacement().fold(s -> s, Message::rawFormatted);
|
||||
}
|
||||
return replacementsArray;
|
||||
}
|
||||
|
||||
+2
-1
@@ -264,7 +264,8 @@ public final class AsyncSafetyTeleporterAction {
|
||||
Logging.finer("Teleported async %s to %s", teleportee.getName(), location);
|
||||
return Attempt.success(null);
|
||||
}
|
||||
Logging.warning("Failed to async teleport %s to %s", teleportee.getName(), location);
|
||||
Logging.warning("Failed to async teleport %s to %s. This is usually caused by another " +
|
||||
"plugin canceling the teleport event and not a bug with Multiverse.", teleportee.getName(), location);
|
||||
return Attempt.failure(TeleportFailureReason.TELEPORT_FAILED);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ public final class ReflectHelper {
|
||||
* the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
public static Class<?> getClass(String classPath) {
|
||||
try {
|
||||
@@ -211,6 +212,7 @@ public final class ReflectHelper {
|
||||
* used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
public static <C> Method getMethod(Class<C> clazz, String methodName, Class<?>... parameterTypes) {
|
||||
try {
|
||||
@@ -235,6 +237,7 @@ public final class ReflectHelper {
|
||||
* used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
public static <C> Method getMethod(C classInstance, String methodName, Class<?>... parameterTypes) {
|
||||
return getMethod(classInstance.getClass(), methodName, parameterTypes);
|
||||
@@ -254,6 +257,7 @@ public final class ReflectHelper {
|
||||
* be used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <C, R> R invokeMethod(C classInstance, Method method, Object...parameters) {
|
||||
@@ -276,6 +280,7 @@ public final class ReflectHelper {
|
||||
* handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
public static <C> Field getField(Class<C> clazz, String fieldName) {
|
||||
try {
|
||||
@@ -299,6 +304,7 @@ public final class ReflectHelper {
|
||||
* handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
public static <C> Field getField(C classInstance, String fieldName) {
|
||||
return getField(classInstance.getClass(), fieldName);
|
||||
@@ -318,6 +324,7 @@ public final class ReflectHelper {
|
||||
* used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
public static <C, V> V getFieldValue(C classInstance, @Nullable Field field, @NotNull Class<V> fieldType) {
|
||||
try {
|
||||
@@ -345,6 +352,7 @@ public final class ReflectHelper {
|
||||
* which returns a {@link Try} that can be used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable
|
||||
public static <C, V> V getFieldValue(C classInstance, @Nullable String fieldName, @NotNull Class<V> fieldType) {
|
||||
return getFieldValue(classInstance, getField(classInstance, fieldName), fieldType);
|
||||
|
||||
@@ -118,6 +118,20 @@ public final class StringFormatter {
|
||||
* @return The parsed args
|
||||
*/
|
||||
public static @NotNull Collection<String> parseQuotesInArgs(@NotNull String[] args) {
|
||||
return parseQuotesInArgs(args, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse quotes in args into a single string. E.g. ["\"my", "string\""] -> ["my string"]
|
||||
*
|
||||
* @param args The args to parse
|
||||
* @param separator The separator to use between args when joining them
|
||||
* @return The parsed args
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public static @NotNull Collection<String> parseQuotesInArgs(@NotNull String[] args, @NotNull String separator) {
|
||||
List<String> result = new ArrayList<>(args.length);
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
@@ -131,13 +145,13 @@ public final class StringFormatter {
|
||||
quoteStartIndex = i;
|
||||
current.append(arg.substring(1));
|
||||
} else if (inQuotes && arg.endsWith("\"")) {
|
||||
current.append(" ").append(arg, 0, arg.length() - 1);
|
||||
current.append(separator).append(arg, 0, arg.length() - 1);
|
||||
result.add(current.toString());
|
||||
current.setLength(0);
|
||||
inQuotes = false;
|
||||
quoteStartIndex = -1;
|
||||
} else if (inQuotes) {
|
||||
current.append(" ").append(arg);
|
||||
current.append(separator).append(arg);
|
||||
} else if (arg.startsWith("\"") && arg.endsWith("\"") && arg.length() > 1) {
|
||||
// Fully quoted in one token
|
||||
result.add(arg.substring(1, arg.length() - 1));
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle API changes in {@link Entity} class.
|
||||
*/
|
||||
// TODO: Consider making this part of the public API in v5.9.
|
||||
@ApiStatus.Internal
|
||||
public final class EntityCompatibility {
|
||||
|
||||
private static final boolean HAS_GET_ENTITY_SPAWN_REASON_METHOD;
|
||||
|
||||
static {
|
||||
HAS_GET_ENTITY_SPAWN_REASON_METHOD = ReflectHelper.hasMethod(Entity.class, "getEntitySpawnReason");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reason that initially spawned the entity when supported by the server.
|
||||
*
|
||||
* @param entity The entity to query.
|
||||
* @return The entity's spawn reason, or null when the API is unavailable.
|
||||
*/
|
||||
@Nullable
|
||||
public static SpawnReason getEntitySpawnReason(@NotNull Entity entity) {
|
||||
if (HAS_GET_ENTITY_SPAWN_REASON_METHOD) {
|
||||
return entity.getEntitySpawnReason();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private EntityCompatibility() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
static <T, F extends FailureReason> Attempt.Failure<T, F> failureRef(
|
||||
F failureReason, MessageReplacement... messageReplacements) {
|
||||
return new Failure<>(failureReason, Message.of(failureReason, "Failed!", messageReplacements));
|
||||
return new Failure<>(failureReason, Message.of(failureReason, messageReplacements));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -220,7 +220,7 @@ public sealed interface Result<S extends SuccessReason, F extends FailureReason>
|
||||
|
||||
Success(S successReason, MessageReplacement[] replacements) {
|
||||
this.successReason = successReason;
|
||||
this.message = Message.of(successReason, "Success!", replacements);
|
||||
this.message = Message.of(successReason, replacements);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,7 +273,7 @@ public sealed interface Result<S extends SuccessReason, F extends FailureReason>
|
||||
|
||||
Failure(F failureReason, MessageReplacement[] replacements) {
|
||||
this.failureReason = failureReason;
|
||||
this.message = Message.of(failureReason, "Failed!", replacements);
|
||||
this.message = Message.of(failureReason, replacements);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -55,6 +55,7 @@ public final class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
worldConfig.load();
|
||||
worldConfig.setEnvironment(world.getEnvironment());
|
||||
worldConfig.setSeed(world.getSeed());
|
||||
worldConfig.setLegacyWorldName(world.getName());
|
||||
}
|
||||
|
||||
private void setupSpawnLocation(World world) {
|
||||
|
||||
@@ -49,7 +49,7 @@ final class WorldConfig {
|
||||
@NotNull ConfigurationSection configSection,
|
||||
@NotNull MultiverseCore multiverseCore) {
|
||||
this.keyOrName = keyOrName;
|
||||
this.configNodes = new WorldConfigNodes(multiverseCore);
|
||||
this.configNodes = new WorldConfigNodes(multiverseCore, keyOrName);
|
||||
this.configHandle = MemoryConfigurationHandle.builder(configSection, configNodes.getNodes())
|
||||
.logger(Logging.getLogger())
|
||||
.migrator(migrator())
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Objects;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Difficulty;
|
||||
@@ -17,6 +18,7 @@ import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.node.MapConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
@@ -25,13 +27,20 @@ import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.ListConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.MaterialConverter;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.helpers.AliasNameConflictChecker;
|
||||
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
import org.mvplugins.multiverse.core.world.location.NullSpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Represents nodes in a world configuration.
|
||||
*/
|
||||
@@ -42,12 +51,18 @@ final class WorldConfigNodes {
|
||||
private WorldManager worldManager;
|
||||
private EnforcementHandler enforcementHandler;
|
||||
private CoreConfig config;
|
||||
private AliasNameConflictChecker aliasNameConflictChecker;
|
||||
private MVCommandManager commandManager;
|
||||
private WorldKeyOrName keyOrName;
|
||||
private MultiverseWorld world = null;
|
||||
|
||||
WorldConfigNodes(@NotNull MultiverseCore multiverseCore) {
|
||||
WorldConfigNodes(@NotNull MultiverseCore multiverseCore, @NotNull WorldKeyOrName keyOrName) {
|
||||
this.worldManager = multiverseCore.getServiceLocator().getService(WorldManager.class);
|
||||
this.enforcementHandler = multiverseCore.getServiceLocator().getService(EnforcementHandler.class);
|
||||
this.config = multiverseCore.getServiceLocator().getService(CoreConfig.class);
|
||||
this.aliasNameConflictChecker = multiverseCore.getServiceLocator().getService(AliasNameConflictChecker.class);
|
||||
this.commandManager = multiverseCore.getServiceLocator().getService(MVCommandManager.class);
|
||||
this.keyOrName = keyOrName;
|
||||
}
|
||||
|
||||
MultiverseWorld getWorld() {
|
||||
@@ -90,11 +105,18 @@ final class WorldConfigNodes {
|
||||
|
||||
final ConfigNode<String> alias = node(ConfigNode.builder("alias", String.class)
|
||||
.defaultValue("")
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
.onLoadAndChange((sender, oldValue, newValue) -> {
|
||||
worldManager.getWorldStore().changeAlias(
|
||||
ChatTextFormatter.removeColor(oldValue),
|
||||
ChatTextFormatter.removeColor(newValue),
|
||||
keyOrName.usableKey()
|
||||
);
|
||||
if (world == null) return;
|
||||
world.updateColourlessAlias();
|
||||
worldManager.getWorldStore().changeAlias(
|
||||
ChatTextFormatter.removeColor(oldValue), ChatTextFormatter.removeColor(newValue), world);
|
||||
if (config.getWarnAliasConflicts()) {
|
||||
aliasNameConflictChecker.checkDuplicateFor(world)
|
||||
.sendConflictMessage(commandManager.getCommandIssuer(sender));
|
||||
}
|
||||
}));
|
||||
|
||||
final ConfigNode<Boolean> allowAdvancementGrant = node(ConfigNode.builder("allow-advancement-grant", Boolean.class)
|
||||
@@ -153,11 +175,22 @@ final class WorldConfigNodes {
|
||||
final ConfigNode<Material> entryFeeCurrency = node(ConfigNode.builder("entry-fee.currency", Material.class)
|
||||
.defaultValue(MVEconomist.VAULT_ECONOMY_MATERIAL)
|
||||
.name("entryfee-currency")
|
||||
.stringParser((value, clazz) -> {
|
||||
if (value != null && value.equalsIgnoreCase(MVEconomist.VAULT_ECONOMY_CODE)) {
|
||||
return Try.success(MVEconomist.VAULT_ECONOMY_MATERIAL);
|
||||
}
|
||||
return Try.of(() -> MaterialConverter.stringToMaterial(value))
|
||||
.filter(Objects::nonNull)
|
||||
.orElse(Try.failure(new MultiverseException(Message.of(
|
||||
MVCorei18n.CONFIG_STRING_PARSER_INVALIDMATERIAL,
|
||||
replace("{input}").with(value)))));
|
||||
})
|
||||
.serializer(new NodeSerializer<>() {
|
||||
@Override
|
||||
public Material deserialize(Object object, Class<Material> type) {
|
||||
return Option.of(object)
|
||||
.map(String::valueOf)
|
||||
.filter(s -> !s.isBlank())
|
||||
.map(materialStr -> {
|
||||
if (materialStr.equalsIgnoreCase(MVEconomist.VAULT_ECONOMY_CODE)) {
|
||||
return MVEconomist.VAULT_ECONOMY_MATERIAL;
|
||||
@@ -290,6 +323,8 @@ final class WorldConfigNodes {
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<String> legacyWorldName = node(ConfigNode.builder("read-only.legacy-world-name", String.class)
|
||||
.onLoadAndChange((oldValue, newValue) -> worldManager.getWorldStore().changeLegacyWorldName(
|
||||
oldValue, newValue, keyOrName.usableKey()))
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<Long> seed = node(ConfigNode.builder("read-only.seed", Long.class)
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.mvplugins.multiverse.core.utils.compatibility.WorldCreatorCompatibili
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.utils.FileUtils;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
@@ -154,10 +155,14 @@ public final class WorldManager {
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public Try<Void> initAllWorlds() {
|
||||
return updateWorldsFromConfig().andThenTry(() -> {
|
||||
importExistingWorlds();
|
||||
autoLoadWorlds();
|
||||
}).flatMap(ignore -> saveWorldsConfig());
|
||||
return updateWorldsFromConfig()
|
||||
.andThenTry(this::importExistingWorlds)
|
||||
.andThenTry(this::autoLoadWorlds)
|
||||
.flatMap(ignore -> saveWorldsConfig())
|
||||
.onFailure(ex -> {
|
||||
Logging.severe("Failed to load worlds from config: %s", ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,7 +245,10 @@ public final class WorldManager {
|
||||
.filter(world -> !isLoadedWorld(world) && world.isAutoLoad())
|
||||
.forEach(world -> loadWorld(LoadWorldOptions.world(world))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to autoload world %s: %s", world.getName(), failure))
|
||||
Logging.severe("Failed to autoload world '%s': %s: %s",
|
||||
world.getName(),
|
||||
failure,
|
||||
ChatTextFormatter.removeColor(failure.getFailureMessage().formatted())))
|
||||
.onSuccess(newMVWorld ->
|
||||
Logging.fine("Autoloaded world %s", newMVWorld.getName())));
|
||||
}
|
||||
@@ -278,15 +286,24 @@ public final class WorldManager {
|
||||
WorldKeyOrName keyOrName = keyOrNameWithOptions.keyOrName();
|
||||
CreateWorldOptions options = keyOrNameWithOptions.options();
|
||||
String generatorString = generatorProvider.parseGeneratorString(keyOrName.usableName(), options.generator());
|
||||
WorldCreator worldCreator = WorldCreatorCompatibility.ofKeyOrName(keyOrName)
|
||||
.environment(options.environment())
|
||||
.generateStructures(options.generateStructures())
|
||||
.generatorSettings(options.generatorSettings())
|
||||
.seed(options.seed())
|
||||
.type(options.worldType());
|
||||
WorldCreatorCompatibility.setBonusChest(worldCreator, options.bonusChest());
|
||||
options.forcedSpawnPosition().peek(position -> WorldCreatorCompatibility.setForcedSpawnPosition(worldCreator, position));
|
||||
return addBiomeProviderToCreator(worldCreator, keyOrName.usableName(), options.biome())
|
||||
|
||||
return Try.of(() -> {
|
||||
WorldCreator creator = WorldCreatorCompatibility.ofKeyOrName(keyOrName)
|
||||
.environment(options.environment())
|
||||
.generateStructures(options.generateStructures())
|
||||
.generatorSettings(options.generatorSettings())
|
||||
.seed(options.seed())
|
||||
.type(options.worldType());
|
||||
WorldCreatorCompatibility.setBonusChest(creator, options.bonusChest());
|
||||
options.forcedSpawnPosition()
|
||||
.peek(position -> WorldCreatorCompatibility.setForcedSpawnPosition(creator, position));
|
||||
return creator;
|
||||
})
|
||||
.fold(throwable -> Attempt.<WorldCreator, WorldCreatorFailureReason>failure(WorldCreatorFailureReason.BUKKIT_CREATION_FAILED,
|
||||
Replace.WORLD.with(keyOrName.usableName()),
|
||||
Replace.ERROR.with(throwable)),
|
||||
Attempt::<WorldCreator, WorldCreatorFailureReason>success)
|
||||
.mapAttempt(creator -> addBiomeProviderToCreator(creator, keyOrName.usableName(), options.biome()))
|
||||
.mapAttempt(creator -> addGeneratorToCreator(creator, generatorString))
|
||||
.mapAttempt(this::createBukkitWorld)
|
||||
.transform(CreateFailureReason.WORLD_CREATOR_FAILED)
|
||||
@@ -355,11 +372,15 @@ public final class WorldManager {
|
||||
WorldKeyOrName keyOrName = keyOrNameWithOptions.keyOrName();
|
||||
ImportWorldOptions options = keyOrNameWithOptions.options();
|
||||
String generatorString = generatorProvider.parseGeneratorString(keyOrName.usableName(), options.generator());
|
||||
WorldCreator worldCreator = WorldCreatorCompatibility.ofKeyOrName(keyOrName)
|
||||
.environment(options.environment())
|
||||
.generatorSettings(options.generatorSettings());
|
||||
|
||||
return addBiomeProviderToCreator(worldCreator, keyOrName.usableName(), options.biome())
|
||||
return Try.of(() -> WorldCreatorCompatibility.ofKeyOrName(keyOrName)
|
||||
.environment(options.environment())
|
||||
.generatorSettings(options.generatorSettings()))
|
||||
.fold(throwable -> Attempt.<WorldCreator, WorldCreatorFailureReason>failure(WorldCreatorFailureReason.BUKKIT_CREATION_FAILED,
|
||||
Replace.WORLD.with(keyOrName.usableName()),
|
||||
Replace.ERROR.with(throwable)),
|
||||
Attempt::<WorldCreator, WorldCreatorFailureReason>success)
|
||||
.mapAttempt(creator -> addBiomeProviderToCreator(creator, keyOrName.usableName(), options.biome()))
|
||||
.mapAttempt(creator -> addGeneratorToCreator(creator, generatorString))
|
||||
.mapAttempt(this::createBukkitWorld)
|
||||
.transform(ImportFailureReason.WORLD_CREATOR_FAILED)
|
||||
@@ -524,10 +545,14 @@ public final class WorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
WorldCreator worldCreator = WorldCreatorCompatibility.ofNameAndKey(mvWorld.getKey(), mvWorld.getName())
|
||||
.environment(mvWorld.getEnvironment())
|
||||
.seed(mvWorld.getSeed());
|
||||
return addBiomeProviderToCreator(worldCreator, mvWorld.getName(), mvWorld.getBiome())
|
||||
return Try.of(() -> WorldCreatorCompatibility.ofNameAndKey(mvWorld.getKey(), mvWorld.getName())
|
||||
.environment(mvWorld.getEnvironment())
|
||||
.seed(mvWorld.getSeed()))
|
||||
.fold(throwable -> Attempt.<WorldCreator, WorldCreatorFailureReason>failure(WorldCreatorFailureReason.BUKKIT_CREATION_FAILED,
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.ERROR.with(throwable)),
|
||||
Attempt::<WorldCreator, WorldCreatorFailureReason>success)
|
||||
.mapAttempt(creator -> addBiomeProviderToCreator(creator, mvWorld.getName(), mvWorld.getBiome()))
|
||||
.mapAttempt(creator -> addGeneratorToCreator(creator, mvWorld.getGenerator()))
|
||||
.mapAttempt(this::createBukkitWorld)
|
||||
.transform(LoadFailureReason.WORLD_CREATOR_FAILED)
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -119,16 +120,39 @@ final class WorldStore {
|
||||
unloadedList.add(unloadedMap.get(world.getKey().toString()));
|
||||
}
|
||||
|
||||
void changeAlias(@Nullable String oldAlias, @Nullable String newAlias, @NotNull MultiverseWorld world) {
|
||||
void changeLegacyWorldName(@Nullable String oldName, @Nullable String newName, @NotNull NamespacedKey worldKey) {
|
||||
if (Objects.equals(oldName, newName)) {
|
||||
return;
|
||||
}
|
||||
String key = worldKey.toString();
|
||||
updateLegacyWorldName(loadedMap, oldName, newName, key);
|
||||
updateLegacyWorldName(unloadedMap, oldName, newName, key);
|
||||
}
|
||||
|
||||
private <T extends MultiverseWorld> void updateLegacyWorldName(
|
||||
Map<String, T> map, @Nullable String oldName, @Nullable String newName, String key) {
|
||||
T world = map.get(key);
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
if (oldName != null && !key.equalsIgnoreCase(oldName)) {
|
||||
map.remove(oldName, world);
|
||||
}
|
||||
if (newName != null) {
|
||||
map.put(newName, world);
|
||||
}
|
||||
}
|
||||
|
||||
void changeAlias(@Nullable String oldAlias, @Nullable String newAlias, @NotNull NamespacedKey worldKey) {
|
||||
if (Objects.equals(oldAlias, newAlias)) {
|
||||
// nothing changed, ignore
|
||||
return;
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(oldAlias)) {
|
||||
aliasMap.remove(oldAlias, world.getKey().toString());
|
||||
aliasMap.remove(oldAlias.toLowerCase(Locale.ROOT), worldKey.toString());
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(newAlias)) {
|
||||
aliasMap.put(newAlias, world.getKey().toString());
|
||||
aliasMap.put(newAlias.toLowerCase(Locale.ROOT), worldKey.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +287,7 @@ final class WorldStore {
|
||||
return null;
|
||||
}
|
||||
//TODO: Not sure if we should fail if there is multiple alias of the same name, but for now just return the first one
|
||||
return aliasMap.get(worldNameOrAlias).stream()
|
||||
return aliasMap.get(worldNameOrAlias.toLowerCase(Locale.ROOT)).stream()
|
||||
.findFirst()
|
||||
.orElse(worldNameOrAlias);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.utils.ServerProperties;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyParseFailReason;
|
||||
|
||||
@@ -34,16 +36,18 @@ final class WorldsConfigManager {
|
||||
|
||||
private final SortedMap<WorldKeyOrName, WorldConfig> worldConfigMap;
|
||||
private final File worldConfigFile;
|
||||
private final ServerProperties serverProperties;
|
||||
private YamlConfiguration worldsConfig;
|
||||
|
||||
private final MultiverseCore multiverseCore;
|
||||
|
||||
@Inject
|
||||
WorldsConfigManager(@NotNull MultiverseCore core, @NotNull MultiverseCore multiverseCore) {
|
||||
WorldsConfigManager(@NotNull MultiverseCore multiverseCore, @NotNull ServerProperties serverProperties) {
|
||||
worldConfigMap = new TreeMap<>();
|
||||
worldConfigFile = core.getDataFolder().toPath().resolve(CONFIG_FILENAME).toFile();
|
||||
worldConfigFile = multiverseCore.getDataFolder().toPath().resolve(CONFIG_FILENAME).toFile();
|
||||
|
||||
this.multiverseCore = multiverseCore;
|
||||
this.serverProperties = serverProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,9 +206,59 @@ final class WorldsConfigManager {
|
||||
worldConfigMap.remove(s);
|
||||
}
|
||||
|
||||
migrateLevelNameChange();
|
||||
|
||||
return new NewAndRemovedWorlds(newWorldsAdded, worldsRemoved);
|
||||
}
|
||||
|
||||
private void migrateLevelNameChange() {
|
||||
serverProperties.getLevelName()
|
||||
.peek(levelName -> {
|
||||
boolean didMigrate = migrateLevelNameChange(
|
||||
WorldKeyOrName.parseKey(NamespacedKey.minecraft("overworld")),
|
||||
levelName
|
||||
);
|
||||
didMigrate = migrateLevelNameChange(
|
||||
WorldKeyOrName.parseKey(NamespacedKey.minecraft("the_nether")),
|
||||
DimensionFinder.DEFAULT_NETHER_FORMAT.replaceOverworld(levelName)
|
||||
) || didMigrate;
|
||||
didMigrate = migrateLevelNameChange(
|
||||
WorldKeyOrName.parseKey(NamespacedKey.minecraft("the_end")),
|
||||
DimensionFinder.DEFAULT_END_FORMAT.replaceOverworld(levelName)
|
||||
) || didMigrate;
|
||||
|
||||
if (didMigrate) {
|
||||
Logging.warning("We have detected and migrated your default world's name due to a level-name change.");
|
||||
Logging.warning("Please check your worlds.yml file and world list to ensure everything is correct.");
|
||||
Logging.warning("Additionally, all references to the old world name will no longer work. " +
|
||||
"Please update all references within your configuration files/plugins.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean migrateLevelNameChange(WorldKeyOrName keyOrName, String newWorldName) {
|
||||
return getWorldConfig(keyOrName).map(worldConfig -> {
|
||||
if (newWorldName.equals(worldConfig.getLegacyWorldName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasConflictKey = WorldKeyOrName.parseKey(newWorldName).map(this::getWorldConfig)
|
||||
.fold(failure -> false, Option::isDefined);
|
||||
if (hasConflictKey) {
|
||||
Logging.severe("Unable to migrate default world's name caused by level-name change.");
|
||||
Logging.severe("We have detected a clash between the default name '%s' and an existing non-default world in your worlds.yml file.", newWorldName);
|
||||
Logging.severe("Please change the level-name in server.properties to something else and restart the server.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.info("Updating legacy world name for %s from %s to %s due to level-name change.",
|
||||
keyOrName, worldConfig.getLegacyWorldName(), newWorldName);
|
||||
worldConfig.setLegacyWorldName(newWorldName);
|
||||
worldConfig.save();
|
||||
return true;
|
||||
}).getOrElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the worlds.yml file has been loaded.
|
||||
*
|
||||
|
||||
@@ -7,58 +7,35 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Service
|
||||
public final class EntityPurger {
|
||||
|
||||
public int purgeEntities(LoadedMultiverseWorld world) {
|
||||
AtomicInteger purgeCount = new AtomicInteger(0);
|
||||
world.getBukkitWorld().peek(bukkitWorld -> {
|
||||
for (Entity entity : bukkitWorld.getEntities()) {
|
||||
if (!world.getEntitySpawnConfig().shouldAllowSpawn(entity)) {
|
||||
entity.remove();
|
||||
purgeCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
});
|
||||
return purgeCount.get();
|
||||
return purgeEntitiesWithCondition(world, entity -> !world.getEntitySpawnConfig().shouldAllowSpawn(entity));
|
||||
}
|
||||
|
||||
public int purgeEntities(LoadedMultiverseWorld world, SpawnCategory spawnCategory) {
|
||||
AtomicInteger purgeCount = new AtomicInteger(0);
|
||||
world.getBukkitWorld().peek(bukkitWorld -> {
|
||||
for (Entity entity : bukkitWorld.getEntities()) {
|
||||
if (entity.getSpawnCategory() == spawnCategory) {
|
||||
entity.remove();
|
||||
purgeCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
});
|
||||
return purgeCount.get();
|
||||
return purgeEntitiesWithCondition(world, entity -> entity.getSpawnCategory().equals(spawnCategory));
|
||||
}
|
||||
|
||||
public int purgeEntities(LoadedMultiverseWorld world, SpawnCategory... spawnCategories) {
|
||||
Set<SpawnCategory> spawnCategoriesSet = Set.of(spawnCategories);
|
||||
AtomicInteger purgeCount = new AtomicInteger(0);
|
||||
world.getBukkitWorld().peek(bukkitWorld -> bukkitWorld.getEntities().stream()
|
||||
.filter(entity -> !(entity instanceof Player))
|
||||
.filter(entity -> spawnCategoriesSet.contains(entity.getSpawnCategory()))
|
||||
.forEach(entity -> {
|
||||
entity.remove();
|
||||
purgeCount.incrementAndGet();
|
||||
}));
|
||||
return purgeCount.get();
|
||||
return purgeEntitiesWithCondition(world, entity -> spawnCategoriesSet.contains(entity.getSpawnCategory()));
|
||||
}
|
||||
|
||||
public int purgeAllEntities(LoadedMultiverseWorld world) {
|
||||
AtomicInteger purgeCount = new AtomicInteger(0);
|
||||
world.getBukkitWorld().peek(bukkitWorld -> bukkitWorld.getEntities().stream()
|
||||
.filter(entity -> !(entity instanceof Player))
|
||||
.forEach(entity -> {
|
||||
entity.remove();
|
||||
purgeCount.incrementAndGet();
|
||||
}));
|
||||
return purgeCount.get();
|
||||
return purgeEntitiesWithCondition(world, entity -> true);
|
||||
}
|
||||
|
||||
private int purgeEntitiesWithCondition(LoadedMultiverseWorld world, Predicate<Entity> condition) {
|
||||
return Math.toIntExact(world.getBukkitWorld()
|
||||
.map(bukkitWorld -> bukkitWorld.getEntities().stream()
|
||||
.filter(entity -> !(entity instanceof Player))
|
||||
.filter(condition)
|
||||
.peek(Entity::remove)
|
||||
.count())
|
||||
.getOrElse(0L));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.handle.MemoryConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
@@ -16,13 +17,19 @@ import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.ListConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.Node;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
public final class SpawnCategoryConfig {
|
||||
|
||||
private final CoreConfig config;
|
||||
@@ -73,7 +80,7 @@ public final class SpawnCategoryConfig {
|
||||
}
|
||||
|
||||
private void applyTickPerSpawns(@NotNull World bukkitWorld) {
|
||||
if (!config.getApplyEntitySpawnLimit()) {
|
||||
if (!config.getApplyEntitySpawnRate()) {
|
||||
Logging.finer("World %s %s skipping setTicksPerSpawns due to core config", world.getName(), spawnCategory);
|
||||
return;
|
||||
}
|
||||
@@ -177,6 +184,7 @@ public final class SpawnCategoryConfig {
|
||||
final ConfigNode<Integer> tickRate = node(ConfigNode.builder("tick-rate", Integer.class)
|
||||
.defaultValue(-2)
|
||||
.suggester(input -> List.of("@unset", "@bukkit", "10", "100", "400", "1000"))
|
||||
.stringParser(SpawnValueStringParser.INSTANCE)
|
||||
.serializer(SpawnValueSerializer.INSTANCE)
|
||||
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
|
||||
.onChange((sender, oldValue, newValue) -> {
|
||||
@@ -193,6 +201,7 @@ public final class SpawnCategoryConfig {
|
||||
final ConfigNode<Integer> spawnLimit = node(ConfigNode.builder("spawn-limit", Integer.class)
|
||||
.defaultValue(-2)
|
||||
.suggester(input -> List.of("@unset", "@bukkit", "10", "100", "400", "1000"))
|
||||
.stringParser(SpawnValueStringParser.INSTANCE)
|
||||
.serializer(SpawnValueSerializer.INSTANCE)
|
||||
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
|
||||
.onChange((sender, oldValue, newValue) -> {
|
||||
@@ -215,6 +224,27 @@ public final class SpawnCategoryConfig {
|
||||
.build());
|
||||
}
|
||||
|
||||
private static class SpawnValueStringParser implements NodeStringParser<Integer> {
|
||||
private static final SpawnValueStringParser INSTANCE = new SpawnValueStringParser();
|
||||
|
||||
@Override
|
||||
public @NotNull Try<Integer> parse(@Nullable String input, @NotNull Class<Integer> type) {
|
||||
String str = String.valueOf(input).trim();
|
||||
if (str.equalsIgnoreCase("@unset")) {
|
||||
return Try.success(-2);
|
||||
}
|
||||
if (str.equalsIgnoreCase("@bukkit")) {
|
||||
return Try.success(-1);
|
||||
}
|
||||
try {
|
||||
return Try.success(Integer.parseInt(str));
|
||||
} catch (NumberFormatException e) {
|
||||
return Try.failure(new MultiverseException(Message.of(MVCorei18n.CONFIG_STRING_PARSER_INVALIDINTEGER,
|
||||
replace("{input}").with(input))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpawnValueSerializer implements NodeSerializer<Integer> {
|
||||
private static final SpawnValueSerializer INSTANCE = new SpawnValueSerializer();
|
||||
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Checks world aliases for conflicts with other world aliases and names.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
@Service
|
||||
public class AliasNameConflictChecker {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
|
||||
@Inject
|
||||
private AliasNameConflictChecker(@NotNull WorldManager worldManager) {
|
||||
this.worldManager = worldManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the target world's alias conflicts with another world's alias or name.
|
||||
* Color formatting and letter case are ignored when aliases are compared.
|
||||
*
|
||||
* @param targetWorld The world whose alias should be checked.
|
||||
* @return The detected alias conflicts.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public @NotNull AliasNameConflictResult checkDuplicateFor(MultiverseWorld targetWorld) {
|
||||
AliasNameConflictResult aliasNameConflictResult = new AliasNameConflictResult(targetWorld);
|
||||
String targetWorldAlias = ChatTextFormatter.removeColor(targetWorld.getAlias());
|
||||
if (Strings.isNullOrEmpty(targetWorldAlias)) {
|
||||
return aliasNameConflictResult;
|
||||
}
|
||||
|
||||
for (MultiverseWorld otherWorld : worldManager.getWorlds()) {
|
||||
if (otherWorld.getKey().equals(targetWorld.getKey())) {
|
||||
continue;
|
||||
}
|
||||
if (targetWorldAlias.equalsIgnoreCase(ChatTextFormatter.removeColor(otherWorld.getAlias()))) {
|
||||
aliasNameConflictResult.getDuplicateAliases().add(otherWorld);
|
||||
}
|
||||
if (targetWorldAlias.equalsIgnoreCase(otherWorld.getName())) {
|
||||
aliasNameConflictResult.getDuplicateWorldNames().add(otherWorld);
|
||||
}
|
||||
}
|
||||
return aliasNameConflictResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the alias conflicts detected for a world.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public static class AliasNameConflictResult {
|
||||
private final MultiverseWorld targetWorld;
|
||||
private final List<MultiverseWorld> duplicateAliases;
|
||||
private final List<MultiverseWorld> duplicateWorldNames;
|
||||
|
||||
private AliasNameConflictResult(@NotNull MultiverseWorld targetWorld) {
|
||||
this.targetWorld = targetWorld;
|
||||
duplicateAliases = new ArrayList<>();
|
||||
duplicateWorldNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends localized messages describing the detected conflicts to the command issuer.
|
||||
* No message is sent when there are no conflicts.
|
||||
*
|
||||
* @param issuer The command issuer to notify.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public void sendConflictMessage(MVCommandIssuer issuer) {
|
||||
if (!hasConflict()) {
|
||||
return;
|
||||
}
|
||||
issuer.sendError(MVCorei18n.ALIASNAMECONFLICT_DETECTED,
|
||||
Replace.WORLD.with(targetWorld.getName()),
|
||||
replace("{alias}").with(targetWorld.getColourlessAlias()));
|
||||
duplicateAliases.forEach(conflictingWorld ->
|
||||
issuer.sendError(MVCorei18n.ALIASNAMECONFLICT_DUPLICATEALIAS,
|
||||
Replace.WORLD.with(conflictingWorld.getName()),
|
||||
replace("{alias}").with(conflictingWorld.getColourlessAlias())));
|
||||
duplicateWorldNames.forEach(conflictingWorld ->
|
||||
issuer.sendError(MVCorei18n.ALIASNAMECONFLICT_DUPLICATEWORLDNAME,
|
||||
Replace.WORLD.with(conflictingWorld.getName())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether any alias or world name conflicts were detected.
|
||||
*
|
||||
* @return {@code true} if at least one conflict was detected, otherwise {@code false}.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public boolean hasConflict() {
|
||||
return !duplicateAliases.isEmpty() || !duplicateWorldNames.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world whose alias was checked.
|
||||
*
|
||||
* @return The checked world.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public @NotNull MultiverseWorld getTargetWorld() {
|
||||
return targetWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the worlds whose aliases match the target world's alias.
|
||||
*
|
||||
* @return The worlds with conflicting aliases.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public @NotNull List<MultiverseWorld> getDuplicateAliases() {
|
||||
return duplicateAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the worlds whose names match the target world's alias.
|
||||
*
|
||||
* @return The worlds with names that conflict with the target world's alias.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public @NotNull List<MultiverseWorld> getDuplicateWorldNames() {
|
||||
return duplicateWorldNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-5
@@ -27,6 +27,9 @@ import java.util.stream.Stream;
|
||||
@Service
|
||||
public final class PotentialWorldFinder {
|
||||
|
||||
private static final int MAX_RECURSION_DEPTH = 3;
|
||||
private static final int MAX_FOLDER_TRAVERSAL_LIMIT = 100;
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final WorldNameChecker worldNameChecker;
|
||||
|
||||
@@ -56,6 +59,7 @@ public final class PotentialWorldFinder {
|
||||
private Stream<String> findFromRootFolder() {
|
||||
Path worldContainer = Bukkit.getWorldContainer().toPath();
|
||||
return Arrays.stream(listFolders(worldContainer))
|
||||
.limit(MAX_FOLDER_TRAVERSAL_LIMIT)
|
||||
.filter(worldNameChecker::isValidWorldFolder)
|
||||
.map(File::getName)
|
||||
.filter(worldNameChecker::isValidWorldName)
|
||||
@@ -65,15 +69,29 @@ public final class PotentialWorldFinder {
|
||||
private Stream<String> findFromDimensionsFolder() {
|
||||
Path worldContainer = BukkitCompatibility.getWorldFoldersDirectory();
|
||||
return Arrays.stream(listFolders(worldContainer))
|
||||
.map(File::getName)
|
||||
.flatMap(namespace -> Arrays.stream(listFolders(worldContainer.resolve(namespace)))
|
||||
.filter(worldNameChecker::isValidWorldFolder)
|
||||
.map(File::getName)
|
||||
.map(key -> namespace + ":" + key))
|
||||
.limit(MAX_FOLDER_TRAVERSAL_LIMIT)
|
||||
.flatMap(namespaceFile -> recursiveFindFromNamespaceFolder("", namespaceFile.toPath(), 0)
|
||||
.map(worldKey -> namespaceFile.getName() + ":" + worldKey))
|
||||
.filter(namespacedKey -> NamespacedKey.fromString(namespacedKey) != null)
|
||||
.filter(namespacedKey -> !worldManager.isWorld(namespacedKey));
|
||||
}
|
||||
|
||||
private Stream<String> recursiveFindFromNamespaceFolder(String prefix, Path subWorldContainer, int depth) {
|
||||
if (depth >= MAX_RECURSION_DEPTH) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return Arrays.stream(listFolders(subWorldContainer))
|
||||
.limit(MAX_FOLDER_TRAVERSAL_LIMIT)
|
||||
.flatMap(folder -> worldNameChecker.isValidWorldFolder(folder)
|
||||
? Stream.of(concatPath(prefix, folder.getName()))
|
||||
: recursiveFindFromNamespaceFolder(
|
||||
concatPath(prefix, folder.getName()), folder.toPath(), depth + 1));
|
||||
}
|
||||
|
||||
private String concatPath(String prefix, String folderName) {
|
||||
return prefix.isEmpty() ? folderName : prefix + "/" + folderName;
|
||||
}
|
||||
|
||||
private @NotNull File[] listFolders(Path folder) {
|
||||
return Option.of(folder.toFile().listFiles(File::isDirectory))
|
||||
.getOrElse(() -> new File[0]);
|
||||
|
||||
@@ -126,6 +126,8 @@ public final class CloneWorldOptions implements KeepWorldSettingsOptions {
|
||||
* Gets the name of the new world.
|
||||
*
|
||||
* @return The name of the new world.
|
||||
*
|
||||
* @deprecated Use {@link #newWorldKeyOrName()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
|
||||
@@ -101,10 +101,13 @@ public final class CreateWorldOptions {
|
||||
* Gets the name of the world to create.
|
||||
*
|
||||
* @return The name of the world to create.
|
||||
*
|
||||
* @deprecated Use {@link #keyOrName()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public @NotNull String worldName() {
|
||||
return keyOrName.fold(name -> name ,WorldKeyOrName::usableName);
|
||||
return keyOrName.fold(name -> name, WorldKeyOrName::usableName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,11 +85,13 @@ public final class ImportWorldOptions {
|
||||
* Gets the name of the world to create.
|
||||
*
|
||||
* @return The name of the world to create.
|
||||
*
|
||||
* @deprecated Use {@link #keyOrName()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public @NotNull String worldName() {
|
||||
return keyOrName.fold(name -> name ,WorldKeyOrName::usableName);
|
||||
return keyOrName.fold(name -> name, WorldKeyOrName::usableName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ mv-core.config.string.parser.invalidinteger=Unable to convert '{input}' to numbe
|
||||
mv-core.config.string.parser.invaliddouble=Unable to convert '{input}' to number. (double)
|
||||
mv-core.config.string.parser.invalidfloat=Unable to convert '{input}' to number. (float)
|
||||
mv-core.config.string.parser.invalidlong=Unable to convert '{input}' to number. (long)
|
||||
mv-core.config.string.parser.invalidmaterial='{input}' is not a valid material.
|
||||
|
||||
# core config
|
||||
mv-core.core.config.customportalsearchradius.nonnegative=The value must be greater than or equal to 0.
|
||||
@@ -32,7 +33,7 @@ mv-core.check.location=The destination's location is: &f{location}
|
||||
# /mv clone
|
||||
mv-core.clone.description=Clones a world.
|
||||
mv-core.clone.world.description=The target world to clone.
|
||||
mv-core.clone.newWorld.description=The new cloned world name.
|
||||
mv-core.clone.newworld.description=The new cloned world name.
|
||||
mv-core.clone.cloning=Cloning world '{world}' to '{newworld}'...
|
||||
mv-core.clone.success=&aWorld cloned to '{world}'!
|
||||
|
||||
@@ -53,7 +54,7 @@ mv-core.coordinates.description=Simply sends your coordinates
|
||||
mv-core.coordinates.info.title=&b--- Location Information ---
|
||||
mv-core.coordinates.info.world=&bWorld: &f{world}
|
||||
mv-core.coordinates.info.alias=&bAlias: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&bWorld Scale: &f{scale}
|
||||
mv-core.coordinates.info.worldscale=&bWorld Scale: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&bCoordinates: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&bDirection: &f{direction}
|
||||
|
||||
@@ -356,6 +357,11 @@ mv-core.teleportfailurereason.teleport.failed=The teleport was rejected. Please
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=An error occurred during the teleport. Check server logs for more details.
|
||||
mv-core.teleportfailurereason.event.cancelled=The teleport was cancelled by another plugin.
|
||||
|
||||
# alias name conflict
|
||||
mv-core.aliasnameconflict.detected=Alias conflict detected for world '{world}' with alias '{alias}'. This may cause world list and selection to be confusing!
|
||||
mv-core.aliasnameconflict.duplicatealias= - Conflicts with '{world}' world's alias: '{alias}'
|
||||
mv-core.aliasnameconflict.duplicateworldname= - Conflicts with world name: '{world}'
|
||||
|
||||
# world manager result
|
||||
mv-core.cloneworld.invalidworldname=World '{world}' contains invalid characters!
|
||||
mv-core.cloneworld.worldexistfolder=World '{world}' exists in server folders! You need to delete it first before cloning.
|
||||
@@ -379,14 +385,14 @@ mv-core.importworld.worldexistunloaded=World '{world}' already exists, but it's
|
||||
mv-core.importworld.worldexistloaded=World '{world}' already exists!
|
||||
mv-core.importworld.worldfolderinvalid=World '{world}' folder contents does not seem to be a valid world! Make sure it contains the correct world structure of data and region folders.\n&cIf the server software does something different with world folders, or you are very certain the world is valid, use '&3--skip-folder-check&c' flag.
|
||||
mv-core.importworld.bukkitenvironmentmismatch=Environment mismatch detected!&f The world '{world}' is already loaded with environment '{bukkitEnvironment}', but Multiverse is trying to import it with environment '{mvEnvironment}'.
|
||||
mv-core.importworld.bukkitnamespacemismatch=Namespace mismatch detected!&f The world '{world}' is already loaded with namespace '{bukkitNamespace}', but Multiverse is trying to import it with namespace '{mvNamespace}'.
|
||||
mv-core.importworld.bukkitnamespacedkeymismatch=Namespace mismatch detected!&f The world '{world}' is already loaded with namespace '{bukkitNamespace}', but Multiverse is trying to import it with namespace '{mvNamespace}'.
|
||||
|
||||
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.loadworld.bukkitnamespacemismatch=Namespace mismatch detected!&f The world '{world}' is already loaded with namespace '{bukkitNamespace}', but Multiverse is trying to load it with namespace '{mvNamespace}'.
|
||||
mv-core.loadworld.bukkitnamespacedkeymismatch=Namespace mismatch detected!&f The world '{world}' is already loaded with namespace '{bukkitNamespace}', but Multiverse is trying to load it with namespace '{mvNamespace}'.
|
||||
|
||||
mv-core.removeworld.worldnonexistent=World '{world}' not found!
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ mv-core.check.location=La localización de su destino es: &f{location}
|
||||
# /mv clone
|
||||
mv-core.clone.description=Clona un mundo.
|
||||
mv-core.clone.world.description=El mundo a clonar.
|
||||
mv-core.clone.newWorld.description=El nombre del nuevo mundo clonado.
|
||||
mv-core.clone.newworld.description=El nombre del nuevo mundo clonado.
|
||||
mv-core.clone.cloning=Clonando mundo '{world}' a '{newworld}'...
|
||||
mv-core.clone.success=&a¡Mundo clonado a '{world}'!
|
||||
|
||||
@@ -53,7 +53,7 @@ mv-core.coordinates.description=Simple y llanamente te muestra tus coordenadas.
|
||||
mv-core.coordinates.info.title=&b--- Información de la localización ---
|
||||
mv-core.coordinates.info.world=&bMundo: &f{world}
|
||||
mv-core.coordinates.info.alias=&bAlias: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&bEscala del mundo: &f{scale}
|
||||
mv-core.coordinates.info.worldscale=&bEscala del mundo: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&bCoordenadas: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&bDirección: &f{direction}
|
||||
|
||||
@@ -330,6 +330,11 @@ mv-core.teleportfailurereason.teleport.failed=Algo ha evitado la teletransportac
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=Un error ha ocurrido durante la teletransportación. Mira la consola para ver los detalles.
|
||||
mv-core.teleportfailurereason.event.cancelled=El teletransporte fue cancelado por otro plugin.
|
||||
|
||||
# alias name conflict
|
||||
mv-core.aliasnameconflict.detected=Se ha detectado un conflicto de alias para el mundo '{world}' con alias '{alias}'. Esto puede hacer que la lista y selección de mundos sea confusa.
|
||||
mv-core.aliasnameconflict.duplicatealias= - Entra en conflicto con el alias del mundo '{world}': '{alias}'
|
||||
mv-core.aliasnameconflict.duplicateworldname= - Entra en conflicto con el nombre del mundo: '{world}'
|
||||
|
||||
# world manager result
|
||||
mv-core.cloneworld.invalidworldname=¡El mundo '{world}' contiene caracteres inválidos!
|
||||
mv-core.cloneworld.worldexistfolder=¡El mundo '{world}' ya existe en los archivos del servidor! Necesitas eliminarlo antes de clonarlo.
|
||||
|
||||
@@ -32,7 +32,7 @@ mv-core.check.location=&fLokalizacja celu: &a{location}
|
||||
# /mv clone
|
||||
mv-core.clone.description=Klonuje świat.
|
||||
mv-core.clone.world.description=Świat do sklonowania.
|
||||
mv-core.clone.newWorld.description=Nazwa nowego sklonowanego świata.
|
||||
mv-core.clone.newworld.description=Nazwa nowego sklonowanego świata.
|
||||
mv-core.clone.cloning=&e&l! &fTrwa klonowanie świata '&a{world}&f' do '&a{newworld}&f'...
|
||||
mv-core.clone.success=&a&l✔ &fŚwiat został sklonowany jako '&a{world}&f'!
|
||||
|
||||
@@ -56,7 +56,7 @@ mv-core.coordinates.description=Wysyła twoje koordynaty.
|
||||
mv-core.coordinates.info.title=&a&l--- Informacje o Lokalizacji ---
|
||||
mv-core.coordinates.info.world=&fŚwiat: &a{world}
|
||||
mv-core.coordinates.info.alias=&fAlias: &a{alias}
|
||||
mv-core.coordinates.info.worldScale=&fSkala świata: &a{scale}
|
||||
mv-core.coordinates.info.worldscale=&fSkala świata: &a{scale}
|
||||
mv-core.coordinates.info.coordinates=&fKoordynaty: &a{coordinates}
|
||||
mv-core.coordinates.info.direction=&fKierunek: &a{direction}
|
||||
|
||||
@@ -365,6 +365,11 @@ mv-core.teleportfailurereason.teleport.failed=&c&l✘ &cTeleportacja została od
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=&c&l✘ &cWystąpił błąd podczas teleportacji. Sprawdź logi serwera po więcej informacji.
|
||||
mv-core.teleportfailurereason.event.cancelled=&c&l✘ &cTeleportacja została anulowana przez inny plugin.
|
||||
|
||||
# alias name conflict
|
||||
mv-core.aliasnameconflict.detected=&c&l✘ &cWykryto konflikt aliasu dla świata '&a{world}&c' z aliasem '&a{alias}&c'. Może to utrudnić czytelność listy światów i ich wybieranie!
|
||||
mv-core.aliasnameconflict.duplicatealias= - Konflikt z aliasem świata '&a{world}&r': '&a{alias}&r'
|
||||
mv-core.aliasnameconflict.duplicateworldname= - Konflikt z nazwą świata: '&a{world}&r'
|
||||
|
||||
# world manager result
|
||||
mv-core.cloneworld.invalidworldname=&c&l✘ &cŚwiat '&a{world}&c' zawiera niedozwolone znaki!
|
||||
mv-core.cloneworld.worldexistfolder=&c&l✘ &cŚwiat '&a{world}&c' istnieje już w folderach serwera! Musisz go najpierw usunąć przed klonowaniem.
|
||||
@@ -388,14 +393,14 @@ mv-core.importworld.worldexistunloaded=&e&l! &eŚwiat '&a{world}&e' już istniej
|
||||
mv-core.importworld.worldexistloaded=&c&l✘ &cŚwiat '&a{world}&c' już istnieje!
|
||||
mv-core.importworld.worldfolderinvalid=&c&l✘ &cFolder świata '&a{world}&c' nie wygląda na poprawny świat! Upewnij się, że zawiera poprawną strukturę folderów data oraz region.\n&eJeśli oprogramowanie serwera działa inaczej lub masz pewność, że świat jest poprawny, użyj flagi '&a--skip-folder-check&e'.
|
||||
mv-core.importworld.bukkitenvironmentmismatch=&c&l✘ &cWykryto niezgodność environment! &fŚwiat '&a{world}&f' jest już załadowany z environment '&a{bukkitEnvironment}&f', ale Multiverse próbuje zaimportować go jako '&a{mvEnvironment}&f'.
|
||||
mv-core.importworld.bukkitnamespacemismatch=&c&l✘ &cWykryto niezgodność namespace! &fŚwiat '&a{world}&f' jest już załadowany z namespace '&a{bukkitNamespace}&f', ale Multiverse próbuje zaimportować go jako '&a{mvNamespace}&f'.
|
||||
mv-core.importworld.bukkitnamespacedkeymismatch=&c&l✘ &cWykryto niezgodność namespace! &fŚwiat '&a{world}&f' jest już załadowany z namespace '&a{bukkitNamespace}&f', ale Multiverse próbuje zaimportować go jako '&a{mvNamespace}&f'.
|
||||
|
||||
mv-core.loadworld.worldalreadyloading=&e&l! &eŚwiat '&a{world}&e' jest już ładowany! Poczekaj...
|
||||
mv-core.loadworld.worldnonexistent=&c&l✘ &cŚwiat '&a{world}&c' nie istnieje! Użyj '&a/mv create {world} <environment>&f', aby go stworzyć.
|
||||
mv-core.loadworld.worldexistfolder=&e&l! &eŚwiat '&a{world}&e' istnieje w folderach serwera, ale nie jest znany Multiverse! &fWpisz '&a/mv import {world} <environment>&f', jeśli chcesz go zaimportować.
|
||||
mv-core.loadworld.worldexistloaded=&e&l! &eŚwiat '&a{world}&e' jest już załadowany!
|
||||
mv-core.loadworld.bukkitenvironmentmismatch=&c&l✘ &cWykryto niezgodność environment! &fŚwiat '&a{world}&f' jest już załadowany z environment '&a{bukkitEnvironment}&f', ale Multiverse próbuje załadować go jako '&a{mvEnvironment}&f'.
|
||||
mv-core.loadworld.bukkitnamespacemismatch=&c&l✘ &cWykryto niezgodność namespace! &fŚwiat '&a{world}&f' jest już załadowany z namespace '&a{bukkitNamespace}&f', ale Multiverse próbuje załadować go jako '&a{mvNamespace}&f'.
|
||||
mv-core.loadworld.bukkitnamespacedkeymismatch=&c&l✘ &cWykryto niezgodność namespace! &fŚwiat '&a{world}&f' jest już załadowany z namespace '&a{bukkitNamespace}&f', ale Multiverse próbuje załadować go jako '&a{mvNamespace}&f'.
|
||||
|
||||
mv-core.removeworld.worldnonexistent=&c&l✘ &cNie znaleziono świata '&a{world}&c'!
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ mv-core.check.location=Местоположение пункта назначе
|
||||
# /mv clone
|
||||
mv-core.clone.description=Клонирует мир.
|
||||
mv-core.clone.world.description=Целевой мир для клонирования.
|
||||
mv-core.clone.newWorld.description=Название нового клонированного мира.
|
||||
mv-core.clone.newworld.description=Название нового клонированного мира.
|
||||
mv-core.clone.cloning=Клонирование мира '{world}' в '{newworld}'...
|
||||
mv-core.clone.success=&aМир клонирован в '{world}'!
|
||||
|
||||
@@ -53,7 +53,7 @@ mv-core.coordinates.description=Просто отправляет ваши ко
|
||||
mv-core.coordinates.info.title=&b--- Информация о местоположении ---
|
||||
mv-core.coordinates.info.world=&bМир: &f{world}
|
||||
mv-core.coordinates.info.alias=&bПсевдоним: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&bМасштаб мира: &f{scale}
|
||||
mv-core.coordinates.info.worldscale=&bМасштаб мира: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&bКоординаты: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&bНаправление: &f{direction}
|
||||
|
||||
@@ -325,6 +325,11 @@ mv-core.teleportfailurereason.teleport.failed=Что-то отклонило т
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=Произошла ошибка во время телепортации. См. консоль для подробностей.
|
||||
mv-core.teleportfailurereason.event.cancelled=Телепортация была отменена другим плагином.
|
||||
|
||||
# alias name conflict
|
||||
mv-core.aliasnameconflict.detected=Обнаружен конфликт псевдонима для мира '{world}' с псевдонимом '{alias}'. Это может затруднить понимание списка миров и их выбор!
|
||||
mv-core.aliasnameconflict.duplicatealias= - Конфликтует с псевдонимом мира '{world}': '{alias}'
|
||||
mv-core.aliasnameconflict.duplicateworldname= - Конфликтует с названием мира: '{world}'
|
||||
|
||||
# world manager result
|
||||
mv-core.cloneworld.invalidworldname=Мир '{world}' содержит недопустимые символы!
|
||||
mv-core.cloneworld.worldexistfolder=Мир '{world}' существует в папках сервера! Вам нужно удалить его перед клонированием.
|
||||
|
||||
@@ -32,7 +32,7 @@ mv-core.check.location=Hedefin konumu: &f{location}
|
||||
# /mv clone
|
||||
mv-core.clone.description=Bir dünyayı kopyalar.
|
||||
mv-core.clone.world.description=Kopyalanacak hedef dünya.
|
||||
mv-core.clone.newWorld.description=Yeni kopyalanan dünyanın adı.
|
||||
mv-core.clone.newworld.description=Yeni kopyalanan dünyanın adı.
|
||||
mv-core.clone.cloning='{world}' dünyası '{newworld}' olarak kopyalanıyor...
|
||||
mv-core.clone.success=&aDünya '{world}' olarak kopyalandı!
|
||||
|
||||
@@ -53,7 +53,7 @@ mv-core.coordinates.description=Koordinatlarınızı gösterir
|
||||
mv-core.coordinates.info.title=&b--- Konum Bilgisi ---
|
||||
mv-core.coordinates.info.world=&bDünya: &f{world}
|
||||
mv-core.coordinates.info.alias=&bTakma Ad: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&bDünya Ölçeği: &f{scale}
|
||||
mv-core.coordinates.info.worldscale=&bDünya Ölçeği: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&bKoordinatlar: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&bYön: &f{direction}
|
||||
|
||||
@@ -356,6 +356,11 @@ mv-core.teleportfailurereason.teleport.failed=Işınlama reddedildi. Başka bir
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=Işınlama sırasında bir hata oluştu. Daha fazla ayrıntı için sunucu günlüklerini kontrol edin.
|
||||
mv-core.teleportfailurereason.event.cancelled=Işınlama başka bir eklenti tarafından iptal edildi.
|
||||
|
||||
# alias name conflict
|
||||
mv-core.aliasnameconflict.detected='{alias}' takma adına sahip '{world}' dünyası için takma ad çakışması algılandı. Bu, dünya listesini ve seçimini kafa karıştırıcı hale getirebilir!
|
||||
mv-core.aliasnameconflict.duplicatealias= - '{world}' dünyasının takma adıyla çakışıyor: '{alias}'
|
||||
mv-core.aliasnameconflict.duplicateworldname= - Dünya adıyla çakışıyor: '{world}'
|
||||
|
||||
# world manager result
|
||||
mv-core.cloneworld.invalidworldname='{world}' dünyası geçersiz karakterler içeriyor!
|
||||
mv-core.cloneworld.worldexistfolder='{world}' dünyası sunucu klasörlerinde mevcut! Kopyalamadan önce silmeniz gerekiyor.
|
||||
@@ -379,14 +384,14 @@ mv-core.importworld.worldexistunloaded='{world}' dünyası zaten mevcut, ancak y
|
||||
mv-core.importworld.worldexistloaded='{world}' dünyası zaten mevcut!
|
||||
mv-core.importworld.worldfolderinvalid='{world}' dünya klasörünün içeriği geçerli bir dünya gibi görünmüyor! Doğru veri ve bölge klasörleri içerdiğinden emin olun.\n&cSunucu yazılımı dünya klasörleriyle farklı bir şey yapıyorsa veya dünyanın geçerli olduğundan çok eminseniz, '&3--skip-folder-check&c' bayrağını kullanın.
|
||||
mv-core.importworld.bukkitenvironmentmismatch=Ortam uyuşmazlığı tespit edildi!&f '{world}' dünyası '{bukkitEnvironment}' ortamıyla zaten yüklü, ancak Multiverse onu '{mvEnvironment}' ortamıyla içe aktarmaya çalışıyor.
|
||||
mv-core.importworld.bukkitnamespacemismatch=Ad alanı uyuşmazlığı tespit edildi!&f '{world}' dünyası '{bukkitNamespace}' ad alanıyla zaten yüklü, ancak Multiverse onu '{mvNamespace}' ad alanıyla içe aktarmaya çalışıyor.
|
||||
mv-core.importworld.bukkitnamespacedkeymismatch=Ad alanı uyuşmazlığı tespit edildi!&f '{world}' dünyası '{bukkitNamespace}' ad alanıyla zaten yüklü, ancak Multiverse onu '{mvNamespace}' ad alanıyla içe aktarmaya çalışıyor.
|
||||
|
||||
mv-core.loadworld.worldalreadyloading='{world}' dünyası zaten yükleniyor! Lütfen bekleyin...
|
||||
mv-core.loadworld.worldnonexistent='{world}' dünyası bulunamadı! Oluşturmak için '&a/mv create {world} <ortam>&f' kullanın.
|
||||
mv-core.loadworld.worldexistfolder='{world}' dünyası sunucu klasörlerinde mevcut, ancak Multiverse tarafından bilinmiyor!&f İçe aktarmak istiyorsanız '&a/mv import {world} <ortam>&f' yazın.
|
||||
mv-core.loadworld.worldexistloaded='{world}' dünyası zaten yüklü!
|
||||
mv-core.loadworld.bukkitenvironmentmismatch=Ortam uyuşmazlığı tespit edildi!&f '{world}' dünyası '{bukkitEnvironment}' ortamıyla zaten yüklü, ancak Multiverse onu '{mvEnvironment}' ortamıyla yüklemeye çalışıyor.
|
||||
mv-core.loadworld.bukkitnamespacemismatch=Ad alanı uyuşmazlığı tespit edildi!&f '{world}' dünyası '{bukkitNamespace}' ad alanıyla zaten yüklü, ancak Multiverse onu '{mvNamespace}' ad alanıyla yüklemeye çalışıyor.
|
||||
mv-core.loadworld.bukkitnamespacedkeymismatch=Ad alanı uyuşmazlığı tespit edildi!&f '{world}' dünyası '{bukkitNamespace}' ad alanıyla zaten yüklü, ancak Multiverse onu '{mvNamespace}' ad alanıyla yüklemeye çalışıyor.
|
||||
|
||||
mv-core.removeworld.worldnonexistent='{world}' dünyası bulunamadı!
|
||||
|
||||
@@ -441,4 +446,4 @@ mv-core.generic.error.details=Hata: {error}
|
||||
mv-core.generic.null=Null!
|
||||
mv-core.generic.you=sen
|
||||
mv-core.generic.playercount={count} oyuncu
|
||||
mv-core.generic.teleportplayers.failed=Bir veya daha fazla oyuncu dünyadan ışınlanamadı!
|
||||
mv-core.generic.teleportplayers.failed=Bir veya daha fazla oyuncu dünyadan ışınlanamadı!
|
||||
@@ -32,7 +32,7 @@ mv-core.check.location=目标的地址是: &f{location}
|
||||
# /mv clone
|
||||
mv-core.clone.description=复制一个世界。
|
||||
mv-core.clone.world.description=需要复制的原世界。
|
||||
mv-core.clone.newWorld.description=新世界的名称。
|
||||
mv-core.clone.newworld.description=新世界的名称。
|
||||
mv-core.clone.cloning=正在复制 '{world}' 到 '{newworld}'……
|
||||
mv-core.clone.success=&a已将世界复制到 '{world}'!
|
||||
|
||||
@@ -53,7 +53,7 @@ mv-core.coordinates.description=直接发送你的坐标
|
||||
mv-core.coordinates.info.title=&b--- 位置信息 ---
|
||||
mv-core.coordinates.info.world=&b世界: &f{world}
|
||||
mv-core.coordinates.info.alias=&b别名: &f{alias}
|
||||
mv-core.coordinates.info.worldScale=&b世界比例: &f{scale}
|
||||
mv-core.coordinates.info.worldscale=&b世界比例: &f{scale}
|
||||
mv-core.coordinates.info.coordinates=&b坐标: &f{coordinates}
|
||||
mv-core.coordinates.info.direction=&b方向: &f{direction}
|
||||
|
||||
@@ -331,6 +331,11 @@ mv-core.teleportfailurereason.teleport.failed=传送被拒绝。请确保其他
|
||||
mv-core.teleportfailurereason.teleport.failed.exception=在传送过程中出现了一个错误。查看服务器日志获取更多信息。
|
||||
mv-core.teleportfailurereason.event.cancelled=传送被另一个插件取消了。
|
||||
|
||||
# alias name conflict
|
||||
mv-core.aliasnameconflict.detected=检测到世界 '{world}' 的别名 '{alias}' 存在冲突。这可能会让世界列表和选择变得混乱!
|
||||
mv-core.aliasnameconflict.duplicatealias= - 与世界 '{world}' 的别名冲突:'{alias}'
|
||||
mv-core.aliasnameconflict.duplicateworldname= - 与世界名称冲突:'{world}'
|
||||
|
||||
# world manager result
|
||||
mv-core.cloneworld.invalidworldname=世界 '{world}' 包含无效的字符!
|
||||
mv-core.cloneworld.worldexistfolder=世界 '{world}' 已经存在于服务器的文件中!在复制前你需要先删除它。
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.mvplugins.multiverse.core.locale.PluginLocales
|
||||
import org.mvplugins.multiverse.core.locale.message.Message
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace
|
||||
import java.util.Locale
|
||||
import java.util.Properties
|
||||
import kotlin.test.*
|
||||
|
||||
class LocalizationTest : TestWithMockBukkit() {
|
||||
@@ -31,6 +32,18 @@ class LocalizationTest : TestWithMockBukkit() {
|
||||
locales = commandManager.locales
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Default locale bundle contains every message key`() {
|
||||
val properties = Properties()
|
||||
assertNotNull(javaClass.getResourceAsStream("/multiverse-core_en.properties")).use {
|
||||
properties.load(it)
|
||||
}
|
||||
|
||||
MVCorei18n.entries.forEach { message ->
|
||||
assertTrue(properties.containsKey(message.messageKey.key), "Missing locale key: ${message.messageKey.key}")
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Given a Message with only a non-localized message")
|
||||
inner class BasicMessage {
|
||||
@@ -45,7 +58,7 @@ class LocalizationTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `The formatted message should be the same as the original`() {
|
||||
assertEquals(messageString, message.formatted())
|
||||
assertEquals(messageString, message.rawFormatted())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,7 +110,7 @@ class LocalizationTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `The formatted message should be the replaced message string`() {
|
||||
assertEquals(replacedMessageString, message.formatted())
|
||||
assertEquals(replacedMessageString, message.rawFormatted())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -156,7 +169,7 @@ class LocalizationTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `The formatted message should be the replaced message string`() {
|
||||
assertEquals(replacedMessageString, message.formatted())
|
||||
assertEquals(replacedMessageString, message.rawFormatted())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,8 +222,13 @@ class LocalizationTest : TestWithMockBukkit() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `The formatted message should be the replaced original string`() {
|
||||
assertEquals(replacedMessageString, message.formatted())
|
||||
fun `The raw formatted message should be the replaced original string`() {
|
||||
assertEquals(replacedMessageString, message.rawFormatted())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `The formatted message should be different from the replaced original string`() {
|
||||
assertNotEquals(replacedMessageString, message.formatted())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -271,7 +289,7 @@ class LocalizationTest : TestWithMockBukkit() {
|
||||
|
||||
@Test
|
||||
fun `The formatted message should be the replaced original string`() {
|
||||
assertEquals(replacedMessageString, message.formatted())
|
||||
assertEquals(replacedMessageString, message.rawFormatted())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -49,6 +49,38 @@ class WorldManagerTest : TestWithMockBukkit() {
|
||||
assertNotNull(world2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Legacy world name changes update loaded and unloaded lookups`() {
|
||||
val worldKey = world.key.toString()
|
||||
assertTrue(world.worldConfig.setLegacyWorldName("renamed_world").isSuccess)
|
||||
|
||||
assertFalse(worldManager.getWorld("world").isDefined)
|
||||
assertSame(world, worldManager.getLoadedWorld("RENAMED_WORLD").get())
|
||||
assertSame(world, worldManager.getLoadedWorld(worldKey).get())
|
||||
|
||||
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world)).isSuccess)
|
||||
assertFalse(worldManager.getWorld("world").isDefined)
|
||||
assertFalse(worldManager.getLoadedWorld("renamed_world").isDefined)
|
||||
assertEquals(world.key, worldManager.getUnloadedWorld("RENAMED_WORLD").get().key)
|
||||
assertEquals(world.key, worldManager.getUnloadedWorld(worldKey).get().key)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Setting up loaded world config refreshes legacy world name lookups`() {
|
||||
assertTrue(world.worldConfig.setLegacyWorldName("stale_name").isSuccess)
|
||||
assertTrue(world.worldConfig.save().isSuccess)
|
||||
|
||||
world.setWorldConfig(world.worldConfig)
|
||||
|
||||
assertEquals("world", world.name)
|
||||
assertSame(world, worldManager.getLoadedWorld("world").get())
|
||||
assertFalse(worldManager.getWorld("stale_name").isDefined)
|
||||
|
||||
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world)).isSuccess)
|
||||
assertTrue(worldManager.getUnloadedWorld("world").isDefined)
|
||||
assertFalse(worldManager.getWorld("stale_name").isDefined)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Create world with custom options`() {
|
||||
assertTrue(worldManager.createWorld(
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package org.mvplugins.multiverse.core.world.helper
|
||||
|
||||
import org.mvplugins.multiverse.core.TestWithMockBukkit
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld
|
||||
import org.mvplugins.multiverse.core.world.WorldManager
|
||||
import org.mvplugins.multiverse.core.world.helpers.AliasNameConflictChecker
|
||||
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class AliasNameConflictCheckerTest : TestWithMockBukkit() {
|
||||
|
||||
private lateinit var conflictChecker: AliasNameConflictChecker
|
||||
private lateinit var firstWorld: LoadedMultiverseWorld
|
||||
private lateinit var secondWorld: LoadedMultiverseWorld
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
val worldManager = requireNotNull(serviceLocator.getActiveService(WorldManager::class.java))
|
||||
conflictChecker = requireNotNull(serviceLocator.getService(AliasNameConflictChecker::class.java))
|
||||
|
||||
assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("FirstWorld")).isSuccess)
|
||||
assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("SecondWorld")).isSuccess)
|
||||
firstWorld = worldManager.getLoadedWorld("FirstWorld").get()
|
||||
secondWorld = worldManager.getLoadedWorld("SecondWorld").get()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Aliases differing only by case conflict`() {
|
||||
assertTrue(firstWorld.setAlias("SharedAlias").isSuccess)
|
||||
assertTrue(secondWorld.setAlias("sharedalias").isSuccess)
|
||||
|
||||
val result = conflictChecker.checkDuplicateFor(secondWorld)
|
||||
|
||||
assertEquals(listOf(firstWorld), result.duplicateAliases)
|
||||
assertTrue(result.duplicateWorldNames.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Alias and world name differing only by case conflict`() {
|
||||
assertTrue(firstWorld.setAlias("SECONDWORLD").isSuccess)
|
||||
|
||||
val result = conflictChecker.checkDuplicateFor(firstWorld)
|
||||
|
||||
assertTrue(result.duplicateAliases.isEmpty())
|
||||
assertEquals(listOf(secondWorld), result.duplicateWorldNames)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ world:
|
||||
world-name-format:
|
||||
nether: '%overworld%_nether'
|
||||
end: '%overworld%_the_end'
|
||||
warn-alias-conflicts: true
|
||||
|
||||
teleport:
|
||||
use-finer-teleport-permissions: true
|
||||
|
||||
Reference in new issue
Block a user