mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 08:42:11 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a8f65ec82
|
||
|
|
feb854817f
|
||
|
|
97a73bf591 | ||
|
|
4e66693b21 | ||
|
|
f9fc0f1a28 | ||
|
|
f246094915 |
No files matched your search
@@ -0,0 +1 @@
|
||||
.gitea/CODEOWNERS linguist-detectable=false
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pull requests request review from the eligible staff team(s).
|
||||
# One approval from either an Admin or Developer satisfies branch protection.
|
||||
* @trueog/Admins @trueog/Developers
|
||||
@@ -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.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: 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.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: 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.1.2"]}'
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-1.21.11"]}'
|
||||
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
|
||||
@@ -0,0 +1,4 @@
|
||||
kotlin version: 2.0.21
|
||||
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||
1. Kotlin compile daemon is ready
|
||||
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.2'
|
||||
}
|
||||
|
||||
group = 'org.mvplugins.multiverse.core'
|
||||
description = 'Multiverse-Core'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = 'aikar repo'
|
||||
url = uri('https://repo.aikar.co/content/groups/aikar/')
|
||||
}
|
||||
|
||||
maven {
|
||||
name = 'glaremasters repo'
|
||||
url = 'https://repo.glaremasters.me/repository/towny/'
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "helpchatRepoReleases"
|
||||
url = uri("https://repo.helpch.at/releases/")
|
||||
}
|
||||
}
|
||||
|
||||
configure(apiDependencies) {
|
||||
serverApiVersion = '1.21.11-R0.1-SNAPSHOT'
|
||||
mockBukkitServerApiVersion = '1.21'
|
||||
mockBukkitVersion = '4.110.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Economy
|
||||
externalPlugin('com.github.MilkBowl:VaultAPI:1.7.1') {
|
||||
exclude group: 'org.bukkit', module: 'bukkit'
|
||||
}
|
||||
|
||||
// PlaceholderAPI
|
||||
externalPlugin 'me.clip:placeholderapi:2.11.6'
|
||||
|
||||
// Command Framework
|
||||
shadowed 'co.aikar:acf-paper:0.5.1-SNAPSHOT'
|
||||
|
||||
// Config
|
||||
shadowed('io.github.townyadvanced.commentedconfiguration:CommentedConfiguration:1.0.1') {
|
||||
exclude group: 'org.spigotmc', module: 'spigot-api'
|
||||
}
|
||||
|
||||
// Utils
|
||||
shadowed 'io.vavr:vavr:0.10.7'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.1.1'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.1.1') {
|
||||
exclude group: 'org.apache.maven', module: 'maven-core'
|
||||
}
|
||||
shadowed('com.dumptruckman.minecraft:Logging:1.1.1') {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
shadowed 'de.themoep.idconverter:mappings:1.2-SNAPSHOT'
|
||||
shadowed('org.bstats:bstats-bukkit:3.1.0') {
|
||||
exclude group: 'org.bukkit', module: 'bukkit'
|
||||
}
|
||||
shadowed 'net.minidev:json-smart:2.5.2'
|
||||
shadowed 'org.jetbrains:annotations:26.0.2'
|
||||
shadowed 'io.papermc:paperlib:1.0.8'
|
||||
|
||||
// Tests
|
||||
testImplementation('com.googlecode.json-simple:json-simple:1.1.1') {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
testImplementation("org.hamcrest:hamcrest:3.0")
|
||||
}
|
||||
|
||||
ext.bitlyAccessToken = System.getenv('BITLY_ACCESS_TOKEN') ?: 'bitly-access-token'
|
||||
|
||||
tasks.register('prepareSource', Sync) {
|
||||
inputs.property 'bitlyAccessToken', bitlyAccessToken
|
||||
from sourceSets.main.java
|
||||
into "$buildDir/src"
|
||||
filter(ReplaceTokens, tokens: [
|
||||
'bitly-access-token': bitlyAccessToken,
|
||||
])
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'co.aikar', 'org.mvplugins.multiverse.external.acf'
|
||||
relocate 'com.dumptruckman.minecraft.util.Logging', 'org.mvplugins.multiverse.core.utils.CoreLogging'
|
||||
relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.core.utils.DebugFileLogger'
|
||||
relocate 'de.themoep.idconverter', 'org.mvplugins.multiverse.external.idconverter'
|
||||
relocate 'io.github.townyadvanced.commentedconfiguration', 'org.mvplugins.multiverse.external.commentedconfiguration'
|
||||
relocate 'org.bstats', 'org.mvplugins.multiverse.external.bstats'
|
||||
relocate 'com.sun', 'org.mvplugins.multiverse.external.sun'
|
||||
relocate 'net.minidev', 'org.mvplugins.multiverse.external.minidev'
|
||||
relocate 'org.objectweb', 'org.mvplugins.multiverse.external.objectweb'
|
||||
relocate 'io.vavr', 'org.mvplugins.multiverse.external.vavr'
|
||||
relocate 'jakarta', 'org.mvplugins.multiverse.external.jakarta'
|
||||
relocate 'javassist', 'org.mvplugins.multiverse.external.javassist'
|
||||
relocate 'org.aopalliance', 'org.mvplugins.multiverse.external.aopalliance'
|
||||
relocate 'org.glassfish', 'org.mvplugins.multiverse.external.glassfish'
|
||||
relocate 'org.jvnet', 'org.mvplugins.multiverse.external.jvnet'
|
||||
relocate 'org.intellij', 'org.mvplugins.multiverse.external.intellij'
|
||||
relocate 'org.jetbrains', 'org.mvplugins.multiverse.external.jetbrains'
|
||||
relocate 'io.papermc.lib', 'org.mvplugins.multiverse.external.paperlib'
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = "https://maven.pkg.github.com/Multiverse/Multiverse-Core"
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import groovy.util.Node
|
||||
import groovy.util.NodeList
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.credentials.PasswordCredentials
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.api.tasks.SourceSetContainer
|
||||
import org.gradle.api.tasks.Sync
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.api.tasks.javadoc.Javadoc
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.gradle.external.javadoc.StandardJavadocDocletOptions
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
import org.gradle.language.jvm.tasks.ProcessResources
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
eclipse
|
||||
`maven-publish`
|
||||
id("com.gradleup.shadow") version "8.3.9"
|
||||
kotlin("jvm") version "2.0.21"
|
||||
}
|
||||
|
||||
group = "org.mvplugins.multiverse.core"
|
||||
version = System.getenv("GITHUB_VERSION") ?: "5.3.4.17"
|
||||
description = "Multiverse-Core"
|
||||
|
||||
val sourceSets = extensions.getByType<SourceSetContainer>()
|
||||
|
||||
val serverApi: Configuration by configurations.creating
|
||||
val externalPlugin: Configuration by configurations.creating
|
||||
val shadowed: Configuration by configurations.creating
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
|
||||
maven {
|
||||
url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
|
||||
}
|
||||
|
||||
maven {
|
||||
url = uri("https://repo.onarandombox.com/content/groups/public")
|
||||
}
|
||||
|
||||
maven {
|
||||
url = uri("https://hub.spigotmc.org/nexus/content/groups/public/")
|
||||
}
|
||||
|
||||
maven {
|
||||
url = uri("https://jitpack.io")
|
||||
}
|
||||
|
||||
maven {
|
||||
url = uri("https://repo.minebench.de/")
|
||||
}
|
||||
|
||||
maven {
|
||||
url = uri("https://repo.maven.apache.org/maven2/")
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "aikar repo"
|
||||
url = uri("https://repo.aikar.co/content/groups/aikar/")
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "glaremasters repo"
|
||||
url = uri("https://repo.glaremasters.me/repository/towny/")
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "helpchatRepoReleases"
|
||||
url = uri("https://repo.helpch.at/releases/")
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "papermc repo"
|
||||
url = uri("https://repo.papermc.io/repository/maven-public/")
|
||||
}
|
||||
|
||||
// Purpur API
|
||||
maven("https://repo.purpurmc.org/snapshots")
|
||||
}
|
||||
|
||||
// Configuration wiring like in the Groovy build
|
||||
configurations["compileOnly"].extendsFrom(serverApi)
|
||||
configurations["runtimeClasspath"].extendsFrom(serverApi)
|
||||
|
||||
configurations["implementation"].extendsFrom(externalPlugin)
|
||||
|
||||
configurations["compileClasspath"].extendsFrom(shadowed)
|
||||
configurations["testCompileClasspath"].extendsFrom(shadowed)
|
||||
configurations["testRuntimeClasspath"].extendsFrom(shadowed)
|
||||
|
||||
dependencies {
|
||||
// Server API: Purpur 1.19.4
|
||||
serverApi("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT")
|
||||
|
||||
// Economy
|
||||
externalPlugin("com.github.MilkBowl:VaultAPI:1.7.1") {
|
||||
exclude(group = "org.bukkit", module = "bukkit")
|
||||
}
|
||||
|
||||
// PlaceholderAPI
|
||||
externalPlugin("me.clip:placeholderapi:2.11.6")
|
||||
|
||||
// Command Framework
|
||||
shadowed("co.aikar:acf-paper:0.5.1-SNAPSHOT")
|
||||
|
||||
// Config
|
||||
shadowed("io.github.townyadvanced.commentedconfiguration:CommentedConfiguration:1.0.1") {
|
||||
exclude(group = "org.spigotmc", module = "spigot-api")
|
||||
}
|
||||
|
||||
// Utils
|
||||
shadowed("io.vavr:vavr:0.10.4")
|
||||
shadowed("org.glassfish.hk2:hk2-locator:3.0.3")
|
||||
shadowed("org.glassfish.hk2:hk2-inhabitant-generator:3.0.3") {
|
||||
exclude(group = "org.apache.maven", module = "maven-core")
|
||||
}
|
||||
shadowed("com.dumptruckman.minecraft:Logging:1.1.1") {
|
||||
exclude(group = "junit", module = "junit")
|
||||
}
|
||||
shadowed("de.themoep.idconverter:mappings:1.2-SNAPSHOT")
|
||||
shadowed("org.bstats:bstats-bukkit:3.1.0") {
|
||||
exclude(group = "org.bukkit", module = "bukkit")
|
||||
}
|
||||
shadowed("net.minidev:json-smart:2.4.9")
|
||||
shadowed("org.jetbrains:annotations:22.0.0")
|
||||
shadowed("io.papermc:paperlib:1.0.8")
|
||||
|
||||
// Tests
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21")
|
||||
testImplementation("com.github.seeseemelk:MockBukkit-v1.19:3.1.0")
|
||||
testImplementation("com.googlecode.json-simple:json-simple:1.1.1") {
|
||||
exclude(group = "junit", module = "junit")
|
||||
}
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("com.natpryce:hamkrest:1.8.0.1")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
|
||||
|
||||
// Annotation Processors
|
||||
annotationProcessor("org.glassfish.hk2:hk2-metadata-generator:3.0.3")
|
||||
testAnnotationProcessor("org.glassfish.hk2:hk2-metadata-generator:3.0.3")
|
||||
}
|
||||
|
||||
java {
|
||||
// Plugin is compiled for Java 17 (GraalVM 17-friendly)
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
val bitlyAccessToken: String = System.getenv("BITLY_ACCESS_TOKEN") ?: "bitly-access-token"
|
||||
|
||||
// === Token-filtered source generation (prepareSource) ===
|
||||
val prepareSource by tasks.registering(Sync::class) {
|
||||
inputs.property("bitlyAccessToken", bitlyAccessToken)
|
||||
from(sourceSets["main"].java)
|
||||
// avoid deprecated buildDir getter
|
||||
into(layout.buildDirectory.dir("src"))
|
||||
filter<ReplaceTokens>(
|
||||
"tokens" to mapOf(
|
||||
"bitly-access-token" to bitlyAccessToken,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Use ONLY the filtered sources for compileJava (fixes duplicate classes)
|
||||
tasks.named<JavaCompile>("compileJava") {
|
||||
dependsOn(prepareSource)
|
||||
source = prepareSource.get().outputs.files.asFileTree
|
||||
sourceCompatibility = JavaVersion.VERSION_17.toString()
|
||||
targetCompatibility = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
tasks.named<JavaCompile>("compileTestJava") {
|
||||
sourceCompatibility = JavaVersion.VERSION_17.toString()
|
||||
targetCompatibility = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add(
|
||||
"-Aorg.glassfish.hk2.metadata.location=META-INF/hk2-locator/Multiverse-Core",
|
||||
)
|
||||
}
|
||||
|
||||
tasks.withType<Javadoc>().configureEach {
|
||||
(options as StandardJavadocDocletOptions).encoding = "UTF-8"
|
||||
}
|
||||
|
||||
tasks.named<KotlinCompile>("compileTestKotlin") {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_17)
|
||||
javaParameters.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
// We're not using Kotlin in the plugin itself, just tests!
|
||||
tasks.named<KotlinCompile>("compileKotlin") {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
configurations
|
||||
.filterNot { it.name.startsWith("test") }
|
||||
.forEach {
|
||||
it.exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
|
||||
}
|
||||
|
||||
tasks.named<ProcessResources>("processResources") {
|
||||
val props = mapOf("version" to project.version.toString())
|
||||
inputs.properties(props)
|
||||
filteringCharset = "UTF-8"
|
||||
filesMatching("plugin.yml") {
|
||||
expand(props)
|
||||
}
|
||||
// Never up-to-date: tests depend on fresh version expansion
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
tasks.named<Javadoc>("javadoc") {
|
||||
source = sourceSets["main"].allJava
|
||||
classpath = configurations["compileClasspath"]
|
||||
}
|
||||
|
||||
// Removed: configurations["api"].isCanBeResolved = true (deprecated in Gradle 8/9)
|
||||
|
||||
val jarTask = tasks.named<Jar>("jar")
|
||||
val shadowJarTask = tasks.named<ShadowJar>("shadowJar")
|
||||
|
||||
listOf("apiElements", "runtimeElements").forEach { configurationName ->
|
||||
configurations.named(configurationName) {
|
||||
outgoing.artifacts.removeIf { artifact ->
|
||||
artifact.buildDependencies.getDependencies(null).contains(jarTask.get())
|
||||
}
|
||||
outgoing.artifact(shadowJarTask)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
relocate("co.aikar", "org.mvplugins.multiverse.external.acf")
|
||||
relocate("com.dumptruckman.minecraft.util.Logging", "org.mvplugins.multiverse.core.utils.CoreLogging")
|
||||
relocate("com.dumptruckman.minecraft.util.DebugLog", "org.mvplugins.multiverse.core.utils.DebugFileLogger")
|
||||
relocate("de.themoep.idconverter", "org.mvplugins.multiverse.external.idconverter")
|
||||
relocate("io.github.townyadvanced.commentedconfiguration", "org.mvplugins.multiverse.external.commentedconfiguration")
|
||||
relocate("me.main__.util", "org.mvplugins.multiverse.external.serializationconfig")
|
||||
relocate("org.bstats", "org.mvplugins.multiverse.external.bstats")
|
||||
relocate("com.sun", "org.mvplugins.multiverse.external.sun")
|
||||
relocate("net.minidev", "org.mvplugins.multiverse.external.minidev")
|
||||
relocate("org.objectweb", "org.mvplugins.multiverse.external.objectweb")
|
||||
relocate("io.vavr", "org.mvplugins.multiverse.external.vavr")
|
||||
relocate("jakarta", "org.mvplugins.multiverse.external.jakarta")
|
||||
relocate("javassist", "org.mvplugins.multiverse.external.javassist")
|
||||
relocate("org.aopalliance", "org.mvplugins.multiverse.external.aopalliance")
|
||||
relocate("org.glassfish", "org.mvplugins.multiverse.external.glassfish")
|
||||
relocate("org.jvnet", "org.mvplugins.multiverse.external.jvnet")
|
||||
relocate("org.intellij", "org.mvplugins.multiverse.external.intellij")
|
||||
relocate("org.jetbrains", "org.mvplugins.multiverse.external.jetbrains")
|
||||
relocate("io.papermc.lib", "org.mvplugins.multiverse.external.paperlib")
|
||||
|
||||
configurations = listOf(project.configurations["shadowed"])
|
||||
|
||||
archiveClassifier.set("")
|
||||
|
||||
dependencies {
|
||||
exclude { dependency ->
|
||||
dependency.moduleGroup == "org.jetbrains.kotlin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("build") {
|
||||
dependsOn("shadowJar")
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("maven") {
|
||||
from(components["java"])
|
||||
|
||||
pom.withXml {
|
||||
val pomNode = asNode()
|
||||
|
||||
val dependenciesNode = (
|
||||
(pomNode.get("dependencies") as? NodeList)
|
||||
?.firstOrNull() as? Node
|
||||
) ?: (pomNode.appendNode("dependencies") as Node)
|
||||
|
||||
val dependencyNodes = dependenciesNode.children().filterIsInstance<Node>().toList()
|
||||
|
||||
// Remove Kotlin dependency
|
||||
dependencyNodes
|
||||
.filter { dependencyNode ->
|
||||
val groupIdNodes = dependencyNode.get("groupId") as? NodeList ?: return@filter false
|
||||
val groupId = (groupIdNodes.firstOrNull() as? Node)?.text()
|
||||
groupId == "org.jetbrains.kotlin"
|
||||
}
|
||||
.forEach { dependencyNode ->
|
||||
dependenciesNode.remove(dependencyNode)
|
||||
}
|
||||
|
||||
// Switch runtime deps to provided
|
||||
dependencyNodes.forEach { dependencyNode ->
|
||||
val scopeNodes = dependencyNode.get("scope") as? NodeList ?: return@forEach
|
||||
val scopeNode = scopeNodes.firstOrNull() as? Node ?: return@forEach
|
||||
if (scopeNode.text() == "runtime") {
|
||||
scopeNode.setValue("provided")
|
||||
}
|
||||
}
|
||||
|
||||
// Add server API (Purpur) to pom as provided
|
||||
configurations["serverApi"].allDependencies.forEach { dependency ->
|
||||
val depNode = dependenciesNode.appendNode("dependency")
|
||||
depNode.appendNode("groupId", dependency.group)
|
||||
depNode.appendNode("artifactId", dependency.name)
|
||||
depNode.appendNode("version", dependency.version)
|
||||
depNode.appendNode("scope", "provided")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/Multiverse/Multiverse-Core")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = "multiverse"
|
||||
val releasesRepoUrl = uri("https://repo.dumptruckman.com/multiverse-releases")
|
||||
val snapshotsRepoUrl = uri("https://repo.dumptruckman.com/multiverse-snapshots")
|
||||
url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
|
||||
maven {
|
||||
// todo: remove before mv5 release
|
||||
name = "multiverseBeta"
|
||||
url = uri("https://repo.c0ding.party/multiverse-beta")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -83,10 +85,9 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -133,10 +134,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
Vendored
+12
-10
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
url = uri('https://repo.onarandombox.com/multiverse-releases')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'multiverse-core'
|
||||
@@ -0,0 +1,11 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
url = uri("https://repo.onarandombox.com/multiverse-releases")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Multiverse-Core"
|
||||
|
||||
@@ -126,7 +126,6 @@ public class MultiverseCore extends MultiverseModule {
|
||||
MultiverseCoreApi.shutdown();
|
||||
shutdownDependencyInjection();
|
||||
PluginServiceLocatorFactory.get().shutdown();
|
||||
Logging.info("- Disabled");
|
||||
Logging.shutdown();
|
||||
}
|
||||
|
||||
@@ -226,12 +225,11 @@ public class MultiverseCore extends MultiverseModule {
|
||||
* Logs the enable message.
|
||||
*/
|
||||
private void logEnableMessage() {
|
||||
Logging.config("\u001B[32mVersion %s (API v%s) Enabled - By %s\u001B[0m",
|
||||
Logging.config("\u001B[32mVersion %s (API v%s) Enabled - By %s\u001B[39m",
|
||||
this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(getDescription().getAuthors()));
|
||||
|
||||
if (configProvider.get().isShowingDonateMessage()) {
|
||||
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a " +
|
||||
"small donation: https://github.com/sponsors/Multiverse\u001B[0m");
|
||||
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a small donation: https://github.com/sponsors/Multiverse\u001B[39m");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.function.Consumer;
|
||||
/**
|
||||
* Provides access to the MultiverseCore API.
|
||||
*/
|
||||
public final class MultiverseCoreApi {
|
||||
public class MultiverseCoreApi {
|
||||
|
||||
private static MultiverseCoreApi instance;
|
||||
private static final List<Consumer<MultiverseCoreApi>> whenLoadedCallbacks = new ArrayList<>();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mvplugins.multiverse.core;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
@@ -13,13 +12,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.utils.MinecraftTimeFormatter;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.List;
|
||||
@@ -29,17 +26,12 @@ import java.util.Locale;
|
||||
final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
|
||||
private final MultiverseCore plugin;
|
||||
private final CoreConfig coreConfig;
|
||||
private final WorldManager worldManager;
|
||||
private final MVEconomist economist;
|
||||
|
||||
@Inject
|
||||
public PlaceholderExpansionHook(@NotNull MultiverseCore plugin,
|
||||
@NotNull CoreConfig coreConfig,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull MVEconomist economist) {
|
||||
public PlaceholderExpansionHook(MultiverseCore plugin, WorldManager worldManager, MVEconomist economist) {
|
||||
this.plugin = plugin;
|
||||
this.coreConfig = coreConfig;
|
||||
this.worldManager = worldManager;
|
||||
this.economist = economist;
|
||||
}
|
||||
@@ -50,13 +42,6 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return super.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String msg) {
|
||||
if (coreConfig.getWarnInvalidPapiFormat()) {
|
||||
super.warning(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "multiverse-core";
|
||||
@@ -95,115 +80,136 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return null;
|
||||
}
|
||||
|
||||
final var placeholder = paramsArray.remove(0).toLowerCase(Locale.ENGLISH);
|
||||
final var placeholder = paramsArray.remove(0);
|
||||
|
||||
return parseWorldName(offlinePlayer, paramsArray)
|
||||
.flatMap(worldName -> worldManager.getWorld(worldName)
|
||||
.onEmpty(() -> warning("Multiverse World not found: " + worldName)))
|
||||
.flatMap(world -> world.asLoadedWorld()
|
||||
.flatMap(loadedWorld -> getLoadedWorldPlaceHolderValue(placeholder, paramsArray, loadedWorld))
|
||||
.orElse(() -> getWorldPlaceHolderValue(placeholder, paramsArray, world)))
|
||||
.getOrElse(() -> coreConfig.getInvalidPapiFormatReturnsBlank() ? "" : null);
|
||||
String worldName = parseWorldName(offlinePlayer, paramsArray);
|
||||
if (worldName == null) return null;
|
||||
|
||||
return worldManager.getLoadedWorld(worldName)
|
||||
.onEmpty(() -> warning("Multiverse World not found: " + worldName))
|
||||
.map(world -> getWorldPlaceHolderValue(placeholder, paramsArray, world))
|
||||
.getOrNull();
|
||||
}
|
||||
|
||||
private @NotNull Option<String> parseWorldName(OfflinePlayer offlinePlayer, List<String> paramsArray) {
|
||||
private @Nullable String parseWorldName(OfflinePlayer offlinePlayer, List<String> paramsArray) {
|
||||
// No world defined, get from player
|
||||
if (paramsArray.isEmpty()) {
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
return Option.of(player.getWorld().getName());
|
||||
return player.getWorld().getName();
|
||||
} else {
|
||||
warning("You must specify a world name for non-player placeholders");
|
||||
return Option.none();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Try get from params
|
||||
String paramWorldName = paramsArray.get(paramsArray.size() - 1);
|
||||
if (worldManager.isWorld(paramWorldName)) {
|
||||
if (worldManager.isLoadedWorld(paramWorldName)) {
|
||||
paramsArray.remove(paramsArray.size() - 1);
|
||||
return Option.of(paramWorldName);
|
||||
return paramWorldName;
|
||||
}
|
||||
|
||||
// Param not a world, fallback to player
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
return Option.of(player.getWorld().getName());
|
||||
return player.getWorld().getName();
|
||||
}
|
||||
warning("Multiverse World not found: " + paramWorldName);
|
||||
return Option.none();
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NotNull Option<String> getWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull MultiverseWorld world) {
|
||||
return Option.of(switch (placeholder) {
|
||||
case "alias" -> world.getAliasOrName();
|
||||
case "animalspawn" -> String.valueOf(world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(SpawnCategory.ANIMAL)
|
||||
.isSpawn());
|
||||
case "autoheal" -> String.valueOf(world.getAutoHeal());
|
||||
case "blacklist" -> String.join(", ", world.getWorldBlacklist());
|
||||
case "currency" -> String.valueOf(world.getCurrency());
|
||||
case "difficulty" -> world.getDifficulty().toString();
|
||||
case "entryfee" -> economist.formatPrice(world.getPrice(), world.getCurrency());
|
||||
case "environment" -> world.getEnvironment().toString().toLowerCase();
|
||||
case "flight" -> String.valueOf(world.isAllowFlight());
|
||||
case "gamemode" -> world.getGameMode().toString().toLowerCase();
|
||||
case "generator" -> world.getGenerator();
|
||||
case "hunger" -> String.valueOf(world.isHunger());
|
||||
case "isloaded" -> String.valueOf(world.isLoaded());
|
||||
case "key" -> String.valueOf(world.getKey());
|
||||
case "meta" -> {
|
||||
if (placeholderParams.isEmpty()) {
|
||||
warning("No meta key specified.");
|
||||
yield null;
|
||||
}
|
||||
String metaKey = String.join("_", placeholderParams);
|
||||
yield world.getMeta(metaKey)
|
||||
.getOrElse(() -> {
|
||||
warning("Meta key '" + metaKey + "' not found for world '" + world.getName() + "'.");
|
||||
return null;
|
||||
});
|
||||
private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
// Switch to find what specific placeholder we want
|
||||
switch (placeholder.toLowerCase(Locale.ENGLISH)) {
|
||||
case "alias" -> {
|
||||
return world.getAliasOrName();
|
||||
}
|
||||
case "monstersspawn" -> String.valueOf(world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(SpawnCategory.MONSTER)
|
||||
.isSpawn());
|
||||
case "name" -> world.getName();
|
||||
case "playerlimit" -> String.valueOf(world.getPlayerLimit());
|
||||
case "price" -> String.valueOf(world.getPrice());
|
||||
case "pvp" -> String.valueOf(world.getPvp());
|
||||
case "seed" -> String.valueOf(world.getSeed());
|
||||
case "weather" -> String.valueOf(world.isAllowWeather());
|
||||
case "playercount", "time", "type" -> {
|
||||
warning("Placeholder '" + placeholder + "' is only available for loaded worlds.");
|
||||
yield null;
|
||||
case "animalspawn" -> {
|
||||
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.ANIMAL).isSpawn());
|
||||
}
|
||||
default -> {
|
||||
warning("Unknown Placeholder: " + placeholder);
|
||||
yield null;
|
||||
case "autoheal" -> {
|
||||
return String.valueOf(world.getAutoHeal());
|
||||
}
|
||||
case "blacklist" -> {
|
||||
return String.join(", ", world.getWorldBlacklist());
|
||||
}
|
||||
case "currency" -> {
|
||||
return String.valueOf(world.getCurrency());
|
||||
}
|
||||
case "difficulty" -> {
|
||||
return world.getDifficulty().toString();
|
||||
}
|
||||
case "entryfee" -> {
|
||||
return economist.formatPrice(world.getPrice(), world.getCurrency());
|
||||
}
|
||||
case "environment" -> {
|
||||
return world.getEnvironment().toString().toLowerCase();
|
||||
}
|
||||
case "flight" -> {
|
||||
return String.valueOf(world.isAllowFlight());
|
||||
}
|
||||
case "gamemode" -> {
|
||||
return world.getGameMode().toString().toLowerCase();
|
||||
}
|
||||
case "generator" -> {
|
||||
return world.getGenerator();
|
||||
}
|
||||
case "hunger" -> {
|
||||
return String.valueOf(world.isHunger());
|
||||
}
|
||||
case "monstersspawn" -> {
|
||||
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.MONSTER).isSpawn());
|
||||
}
|
||||
case "name" -> {
|
||||
return world.getName();
|
||||
}
|
||||
case "playercount" -> {
|
||||
return String.valueOf(world.getBukkitWorld()
|
||||
.map(World::getPlayers)
|
||||
.map(List::size)
|
||||
.getOrElse(-1));
|
||||
}
|
||||
case "playerlimit" -> {
|
||||
return String.valueOf(world.getPlayerLimit());
|
||||
}
|
||||
case "price" -> {
|
||||
return String.valueOf(world.getPrice());
|
||||
}
|
||||
case "pvp" -> {
|
||||
return String.valueOf(world.getPvp());
|
||||
}
|
||||
case "seed" -> {
|
||||
return String.valueOf(world.getSeed());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull Option<String> getLoadedWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
return Option.of(switch (placeholder) {
|
||||
case "playercount" -> String.valueOf(world.getBukkitWorld()
|
||||
.map(World::getPlayers)
|
||||
.map(List::size)
|
||||
.getOrElse(-1));
|
||||
case "time" -> {
|
||||
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.get(0) : "";
|
||||
long time = world.getBukkitWorld().map(World::getTime).getOrElse(0L);
|
||||
yield switch (timeFormat) {
|
||||
case "" -> String.valueOf(time);
|
||||
case "12h" -> MinecraftTimeFormatter.format12h(time);
|
||||
case "24h" -> MinecraftTimeFormatter.format24h(time);
|
||||
default -> MinecraftTimeFormatter.formatTime(time, timeFormat);
|
||||
};
|
||||
switch (timeFormat) {
|
||||
case "" -> {
|
||||
return String.valueOf(time);
|
||||
}
|
||||
case "12h" -> {
|
||||
return MinecraftTimeFormatter.format12h(time);
|
||||
}
|
||||
case "24h" -> {
|
||||
return MinecraftTimeFormatter.format24h(time);
|
||||
}
|
||||
default -> {
|
||||
return MinecraftTimeFormatter.formatTime(time, timeFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
case "type" -> world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
|
||||
default -> null;
|
||||
});
|
||||
case "type" -> {
|
||||
return world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
|
||||
}
|
||||
case "weather" -> {
|
||||
return String.valueOf(world.isAllowWeather());
|
||||
}
|
||||
default -> {
|
||||
warning("Unknown placeholder: " + placeholder);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.command;
|
||||
|
||||
import co.aikar.commands.CommandCompletionContext;
|
||||
import co.aikar.commands.CommandCompletionFilter;
|
||||
import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility filters for command completion matching.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class MVCommandCompletionFilters {
|
||||
|
||||
/**
|
||||
* Matches namespaced keys (for example, {@code minecraft:stone}) using namespace-aware checks.
|
||||
* <p>
|
||||
* This filter accepts completions that:
|
||||
* <ul>
|
||||
* <li>start with the current input,</li>
|
||||
* <li>have a namespace starting with the input, or</li>
|
||||
* <li>have a key value containing the input.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static final CommandCompletionFilter NAMESPACED_KEY = (context, completion) -> {
|
||||
String[] split = completion.split(":", 2);
|
||||
if (split.length < 2) {
|
||||
return ApacheCommonsLangUtil.startsWithIgnoreCase(completion, context.getInput());
|
||||
}
|
||||
String lowerCase = context.getInput().toLowerCase(Locale.ROOT);
|
||||
return ApacheCommonsLangUtil.startsWithIgnoreCase(completion, context.getInput())
|
||||
|| split[0].toLowerCase(Locale.ROOT).startsWith(lowerCase)
|
||||
|| split[1].toLowerCase(Locale.ROOT).contains(lowerCase);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the namespaced-key completion filter with a generic type-safe signature.
|
||||
* Use this method instead of {@link MVCommandCompletionFilters#NAMESPACED_KEY}
|
||||
* to avoid raw type unchecked warnings.
|
||||
*
|
||||
* @param <C> completion context type
|
||||
* @return the namespaced key filter
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static <C extends CommandCompletionContext> CommandCompletionFilter<C> namespacedKey() {
|
||||
return NAMESPACED_KEY;
|
||||
}
|
||||
|
||||
private MVCommandCompletionFilters() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import co.aikar.commands.BukkitCommandCompletionContext;
|
||||
import co.aikar.commands.CommandCompletionFilter;
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import co.aikar.commands.PaperCommandCompletions;
|
||||
import co.aikar.commands.RegisteredCommand;
|
||||
@@ -53,9 +52,8 @@ import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorPlugin;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PotentialWorldFinder;
|
||||
|
||||
import static org.mvplugins.multiverse.core.utils.StringFormatter.addOnToCommaSeparated;
|
||||
import static org.mvplugins.multiverse.core.utils.StringFormatter.addonToCommaSeperated;
|
||||
|
||||
@Service
|
||||
public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
@@ -67,7 +65,6 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
private final CorePermissionsChecker corePermissionsChecker;
|
||||
private final AnchorManager anchorManager;
|
||||
private final GeneratorProvider generatorProvider;
|
||||
private final PotentialWorldFinder potentialWorldFinder;
|
||||
|
||||
@Inject
|
||||
MVCommandCompletions(
|
||||
@@ -77,8 +74,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
@NotNull CoreConfig config,
|
||||
@NotNull CorePermissionsChecker corePermissionsChecker,
|
||||
@NotNull AnchorManager anchorManager,
|
||||
@NotNull GeneratorProvider generatorProvider,
|
||||
@NotNull PotentialWorldFinder potentialWorldFinder
|
||||
@NotNull GeneratorProvider generatorProvider
|
||||
) {
|
||||
super(mvCommandManager);
|
||||
this.commandManager = mvCommandManager;
|
||||
@@ -88,29 +84,27 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
this.corePermissionsChecker = corePermissionsChecker;
|
||||
this.anchorManager = anchorManager;
|
||||
this.generatorProvider = generatorProvider;
|
||||
this.potentialWorldFinder = potentialWorldFinder;
|
||||
|
||||
registerAsyncCompletion("anchornames", this::suggestAnchorNames, CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("anchornames", this::suggestAnchorNames);
|
||||
registerAsyncCompletion("commands", this::suggestCommands);
|
||||
registerAsyncCompletion("destinations", this::suggestDestinations);
|
||||
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
|
||||
registerStaticCompletion("environments", List.of("normal", "nether", "the_end")); // Don't tab complete the "custom" environment
|
||||
registerAsyncCompletion("flags", this::suggestFlags);
|
||||
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules, MVCommandCompletionFilters.namespacedKey());
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules);
|
||||
registerAsyncCompletion("gamerulesvalues", this::suggestGamerulesValues);
|
||||
registerAsyncCompletion("generatorplugins", this::suggestGeneratorPlugins, CommandCompletionFilter.contains());
|
||||
registerStaticCompletion("materials", suggestEnums(Material.class), CommandCompletionFilter.contains());
|
||||
registerStaticCompletion("mvconfigs", config.getStringPropertyHandle().getAllPropertyNames(), CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("generatorplugins", this::suggestGeneratorPlugins);
|
||||
registerStaticCompletion("materials", suggestEnums(Material.class));
|
||||
registerStaticCompletion("mvconfigs", config.getStringPropertyHandle().getAllPropertyNames());
|
||||
registerAsyncCompletion("mvconfigvalues", this::suggestMVConfigValues);
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds, MVCommandCompletionFilters.namespacedKey());
|
||||
registerAsyncCompletion("mvworldmetakey", this::suggestMVWorldMetaKey, CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName, CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
|
||||
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName);
|
||||
registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue);
|
||||
registerCompletion("playersarray", this::suggestPlayersArray, CommandCompletionFilter.contains()); // getting online players cannot be async
|
||||
registerCompletion("playersarray", this::suggestPlayersArray); // getting online players cannot be async
|
||||
registerStaticCompletion("propsmodifyaction", suggestEnums(PropertyModifyAction.class));
|
||||
registerStaticCompletion("spawncategories", suggestEnums(SpawnCategory.class));
|
||||
registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName, CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName);
|
||||
registerAsyncCompletion("spawncategorypropsvalue", this::suggestSpawnCategoryPropsValue);
|
||||
|
||||
setDefaultCompletion("destinations", DestinationInstance.class);
|
||||
@@ -169,7 +163,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
}
|
||||
}
|
||||
if (context.hasConfig("multiple")) {
|
||||
return addOnToCommaSeparated(context.getInput(), handler.getCompletions(context));
|
||||
return addonToCommaSeperated(context.getInput(), handler.getCompletions(context));
|
||||
}
|
||||
return handler.getCompletions(context);
|
||||
}
|
||||
@@ -303,20 +297,13 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
.toList();
|
||||
}
|
||||
case "potential" -> {
|
||||
return potentialWorldFinder.findPotentialWorlds();
|
||||
return worldManager.getPotentialWorlds();
|
||||
}
|
||||
}
|
||||
Logging.severe("Invalid MVWorld scope: " + scope);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVWorldMetaKey(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(MultiverseWorld.class))
|
||||
.map(MultiverseWorld::getAllMeta)
|
||||
.map(Map::keySet)
|
||||
.getOrElse(Collections.emptySet());
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVWorldPropsName(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> {
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
|
||||
@@ -347,7 +334,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
matchedPlayers.add(name);
|
||||
}
|
||||
}
|
||||
return addOnToCommaSeparated(context.getInput(), matchedPlayers);
|
||||
return addonToCommaSeperated(context.getInput(), matchedPlayers);
|
||||
}
|
||||
|
||||
private Collection<String> suggestSpawnCategoryPropsName(BukkitCommandCompletionContext context) {
|
||||
|
||||
@@ -42,7 +42,6 @@ import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.tick.TickDuration;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
@@ -94,7 +93,6 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
registerIssuerAwareContext(PlayerArrayValue.class, playerArrayContextBuilder().generateContext(PlayerArrayValue::new));
|
||||
registerIssuerAwareContext(PlayerLocation.class, this::parsePlayerLocation);
|
||||
registerContext(SpawnCategory[].class, this::parseSpawnCategories);
|
||||
registerContext(TickDuration.class, this::parseTickDuration);
|
||||
}
|
||||
|
||||
private MVCommandIssuer parseMVCommandIssuer(BukkitCommandExecutionContext context) {
|
||||
@@ -395,13 +393,4 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
}
|
||||
return categories.toArray(new SpawnCategory[0]);
|
||||
}
|
||||
|
||||
private TickDuration parseTickDuration(BukkitCommandExecutionContext context) {
|
||||
String arg = context.popFirstArg();
|
||||
return TickDuration.parseString(arg)
|
||||
.getOrElseThrow(failure ->
|
||||
new InvalidCommandArgument("Invalid time duration format: \"" +arg + "\". Use a number " +
|
||||
"followed by an optional suffix (s for seconds, d for game days) or just a number " +
|
||||
"for ticks. Examples: 100, 5s, 2d"));
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ public class MVCommandManager extends PaperCommandManager {
|
||||
this.config = config;
|
||||
this.pluginLocales = new PluginLocales(this);
|
||||
this.locales = this.pluginLocales;
|
||||
this.pluginLocales.loadLanguages();
|
||||
|
||||
MVCommandConditions.load(this, worldManager, worldNameChecker);
|
||||
this.enableUnstableAPI("help");
|
||||
|
||||
@@ -5,10 +5,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -42,34 +40,34 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
|
||||
private final Class<T> type;
|
||||
private final boolean optional;
|
||||
private final Supplier<? extends T> defaultValueSupplier;
|
||||
private final T defaultValue;
|
||||
private final Function<String, T> context;
|
||||
private final Function<String, Collection<String>> completion;
|
||||
|
||||
/**
|
||||
* Creates a new flag.
|
||||
*
|
||||
* @param key The key for the new flag.
|
||||
* @param aliases The aliases that also refer to this flag.
|
||||
* @param type The type of the value.
|
||||
* @param optional Allow for flag without value.
|
||||
* @param defaultValueSupplier The default value if optional is true and user does not specify a value.
|
||||
* @param context Function to parse string into value type.
|
||||
* @param completion Function to get completion for this flag.
|
||||
* @param key The key for the new flag.
|
||||
* @param aliases The aliases that also refer to this flag.
|
||||
* @param type The type of the value.
|
||||
* @param optional Allow for flag without value.
|
||||
* @param defaultValue The default value if optional is true and user does not specify a value.
|
||||
* @param context Function to parse string into value type.
|
||||
* @param completion Function to get completion for this flag.
|
||||
*/
|
||||
protected CommandValueFlag(
|
||||
@NotNull String key,
|
||||
@NotNull List<String> aliases,
|
||||
@NotNull Class<T> type,
|
||||
boolean optional,
|
||||
@Nullable Supplier<? extends T> defaultValueSupplier,
|
||||
@Nullable T defaultValue,
|
||||
@Nullable Function<String, T> context,
|
||||
@Nullable Function<String, Collection<String>> completion
|
||||
) {
|
||||
super(key, aliases);
|
||||
this.type = type;
|
||||
this.optional = optional;
|
||||
this.defaultValueSupplier = defaultValueSupplier;
|
||||
this.defaultValue = defaultValue;
|
||||
this.context = context;
|
||||
this.completion = completion;
|
||||
}
|
||||
@@ -98,7 +96,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
* @return The default value.
|
||||
*/
|
||||
public @Nullable T getDefaultValue() {
|
||||
return defaultValueSupplier == null ? null : defaultValueSupplier.get();
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,7 +126,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
public static class Builder<T, S extends Builder<T, S>> extends CommandFlag.Builder<S> {
|
||||
protected final Class<T> type;
|
||||
protected boolean optional = false;
|
||||
protected Supplier<? extends T> defaultValueSupplier = null;
|
||||
protected T defaultValue = null;
|
||||
protected Function<String, T> context = null;
|
||||
protected Function<String, Collection<String>> completion = null;
|
||||
|
||||
@@ -160,21 +158,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NotNull S defaultValue(@NotNull T defaultValue) {
|
||||
return defaultValue(() -> defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default value supplier. Used if optional is true and user does not specify a value.
|
||||
* Supplier is only called when command is executed with the flag is present in input.
|
||||
*
|
||||
* @param defaultValueSupplier The default value supplier
|
||||
* @return The builder
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull S defaultValue(@NotNull Supplier<? extends T> defaultValueSupplier) {
|
||||
this.defaultValueSupplier = defaultValueSupplier;
|
||||
this.defaultValue = defaultValue;
|
||||
return (S) this;
|
||||
}
|
||||
|
||||
@@ -210,7 +194,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
if (context == null && !String.class.equals(type)) {
|
||||
throw new IllegalStateException("Context is required for non-string value flags");
|
||||
}
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValueSupplier, context, completion);
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValue, context, completion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +207,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
public static class EnumBuilder<T extends Enum<T>, S extends EnumBuilder<T, S>> extends CommandFlag.Builder<S> {
|
||||
protected final Class<T> type;
|
||||
protected boolean optional = false;
|
||||
protected Supplier<? extends T> defaultValueSupplier = null;
|
||||
protected T defaultValue = null;
|
||||
protected Function<String, T> context = null;
|
||||
protected Function<String, Collection<String>> completion = null;
|
||||
|
||||
@@ -269,21 +253,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NotNull S defaultValue(@NotNull T defaultValue) {
|
||||
return defaultValue(() -> defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default value to supply. Used if optional is true and user does not specify a value.
|
||||
* Supplier is only called when command is executed with the flag is present in input.
|
||||
*
|
||||
* @param defaultValueSupplier The default value supplier.
|
||||
* @return The builder.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull S defaultValue(@NotNull Supplier<? extends T> defaultValueSupplier) {
|
||||
this.defaultValueSupplier = defaultValueSupplier;
|
||||
this.defaultValue = defaultValue;
|
||||
return (S) this;
|
||||
}
|
||||
|
||||
@@ -294,7 +264,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
*/
|
||||
@Override
|
||||
public @NotNull CommandValueFlag<T> build() {
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValueSupplier, context, completion);
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValue, context, completion);
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.command.flags;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Service
|
||||
public class RemovePlayerDestinationFlags extends FlagBuilder {
|
||||
|
||||
public static final String NAME = "removeplayer";
|
||||
|
||||
private WorldManager worldManager;
|
||||
private DestinationsProvider destinationsProvider;
|
||||
private WorldDestination worldDestination;
|
||||
|
||||
protected RemovePlayerDestinationFlags(
|
||||
@NotNull String name,
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(name, flagsManager);
|
||||
this.worldManager = worldManager;
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.worldDestination = worldDestination;
|
||||
}
|
||||
|
||||
@Inject
|
||||
private RemovePlayerDestinationFlags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager);
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.worldManager = worldManager;
|
||||
this.worldDestination = worldDestination;
|
||||
}
|
||||
|
||||
public final CommandValueFlag<DestinationInstance> removePlayers = flag(CommandValueFlag.builder("--remove-players", DestinationInstance.class)
|
||||
.addAlias("-r")
|
||||
.defaultValue(() -> worldManager.getDefaultWorld()
|
||||
.map(defaultWorld -> worldDestination.fromWorld(defaultWorld))
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("No default world found, so the --remove-players flag requires a destination argument."))) //TODO: locale
|
||||
.completion(input -> destinationsProvider.suggestDestinationStrings(Bukkit.getConsoleSender(), input))
|
||||
.context(input -> destinationsProvider.parseDestination(input)
|
||||
.getOrThrow(failure ->
|
||||
MVInvalidCommandArgument.of(failure.getFailureMessage())))
|
||||
.optional()
|
||||
.build());
|
||||
}
|
||||
@@ -1,19 +1,12 @@
|
||||
package org.mvplugins.multiverse.core.command.flags;
|
||||
|
||||
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.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
|
||||
/**
|
||||
* @deprecated The --remove-players flag is being removed in favor of a more flexible system that allows for specifying
|
||||
* a destination for players to be teleported to when a world is unloaded. See {@link RemovePlayerDestinationFlags}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Service
|
||||
public class RemovePlayerFlags extends FlagBuilder {
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions;
|
||||
|
||||
@@ -48,7 +48,7 @@ class CloneCommand extends CoreCommand {
|
||||
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.clone.world.description}")
|
||||
MultiverseWorld world,
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Syntax("<new-world-name>")
|
||||
@Description("{@@mv-core.clone.newWorld.description}")
|
||||
@@ -114,7 +114,7 @@ class CloneCommand extends CoreCommand {
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvcl|mvclone")
|
||||
void onCloneCommand(MVCommandIssuer issuer, MultiverseWorld world, String newWorldName, String[] flags) {
|
||||
void onCloneCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String newWorldName, String[] flags) {
|
||||
super.onCloneCommand(issuer, world, newWorldName, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
@@ -20,16 +19,14 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt.Failure;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
@@ -55,9 +52,8 @@ class CreateCommand extends CoreCommand {
|
||||
@Subcommand("create")
|
||||
@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> " +
|
||||
"--biome <biome> --generator-settings <json-settings> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
|
||||
+ "--no-structures --biome <biome>]")
|
||||
@Description("{@@mv-core.create.description}")
|
||||
void onCreateCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -71,9 +67,8 @@ class CreateCommand extends CoreCommand {
|
||||
World.Environment environment,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --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,...>]")
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
|
||||
+ "--no-structures --biome <biome>]")
|
||||
@Description("{@@mv-core.create.flags.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -83,17 +78,14 @@ class CreateCommand extends CoreCommand {
|
||||
issuer.sendInfo(MVCorei18n.CREATE_LOADING);
|
||||
|
||||
worldManager.createWorld(CreateWorldOptions.worldName(worldName)
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.bonusChest(parsedFlags.hasFlag(flags.bonusChest))
|
||||
.environment(environment)
|
||||
.forcedSpawnPosition(parsedFlags.flagValue(flags.forceSpawnPosition))
|
||||
.generator(parsedFlags.flagValue(flags.generator, ""))
|
||||
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
|
||||
.generateStructures(!parsedFlags.hasFlag(flags.noStructures))
|
||||
.seed(parsedFlags.flagValue(flags.seed))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties)))
|
||||
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL)))
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.seed(parsedFlags.flagValue(flags.seed))
|
||||
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.generator(parsedFlags.flagValue(flags.generator, ""))
|
||||
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
|
||||
.generateStructures(!parsedFlags.hasFlag(flags.noStructures)))
|
||||
.onSuccess(newWorld -> messageSuccess(issuer, newWorld))
|
||||
.onFailure(failure -> messageFailure(issuer, failure));
|
||||
}
|
||||
@@ -188,19 +180,6 @@ class CreateCommand extends CoreCommand {
|
||||
.addAlias("-b")
|
||||
.completion(input -> biomeProviderFactory.suggestBiomeString(input))
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<String> properties = flag(CommandValueFlag.builder("--properties", String.class)
|
||||
.addAlias("-p")
|
||||
.build());
|
||||
|
||||
private final CommandFlag bonusChest = flag(CommandFlag.builder("--generate-bonus-chest")
|
||||
.addAlias("-c")
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<EntityPosition> forceSpawnPosition = flag(CommandValueFlag.builder("--force-spawn-position", EntityPosition.class)
|
||||
.addAlias("-f")
|
||||
.context(input -> Try.of(() -> EntityPosition.fromString(input)).getOrElseThrow(MVInvalidCommandArgument::causeBy))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -15,11 +15,11 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
@@ -31,8 +31,6 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
class DeleteCommand extends CoreCommand {
|
||||
|
||||
@@ -40,7 +38,7 @@ class DeleteCommand extends CoreCommand {
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
private final WorldTickDeferrer worldTickDeferrer;
|
||||
private final RemovePlayerDestinationFlags flags;
|
||||
private final RemovePlayerFlags flags;
|
||||
|
||||
@Inject
|
||||
DeleteCommand(
|
||||
@@ -48,7 +46,7 @@ class DeleteCommand extends CoreCommand {
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
@NotNull WorldTickDeferrer worldTickDeferrer,
|
||||
@NotNull RemovePlayerDestinationFlags flags
|
||||
@NotNull RemovePlayerFlags flags
|
||||
) {
|
||||
this.commandQueueManager = commandQueueManager;
|
||||
this.worldManager = worldManager;
|
||||
@@ -59,8 +57,8 @@ class DeleteCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("delete")
|
||||
@CommandPermission("multiverse.core.delete")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerDestinationFlags.NAME)
|
||||
@Syntax("<world> [--remove-players [destination]]")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.delete.description}")
|
||||
void onDeleteCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -71,7 +69,7 @@ class DeleteCommand extends CoreCommand {
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players [destination]]")
|
||||
@Syntax("[--remove-players]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -86,11 +84,10 @@ class DeleteCommand extends CoreCommand {
|
||||
private void runDeleteCommand(MVCommandIssuer issuer, MultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
issuer.sendInfo(MVCorei18n.DELETE_DELETING, Replace.WORLD.with(world.getName()));
|
||||
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
&& world.isLoaded()
|
||||
&& world instanceof LoadedMultiverseWorld loadedWorld
|
||||
? playerWorldTeleporter.transferAllFromWorldToDestination(loadedWorld, removeToDestination)
|
||||
? playerWorldTeleporter.removeFromWorld(loadedWorld)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> worldTickDeferrer.deferWorldTick(() -> doWorldDeleting(issuer, world)))
|
||||
@@ -116,7 +113,7 @@ class DeleteCommand extends CoreCommand {
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
@NotNull WorldTickDeferrer worldTickDeferrer,
|
||||
@NotNull RemovePlayerDestinationFlags flags) {
|
||||
@NotNull RemovePlayerFlags flags) {
|
||||
super(commandQueueManager, worldManager, playerWorldTeleporter, worldTickDeferrer, flags);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,13 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.LogsType;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.UploadType;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
|
||||
@Service
|
||||
final class DumpsCommand extends CoreCommand {
|
||||
@@ -32,17 +35,22 @@ final class DumpsCommand extends CoreCommand {
|
||||
@Subcommand("dumps")
|
||||
@CommandPermission("multiverse.core.dumps")
|
||||
@CommandCompletion("@flags:groupName=" + Flags.NAME)
|
||||
@Syntax("[--upload <pastesdev|mclogs>]")
|
||||
@Syntax("[--logs <mclogs | append>] [--upload <pastesdev | pastegg>] [--paranoid]")
|
||||
@Description("{@@mv-core.dumps.description}")
|
||||
void onDumpsCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--upload <pastesdev|mclogs>]")
|
||||
@Syntax("[--logs <mclogs | append>] [--upload <pastesdev | pastegg>] [--paranoid]")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
// Grab all our flags
|
||||
boolean paranoid = parsedFlags.hasFlag(flags.paranoid);
|
||||
LogsType logsType = parsedFlags.flagValue(flags.logs, LogsType.MCLOGS);
|
||||
UploadType servicesType = parsedFlags.flagValue(flags.upload, UploadType.PASTESDEV);
|
||||
dumpsService.postLogs(issuer, servicesType);
|
||||
|
||||
dumpsService.postLogs(issuer, logsType, servicesType, paranoid);
|
||||
}
|
||||
|
||||
@Service
|
||||
@@ -55,9 +63,19 @@ final class DumpsCommand extends CoreCommand {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandValueFlag<LogsType> logs = flag(CommandValueFlag
|
||||
.enumBuilder("--logs", LogsType.class)
|
||||
.addAlias("-l")
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<UploadType> upload = flag(CommandValueFlag
|
||||
.enumBuilder("--upload", UploadType.class)
|
||||
.addAlias("-u")
|
||||
.build());
|
||||
|
||||
// Does not upload logs or plugin list (except if --logs mclogs is there)
|
||||
private final CommandFlag paranoid = flag(CommandFlag.builder("--paranoid")
|
||||
.addAlias("-p")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,11 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import net.minidev.json.JSONArray;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
@@ -26,10 +18,8 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
|
||||
enum UploadType {
|
||||
// BEGIN CHECKSTYLE-SUPPRESSION: JavadocVariable
|
||||
// PASTEBIN(PasteServiceType.PASTEBIN, "pastebin.com"),
|
||||
PASTESDEV(PasteServiceType.PASTESDEV, "pastes.dev"),
|
||||
MCLOGS(PasteServiceType.MCLOGS, "mclo.gs"),
|
||||
;
|
||||
PASTEGG(PasteServiceType.PASTEGG, "paste.gg"),
|
||||
PASTESDEV(PasteServiceType.PASTESDEV, "pastes.dev");
|
||||
// END CHECKSTYLE-SUPPRESSION: JavadocVariable
|
||||
|
||||
private final PasteServiceType pasteServiceType;
|
||||
@@ -41,21 +31,34 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final MultiverseCore plugin;
|
||||
enum LogsType {
|
||||
/**
|
||||
* Appends the log file to the debug info.
|
||||
*/
|
||||
APPEND,
|
||||
/**
|
||||
* Uploads the log file to a paste service.
|
||||
*/
|
||||
MCLOGS
|
||||
}
|
||||
|
||||
private final MVCommandIssuer issuer;
|
||||
private final LogsType logsType;
|
||||
private final UploadType uploadType;
|
||||
private final boolean paranoid;
|
||||
private final String logs;
|
||||
private final MVDumpsDebugInfoEvent versionEvent;
|
||||
|
||||
DumpsLogPoster(@NotNull MultiverseCore core,
|
||||
@NotNull MVCommandIssuer issuer,
|
||||
DumpsLogPoster(@NotNull MVCommandIssuer issuer,
|
||||
@NotNull DumpsLogPoster.LogsType logsType,
|
||||
@NotNull DumpsLogPoster.UploadType uploadType,
|
||||
boolean paranoid,
|
||||
@NotNull String logs,
|
||||
@NotNull MVDumpsDebugInfoEvent versionEvent) {
|
||||
this.plugin = core;
|
||||
this.issuer = issuer;
|
||||
this.logsType = logsType;
|
||||
this.uploadType = uploadType;
|
||||
this.paranoid = paranoid;
|
||||
this.logs = logs;
|
||||
this.versionEvent = versionEvent;
|
||||
}
|
||||
@@ -66,12 +69,24 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
handleVersionEvent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:MissingSwitchDefault")
|
||||
private void handleLogs() {
|
||||
versionEvent.putDetailedDebugInfo("latest.log", logs);
|
||||
Logging.finer("Logs type is: " + logsType);
|
||||
|
||||
if (!paranoid) {
|
||||
switch (logsType) {
|
||||
case MCLOGS -> sendDumpsUrl("Logs", postRawDataToMcLogs(logs));
|
||||
case APPEND -> versionEvent.putDetailedDebugInfo("latest.log", logs);
|
||||
default -> Logging.finer("Not uploading logs.");
|
||||
}
|
||||
} else {
|
||||
Logging.finer("Paranoid mode is on, not uploading logs.");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleVersionEvent() {
|
||||
Logging.finer("Upload service is: " + uploadType);
|
||||
|
||||
final Map<String, String> files = versionEvent.getDetailedDebugInfo();
|
||||
sendDumpsUrl(uploadType.serviceName, postFilesToService(files));
|
||||
}
|
||||
@@ -80,77 +95,37 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
issuer.sendInfo(MVCorei18n.DUMPS_URL_LIST, "{service}", service, "{link}", url);
|
||||
}
|
||||
|
||||
private String postRawDataToMcLogs(@NotNull String rawPasteData) {
|
||||
PasteService pasteService = PasteServiceFactory.getService(PasteServiceType.MCLOGS, true);
|
||||
|
||||
return pasteToService(() -> pasteService.postData(rawPasteData));
|
||||
}
|
||||
|
||||
private String postFilesToService(@NotNull Map<String, String> pasteFiles) {
|
||||
PasteService pasteService = PasteServiceFactory.getService(uploadType.pasteServiceType, false);
|
||||
return pasteToService(() -> pasteService.postData(encodePasteAsJson(pasteFiles)));
|
||||
}
|
||||
PasteService pasteService = PasteServiceFactory.getService(uploadType.pasteServiceType, true);
|
||||
|
||||
private String encodePasteAsJson(Map<String, String> files) {
|
||||
JSONObject rootObject = new JSONObject();
|
||||
JSONArray plugins = new JSONArray();
|
||||
JSONArray filesObject = new JSONArray();
|
||||
|
||||
for (Plugin plugin : plugin.getServer().getPluginManager().getPlugins()) {
|
||||
plugins.add(new JSONObject(Map.of(
|
||||
"name", plugin.getName(),
|
||||
"version", plugin.getDescription().getVersion(),
|
||||
"description", Option.of(plugin.getDescription().getDescription()).getOrElse(""),
|
||||
"authors", plugin.getDescription().getAuthors(),
|
||||
"website", Option.of(plugin.getDescription().getWebsite()).getOrElse(""),
|
||||
"enabled", plugin.isEnabled()
|
||||
)));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : files.entrySet()) {
|
||||
String fileType = "text";
|
||||
if (entry.getKey().endsWith(".yml") || entry.getKey().endsWith(".yaml")) {
|
||||
fileType = "yaml";
|
||||
} else if (entry.getKey().endsWith(".json")) {
|
||||
fileType = "json";
|
||||
} else if (entry.getKey().endsWith(".md")) {
|
||||
fileType = "markdown";
|
||||
} else if (entry.getKey().endsWith(".log")) {
|
||||
fileType = "accesslog";
|
||||
return pasteToService(() -> {
|
||||
if (pasteService.supportsMultiFile()) {
|
||||
return pasteService.postData(pasteFiles);
|
||||
} else {
|
||||
return pasteService.postData(this.encodeAsString(pasteFiles));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
filesObject.add(new JSONObject(Map.of(
|
||||
"name", entry.getKey(),
|
||||
"data", entry.getValue(),
|
||||
"type", fileType
|
||||
)));
|
||||
private String encodeAsString(Map<String, String> files) {
|
||||
StringBuilder uploadData = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : files.entrySet()) {
|
||||
String file = entry.getKey();
|
||||
String data = entry.getValue();
|
||||
uploadData.append("# ---------- ")
|
||||
.append(file)
|
||||
.append(" ----------\n\n")
|
||||
.append(data)
|
||||
.append("\n\n");
|
||||
}
|
||||
rootObject.put("server", new JSONObject(Map.of(
|
||||
"name", plugin.getServer().getName(),
|
||||
"version", plugin.getServer().getVersion(),
|
||||
"bukkitVersion", plugin.getServer().getBukkitVersion(),
|
||||
"onlineMode", plugin.getServer().getOnlineMode(),
|
||||
"javaVersion", Runtime.version().toString(),
|
||||
"operatingSystem", System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch"),
|
||||
"uptime", calculateUptimeString(),
|
||||
"memory", "%d free / %d total / %d max".formatted(
|
||||
toMb(Runtime.getRuntime().freeMemory()),
|
||||
toMb(Runtime.getRuntime().totalMemory()),
|
||||
toMb(Runtime.getRuntime().maxMemory()))
|
||||
)));
|
||||
|
||||
rootObject.put("plugins", plugins);
|
||||
rootObject.put("files", filesObject);
|
||||
rootObject.put("createdAt", Instant.now().toString());
|
||||
|
||||
return rootObject.toJSONString();
|
||||
}
|
||||
|
||||
private long toMb(long value) {
|
||||
return value / 1024 / 1024;
|
||||
}
|
||||
|
||||
private String calculateUptimeString() {
|
||||
long uptimeMs = System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().getStartTime();
|
||||
long totalSeconds = uptimeMs / 1000;
|
||||
long hours = totalSeconds / 3600;
|
||||
long minutes = (totalSeconds % 3600) / 60;
|
||||
long seconds = totalSeconds % 60;
|
||||
return String.format("%d hrs, %d mins, %d sec", hours, minutes, seconds);
|
||||
return uploadData.toString();
|
||||
}
|
||||
|
||||
private String pasteToService(LogPaster paster) {
|
||||
|
||||
@@ -5,15 +5,17 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.module.MultiverseModulesRegistry;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.LogsType;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.UploadType;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent;
|
||||
@@ -37,12 +39,16 @@ final class DumpsService {
|
||||
this.fileUtils = fileUtils;
|
||||
}
|
||||
|
||||
void postLogs(MVCommandIssuer issuer, UploadType servicesType) {
|
||||
issuer.sendInfo(MVCorei18n.DUMPS_STARTING);
|
||||
|
||||
void postLogs(MVCommandIssuer issuer, LogsType logsType, UploadType servicesType, boolean paranoid) {
|
||||
// Initialise and add info to the debug event
|
||||
MVDumpsDebugInfoEvent versionEvent = createAndCallDebugInfoEvent();
|
||||
new DumpsLogPoster(plugin, issuer, servicesType, getLogs(), versionEvent)
|
||||
|
||||
// Add plugin list if user isn't paranoid
|
||||
if (!paranoid) {
|
||||
versionEvent.putDetailedDebugInfo("plugins.md", "# Plugins\n\n" + getPluginList());
|
||||
}
|
||||
|
||||
new DumpsLogPoster(issuer, logsType, servicesType, paranoid, getLogs(), versionEvent)
|
||||
.runTaskAsynchronously(plugin);
|
||||
}
|
||||
|
||||
@@ -67,18 +73,18 @@ final class DumpsService {
|
||||
private @NotNull String readLogsFromFile(Path logsPath) {
|
||||
String logs = "Could not read log";
|
||||
|
||||
// Try reading as UTF-8 first
|
||||
// Try reading as ANSI encoded
|
||||
try {
|
||||
logs = Files.readString(logsPath, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
Logging.severe("Could not read logs/latest.log as UTF-8. Trying ISO-8859-1. See below for stack trace");
|
||||
Logging.severe("Could not read logs/latest.log. See below for stack trace");
|
||||
e.printStackTrace();
|
||||
|
||||
try {
|
||||
logs = Files.readString(logsPath, StandardCharsets.ISO_8859_1);
|
||||
logs = Files.readString(logsPath, StandardCharsets.UTF_8);
|
||||
} catch (IOException ex) {
|
||||
// It is some other strange encoding
|
||||
Logging.severe("Could not read ./logs/latest.log as ISO-8859-1. See below for stack trace");
|
||||
Logging.severe("Could not read ./logs/latest.log. See below for stack trace");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -86,6 +92,15 @@ final class DumpsService {
|
||||
return logs;
|
||||
}
|
||||
|
||||
private String getDebugInfoString() {
|
||||
return "# Multiverse-Core Version info" + "\n\n"
|
||||
+ " - Multiverse-Core Version: " + this.plugin.getDescription().getVersion() + '\n'
|
||||
+ " - Bukkit Version: " + this.plugin.getServer().getVersion() + '\n'
|
||||
+ " - Loaded Worlds: " + worldManager.getLoadedWorlds() + '\n'
|
||||
+ " - Multiverse Plugins Loaded: " + StringFormatter.joinAnd(MultiverseModulesRegistry.get().getRegisteredPlugins()) + '\n'
|
||||
+ " - Multiverse Plugins Count: " + MultiverseModulesRegistry.get().getPluginCount() + '\n';
|
||||
}
|
||||
|
||||
private MVDumpsDebugInfoEvent createAndCallDebugInfoEvent() {
|
||||
MVDumpsDebugInfoEvent event = new MVDumpsDebugInfoEvent();
|
||||
addDebugInfoToEvent(event);
|
||||
@@ -97,63 +112,34 @@ final class DumpsService {
|
||||
// Add the legacy file, but as markdown, so it's readable
|
||||
event.putDetailedDebugInfo("version.md", this.getDebugInfoString());
|
||||
|
||||
// Add server.properties if we found it
|
||||
if (fileUtils.getServerProperties() != null) {
|
||||
event.putDetailedDebugInfo(fileUtils.getServerProperties().getName(), fileUtils.getServerProperties());
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find server.properties. Not including file");
|
||||
}
|
||||
// add config.yml
|
||||
File configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
event.putDetailedDebugInfo("Multiverse-Core/config.yml", configFile);
|
||||
|
||||
// add worlds.yml
|
||||
File worldsFile = new File(plugin.getDataFolder(), "worlds.yml");
|
||||
event.putDetailedDebugInfo("Multiverse-Core/worlds.yml", worldsFile);
|
||||
|
||||
// Add bukkit.yml if we found it
|
||||
if (fileUtils.getBukkitConfig() != null) {
|
||||
event.putDetailedDebugInfo(fileUtils.getBukkitConfig().getName(), fileUtils.getBukkitConfig());
|
||||
event.putDetailedDebugInfo(fileUtils.getBukkitConfig().getPath(), fileUtils.getBukkitConfig());
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find bukkit.yml. Not including file");
|
||||
}
|
||||
|
||||
// Add spigot.yml
|
||||
File spigotYml = fileUtils.getServerFolder().toPath().resolve("spigot.yml").toFile();
|
||||
if (spigotYml.isFile()) {
|
||||
event.putDetailedDebugInfo(spigotYml.getName(), spigotYml);
|
||||
// Add server.properties if we found it
|
||||
if (fileUtils.getServerProperties() != null) {
|
||||
event.putDetailedDebugInfo(fileUtils.getServerProperties().getPath(), fileUtils.getServerProperties());
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find spigot.yml. Not including file");
|
||||
Logging.warning("/mv dumps could not find server.properties. Not including file");
|
||||
}
|
||||
|
||||
// Add paper-global.yml
|
||||
File paperGlobalYml = fileUtils.getServerFolder().toPath().resolve("config/paper-global.yml").toFile();
|
||||
if (paperGlobalYml.isFile()) {
|
||||
event.putDetailedDebugInfo(paperGlobalYml.getName(), paperGlobalYml);
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find paper-global.yml. Not including file");
|
||||
}
|
||||
|
||||
// add paper-world-defaults.yml
|
||||
File paperWorldDefaultsYml = fileUtils.getServerFolder().toPath().resolve("config/paper-world-defaults.yml").toFile();
|
||||
if (paperWorldDefaultsYml.isFile()) {
|
||||
event.putDetailedDebugInfo(paperWorldDefaultsYml.getName(), paperWorldDefaultsYml);
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find paper-global.yml. Not including file");
|
||||
}
|
||||
|
||||
// add multiverse-core config.yml
|
||||
File configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
event.putDetailedDebugInfo("multiverse-core/config.yml", configFile);
|
||||
|
||||
// add multiverse-core worlds.yml
|
||||
File worldsFile = new File(plugin.getDataFolder(), "worlds.yml");
|
||||
event.putDetailedDebugInfo("multiverse-core/worlds.yml", worldsFile);
|
||||
|
||||
// add multiverse-core anchors.yml
|
||||
File anchorsFile = new File(plugin.getDataFolder(), "anchors.yml");
|
||||
event.putDetailedDebugInfo("multiverse-core/anchors.yml", anchorsFile);
|
||||
}
|
||||
|
||||
private String getDebugInfoString() {
|
||||
return "# Multiverse-Core Version info" + "\n\n"
|
||||
+ " - Multiverse-Core Version: " + this.plugin.getDescription().getVersion() + '\n'
|
||||
+ " - Bukkit Version: " + this.plugin.getServer().getVersion() + '\n'
|
||||
+ " - Loaded Worlds: " + worldManager.getLoadedWorlds() + '\n'
|
||||
+ " - Multiverse Plugins Loaded: " + StringFormatter.joinAnd(MultiverseModulesRegistry.get().getRegisteredPlugins()) + '\n'
|
||||
+ " - Multiverse Plugins Count: " + MultiverseModulesRegistry.get().getPluginCount() + '\n';
|
||||
private String getPluginList() {
|
||||
return " - " + StringUtils.join(Arrays.stream(plugin.getServer().getPluginManager().getPlugins())
|
||||
.map(Object::toString)
|
||||
.sorted()
|
||||
.toList(), "\n - ");
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class ImportCommand extends CoreCommand {
|
||||
void onImportCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
// @Conditions("worldname:scope=new")
|
||||
@Conditions("worldname:scope=new")
|
||||
@Syntax("<name>")
|
||||
@Description("{@@mv-core.import.name.description}")
|
||||
String worldName,
|
||||
@@ -67,8 +67,7 @@ class ImportCommand extends CoreCommand {
|
||||
worldManager.importWorld(ImportWorldOptions.worldName(worldName)
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.generator(parsedFlags.flagValue(flags.generator))
|
||||
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
|
||||
.generator(parsedFlags.flagValue(flags.generator, String.class))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
|
||||
.onSuccess(newWorld -> {
|
||||
@@ -111,11 +110,6 @@ class ImportCommand extends CoreCommand {
|
||||
private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check")
|
||||
.addAlias("-f")
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<String> generatorSettings = flag(CommandValueFlag
|
||||
.builder("--generator-settings", String.class)
|
||||
.addAlias("-gs")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -87,7 +87,6 @@ class InfoCommand extends CoreCommand {
|
||||
private Map<String, String> getInfo(LoadedMultiverseWorld world) {
|
||||
Map<String, String> outMap = new LinkedHashMap<>();
|
||||
|
||||
outMap.put("World Key", world.getKey().toString());
|
||||
outMap.put("World Name", world.getName());
|
||||
outMap.put("World Alias", world.getAlias());
|
||||
outMap.put("World UID", world.getUID().toString());
|
||||
@@ -104,7 +103,6 @@ class InfoCommand extends CoreCommand {
|
||||
outMap.put("World Type", world.getWorldType().map(WorldType::getName).getOrNull());
|
||||
outMap.put("Biome", world.getBiome());
|
||||
outMap.put("Generator", world.getGenerator());
|
||||
outMap.put("Generator Settings", world.getGeneratorSettings());
|
||||
outMap.put("Generate Structures", world.canGenerateStructures().map(String::valueOf).getOrNull());
|
||||
|
||||
outMap.put("Auto Load", String.valueOf(world.isAutoLoad()));
|
||||
@@ -114,12 +112,13 @@ class InfoCommand extends CoreCommand {
|
||||
outMap.put("World Scale", String.valueOf(world.getScale()));
|
||||
outMap.put("Weather Enabled", String.valueOf(world.isAllowWeather()));
|
||||
outMap.put("Allow Flight", String.valueOf(world.isAllowFlight()));
|
||||
outMap.put("Grant Advancements", String.valueOf(world.isAllowAdvancementGrant()));
|
||||
outMap.put("Auto Heal", String.valueOf(world.getAutoHeal()));
|
||||
outMap.put("Hunger Depletes", String.valueOf(world.isHunger()));
|
||||
outMap.put("Keep Spawn In Memory", String.valueOf(world.isKeepSpawnInMemory()));
|
||||
outMap.put("PVP Enabled", String.valueOf(world.getPvp()));
|
||||
outMap.put("Portal Form", String.valueOf(world.getPortalForm()));
|
||||
outMap.put("Player Limit", String.valueOf(world.getPlayerLimit()));
|
||||
// getAnimalSpawningInfo(outMap, world);
|
||||
// getMonsterSpawningInfo(outMap, world);
|
||||
outMap.put("World Blacklist", String.join(", ", world.getWorldBlacklist()));
|
||||
|
||||
return outMap;
|
||||
@@ -136,6 +135,30 @@ class InfoCommand extends CoreCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// private void getAnimalSpawningInfo(Map<String, String> outMap, MultiverseWorld world) {
|
||||
// if (world.isSpawningAnimals()) {
|
||||
// outMap.put("Spawning Animals", "ALL");
|
||||
// } else {
|
||||
// if (!world.getSpawningAnimalsExceptions().isEmpty()) {
|
||||
// outMap.put("Spawning Animals", world.getSpawningAnimalsExceptions().toString());
|
||||
// } else {
|
||||
// outMap.put("Spawning Animals", "NONE");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void getMonsterSpawningInfo(Map<String, String> outMap, MultiverseWorld world) {
|
||||
// if (world.isSpawningMonsters()) {
|
||||
// outMap.put("Spawning Monsters", "ALL");
|
||||
// } else {
|
||||
// if (!world.getSpawningMonstersExceptions().isEmpty()) {
|
||||
// outMap.put("Spawning Monsters", world.getSpawningMonstersExceptions().toString());
|
||||
// } else {
|
||||
// outMap.put("Spawning Monsters", "NONE");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@Service
|
||||
private static final class LegacyAlias extends InfoCommand implements LegacyAliasCommand {
|
||||
@Inject
|
||||
|
||||
@@ -16,8 +16,8 @@ import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
@@ -38,7 +38,7 @@ class LoadCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("load")
|
||||
@CommandPermission("multiverse.core.load")
|
||||
@CommandCompletion("@mvworlds:scope=unloaded @flags:groupName=" + Flags.NAME)
|
||||
@CommandCompletion("@mvworlds:scope=unloaded")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.load.description}")
|
||||
void onLoadCommand(
|
||||
@@ -49,7 +49,7 @@ class LoadCommand extends CoreCommand {
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--skip-folder-check]")
|
||||
@Syntax("[--remove-players]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -67,9 +67,9 @@ class LoadCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends FlagBuilder {
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvload";
|
||||
private static final String NAME = "mvunload";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Flags;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Single;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
|
||||
import org.mvplugins.multiverse.core.display.ContentDisplay;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.MapContentProvider;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
@Subcommand("meta")
|
||||
final class MetaCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PageFilterFlags flags;
|
||||
|
||||
@Inject
|
||||
MetaCommand(@NotNull WorldManager worldManager, @NotNull PageFilterFlags flags) {
|
||||
this.worldManager = worldManager;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Subcommand("info")
|
||||
@CommandPermission("multiverse.core.meta.info")
|
||||
@CommandCompletion("@mvworlds:scope=both|@flags:resolveUntil=arg1,groupName=" + PageFilterFlags.NAME + " " +
|
||||
"@flags:groupName=" + PageFilterFlags.NAME)
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.meta.info.description}")
|
||||
void infoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.meta.info.world.description}")
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--page <page>] [--filter <filter>]")
|
||||
String[] flagArray
|
||||
) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
ContentDisplay.create()
|
||||
.addContent(MapContentProvider.forContent(world.getAllMeta())
|
||||
.withKeyColor(ChatColor.AQUA)
|
||||
.withValueColor(ChatColor.WHITE))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader(Message.of(MVCorei18n.META_INFO_HEADER, replace("{world}").with(world.getName())))
|
||||
.noContentMessage(Message.of(MVCorei18n.META_INFO_NOCONTENT, replace("{world}").with(world.getName())))
|
||||
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
|
||||
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
|
||||
.send(issuer);
|
||||
}
|
||||
|
||||
@Subcommand("modify")
|
||||
@CommandPermission("multiverse.core.meta.modify")
|
||||
@CommandCompletion("@mvworlds:scope=both set|remove @mvworldmetakey @empty")
|
||||
@Syntax("[world] <set|remove> <key> [value]")
|
||||
void modifyCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=4")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@Syntax("<set|remove>")
|
||||
String action,
|
||||
|
||||
@Syntax("<key>")
|
||||
String key,
|
||||
|
||||
@Optional
|
||||
@Single
|
||||
@Syntax("[value]")
|
||||
String value
|
||||
) {
|
||||
switch (action.toLowerCase(Locale.ROOT)) {
|
||||
case "set" -> world.setMeta(key, value)
|
||||
.onSuccess(ignore -> issuer.sendInfo(MVCorei18n.META_MODIFY_SET_SUCCESS,
|
||||
replace("{key}").with(key),
|
||||
replace("{value}").with(value),
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.onFailure(throwable -> issuer.sendError(MVCorei18n.META_MODIFY_SET_FAILURE,
|
||||
replace("{key}").with(key),
|
||||
replace("{value}").with(value),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(throwable)));
|
||||
case "remove" -> world.removeMeta(key)
|
||||
.onSuccess(ignore -> issuer.sendInfo(MVCorei18n.META_MODIFY_REMOVE_SUCCESS,
|
||||
replace("{key}").with(key),
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.onFailure(throwable -> issuer.sendError(MVCorei18n.META_MODIFY_REMOVE_FAILURE,
|
||||
replace("{key}").with(key),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(throwable)));
|
||||
default -> issuer.sendError(MVCorei18n.META_MODIFY_INVALIDACTION, replace("{action}").with(action));
|
||||
}
|
||||
worldManager.saveWorldsConfig();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
@@ -24,12 +23,9 @@ import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
@@ -69,8 +65,7 @@ class RegenCommand extends CoreCommand {
|
||||
@Subcommand("regen")
|
||||
@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]]")
|
||||
@Syntax("<world> [--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]")
|
||||
@Description("{@@mv-core.regen.description}")
|
||||
void onRegenCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -80,8 +75,7 @@ class RegenCommand extends CoreCommand {
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed [seed]] [--reset-world-config] [--reset-gamerules] [--reset-world-border] " +
|
||||
"[--remove-players [destination]]")
|
||||
@Syntax("[--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]")
|
||||
@Description("{@@mv-core.regen.other.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -97,9 +91,8 @@ class RegenCommand extends CoreCommand {
|
||||
issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, Replace.WORLD.with(world.getName()));
|
||||
List<Player> worldPlayers = world.getPlayers().getOrElse(Collections.emptyList());
|
||||
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
? playerWorldTeleporter.transferAllFromWorldToDestination(world, removeToDestination)
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
? playerWorldTeleporter.removeFromWorld(world)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
// todo: using future will hide stacktrace
|
||||
@@ -135,18 +128,13 @@ class RegenCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerDestinationFlags {
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvregen";
|
||||
|
||||
@Inject
|
||||
private Flags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination);
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandValueFlag<String> seed = flag(CommandValueFlag.builder("--seed", String.class)
|
||||
|
||||
@@ -14,13 +14,11 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
@@ -29,8 +27,6 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
class RemoveCommand extends CoreCommand {
|
||||
|
||||
@@ -52,7 +48,7 @@ class RemoveCommand extends CoreCommand {
|
||||
@Subcommand("remove")
|
||||
@CommandPermission("multiverse.core.remove")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> [--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.remove.description}")
|
||||
void onRemoveCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -62,16 +58,13 @@ class RemoveCommand extends CoreCommand {
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Syntax("[--remove-players]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
? world.asLoadedWorld()
|
||||
.map(loadedWorld -> playerWorldTeleporter.transferAllFromWorldToDestination(loadedWorld, removeToDestination))
|
||||
.getOrElse(AsyncAttemptsAggregate::emptySuccess)
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
? worldManager.getLoadedWorld(world).map(playerWorldTeleporter::removeFromWorld).getOrElse(AsyncAttemptsAggregate::emptySuccess)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> doWorldRemoving(issuer, world, parsedFlags))
|
||||
@@ -92,18 +85,13 @@ class RemoveCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerDestinationFlags {
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvremove";
|
||||
|
||||
@Inject
|
||||
private Flags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination);
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world")
|
||||
|
||||
@@ -14,13 +14,11 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
@@ -29,8 +27,6 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
class UnloadCommand extends CoreCommand {
|
||||
|
||||
@@ -52,7 +48,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>")
|
||||
@Description("{@@mv-core.unload.description}")
|
||||
void onUnloadCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -62,16 +58,15 @@ class UnloadCommand extends CoreCommand {
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players [destination]] [--no-save]")
|
||||
@Syntax("[--remove-players] [--no-save]")
|
||||
@Description("{@@mv-core.gamerules.description.page}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
issuer.sendInfo(MVCorei18n.UNLOAD_UNLOADING, Replace.WORLD.with(world.getAliasOrName()));
|
||||
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
? playerWorldTeleporter.transferAllFromWorldToDestination(world, removeToDestination)
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
? playerWorldTeleporter.removeFromWorld(world)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> doWorldUnloading(issuer, world, parsedFlags))
|
||||
@@ -93,18 +88,13 @@ class UnloadCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerDestinationFlags {
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
|
||||
private static final String NAME = "mvunload";
|
||||
|
||||
@Inject
|
||||
private Flags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination);
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world")
|
||||
|
||||
@@ -12,11 +12,8 @@ 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.compatibility.WorldBorderCompatibility;
|
||||
import org.mvplugins.multiverse.core.utils.tick.TickDuration;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
@@ -36,14 +33,15 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Optional
|
||||
@Default("0")
|
||||
@Syntax("[time]")
|
||||
TickDuration duration,
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
worldBorderAction(issuer, world, worldBorder ->
|
||||
onWorldBorderSet(issuer, worldBorder.getSize() + size, duration, world));
|
||||
worldBorderAction(issuer, world, worldBorder -> {
|
||||
onWorldBorderSet(issuer, worldBorder.getSize() + size, time, world);
|
||||
});
|
||||
}
|
||||
|
||||
@Subcommand("center")
|
||||
@@ -147,7 +145,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Optional
|
||||
@Default("0")
|
||||
@Syntax("[time]")
|
||||
TickDuration duration,
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
@@ -160,18 +158,15 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
return;
|
||||
}
|
||||
if (!WorldBorderCompatibility.supportsChangeSizeInTicks() && !duration.isExactTo(ChronoUnit.SECONDS)) {
|
||||
setRoundOffError(issuer, duration);
|
||||
}
|
||||
WorldBorderCompatibility.changeSizeDuration(worldBorder, size, duration);
|
||||
if (duration.toTicks() <= 0) {
|
||||
worldBorder.setSize(size, time);
|
||||
if (time <= 0) {
|
||||
issuer.sendMessage(MVCorei18n.WORLDBORDER_SET_IMMEDIATE,
|
||||
replace("{size}").with(worldBorder.getSize()),
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
} else {
|
||||
issuer.sendMessage(previousSize > size ? MVCorei18n.WORLDBORDER_SET_GROWING : MVCorei18n.WORLDBORDER_SET_SHRINKING,
|
||||
replace("{size}").with(size),
|
||||
replace("{time}").with(String.format("%.2f", duration.toSeconds())),
|
||||
replace("{time}").with(time),
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
}
|
||||
});
|
||||
@@ -206,34 +201,25 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<time>")
|
||||
TickDuration duration,
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
worldBorderAction(issuer, world, worldBorder -> {
|
||||
if (WorldBorderCompatibility.getWarningTimeTicks(worldBorder) == duration.toTicks()) {
|
||||
if (worldBorder.getWarningTime() == time) {
|
||||
issuer.sendMessage(MVCorei18n.WORLDBORDER_WARNINGTIME_NOTHINGCHANGED,
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
return;
|
||||
}
|
||||
if (!WorldBorderCompatibility.supportsSetWarningTimeInTicks() && !duration.isExactTo(ChronoUnit.SECONDS)) {
|
||||
setRoundOffError(issuer, duration);
|
||||
}
|
||||
WorldBorderCompatibility.setWarningTimeDuration(worldBorder, duration);
|
||||
worldBorder.setWarningTime(time);
|
||||
issuer.sendMessage(MVCorei18n.WORLDBORDER_WARNINGTIME_SUCCESS,
|
||||
replace("{time}").with(String.format("%.2f", WorldBorderCompatibility.getWarningTime(worldBorder))),
|
||||
replace("{time}").with(worldBorder.getWarningTime()),
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
});
|
||||
}
|
||||
|
||||
private void setRoundOffError(MVCommandIssuer issuer, TickDuration duration) {
|
||||
issuer.sendError(MVCorei18n.WORLDBORDER_ROUNDOFF_WARNING,
|
||||
replace("{fromTime}").with(String.format("%.2f", duration.toSeconds())),
|
||||
replace("{toTime}").with(Math.round(duration.toSeconds())));
|
||||
}
|
||||
|
||||
private void worldBorderAction(MVCommandIssuer issuer, LoadedMultiverseWorld world, Consumer<WorldBorder> worldBorderAction) {
|
||||
Try.run(() -> world.getWorldBorder().peek(worldBorderAction))
|
||||
.onFailure(error -> issuer.sendError(error.getLocalizedMessage()));
|
||||
|
||||
@@ -186,16 +186,6 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.enforceFlight);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setGamemodeAndFlightEnforceDelay(int delayTicks) {
|
||||
return configHandle.set(configNodes.gamemodeAndFlightEnforceDelay, delayTicks);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public int getGamemodeAndFlightEnforceDelay() {
|
||||
return configHandle.get(configNodes.gamemodeAndFlightEnforceDelay);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnRate(boolean applyEntitySpawnRate) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnRate, applyEntitySpawnRate);
|
||||
@@ -503,26 +493,6 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.registerPapiHook);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setWarnInvalidPapiFormat(boolean warnInvalidPapiFormat) {
|
||||
return configHandle.set(configNodes.warnInvalidPapiFormat, warnInvalidPapiFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public boolean getWarnInvalidPapiFormat() {
|
||||
return configHandle.get(configNodes.warnInvalidPapiFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setInvalidPapiFormatReturnsBlank(boolean invalidPapiFormatReturnsBlank) {
|
||||
return configHandle.set(configNodes.invalidPapiFormatReturnsBlank, invalidPapiFormatReturnsBlank);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public boolean getInvalidPapiFormatReturnsBlank() {
|
||||
return configHandle.get(configNodes.invalidPapiFormatReturnsBlank);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -651,16 +621,6 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.eventPriorityPlayerTeleport);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setEventPriorityPlayerWorldChange(EventPriority eventPriorityPlayerWorldChange) {
|
||||
return configHandle.set(configNodes.eventPriorityPlayerWorldChange, eventPriorityPlayerWorldChange);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public EventPriority getEventPriorityPlayerWorldChange() {
|
||||
return configHandle.get(configNodes.eventPriorityPlayerWorldChange);
|
||||
}
|
||||
|
||||
public Try<Void> setBukkitYmlPath(String bukkitYmlPath) {
|
||||
return configHandle.set(configNodes.bukkitYmlPath, bukkitYmlPath);
|
||||
}
|
||||
|
||||
@@ -135,16 +135,6 @@ final class CoreConfigNodes {
|
||||
.name("enforce-flight")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> gamemodeAndFlightEnforceDelay = node(ConfigNode.builder("world.gamemode-and-flight-enforce-delay", Integer.class)
|
||||
.comment("")
|
||||
.comment("Sets the delay in ticks before Multiverse enforces gamemode and flight ability on world change.")
|
||||
.comment("Increase this value if you are experiencing issues with other plugins overriding gamemode or flight ability.")
|
||||
.comment("Or set to 0 to enforce immediately during world change event.")
|
||||
.defaultValue(1)
|
||||
.name("gamemode-and-flight-enforce-delay")
|
||||
.suggester((sender -> List.of("0", "1", "2", "5", "10")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnRate = node(ConfigNode.builder("world.apply-entity-spawn-rate", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `tick-rate` config in worlds.yml.")
|
||||
@@ -220,10 +210,7 @@ final class CoreConfigNodes {
|
||||
|
||||
final ConfigNode<PassengerModes> passengerMode = node(ConfigNode.builder("teleport.passenger-mode", PassengerModes.class)
|
||||
.comment("")
|
||||
.comment("Configures how passengers and vehicles are handled when an entity is teleported. This config is")
|
||||
.comment("only applied to /mvtp, join-destination and first-spawn-override. It will not touch any other")
|
||||
.comment("teleport by other plugins or commands.")
|
||||
.comment("========= Available options: =========")
|
||||
.comment("Configures how passengers and vehicles are handled when an entity is teleported.")
|
||||
.comment(" default: Server will handle passengers and vehicles. On papermc 1.21.10+, all will be retained by default.")
|
||||
.comment(" On older versions, all will be dismounted if world changes.")
|
||||
.comment(" dismount_passengers: Passengers will be removed from the parent entity before the teleport.")
|
||||
@@ -403,23 +390,6 @@ final class CoreConfigNodes {
|
||||
.name("register-papi-hook")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> warnInvalidPapiFormat = node(ConfigNode.builder("messaging.warn-invalid-papi-format", Boolean.class)
|
||||
.comment("")
|
||||
.comment("This config option defines whether or not Multiverse should warn about invalid PlaceholderAPI formats.")
|
||||
.comment("Only applies if PlaceholderAPI is installed, and only to multiverse placeholders.")
|
||||
.defaultValue(true)
|
||||
.name("warn-invalid-papi-format")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> invalidPapiFormatReturnsBlank = node(ConfigNode.builder("messaging.invalid-papi-format-returns-blank", Boolean.class)
|
||||
.comment("")
|
||||
.comment("By default, invalid formats will return the original placeholder string.")
|
||||
.comment("This config option defines whether or not Multiverse should show invalid PlaceholderAPI formats as blank.")
|
||||
.comment("Only applies if PlaceholderAPI is installed, and only to multiverse placeholders.")
|
||||
.defaultValue(false)
|
||||
.name("invalid-papi-format-returns-blank")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Locale> defaultLocale = node(ConfigNode.builder("messaging.default-locale", Locale.class)
|
||||
.comment("")
|
||||
.comment("This config option defines the default language Multiverse should use.")
|
||||
@@ -558,18 +528,6 @@ final class CoreConfigNodes {
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerWorldChange = node(ConfigNode.builder("event-priority.player-changed-world", EventPriority.class)
|
||||
.defaultValue(EventPriority.NORMAL)
|
||||
.name("event-priority-player-changed-world")
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerChangedWorldEvent.")
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-changed-world", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-changed-world' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
private final ConfigHeaderNode miscHeader = node(ConfigHeaderNode.builder("misc")
|
||||
.comment("")
|
||||
.comment("")
|
||||
|
||||
+17
-28
@@ -3,9 +3,11 @@ package org.mvplugins.multiverse.core.config.handle;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -17,7 +19,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.migration.ConfigMigrator;
|
||||
import org.mvplugins.multiverse.core.config.node.ListValueNode;
|
||||
import org.mvplugins.multiverse.core.config.node.MapValueNode;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.ValueNode;
|
||||
|
||||
@@ -92,13 +93,17 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
}
|
||||
|
||||
protected <T> T deserializeNodeFromConfig(ValueNode<T> node) {
|
||||
if (node.getSerializer() == null) {
|
||||
return Option.of(config.getObject(node.getPath(), node.getType())).getOrElse(node::getDefaultValue);
|
||||
}
|
||||
return Try.of(() -> {
|
||||
var value = config.get(node.getPath());
|
||||
return value == null ? node.getDefaultValue() : node.deserialize(value);
|
||||
})
|
||||
.flatMap(value -> node.validate(value).map(ignore -> value))
|
||||
.onFailure(e -> Logging.warning("Failed to deserialize node %s: %s",
|
||||
node.getPath(), e.getMessage()))
|
||||
if (value == null) {
|
||||
return node.getDefaultValue();
|
||||
}
|
||||
return node.getSerializer().deserialize(value, node.getType());
|
||||
}).flatMap(value -> node.validate(value).map(ignore -> value))
|
||||
.onFailure(e -> Logging.warning("Failed to deserialize node %s: %s", node.getPath(), e.getMessage()))
|
||||
.getOrElse(node::getDefaultValue);
|
||||
}
|
||||
|
||||
@@ -119,7 +124,12 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
if (value == null) {
|
||||
value = node.getDefaultValue();
|
||||
}
|
||||
config.set(node.getPath(), node.serialize(value));
|
||||
if (node.getSerializer() != null) {
|
||||
var serialized = node.getSerializer().serialize(value, node.getType());
|
||||
config.set(node.getPath(), serialized);
|
||||
} else {
|
||||
config.set(node.getPath(), value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,15 +163,6 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
return set(Bukkit.getConsoleSender(), node, value);
|
||||
}
|
||||
|
||||
public <K, V> Try<Void> set(@NotNull MapValueNode<K, V> node, K key, V value) {
|
||||
return node.validateEntry(key, value).map(ignore -> {
|
||||
Map<K,V> map = get(node);
|
||||
map.put(key, value);
|
||||
// node.onSetEntryValue(null, key, null, value);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a node, if the validator is not null, it will be tested first.
|
||||
*
|
||||
@@ -220,18 +221,6 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
});
|
||||
}
|
||||
|
||||
public <K, V> Try<Void> remove(@NotNull MapValueNode<K, V> node, K key) {
|
||||
return node.validateKey(key).map(ignore -> {
|
||||
Map<K,V> map = get(node);
|
||||
V value = map.remove(key);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Cannot remove entry as it is already not in the map!");
|
||||
}
|
||||
// node.onSetEntryValue(key, value, null);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of a node.
|
||||
*
|
||||
|
||||
@@ -112,5 +112,10 @@ public abstract class FileConfigurationHandle<C extends FileConfiguration> exten
|
||||
* @return The configuration handle.
|
||||
*/
|
||||
public abstract @NotNull FileConfigurationHandle<C> build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected B self() {
|
||||
return (B) this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,24 +180,6 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public T deserialize(@Nullable Object object) {
|
||||
return serializer == null
|
||||
? (type.isInstance(object)) ? type.cast(object) : getDefaultValue()
|
||||
: serializer.deserialize(object, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object serialize(T value) {
|
||||
return serializer == null ? value : serializer.serialize(value, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -120,9 +120,9 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
private void setDefaultSuggester() {
|
||||
if (itemSuggester instanceof SenderNodeSuggester senderItemSuggester) {
|
||||
this.suggester = (SenderNodeSuggester)(sender, input) ->
|
||||
StringFormatter.addOnToCommaSeparated(input, senderItemSuggester.suggest(sender, input));
|
||||
StringFormatter.addonToCommaSeperated(input, senderItemSuggester.suggest(sender, input));
|
||||
} else {
|
||||
this.suggester = input -> StringFormatter.addOnToCommaSeparated(input, itemSuggester.suggest(input));
|
||||
this.suggester = input -> StringFormatter.addonToCommaSeperated(input, itemSuggester.suggest(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
//noinspection unchecked
|
||||
super(path, (Class<List<I>>) (Object) List.class);
|
||||
this.itemType = itemType;
|
||||
this.defaultValue = ArrayList::new;
|
||||
this.defaultValue = () -> (List<I>) new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,603 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.config.node;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultStringParserProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeChangeCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeValueCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.DefaultSerializerProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A config node that contains key-value mappings.
|
||||
*
|
||||
* @param <K> The key type.
|
||||
* @param <V> The value type.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public class MapConfigNode<K, V> extends ConfigNode<Map<K, V>> implements MapValueNode<K, V> {
|
||||
|
||||
/**
|
||||
* Creates a new builder for a {@link MapConfigNode}.
|
||||
*
|
||||
* @param path The path of the node.
|
||||
* @param keyType The map key type.
|
||||
* @param valueType The map value type.
|
||||
* @param <K> The key type.
|
||||
* @param <V> The value type.
|
||||
* @return The new builder.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static @NotNull <K, V> Builder<K, V> mapBuilder(
|
||||
@NotNull String path,
|
||||
@NotNull Class<K> keyType,
|
||||
@NotNull Class<V> valueType) {
|
||||
return new Builder<>(path, keyType, valueType);
|
||||
}
|
||||
|
||||
protected final @NotNull Class<K> keyType;
|
||||
protected final @NotNull Class<V> valueType;
|
||||
protected final @Nullable NodeSuggester keySuggester;
|
||||
protected final @Nullable NodeSuggester valueSuggester;
|
||||
protected final @Nullable NodeStringParser<K> keyStringParser;
|
||||
protected final @Nullable NodeStringParser<V> valueStringParser;
|
||||
protected final @Nullable NodeSerializer<K> keySerializer;
|
||||
protected final @Nullable NodeSerializer<V> valueSerializer;
|
||||
protected final @Nullable Function<K, Try<Void>> keyValidator;
|
||||
protected final @Nullable Function<V, Try<Void>> valueValidator;
|
||||
|
||||
protected MapConfigNode(
|
||||
@NotNull String path,
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<Map<K, V>> type,
|
||||
@NotNull String[] aliases,
|
||||
@Nullable Supplier<Map<K, V>> defaultValueSupplier,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<Map<K, V>> stringParser,
|
||||
@Nullable NodeSerializer<Map<K, V>> serializer,
|
||||
@Nullable Function<Map<K, V>, Try<Void>> validator,
|
||||
@Nullable NodeValueCallback<Map<K, V>> onLoad,
|
||||
@Nullable NodeChangeCallback<Map<K, V>> onLoadAndChange,
|
||||
@Nullable NodeChangeCallback<Map<K, V>> onChange,
|
||||
@NotNull Class<K> keyType,
|
||||
@NotNull Class<V> valueType,
|
||||
@Nullable NodeSuggester keySuggester,
|
||||
@Nullable NodeSuggester valueSuggester,
|
||||
@Nullable NodeStringParser<K> keyStringParser,
|
||||
@Nullable NodeStringParser<V> valueStringParser,
|
||||
@Nullable NodeSerializer<K> keySerializer,
|
||||
@Nullable NodeSerializer<V> valueSerializer,
|
||||
@Nullable Function<K, Try<Void>> keyValidator,
|
||||
@Nullable Function<V, Try<Void>> valueValidator) {
|
||||
super(path, comments, name, type, aliases, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
validator, onLoad, onLoadAndChange, onChange);
|
||||
this.keyType = keyType;
|
||||
this.valueType = valueType;
|
||||
this.keySuggester = keySuggester != null
|
||||
? keySuggester
|
||||
: DefaultSuggesterProvider.getDefaultSuggester(keyType);
|
||||
this.valueSuggester = valueSuggester != null
|
||||
? valueSuggester
|
||||
: DefaultSuggesterProvider.getDefaultSuggester(valueType);
|
||||
this.keyStringParser = keyStringParser != null
|
||||
? keyStringParser
|
||||
: DefaultStringParserProvider.getDefaultStringParser(keyType);
|
||||
this.valueStringParser = valueStringParser != null
|
||||
? valueStringParser
|
||||
: DefaultStringParserProvider.getDefaultStringParser(valueType);
|
||||
this.keySerializer = keySerializer != null
|
||||
? keySerializer
|
||||
: DefaultSerializerProvider.getDefaultSerializer(keyType);
|
||||
this.valueSerializer = valueSerializer != null
|
||||
? valueSerializer
|
||||
: DefaultSerializerProvider.getDefaultSerializer(valueType);
|
||||
this.keyValidator = keyValidator != null
|
||||
? keyValidator
|
||||
: defaultYamlKeyValidator();
|
||||
this.valueValidator = valueValidator;
|
||||
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
private Function<K, Try<Void>> defaultYamlKeyValidator() {
|
||||
return key -> Try.of(() -> {
|
||||
if (!REPatterns.YAML_KEY.matcher(String.valueOf(serializeKey(key))).matches()) {
|
||||
throw new MultiverseException("Invalid yaml key: '" + key + "'. Keys can only " +
|
||||
"contain alphanumeric characters, underscores and hyphens.");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void setDefaults() {
|
||||
if ((this.keySuggester != null || this.valueSuggester != null) && this.suggester == null) {
|
||||
setDefaultSuggester();
|
||||
}
|
||||
if (this.keyStringParser != null && this.valueStringParser != null && this.stringParser == null) {
|
||||
setDefaultStringParser();
|
||||
}
|
||||
if ((this.keyValidator != null || this.valueValidator != null) && this.validator == null) {
|
||||
setDefaultValidator();
|
||||
}
|
||||
if ((this.keySerializer != null || this.valueSerializer != null) && this.serializer == null) {
|
||||
setDefaultSerializer();
|
||||
}
|
||||
if (this.defaultValue == null) {
|
||||
this.defaultValue = LinkedHashMap::new;
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaultSuggester() {
|
||||
if (this.keySuggester instanceof SenderNodeSuggester || this.valueSuggester instanceof SenderNodeSuggester) {
|
||||
this.suggester = (SenderNodeSuggester) this::suggestEntries;
|
||||
} else {
|
||||
this.suggester = input -> suggestEntries(null, input);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull Collection<String> suggestEntries(@Nullable CommandSender sender, @Nullable String input) {
|
||||
String content = input == null ? "" : input;
|
||||
int lastComma = content.lastIndexOf(',');
|
||||
String prefix = lastComma == -1 ? "" : content.substring(0, lastComma + 1);
|
||||
String currentEntry = lastComma == -1 ? content : content.substring(lastComma + 1);
|
||||
|
||||
String[] keyValue = REPatterns.EQUALS.split(currentEntry, 2);
|
||||
if (keyValue.length == 2 && valueSuggester != null) {
|
||||
String keyPart = keyValue[0];
|
||||
String valuePart = keyValue[1];
|
||||
return suggestFrom(valueSuggester, sender, valuePart).stream()
|
||||
.map(suggestedValue -> prefix + keyPart + "=" + suggestedValue)
|
||||
.toList();
|
||||
}
|
||||
|
||||
if (keySuggester == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return suggestFrom(keySuggester, sender, currentEntry).stream()
|
||||
.map(suggestedKey -> prefix + suggestedKey + "=")
|
||||
.toList();
|
||||
}
|
||||
|
||||
private @NotNull Collection<String> suggestFrom(
|
||||
@NotNull NodeSuggester suggester,
|
||||
@Nullable CommandSender sender,
|
||||
@Nullable String input) {
|
||||
if (sender != null && suggester instanceof SenderNodeSuggester senderSuggester) {
|
||||
return senderSuggester.suggest(sender, input);
|
||||
}
|
||||
return suggester.suggest(input);
|
||||
}
|
||||
|
||||
private void setDefaultStringParser() {
|
||||
this.stringParser = (input, type) -> Try.of(() -> {
|
||||
Map<K, V> parsed = new LinkedHashMap<>();
|
||||
if (input == null || input.isBlank()) {
|
||||
return parsed;
|
||||
}
|
||||
for (String entry : REPatterns.SEMICOLON.split(input)) {
|
||||
String[] keyValue = REPatterns.EQUALS.split(entry, 2);
|
||||
if (keyValue.length != 2) {
|
||||
throw new IllegalArgumentException("Invalid map entry '" + entry + "'. Expected format key=value");
|
||||
}
|
||||
NodeStringParser<K> keyParser = Objects.requireNonNull(keyStringParser, "keyStringParser");
|
||||
NodeStringParser<V> valueParser = Objects.requireNonNull(valueStringParser, "valueStringParser");
|
||||
K parsedKey = keyParser.parse(keyValue[0].trim(), keyType).get();
|
||||
V parsedValue = valueParser.parse(keyValue[1].trim(), valueType).get();
|
||||
parsed.put(parsedKey, parsedValue);
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
}
|
||||
|
||||
private void setDefaultValidator() {
|
||||
this.validator = value -> Try.of(() -> {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<K, V> entry : value.entrySet()) {
|
||||
if (keyValidator != null) {
|
||||
keyValidator.apply(entry.getKey()).get();
|
||||
}
|
||||
if (valueValidator != null) {
|
||||
valueValidator.apply(entry.getValue()).get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void setDefaultSerializer() {
|
||||
this.serializer = new NodeSerializer<>() {
|
||||
@Override
|
||||
public Map<K, V> deserialize(Object object, Class<Map<K, V>> type) {
|
||||
if (object == null) {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
if (object instanceof Map<?, ?> rawMap) {
|
||||
Map<K, V> result = new LinkedHashMap<>();
|
||||
rawMap.forEach((key, value) -> {
|
||||
Map.Entry<K, V> entry = deserializeEntry(key, value);
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
if (object instanceof ConfigurationSection section) {
|
||||
Map<K, V> result = new LinkedHashMap<>();
|
||||
for (String key : section.getKeys(false)) {
|
||||
Object rawValue = section.get(key);
|
||||
Map.Entry<K, V> entry = deserializeEntry(key, rawValue);
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid map object '" + object + "'. Expected map or section.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Map<K, V> object, Class<Map<K, V>> type) {
|
||||
Map<Object, Object> serialized = new LinkedHashMap<>();
|
||||
if (object == null) {
|
||||
return serialized;
|
||||
}
|
||||
object.forEach((key, value) -> {
|
||||
Map.Entry<Object, Object> entry = serializeEntry(key, value);
|
||||
serialized.put(entry.getKey(), entry.getValue());
|
||||
});
|
||||
return serialized;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<Void> validateKey(@Nullable K key) {
|
||||
return keyValidator == null ? Try.success(null) : keyValidator.apply(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<Void> validateValue(@Nullable V value) {
|
||||
return valueValidator == null ? Try.success(null) : valueValidator.apply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<Void> validateEntry(@Nullable K key, @Nullable V value) {
|
||||
return validateKey(key).flatMap(ignored -> validateValue(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Map.Entry<K, V> deserializeEntry(@Nullable Object key, @Nullable Object value) {
|
||||
K deserializedKey = keySerializer == null
|
||||
? keyType.isInstance(key) ? keyType.cast(key) : null
|
||||
: keySerializer.deserialize(key, keyType);
|
||||
V deserializedValue = valueSerializer == null
|
||||
? keyType.isInstance(value) ? valueType.cast(value) : null
|
||||
: valueSerializer.deserialize(value, valueType);
|
||||
return new SimpleEntry<>(deserializedKey, deserializedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Map.Entry<Object, Object> serializeEntry(@Nullable K key, @Nullable V value) {
|
||||
return new SimpleEntry<>(serializeKey(key), serializeValue(value));
|
||||
}
|
||||
|
||||
private @Nullable Object serializeKey(@Nullable K key) {
|
||||
return keySerializer == null ? key : keySerializer.serialize(key, keyType);
|
||||
}
|
||||
|
||||
private @Nullable Object serializeValue(@Nullable V value) {
|
||||
return valueSerializer == null ? value : valueSerializer.serialize(value, valueType);
|
||||
}
|
||||
|
||||
private record SimpleEntry<K, V>(K key, V value) implements Map.Entry<K, V> {
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link MapConfigNode} instances.
|
||||
*
|
||||
* <p>This builder extends {@link ConfigNode.Builder} with additional configuration specific to map entries:
|
||||
* entry-level suggesters, string parsers, serializers and validators for both keys and values. When any
|
||||
* entry-level component is omitted the builder provides a sensible default (for example default parsers,
|
||||
* suggesters, serializers or an empty map default value).
|
||||
*
|
||||
* @param <K> The map key type.
|
||||
* @param <V> The map value type.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static class Builder<K, V> extends ConfigNode.Builder<Map<K, V>, Builder<K, V>> {
|
||||
|
||||
protected final @NotNull Class<K> keyType;
|
||||
protected final @NotNull Class<V> valueType;
|
||||
protected @Nullable NodeSuggester keySuggester;
|
||||
protected @Nullable NodeSuggester valueSuggester;
|
||||
protected @Nullable NodeStringParser<K> keyStringParser;
|
||||
protected @Nullable NodeStringParser<V> valueStringParser;
|
||||
protected @Nullable NodeSerializer<K> keySerializer;
|
||||
protected @Nullable NodeSerializer<V> valueSerializer;
|
||||
protected @Nullable Function<K, Try<Void>> keyValidator;
|
||||
protected @Nullable Function<V, Try<Void>> valueValidator;
|
||||
|
||||
/**
|
||||
* Creates a new builder for a map config node.
|
||||
*
|
||||
* @param path The configuration path for the node.
|
||||
* @param keyType The runtime type of the map keys.
|
||||
* @param valueType The runtime type of the map values.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
protected Builder(@NotNull String path, @NotNull Class<K> keyType, @NotNull Class<V> valueType) {
|
||||
//noinspection unchecked
|
||||
super(path, (Class<Map<K, V>>) (Object) Map.class);
|
||||
this.keyType = keyType;
|
||||
this.valueType = valueType;
|
||||
this.defaultValue = LinkedHashMap::new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a suggester for map keys. The suggester is used when computing tab-completion suggestions for the
|
||||
* key part of an entry.
|
||||
*
|
||||
* @param keySuggester The suggester to use for keys.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keySuggester(@NotNull NodeSuggester keySuggester) {
|
||||
this.keySuggester = keySuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware suggester for map keys. Use this when suggestions depend on the command sender
|
||||
* (permissions, location, etc.).
|
||||
*
|
||||
* @param keySuggester The sender-aware suggester.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keySuggester(@NotNull SenderNodeSuggester keySuggester) {
|
||||
this.keySuggester = keySuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a suggester for map values. The suggester is used when computing tab-completion suggestions for the
|
||||
* value part of an entry.
|
||||
*
|
||||
* @param valueSuggester The suggester to use for values.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueSuggester(@NotNull NodeSuggester valueSuggester) {
|
||||
this.valueSuggester = valueSuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware suggester for map values. Use this when value suggestions depend on the
|
||||
* command sender.
|
||||
*
|
||||
* @param valueSuggester The sender-aware suggester.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueSuggester(@NotNull SenderNodeSuggester valueSuggester) {
|
||||
this.valueSuggester = valueSuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for keys. The parser is used when parsing user-provided key strings into key
|
||||
* instances (e.g. when parsing a map from a single-line string representation).
|
||||
*
|
||||
* @param keyStringParser The parser to use for keys.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keyStringParser(@NotNull NodeStringParser<K> keyStringParser) {
|
||||
this.keyStringParser = keyStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware string parser for keys. Use this when parsing depends on the sender context.
|
||||
*
|
||||
* @param keyStringParser The sender-aware key parser.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keyStringParser(@NotNull SenderNodeStringParser<K> keyStringParser) {
|
||||
this.keyStringParser = keyStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for values. The parser converts the textual part after '=' in an entry into the
|
||||
* value type.
|
||||
*
|
||||
* @param valueStringParser The parser to use for values.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueStringParser(@NotNull NodeStringParser<V> valueStringParser) {
|
||||
this.valueStringParser = valueStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware string parser for values.
|
||||
*
|
||||
* @param valueStringParser The sender-aware value parser.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueStringParser(@NotNull SenderNodeStringParser<V> valueStringParser) {
|
||||
this.valueStringParser = valueStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for keys. Serializers convert keys to/from persisted forms stored in the configuration
|
||||
* backend.
|
||||
*
|
||||
* @param keySerializer The key serializer.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keySerializer(@NotNull NodeSerializer<K> keySerializer) {
|
||||
this.keySerializer = keySerializer;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for values.
|
||||
*
|
||||
* @param valueSerializer The value serializer.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueSerializer(@NotNull NodeSerializer<V> valueSerializer) {
|
||||
this.valueSerializer = valueSerializer;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a validator for keys. The validator should return a successful {@link Try} for valid keys or a failed
|
||||
* {@link Try} describing the validation error for invalid keys.
|
||||
*
|
||||
* @param keyValidator The key validator.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keyValidator(@NotNull Function<K, Try<Void>> keyValidator) {
|
||||
this.keyValidator = keyValidator;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a validator for values.
|
||||
*
|
||||
* @param valueValidator The value validator.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueValidator(@NotNull Function<V, Try<Void>> valueValidator) {
|
||||
this.valueValidator = valueValidator;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull MapConfigNode<K, V> build() {
|
||||
return new MapConfigNode<>(
|
||||
path,
|
||||
comments.toArray(new String[0]),
|
||||
name,
|
||||
type,
|
||||
aliases,
|
||||
defaultValue,
|
||||
suggester,
|
||||
stringParser,
|
||||
serializer,
|
||||
validator,
|
||||
onLoad,
|
||||
onLoadAndChange,
|
||||
onChange,
|
||||
keyType,
|
||||
valueType,
|
||||
keySuggester,
|
||||
valueSuggester,
|
||||
keyStringParser,
|
||||
valueStringParser,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
keyValidator,
|
||||
valueValidator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.config.node;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link ValueNode} specialization that stores a {@link Map} of key-value pairs.
|
||||
*
|
||||
* @param <K> The key type.
|
||||
* @param <V> The value type.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public interface MapValueNode<K, V> extends ValueNode<Map<K, V>> {
|
||||
|
||||
/**
|
||||
* Validates a single map key.
|
||||
* <br />
|
||||
* Implementations should return a successful {@link Try} when the key is acceptable, or a failed
|
||||
* {@link Try} when the key is invalid.
|
||||
*
|
||||
* @param key The key to validate, or {@code null} if the key is absent.
|
||||
* @return A successful {@link Try} if the key is valid, or a failed {@link Try} if it is not.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Try<Void> validateKey(@Nullable K key);
|
||||
|
||||
/**
|
||||
* Validates a single map value.
|
||||
* <br />
|
||||
* Implementations should return a successful {@link Try} when the value is acceptable, or a failed
|
||||
* {@link Try} when the value is invalid.
|
||||
*
|
||||
* @param value The value to validate, or {@code null} if the value is absent.
|
||||
* @return A successful {@link Try} if the value is valid, or a failed {@link Try} if it is not.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Try<Void> validateValue(@Nullable V value);
|
||||
|
||||
/**
|
||||
* Validates a key-value entry.
|
||||
* <br />
|
||||
* This is typically implemented by composing {@link #validateKey(Object)} and {@link #validateValue(Object)}.
|
||||
* If either part fails, the entry is considered invalid.
|
||||
*
|
||||
* @param key The entry key, or {@code null} if the key is absent.
|
||||
* @param value The entry value, or {@code null} if the value is absent.
|
||||
* @return A successful {@link Try} if both parts are valid, or a failed {@link Try} if either part is invalid.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Try<Void> validateEntry(@Nullable K key, @Nullable V value);
|
||||
|
||||
/**
|
||||
* Deserializes a raw key-value pair into a map entry.
|
||||
* <br />
|
||||
* This method converts a single persisted entry, not the full map. If either component cannot be converted,
|
||||
* implementations may return {@code null} for that side of the entry.
|
||||
*
|
||||
* @param key The raw key object, or {@code null} if no key is present.
|
||||
* @param value The raw value object, or {@code null} if no value is present.
|
||||
* @return A map entry containing the deserialized key and value.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Map.Entry<K, V> deserializeEntry(@Nullable Object key, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Serializes a map entry into raw configuration objects.
|
||||
* <br />
|
||||
* This method converts a single entry to a persisted representation, not the full map. The returned entry
|
||||
* contains the serialized key and serialized value.
|
||||
*
|
||||
* @param key The entry key, or {@code null} if the key is absent.
|
||||
* @param value The entry value, or {@code null} if the value is absent.
|
||||
* @return A map entry containing the serialized key and value.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Map.Entry<Object, Object> serializeEntry(@Nullable K key, @Nullable V value);
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public interface ValueNode<T> extends Node {
|
||||
* Gets the aliases of this node. Serves as shorter or legacy alternatives the {@link #getName()} and must be
|
||||
* unique within a node group.
|
||||
*
|
||||
* @return The aliases of this node, or an empty array if the node has no aliases.
|
||||
* @return The aliases of this node.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@@ -44,36 +44,27 @@ public interface ValueNode<T> extends Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value for this node.
|
||||
* Gets the default value with type {@link T} of the node.
|
||||
*
|
||||
* <p>The returned value is used when no explicit value is present or when deserialization falls back to the
|
||||
* node's default. Implementations may return {@code null} to indicate that no default value is available.
|
||||
*
|
||||
* @return The default value of the node, or {@code null} if none is configured.
|
||||
* @return The default value of the node.
|
||||
*/
|
||||
@Nullable T getDefaultValue();
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node.
|
||||
* Suggests possible string values for this node. Generated based on the current user input.
|
||||
*
|
||||
* <p>The returned values are based on the current partial user input and should be suitable for command-line
|
||||
* tab completion or other interactive hints.
|
||||
*
|
||||
* @param input The current partial user input, or {@code null} if no input has been typed yet.
|
||||
* @param input The current partial user input
|
||||
* @return A collection of possible string values.
|
||||
*/
|
||||
@NotNull Collection<String> suggest(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node using sender context.
|
||||
* Suggests possible string values for this node. Use contextural information from the sender such as
|
||||
* sender name, permissions, or player location for better suggestions.
|
||||
*
|
||||
* <p>Implementations may use the sender's name, permissions, or player location to tailor the returned
|
||||
* suggestions. If no sender-specific logic is available, implementations should fall back to
|
||||
* {@link #suggest(String)}.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The current partial user input, or {@code null} if no input has been typed yet.
|
||||
* @return A collection of possible string values.
|
||||
* @param sender The sender context.
|
||||
* @param input The input string.
|
||||
* @return A collection of possible string values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@@ -81,25 +72,20 @@ public interface ValueNode<T> extends Node {
|
||||
@NotNull Collection<String> suggest(@NotNull CommandSender sender, @Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T}.
|
||||
* Parses the given string into a value of type {@link T}. Used for property set by user input.
|
||||
*
|
||||
* <p>This is typically used for values entered by a user or read from configuration text.
|
||||
*
|
||||
* @param input The string to parse, or {@code null} if the source value is absent.
|
||||
* @return The parsed value, or a failed {@link Try} containing the parsing error.
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*/
|
||||
@NotNull Try<T> parseFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T} with context from the sender.
|
||||
* Used for property set by user input.
|
||||
*
|
||||
* <p>Sender-aware implementations may use permissions or other sender-specific information when converting the
|
||||
* input. If no sender-aware parser is available, implementations should fall back to
|
||||
* {@link #parseFromString(String)}.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The string to parse, or {@code null} if the source value is absent.
|
||||
* @return The parsed value, or a failed {@link Try} containing the parsing error.
|
||||
* @param sender The sender context.
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@@ -111,59 +97,23 @@ public interface ValueNode<T> extends Node {
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
* <p>This method exists for backward compatibility while the preferred API is
|
||||
* {@link #serialize(Object)} and {@link #deserialize(Object)}.
|
||||
*
|
||||
* @return The serializer for this node, or {@code null} if none is configured.
|
||||
*
|
||||
* @deprecated Use {@link #serialize(Object)} and {@link #deserialize(Object)} instead.
|
||||
* @return The serializer for this node.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable NodeSerializer<T> getSerializer();
|
||||
|
||||
/**
|
||||
* Deserializes a raw configuration object into a value of type {@link T}.
|
||||
*
|
||||
* <p>The input is typically a value read from YAML or another configuration source. Implementations should
|
||||
* return a sensible fallback, such as {@link #getDefaultValue()}, when the object cannot be converted.
|
||||
*
|
||||
* @param object The raw object to deserialize.
|
||||
* @return The deserialized value, or {@code null} if the object cannot be converted and no fallback exists.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Nullable T deserialize(@Nullable Object object);
|
||||
|
||||
/**
|
||||
* Serializes a value of type {@link T} into a configuration-friendly object.
|
||||
*
|
||||
* <p>The returned object should be suitable for persistence in a configuration file.
|
||||
*
|
||||
* @param value The value to serialize.
|
||||
* @return The serialized representation, or {@code null} if the value cannot be serialized.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Nullable Object serialize(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Validates the value of this node.
|
||||
*
|
||||
* @param value The value to validate.
|
||||
* @return A successful {@link Try} if the value is valid, or a failed {@link Try} containing the validation
|
||||
* exception if the value is invalid.
|
||||
* @return An empty {@link Try} if the value is valid, or a {@link Try} containing an exception if the value is
|
||||
* invalid.
|
||||
*/
|
||||
Try<Void> validate(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is loaded by {@link BaseConfigurationHandle#load()}.
|
||||
*
|
||||
* <p>This callback is invoked after the value has been deserialized and validated during a configuration load.
|
||||
*
|
||||
* @param value The loaded value, or {@code null} if the configured value could not be resolved.
|
||||
* @param value The loaded value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@@ -173,12 +123,9 @@ public interface ValueNode<T> extends Node {
|
||||
/**
|
||||
* Called when the value of this node is loaded or changed.
|
||||
*
|
||||
* <p>This callback is shared by both initial load and subsequent updates. When invoked during a load, the
|
||||
* {@code oldValue} parameter will be {@code null} and the sender will be the console sender.
|
||||
*
|
||||
* @param sender The sender who triggered the change. If triggered by loading or no target sender is specified,
|
||||
* it will be the console sender.
|
||||
* @param oldValue The previous value, or {@code null} when called during load.
|
||||
* @param sender The sender who triggered the change. If triggered by loading or no target sender is specified,
|
||||
* it will be the console sender.
|
||||
* @param oldValue The old value, will always be null on load.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @since 5.4
|
||||
@@ -189,11 +136,9 @@ public interface ValueNode<T> extends Node {
|
||||
/**
|
||||
* Called when the value of this node is changed by {@link BaseConfigurationHandle#set(ValueNode, Object)}.
|
||||
*
|
||||
* <p>This callback is only invoked for explicit updates after the value already exists.
|
||||
*
|
||||
* @param sender The sender who changed the value, or the console sender if no target sender was specified.
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
* @param sender The sender who changed the value, or console sender if no target sender specified.
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@@ -203,15 +148,12 @@ public interface ValueNode<T> extends Node {
|
||||
/**
|
||||
* Called when the value of this node is set.
|
||||
*
|
||||
* <p>This is the legacy, senderless form of {@link #onLoadAndChange(CommandSender, Object, Object)}.
|
||||
*
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @deprecated Use {@link #onLoadAndChange(CommandSender, Object, Object)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.4", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
default void onSetValue(@Nullable T oldValue, @Nullable T newValue) {
|
||||
onLoadAndChange(Bukkit.getConsoleSender(), oldValue, newValue);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -15,11 +14,13 @@ import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
|
||||
@@ -55,19 +56,6 @@ public final class WorldDestination implements Destination<WorldDestination, Wor
|
||||
return "w";
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly create a world destination instance for a Multiverse World without any string parsing validation needed.
|
||||
*
|
||||
* @param multiverseWorld The destination's target world.
|
||||
* @return The destination instance with the target world.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull WorldDestinationInstance fromWorld(MultiverseWorld multiverseWorld) {
|
||||
return new WorldDestinationInstance(this, multiverseWorld, null, -1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
+19
-22
@@ -35,7 +35,7 @@ public final class DynamicListenerRegistration {
|
||||
private static final boolean hasEventExecutorCreate;
|
||||
|
||||
static {
|
||||
hasEventExecutorCreate = ReflectHelper.hasMethod(EventExecutor.class, "create", Method.class, Class.class);
|
||||
hasEventExecutorCreate = ReflectHelper.getMethod(EventExecutor.class, "create", Method.class, Class.class) != null;
|
||||
}
|
||||
|
||||
private final EventPriorityMapper eventPriorityMapper;
|
||||
@@ -98,22 +98,18 @@ public final class DynamicListenerRegistration {
|
||||
}
|
||||
|
||||
method.setAccessible(true);
|
||||
Object methodOutput = ReflectHelper.invokeMethod(listener, method);
|
||||
if (!(methodOutput instanceof EventRunnable eventRunnable)) {
|
||||
Logging.warning("Event method %s in %s did not return a SingleEventListener",
|
||||
method.getName(), listener.getClass().getName());
|
||||
return;
|
||||
}
|
||||
EventExecutor executor = new EventRunnableExecutor<>(eventClass, eventRunnable);
|
||||
EventPriority priority = getDynamicEventPriority(method);
|
||||
boolean ignoreCancelled = isIgnoreIfCancelled(method);
|
||||
|
||||
ReflectHelper.tryInvokeMethod(listener, method)
|
||||
.onFailure(e -> Logging.warning("Failed to invoke event method %s in %s: $s",
|
||||
method.getName(), listener.getClass().getName(), e.getMessage()))
|
||||
.filter(EventRunnable.class::isInstance)
|
||||
.onFailure(e -> Logging.warning("Event method %s in %s did not return an EventRunnable instance!",
|
||||
method.getName(), listener.getClass().getName()))
|
||||
.map(EventRunnable.class::cast)
|
||||
.map(eventRunnable -> new EventRunnableExecutor<>(eventClass, eventRunnable))
|
||||
.peek(executor -> {
|
||||
EventPriority priority = getDynamicEventPriority(method);
|
||||
boolean ignoreCancelled = isIgnoreIfCancelled(method);
|
||||
Bukkit.getPluginManager().registerEvent(eventClass, listener, priority, executor, plugin, ignoreCancelled);
|
||||
Logging.finest("Registered dynamic event: %s, priority: %s, ignore if cancelled: %s",
|
||||
eventClass.getName(), priority, ignoreCancelled);
|
||||
});
|
||||
Logging.finest("Registering dynamic event for %s with priority %s", eventClass.getName(), priority);
|
||||
Bukkit.getPluginManager().registerEvent(eventClass, listener, priority, executor, plugin, ignoreCancelled);
|
||||
}
|
||||
|
||||
private Class<? extends Event> getEventClass(Method method) {
|
||||
@@ -133,12 +129,13 @@ public final class DynamicListenerRegistration {
|
||||
}
|
||||
|
||||
private Class<? extends Event> getEventClassFromString(String className) {
|
||||
return ReflectHelper.tryGetClass(className)
|
||||
.onFailure(throwable -> Logging.fine("Failed to find event class: %s", className))
|
||||
.filter(Event.class::isAssignableFrom)
|
||||
.onFailure(throwable -> Logging.warning("Class is not an Event: %s", className))
|
||||
.map(clazz -> (Class<? extends Event>) clazz.asSubclass(Event.class))
|
||||
.getOrNull();
|
||||
Class<?> annotatedClass = ReflectHelper.getClass(className);
|
||||
if (annotatedClass == null || !Event.class.isAssignableFrom(annotatedClass)) {
|
||||
// Usually means the server software used did not implement the event
|
||||
Logging.fine("Event class does not exist: %s", className);
|
||||
return null;
|
||||
}
|
||||
return annotatedClass.asSubclass(Event.class);
|
||||
}
|
||||
|
||||
private EventPriority getDynamicEventPriority(Method method) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -25,7 +24,7 @@ public final class MVDumpsDebugInfoEvent extends Event {
|
||||
|
||||
public MVDumpsDebugInfoEvent() {
|
||||
debugInfoBuilder = new StringBuilder();
|
||||
detailedDebugInfo = new LinkedHashMap<>();
|
||||
detailedDebugInfo = new HashMap<>();
|
||||
}
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package org.mvplugins.multiverse.core.event.world;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Called when a world has been successfully cloned.
|
||||
@@ -13,24 +10,14 @@ import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
public final class MVWorldClonedEvent extends MultiverseWorldEvent<LoadedMultiverseWorld> {
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
private final MultiverseWorld fromWorld;
|
||||
private final LoadedMultiverseWorld fromWorld;
|
||||
|
||||
public MVWorldClonedEvent(LoadedMultiverseWorld world, MultiverseWorld fromWorld) {
|
||||
public MVWorldClonedEvent(LoadedMultiverseWorld world, LoadedMultiverseWorld fromWorld) {
|
||||
super(world);
|
||||
this.fromWorld = fromWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Cloning can be done from unloaded worlds as well. Use {@link #getSourceWorld()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.6")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public @Nullable LoadedMultiverseWorld getFromWorld() {
|
||||
return fromWorld.asLoadedWorld().getOrNull();
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public @NotNull MultiverseWorld getSourceWorld() {
|
||||
public LoadedMultiverseWorld getFromWorld() {
|
||||
return fromWorld;
|
||||
}
|
||||
|
||||
@@ -50,4 +37,5 @@ public final class MVWorldClonedEvent extends MultiverseWorldEvent<LoadedMultive
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
-18
@@ -1,8 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.exceptions.command;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizableMessage;
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizedMessage;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
@@ -11,18 +9,6 @@ import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
*/
|
||||
public class MVInvalidCommandArgument extends InvalidCommandArgument {
|
||||
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static MVInvalidCommandArgument causeBy(Throwable throwable) {
|
||||
return causeBy(throwable, true);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
public static MVInvalidCommandArgument of(Message message) {
|
||||
return of(message, true);
|
||||
}
|
||||
@@ -33,10 +19,6 @@ public class MVInvalidCommandArgument extends InvalidCommandArgument {
|
||||
: new MVInvalidCommandArgument(message, showSyntax);
|
||||
}
|
||||
|
||||
private MVInvalidCommandArgument(String message, boolean showSyntax) {
|
||||
super(message, showSyntax);
|
||||
}
|
||||
|
||||
private MVInvalidCommandArgument(Message message, boolean showSyntax) {
|
||||
super(message.formatted(), showSyntax);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
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.LivingEntity;
|
||||
@@ -22,8 +21,6 @@ import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.world.WorldManager;
|
||||
|
||||
@@ -80,37 +77,6 @@ final class MVEntityListener implements CoreListener {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired before other spawn checks is done, helps in performance by cancelling early and preventing unnecessary
|
||||
* checks for spawn reasons that are not allowed in the world.
|
||||
*
|
||||
* @return Event wrapper
|
||||
*/
|
||||
@EventClass("com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent")
|
||||
EventRunnable<?> preCreatureSpawn() {
|
||||
return new EventRunnable<PreCreatureSpawnEvent>() {
|
||||
@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) {
|
||||
return;
|
||||
}
|
||||
|
||||
worldManager.getLoadedWorld(event.getSpawnLocation().getWorld())
|
||||
.peek(world -> {
|
||||
if (!world.getEntitySpawnConfig().shouldAllowSpawn(event.getType())) {
|
||||
Logging.finest("Cancelling Pre Creature Spawn Event for: " + event.getType());
|
||||
event.setCancelled(true);
|
||||
event.setShouldAbortSpawn(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Spawn Category settings.
|
||||
*
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
package org.mvplugins.multiverse.core.listeners;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
@@ -36,7 +38,6 @@ import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventPriorityKey;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.IgnoreIfCancelled;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
@@ -244,7 +245,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
private void teleportToDestinationOnJoin(Player player, DestinationInstance<?, ?> destination) {
|
||||
asyncSafetyTeleporter.to(destination).passengerMode(config.getPassengerMode()).teleportSingle(player)
|
||||
asyncSafetyTeleporter.to(destination).teleportSingle(player)
|
||||
.onSuccess(result -> Logging.fine("Player %s has been teleported on join",
|
||||
player.getName()))
|
||||
.onFailure(failure -> Logging.warning("Failed to teleport player %s on join: %s",
|
||||
@@ -256,9 +257,9 @@ final class MVPlayerListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-changed-world")
|
||||
@DefaultEventPriority(EventPriority.NORMAL)
|
||||
@DefaultEventPriority(EventPriority.MONITOR)
|
||||
void playerChangedWorld(PlayerChangedWorldEvent event) {
|
||||
// Permissions now determine whether or not to handle a gamemode.
|
||||
this.handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld());
|
||||
}
|
||||
|
||||
@@ -270,28 +271,12 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventPriorityKey("mvcore-player-teleport")
|
||||
@DefaultEventPriority(EventPriority.HIGHEST)
|
||||
void playerTeleport(PlayerTeleportEvent event) {
|
||||
Player teleportee = event.getPlayer();
|
||||
|
||||
if (event.isCancelled()) {
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' was already cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.finer("Got teleport event for player '"
|
||||
+ teleportee.getName() + "' with cause '" + event.getCause() + "'");
|
||||
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) { // should never be null, but just in case
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' has null from-location or world.");
|
||||
+ event.getPlayer().getName() + "' with cause '" + event.getCause() + "'");
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location toLocation = event.getTo();
|
||||
if (toLocation == null || toLocation.getWorld() == null) { // may be null on spigot
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' has null to-location or world.");
|
||||
return;
|
||||
}
|
||||
|
||||
Player teleportee = event.getPlayer();
|
||||
Option<String> teleporterName = teleportQueue.popFromQueue(teleportee.getName());
|
||||
CommandSender teleporter = teleporterName.map(name -> {
|
||||
if (name.equalsIgnoreCase("CONSOLE")) {
|
||||
@@ -312,17 +297,17 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
Logging.finer("Teleporter %s is teleporting %s from %s to %s", teleporter.getName(), teleportee.getName(),
|
||||
fromLocation, toLocation);
|
||||
event.getFrom(), event.getTo());
|
||||
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(fromLocation.getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(toLocation.getWorld()).getOrNull();
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld()).getOrNull();
|
||||
if (toWorld == null) {
|
||||
Logging.fine("Player '" + teleportee.getName() + "' is teleporting to world '"
|
||||
+ toLocation.getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ event.getTo().getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ "actions will be taken by Multiverse-Core.");
|
||||
return;
|
||||
}
|
||||
if (fromLocation.getWorld().equals(toLocation.getWorld())) {
|
||||
if (event.getFrom().getWorld().equals(event.getTo().getWorld())) {
|
||||
// The player is Teleporting to the same world.
|
||||
Logging.finer("Player '" + teleportee.getName() + "' is teleporting to the same world.");
|
||||
this.stateSuccess(teleportee.getName(), toWorld.getName());
|
||||
@@ -356,19 +341,20 @@ final class MVPlayerListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@DefaultEventPriority(EventPriority.LOWEST)
|
||||
@IgnoreIfCancelled
|
||||
void playerPortalCheck(PlayerPortalEvent event) {
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (event.getFrom().getWorld() == null) {
|
||||
Logging.warning("PlayerPortalEvent's from world is null!");
|
||||
return;
|
||||
}
|
||||
if (fromLocation.getWorld().getBlockAt(fromLocation).getType() == Material.NETHER_PORTAL) {
|
||||
if (event.getFrom().getWorld().getBlockAt(event.getFrom()).getType() == Material.NETHER_PORTAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Player was actually outside of the portal, adjust the from location
|
||||
Location newLocation = blockSafety.findPortalBlockNextTo(fromLocation);
|
||||
Location newLocation = blockSafety.findPortalBlockNextTo(event.getFrom());
|
||||
if (newLocation != null) {
|
||||
event.setFrom(newLocation);
|
||||
}
|
||||
@@ -381,33 +367,28 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-portal")
|
||||
@DefaultEventPriority(EventPriority.HIGH)
|
||||
@IgnoreIfCancelled
|
||||
void playerPortal(PlayerPortalEvent event) {
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) { // should never be null, but just in case
|
||||
Logging.finer("PlayerPortalEvent's from world is null!");
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location toLocation = event.getTo();
|
||||
if (toLocation == null || toLocation.getWorld() == null) { // may be null on spigot
|
||||
if (event.getTo() == null || event.getTo().getWorld() == null) {
|
||||
Logging.finer("PlayerPortalEvent's to world is null!");
|
||||
return;
|
||||
}
|
||||
if (config.isUsingCustomPortalSearch()) {
|
||||
event.setSearchRadius(config.getCustomPortalSearchRadius());
|
||||
}
|
||||
if (Objects.equals(fromLocation.getWorld(), toLocation.getWorld())) {
|
||||
if (Objects.equals(event.getFrom().getWorld(), event.getTo().getWorld())) {
|
||||
// The player is Portaling to the same world.
|
||||
Logging.finer("Player '" + event.getPlayer().getName() + "' is portaling to the same world.");
|
||||
return;
|
||||
}
|
||||
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(fromLocation.getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(toLocation.getWorld()).getOrNull();
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld()).getOrNull();
|
||||
if (toWorld == null) {
|
||||
Logging.fine("Player '" + event.getPlayer().getName() + "' is portaling to world '"
|
||||
+ toLocation.getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ event.getTo().getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ "actions will be taken by Multiverse-Core.");
|
||||
return;
|
||||
}
|
||||
@@ -421,31 +402,21 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the gamemode for the specified {@link Player}. Delays the enforcement if configured to do so
|
||||
* to ensure multiverse has final say over other plugins changing gamemodes.
|
||||
* Handles the gamemode for the specified {@link Player}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param world The {@link World} the player is supposed to be in.
|
||||
*/
|
||||
private void handleGameModeAndFlight(final Player player, World world) {
|
||||
if (config.getGamemodeAndFlightEnforceDelay() <= 0) {
|
||||
doGameModeAndFlightEnforcement(player, world);
|
||||
return;
|
||||
}
|
||||
server.getScheduler().runTaskLater(
|
||||
this.plugin,
|
||||
() -> doGameModeAndFlightEnforcement(player, world),
|
||||
config.getGamemodeAndFlightEnforceDelay()
|
||||
);
|
||||
}
|
||||
|
||||
private void doGameModeAndFlightEnforcement(Player player, World world) {
|
||||
if (!player.isOnline() || !player.getWorld().equals(world)) {
|
||||
Logging.finer("Player %s is no longer online or not in the expected world '%s'", player.getName(), world.getName());
|
||||
return;
|
||||
}
|
||||
Logging.finer("Handling gamemode and flight for player %s in world '%s'", player.getName(), world.getName());
|
||||
enforcementHandler.handleFlightEnforcement(player);
|
||||
enforcementHandler.handleGameModeEnforcement(player);
|
||||
// We perform this task one tick later to MAKE SURE that the player actually reaches the
|
||||
// destination world, otherwise we'd be changing the player mode if they havent moved anywhere.
|
||||
this.server.getScheduler().runTaskLater(this.plugin, () -> {
|
||||
if (!player.isOnline() || !player.getWorld().equals(world)) {
|
||||
return;
|
||||
}
|
||||
Logging.finer("Handling gamemode and flight for player %s in world '%s'", player.getName(), world.getName());
|
||||
enforcementHandler.handleFlightEnforcement(player);
|
||||
enforcementHandler.handleGameModeEnforcement(player);
|
||||
}, 1L);
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,9 @@ import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.world.PortalCreateEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.IgnoreIfCancelled;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import static org.bukkit.PortalType.CUSTOM;
|
||||
@@ -35,15 +33,11 @@ final class MVPortalListener implements CoreListener {
|
||||
|
||||
private final CoreConfig config;
|
||||
private final WorldManager worldManager;
|
||||
private final MVCommandManager commandManager;
|
||||
|
||||
@Inject
|
||||
MVPortalListener(@NotNull CoreConfig config,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull MVCommandManager commandManager) {
|
||||
MVPortalListener(@NotNull CoreConfig config, @NotNull WorldManager worldManager) {
|
||||
this.config = config;
|
||||
this.worldManager = worldManager;
|
||||
this.commandManager = commandManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +59,6 @@ final class MVPortalListener implements CoreListener {
|
||||
if (!world.getPortalForm().isPortalAllowed(targetType)) {
|
||||
Logging.fine("Cancelling creation of %s portal because portalForm disallows.", targetType);
|
||||
event.setCancelled(true);
|
||||
commandManager.getCommandIssuer(event.getEntity()).sendError(MVCorei18n.PORTALFORM_DISABLED_NETHER);
|
||||
}
|
||||
}).onEmpty(() ->
|
||||
Logging.fine("World '%s' is not managed by Multiverse! Ignoring at PortalCreateEvent.",
|
||||
@@ -108,7 +101,6 @@ final class MVPortalListener implements CoreListener {
|
||||
if (!world.getPortalForm().isPortalAllowed(PortalType.ENDER)) {
|
||||
Logging.fine("Cancelling creation of ENDER portal because portalForm disallows.");
|
||||
event.setCancelled(true);
|
||||
commandManager.getCommandIssuer(event.getPlayer()).sendError(MVCorei18n.PORTALFORM_DISABLED_END);
|
||||
}
|
||||
}).onEmpty(() ->
|
||||
Logging.fine("World '%s' is not managed by Multiverse! Ignoring at PlayerInteractEvent.",
|
||||
|
||||
@@ -91,7 +91,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
|
||||
// /mv dumps
|
||||
DUMPS_DESCRIPTION,
|
||||
DUMPS_STARTING,
|
||||
DUMPS_URL_LIST,
|
||||
|
||||
// /mv gamerule set
|
||||
@@ -147,19 +146,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
LOAD_LOADING,
|
||||
LOAD_SUCCESS,
|
||||
|
||||
// /mv meta info
|
||||
META_INFO_DESCRIPTION,
|
||||
META_INFO_WORLD,
|
||||
META_INFO_HEADER,
|
||||
META_INFO_NOCONTENT,
|
||||
|
||||
// /mv meta modify
|
||||
META_MODIFY_SET_SUCCESS,
|
||||
META_MODIFY_SET_FAILURE,
|
||||
META_MODIFY_REMOVE_SUCCESS,
|
||||
META_MODIFY_REMOVE_FAILURE,
|
||||
META_MODIFY_INVALIDACTION,
|
||||
|
||||
// /mv modify
|
||||
MODIFY_DESCRIPTION,
|
||||
MODIFY_WORLD_DESCRIPTION,
|
||||
@@ -264,8 +250,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
WORLDBORDER_WARNINGTIME_NOTHINGCHANGED,
|
||||
WORLDBORDER_WARNINGTIME_SUCCESS,
|
||||
|
||||
WORLDBORDER_ROUNDOFF_WARNING,
|
||||
|
||||
// commands error
|
||||
COMMANDS_ERROR_PLAYERSONLY,
|
||||
COMMANDS_ERROR_MULTIVERSEWORLDONLY,
|
||||
@@ -294,10 +278,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
DESTINATION_SHARED_FAILUREREASON_WORLDNOTFOUND,
|
||||
DESTINATION_PARSE_FAILUREREASON_INVALIDDESTINATIONID,
|
||||
|
||||
// portal form disabled
|
||||
PORTALFORM_DISABLED_END,
|
||||
PORTALFORM_DISABLED_NETHER,
|
||||
|
||||
// teleport failure reason
|
||||
TELEPORTFAILUREREASON_NULL_DESTINATION,
|
||||
TELEPORTFAILUREREASON_NULL_LOCATION,
|
||||
@@ -312,7 +292,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
CLONEWORLD_WORLDEXISTFOLDER,
|
||||
CLONEWORLD_WORLDEXISTUNLOADED,
|
||||
CLONEWORLD_WORLDEXISTLOADED,
|
||||
CLONEWORLD_FROMWORLDFOLDERINVALID,
|
||||
CLONEWORLD_COPYFAILED,
|
||||
|
||||
CREATEWORLD_INVALIDWORLDNAME,
|
||||
@@ -330,14 +309,12 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
IMPORTWORLD_WORLDEXISTLOADED,
|
||||
IMPORTWORLD_WORLDFOLDERINVALID,
|
||||
IMPORTWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
IMPORTWORLD_BUKKITNAMESPACEDKEYMISMATCH,
|
||||
|
||||
LOADWORLD_WORLDALREADYLOADING,
|
||||
LOADWORLD_WORLDNONEXISTENT,
|
||||
LOADWORLD_WORLDEXISTFOLDER,
|
||||
LOADWORLD_WORLDEXISTLOADED,
|
||||
LOADWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
LOADWORLD_BUKKITNAMESPACEDKEYMISMATCH,
|
||||
|
||||
REMOVEWORLD_WORLDNONEXISTENT,
|
||||
|
||||
@@ -374,12 +351,6 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
|
||||
EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
|
||||
|
||||
// world key parse failures
|
||||
WORLDKEYPARSE_EMPTY,
|
||||
WORLDKEYPARSE_INVALIDWORLDNAME,
|
||||
WORLDKEYPARSE_INVALIDNAMESPACEDKEY,
|
||||
WORLDKEYPARSE_NAMESPACEDKEYUNSUPPORTED,
|
||||
|
||||
// generic
|
||||
GENERIC_SUCCESS,
|
||||
GENERIC_FAILURE,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.locale.message;
|
||||
|
||||
import io.vavr.control.Either;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -108,7 +107,6 @@ public final class MessageReplacement {
|
||||
GAMERULE(replace("{gamerule}")),
|
||||
LOCATION(replace("{location}")),
|
||||
NAME(replace("{name}")),
|
||||
NAMESPACE(replace("{namespace}")),
|
||||
PLAYER(replace("{player}")),
|
||||
REASON(replace("{reason}")),
|
||||
VALUE(replace("{value}")),
|
||||
|
||||
@@ -147,10 +147,9 @@ public abstract class MultiverseModule extends JavaPlugin {
|
||||
Try.of(() -> commandManagerProvider.get())
|
||||
.mapTry(MVCommandManager::getLocales)
|
||||
.andThen(pluginLocales -> {
|
||||
pluginLocales.addBundleClassLoader(this.getClassLoader());
|
||||
pluginLocales.addFileResClassLoader(this);
|
||||
pluginLocales.addBundleClassLoader(this.getClassLoader());
|
||||
pluginLocales.addMessageBundles(this.getName().toLowerCase(Locale.ENGLISH));
|
||||
pluginLocales.loadLanguages();
|
||||
})
|
||||
.onFailure(e -> {
|
||||
Logging.severe("Failed to register locales");
|
||||
|
||||
+8
-5
@@ -238,12 +238,15 @@ public final class AsyncSafetyTeleporterAction {
|
||||
@NotNull Location location,
|
||||
@NotNull List<Entity> passengers
|
||||
) {
|
||||
List<Entity> toTeleport = new ArrayList<>(passengers);
|
||||
toTeleport.addFirst(teleportee);
|
||||
List<Entity> toTeleport = new ArrayList<>(passengers.size() + 1);
|
||||
toTeleport.add(teleportee);
|
||||
toTeleport.addAll(passengers);
|
||||
|
||||
return AsyncAttemptsAggregate.allOfAggregate(toTeleport.stream()
|
||||
.map(passenger -> doAsyncTeleport(passenger, location))
|
||||
.toList())
|
||||
return AsyncAttemptsAggregate.allOfAggregate(
|
||||
toTeleport.stream()
|
||||
.map(passenger -> doAsyncTeleport(passenger, location))
|
||||
.toList()
|
||||
)
|
||||
.onSuccess(() -> Bukkit.getScheduler().runTask(multiverseCore, () -> {
|
||||
passengers.forEach(teleportee::addPassenger);
|
||||
Logging.finer("Mounted %d passengers to %s", passengers.size(), teleportee.getName());
|
||||
|
||||
@@ -132,10 +132,6 @@ public final class BlockSafety {
|
||||
*/
|
||||
public boolean canSpawnAtBlockSafely(@NotNull Block block) {
|
||||
Logging.finest("Checking spawn safety for location: %s, %s, %s", block.getX(), block.getY(), block.getZ());
|
||||
if (!block.getWorld().getWorldBorder().isInside(block.getLocation())) {
|
||||
Logging.finest("Location is outside world border.");
|
||||
return false;
|
||||
}
|
||||
if (isUnsafeSpawnBody(block)) {
|
||||
// Player body will be stuck in solid
|
||||
Logging.finest("Unsafe location for player's body: " + block);
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A map with case-insensitive String keys. All keys are stored in lower-case form.
|
||||
*
|
||||
* @param <T> the type of mapped values
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public class CaseInsensitiveStringMap<T> implements Map<String, T> {
|
||||
|
||||
private final Map<String, T> map;
|
||||
|
||||
public CaseInsensitiveStringMap() {
|
||||
map = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return map.containsKey(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(Object key) {
|
||||
return map.get(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T put(String key, T value) {
|
||||
return map.put(normalizeKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(Object key) {
|
||||
return map.remove(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends String, ? extends T> m) {
|
||||
m.forEach((key, value) -> map.put(normalizeKey(key), value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<String> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<T> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Entry<String, T>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
private String normalizeKey(Object key) {
|
||||
return String.valueOf(key).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -211,31 +210,28 @@ public final class FileUtils {
|
||||
|
||||
private final Path sourceDir;
|
||||
private final Path targetDir;
|
||||
private final List<Path> excludeFiles;
|
||||
private final List<String> excludeFiles;
|
||||
|
||||
private CopyDirFileVisitor(@NotNull Path sourceDir, @NotNull Path targetDir, @NotNull List<String> excludeFiles) {
|
||||
this.sourceDir = sourceDir;
|
||||
this.targetDir = targetDir;
|
||||
this.excludeFiles = excludeFiles.stream()
|
||||
.map(sourceDir::resolve)
|
||||
.toList();
|
||||
Logging.finest(this.excludeFiles.stream().map(Path::toString).collect(Collectors.joining(", ", "Exclude files: [", "]")));
|
||||
this.excludeFiles = excludeFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult preVisitDirectory(@NotNull Path dir, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
public @NotNull FileVisitResult preVisitDirectory(Path dir, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
Path newDir = targetDir.resolve(sourceDir.relativize(dir));
|
||||
if (!Files.isDirectory(newDir)) {
|
||||
Files.createDirectories(newDir);
|
||||
Files.createDirectory(newDir);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
public @NotNull FileVisitResult visitFile(Path file, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
// Pass files that are set to ignore
|
||||
if (excludeFiles.contains(file)) {
|
||||
Logging.finest("Ignoring file: " + file);
|
||||
if (excludeFiles.contains(file.getFileName().toString())) {
|
||||
Logging.finest("Ignoring file: " + file.getFileName());
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
// Copy the files
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.utils.tick.TickDuration;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -15,6 +15,10 @@ import java.time.format.DateTimeFormatter;
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public final class MinecraftTimeFormatter {
|
||||
|
||||
private static final double TIME_MULTIPLIER = 3.6;
|
||||
private static final long DAY_SECONDS = 24 * 60 * 60;
|
||||
private static final long START_OFFSET = 6 * 60 * 60;
|
||||
|
||||
/**
|
||||
* Formats Minecraft time to 12-hour format.
|
||||
*
|
||||
@@ -44,14 +48,19 @@ public final class MinecraftTimeFormatter {
|
||||
* See <a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter documentation</a>
|
||||
* for available patterns.
|
||||
*
|
||||
* @param ticks The Minecraft time to format.
|
||||
* @param format The format string for the time.
|
||||
* @param time The Minecraft time to format.
|
||||
* @param format The format string for the time.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String formatTime(long ticks, String format) {
|
||||
LocalTime localTime = TickDuration.ofTicks(ticks).toLocalTime();
|
||||
public static String formatTime(long time, String format) {
|
||||
// Convert Minecraft time to real-world time
|
||||
long realTime = (long) ((time * TIME_MULTIPLIER) + START_OFFSET) % DAY_SECONDS; // Minecraft ticks to seconds
|
||||
|
||||
// Convert seconds to LocalTime
|
||||
LocalTime localTime = LocalTime.ofSecondOfDay(realTime);
|
||||
|
||||
return Try.of(() -> DateTimeFormatter.ofPattern(format))
|
||||
.map(localTime::format)
|
||||
.getOrElse("invalid time format: " + format);
|
||||
|
||||
@@ -3,8 +3,6 @@ package org.mvplugins.multiverse.core.utils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -13,182 +11,12 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public final class ReflectHelper {
|
||||
|
||||
/**
|
||||
* Try to get the {@link Class} based on its classpath.
|
||||
*
|
||||
* @param classPath The target classpath.
|
||||
* @return A {@link Try} containing the {@link Class} if found, else a failure.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static Try<Class<?>> tryGetClass(@NotNull String classPath) {
|
||||
return Try.of(() -> Class.forName(classPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the {@link Class} for a give classpath is present/valid.
|
||||
*
|
||||
* @param classPath Target classpath.
|
||||
* @return True if class path is a valid class, else false.
|
||||
*/
|
||||
public static boolean hasClass(String classPath) {
|
||||
return tryGetClass(classPath).isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a {@link Method} from a given class.
|
||||
*
|
||||
* @param clazz The class to search the method on.
|
||||
* @param methodName Name of the method to get.
|
||||
* @param parameterTypes Parameters present for that method.
|
||||
* @param <C> The class type.
|
||||
* @return A {@link Try} containing the {@link Method} if found, else a failure.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static <C> Try<Method> tryGetMethod(@NotNull Class<C> clazz, @NotNull String methodName, Class<?>... parameterTypes) {
|
||||
return Try.of(() -> clazz.getMethod(methodName, parameterTypes))
|
||||
.orElse(Try.of(() -> {
|
||||
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a {@link Method} exists on a given class.
|
||||
*
|
||||
* @param clazz The class to search the method on.
|
||||
* @param methodName Name of the method to check.
|
||||
* @param parameterTypes Parameters present for that method.
|
||||
* @return True if method exists, else false.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean hasMethod(@NotNull Class<?> clazz, @NotNull String methodName, Class<?>... parameterTypes) {
|
||||
return tryGetMethod(clazz, methodName, parameterTypes).isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to invoke a {@link Method} on a class instance.
|
||||
*
|
||||
* @param classInstance Instance of the class responsible for the method.
|
||||
* @param method The method to invoke.
|
||||
* @param parameters Parameters needed when invoking the method.
|
||||
* @param <C> The class type.
|
||||
* @param <R> The return type of the method.
|
||||
* @return A {@link Try} containing the return value of the method if found, else a failure.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <C, R> Try<R> tryInvokeMethod(@NotNull C classInstance, @NotNull Method method, Object...parameters) {
|
||||
return Try.of(() -> (R) method.invoke(classInstance, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to invoke a static {@link Method}.
|
||||
*
|
||||
* @param method The static method to invoke.
|
||||
* @param parameters Parameters needed when invoking the method.
|
||||
* @param <R> The return type of the method.
|
||||
* @return A {@link Try} containing the return value of the method if found, else a failure.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <R> Try<R> tryInvokeStaticMethod(@NotNull Method method, Object...parameters) {
|
||||
return Try.of(() -> (R) method.invoke(null, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a {@link Field} from a given class.
|
||||
*
|
||||
* @param clazz The class to search the field on.
|
||||
* @param fieldName Name of the field to get.
|
||||
* @param <C> The class type.
|
||||
* @return A {@link Try} containing the {@link Field} if found, else a failure.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static <C> Try<Field> tryGetField(@NotNull Class<C> clazz, @NotNull String fieldName) {
|
||||
return Try.of(() -> clazz.getField(fieldName))
|
||||
.orElse(Try.of(() -> {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a {@link Field} exists on a given class.
|
||||
*
|
||||
* @param clazz The class to search the field on.
|
||||
* @param fieldName Name of the field to check.
|
||||
* @return True if field exists, else false.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean hasField(@NotNull Class<?> clazz, @NotNull String fieldName) {
|
||||
return tryGetField(clazz, fieldName).isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the value of a {@link Field} from an instance of the class responsible.
|
||||
*
|
||||
* @param classInstance Instance of the class to get the field value from.
|
||||
* @param field The field to get the value from.
|
||||
* @param fieldType Type of the field.
|
||||
* @param <C> The class type.
|
||||
* @param <V> The field value type.
|
||||
* @return A {@link Try} containing the field value if found, else a failure.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static <C, V> Try<V> tryGetFieldValue(@NotNull C classInstance, @NotNull Field field, @NotNull Class<V> fieldType) {
|
||||
return Try.of(() -> fieldType.cast(field.get(classInstance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the value of a static {@link Field}.
|
||||
*
|
||||
* @param field The static field to get the value from.
|
||||
* @param fieldType Type of the field.
|
||||
* @param <V> The field value type.
|
||||
* @return A {@link Try} containing the field value if found, else a failure.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static <V> Try<V> tryGetStaticFieldValue(@NotNull Field field, @NotNull Class<V> fieldType) {
|
||||
return Try.of(() -> fieldType.cast(field.get(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the {@link Class} based on its classpath.
|
||||
*
|
||||
* @param classPath The target classpath.
|
||||
* @return A {@link Class} if found, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryGetClass(String)} instead, which returns a {@link Try} that can be used to handle
|
||||
* the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
public static Class<?> getClass(String classPath) {
|
||||
try {
|
||||
@@ -198,6 +26,16 @@ public final class ReflectHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the {@link Class} for a give classpath is present/valid.
|
||||
*
|
||||
* @param classPath Target classpath.
|
||||
* @return True if class path is a valid class, else false.
|
||||
*/
|
||||
public static boolean hasClass(String classPath) {
|
||||
return getClass(classPath) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a {@link Method} from a given class.
|
||||
*
|
||||
@@ -206,11 +44,7 @@ public final class ReflectHelper {
|
||||
* @param parameterTypes Parameters present for that method.
|
||||
* @param <C> The class type.
|
||||
* @return A {@link Method} if found, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryGetMethod(Class, String, Class[])} instead, which returns a {@link Try} that can be
|
||||
* used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
public static <C> Method getMethod(Class<C> clazz, String methodName, Class<?>... parameterTypes) {
|
||||
try {
|
||||
@@ -230,11 +64,7 @@ public final class ReflectHelper {
|
||||
* @param parameterTypes Parameters present for that method.
|
||||
* @param <C> The class type.
|
||||
* @return A {@link Method} if found, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryGetMethod(Class, String, Class[])} instead, which returns a {@link Try} that can be
|
||||
* used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
public static <C> Method getMethod(C classInstance, String methodName, Class<?>... parameterTypes) {
|
||||
return getMethod(classInstance.getClass(), methodName, parameterTypes);
|
||||
@@ -249,13 +79,8 @@ public final class ReflectHelper {
|
||||
* @param <C> The class type.
|
||||
* @param <R> The return type.
|
||||
* @return Return value of the method call if any, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryInvokeMethod(Object, Method, Object...)} instead, which returns a {@link Try} that can
|
||||
* be used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <C, R> R invokeMethod(C classInstance, Method method, Object...parameters) {
|
||||
try {
|
||||
return (R) method.invoke(classInstance, parameters);
|
||||
@@ -271,11 +96,7 @@ public final class ReflectHelper {
|
||||
* @param fieldName Name of the field to get.
|
||||
* @param <C> The class type.
|
||||
* @return A {@link Field} if found, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryGetField(Class, String)} instead, which returns a {@link Try} that can be used to
|
||||
* handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
public static <C> Field getField(Class<C> clazz, String fieldName) {
|
||||
try {
|
||||
@@ -294,11 +115,7 @@ public final class ReflectHelper {
|
||||
* @param fieldName Name of the field to get.
|
||||
* @param <C> The class type.
|
||||
* @return A {@link Field} if found, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryGetField(Class, String)} instead, which returns a {@link Try} that can be used to
|
||||
* handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
public static <C> Field getField(C classInstance, String fieldName) {
|
||||
return getField(classInstance.getClass(), fieldName);
|
||||
@@ -313,11 +130,7 @@ public final class ReflectHelper {
|
||||
* @param <C> The class type.
|
||||
* @param <V> The field value type.
|
||||
* @return The field value if any, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryGetFieldValue(Object, Field, Class)} instead, which returns a {@link Try} that can be
|
||||
* used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
public static <C, V> V getFieldValue(C classInstance, @Nullable Field field, @NotNull Class<V> fieldType) {
|
||||
try {
|
||||
@@ -340,11 +153,7 @@ public final class ReflectHelper {
|
||||
* @param <C> The class type.
|
||||
* @param <V> The field value type.
|
||||
* @return The field value if any, else null.
|
||||
*
|
||||
* @deprecated Use {@link #tryGetField(Class, String)} then map to {@link #tryGetFieldValue(Object, Field, Class)} instead,
|
||||
* which returns a {@link Try} that can be used to handle the failure case more explicitly.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@Nullable
|
||||
public static <C, V> V getFieldValue(C classInstance, @Nullable String fieldName, @NotNull Class<V> fieldType) {
|
||||
return getFieldValue(classInstance, getField(classInstance, fieldName), fieldType);
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.mvplugins.multiverse.core.utils;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -15,21 +14,6 @@ import java.util.Map;
|
||||
@Service
|
||||
public final class ServerProperties {
|
||||
|
||||
private static ServerProperties instance;
|
||||
|
||||
/**
|
||||
* A very bad static way of obtaining the level name from server.properties of older mc versions that
|
||||
* don't have any way of getting main level name.
|
||||
* <br />
|
||||
* For internal use ONLY!
|
||||
*
|
||||
* @return The level name if it can be obtained, else none.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public static Option<String> getStaticLevelName() {
|
||||
return Option.of(instance).flatMap(ServerProperties::getLevelName);
|
||||
}
|
||||
|
||||
private final Map<String, String> properties;
|
||||
private final FileUtils fileUtils;
|
||||
|
||||
@@ -38,8 +22,6 @@ public final class ServerProperties {
|
||||
this.fileUtils = fileUtils;
|
||||
properties = new HashMap<>();
|
||||
parseServerPropertiesFile();
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
private void parseServerPropertiesFile() {
|
||||
|
||||
@@ -2,17 +2,13 @@ package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -34,15 +30,15 @@ public final class StringFormatter {
|
||||
* @param list the list of strings to join. If the list is empty, an empty string is returned.
|
||||
* @return the concatenated string
|
||||
*/
|
||||
public static @NotNull String joinAnd(@Nullable List<String> list) {
|
||||
public static @NotNull String joinAnd(List<String> list) {
|
||||
return join(list, ", ", " and ");
|
||||
}
|
||||
|
||||
public static @NotNull String join(@Nullable Collection<?> list, @NotNull String separator) {
|
||||
public static @NotNull String join(Collection list, String separator) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return list.stream().map(String::valueOf).collect(Collectors.joining(separator));
|
||||
return list.stream().map(String::valueOf).collect(Collectors.joining(separator)).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +50,7 @@ public final class StringFormatter {
|
||||
* @param lastSeparator the separator to use before the last element. For example, " and ".
|
||||
* @return the concatenated string
|
||||
*/
|
||||
public static @NotNull String join(@Nullable List<String> list, @NotNull String separator, @NotNull String lastSeparator) {
|
||||
public static @NotNull String join(List<String> list, String separator, String lastSeparator) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
@@ -75,30 +71,11 @@ public final class StringFormatter {
|
||||
|
||||
/**
|
||||
* Appends a list of suggestions to the end of the input string, separated by commas.
|
||||
*
|
||||
* @param input The current input
|
||||
* @param addons The autocomplete suggestions
|
||||
* @return A collection of suggestions with the next suggestion appended
|
||||
*
|
||||
* @deprecated Method name has a spelling error. Use {@link #addOnToCommaSeparated(String, Collection)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.5")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public static Collection<String> addonToCommaSeperated(@Nullable String input, @NotNull Collection<String> addons) {
|
||||
return addOnToCommaSeparated(input, addons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a list of suggestions to the end of the input string, separated by commas.
|
||||
*
|
||||
* @param input The current input
|
||||
* @param addons The autocomplete suggestions
|
||||
* @return A collection of suggestions with the next suggestion appended
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public static Collection<String> addOnToCommaSeparated(@Nullable String input, @NotNull Collection<String> addons) {
|
||||
if (Strings.isNullOrEmpty(input)) {
|
||||
return addons;
|
||||
}
|
||||
@@ -117,7 +94,7 @@ public final class StringFormatter {
|
||||
* @param args The args to parse
|
||||
* @return The parsed args
|
||||
*/
|
||||
public static @NotNull Collection<String> parseQuotesInArgs(@NotNull String[] args) {
|
||||
public static Collection<String> parseQuotesInArgs(String[] args) {
|
||||
List<String> result = new ArrayList<>(args.length);
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
@@ -162,28 +139,7 @@ public final class StringFormatter {
|
||||
* @param input The string to add quotes to
|
||||
* @return The quoted string
|
||||
*/
|
||||
@Contract("null -> null")
|
||||
public static @Nullable String quoteMultiWordString(@Nullable String input) {
|
||||
return input != null && input.contains(" ") ? "\"" + input + "\"" : input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a CSV string of key=value pairs into a map.
|
||||
* E.g. "key1=value1,key2=value2" -> {key1=value1, key2=value2}
|
||||
*
|
||||
* @param input The CSV string to parse
|
||||
* @return The parsed map
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public static @Unmodifiable Map<String, String> parseCSVMap(@Nullable String input) {
|
||||
if (Strings.isNullOrEmpty(input)) {
|
||||
return Map.of();
|
||||
}
|
||||
return REPatterns.COMMA.splitAsStream(input)
|
||||
.map(s -> REPatterns.EQUALS.split(s, 2))
|
||||
.filter(parts -> parts.length == 2)
|
||||
.collect(Collectors.toUnmodifiableMap(parts -> parts[0], parts -> parts[1]));
|
||||
public static String quoteMultiWordString(String input) {
|
||||
return input.contains(" ") ? "\"" + input + "\"" : input;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
@@ -10,6 +10,8 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Defers action that cannot be done during world tick.
|
||||
@@ -19,22 +21,26 @@ public final class WorldTickDeferrer {
|
||||
|
||||
private final MultiverseCore plugin;
|
||||
|
||||
private final Option<Object> console;
|
||||
private final Option<Field> isIteratingOverLevelsMethod;
|
||||
private Object console = null;
|
||||
private Field isIteratingOverLevelsMethod = null;
|
||||
|
||||
@Inject
|
||||
WorldTickDeferrer(@NotNull MultiverseCore plugin, @NotNull Server server) {
|
||||
this.plugin = plugin;
|
||||
this.console = ReflectHelper.tryGetMethod(server.getClass(), "getServer")
|
||||
.onFailure(throwable -> Logging.fine("Unable to find getServer method."))
|
||||
.flatMap(getServerMethod -> ReflectHelper.tryInvokeMethod(server, getServerMethod))
|
||||
.onFailure(throwable -> Logging.fine("Unable to find console."))
|
||||
.toOption();
|
||||
this.isIteratingOverLevelsMethod = console.toTry()
|
||||
.map(Object::getClass)
|
||||
.flatMap(consoleClazz -> ReflectHelper.tryGetField(consoleClazz, "isIteratingOverLevels"))
|
||||
.onFailure(throwable -> Logging.fine("Unable to find isIteratingOverLevels field."))
|
||||
.toOption();
|
||||
Method getServerMethod = ReflectHelper.getMethod(server.getClass(), "getServer");
|
||||
if (getServerMethod == null) {
|
||||
Logging.fine("Unable to find getServer method.");
|
||||
return;
|
||||
}
|
||||
this.console = ReflectHelper.invokeMethod(server, getServerMethod);
|
||||
if (console == null) {
|
||||
Logging.fine("Unable to find console.");
|
||||
return;
|
||||
}
|
||||
this.isIteratingOverLevelsMethod = Try.of(() -> console.getClass().getField("isIteratingOverLevels")).getOrNull();
|
||||
if (isIteratingOverLevelsMethod == null) {
|
||||
Logging.fine("Unable to find isIteratingOverLevels field.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,14 +63,16 @@ public final class WorldTickDeferrer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the server is currently doing a world tick.
|
||||
* Check if the the server is currently doing a world tick.
|
||||
*
|
||||
* @return True if the server is currently doing a world tick
|
||||
*/
|
||||
private boolean isIteratingOverLevels() {
|
||||
return isIteratingOverLevelsMethod
|
||||
.flatMap(field -> console
|
||||
.flatMap(c -> ReflectHelper.tryGetFieldValue(c, field, Boolean.class).toOption()))
|
||||
.getOrElse(false);
|
||||
if (console == null || isIteratingOverLevelsMethod == null) {
|
||||
return false;
|
||||
}
|
||||
return Objects.requireNonNullElse(
|
||||
ReflectHelper.getFieldValue(console, isIteratingOverLevelsMethod, Boolean.class),
|
||||
false);
|
||||
}
|
||||
}
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle API changes in {@link Bukkit} class.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public final class BukkitCompatibility {
|
||||
|
||||
private static final Try<Method> GET_LEVEL_DIRECTORY_METHOD;
|
||||
private static final Try<Method> GET_WORLD_NAMESPACED_KEY_METHOD;
|
||||
|
||||
static {
|
||||
GET_LEVEL_DIRECTORY_METHOD = ReflectHelper.tryGetMethod(Server.class, "getLevelDirectory");
|
||||
GET_WORLD_NAMESPACED_KEY_METHOD = ReflectHelper.tryGetMethod(Bukkit.class, "getWorld", NamespacedKey.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the server is using the new dimension storage system introduced in PaperMC 26.1.
|
||||
* <br />
|
||||
* This is based of whether getLevelDirectory method exists in the server class,which is the main API change for
|
||||
* the new dimension storage system.
|
||||
*
|
||||
* @return True if the server is using the new dimension storage system, else false.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public static boolean isUsingNewDimensionStorage() {
|
||||
return GET_LEVEL_DIRECTORY_METHOD
|
||||
.flatMap(method -> ReflectHelper.tryInvokeMethod(Bukkit.getServer(), method))
|
||||
.isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder where all the worlds will be store. Before 26.1, all worlds are stored in the root directory
|
||||
* of the server, which can be obtained by {@link Server#getWorldContainer()}.
|
||||
* <br />
|
||||
* After 26.1, PaperMC changed all worlds are stored in the "[level]/dimensions" folder under the world
|
||||
* level directory, which needs to be manually parsed.
|
||||
*
|
||||
* @return The location where all the worlds folders should be, depending on server's mc version.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
@NotNull
|
||||
public static Path getWorldFoldersDirectory() {
|
||||
Server server = Bukkit.getServer();
|
||||
return GET_LEVEL_DIRECTORY_METHOD.flatMap(method -> ReflectHelper.tryInvokeMethod(server, method))
|
||||
.filter(Path.class::isInstance)
|
||||
.map(Path.class::cast)
|
||||
.map(path -> path.resolve("dimensions"))
|
||||
.getOrElse(() -> server.getWorldContainer().toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the world with the given name or namespaced key (e.g. minecraft:overworld) exists,
|
||||
* and return it if it does.
|
||||
* <br />
|
||||
* Note that some default world names have different namespaced key matched with them.
|
||||
* E.g.: world -> minecraft:overworld, world_nether -> minecraft:the_nether, world_the_end -> minecraft:the_end.
|
||||
*
|
||||
* @param nameOrKey Either a name or namespaced key string representation.
|
||||
* @return The world if it exists
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
@NotNull
|
||||
public static Option<World> getWorldByNameOrKey(@NotNull String nameOrKey) {
|
||||
return Option.of(Bukkit.getWorld(nameOrKey))
|
||||
.orElse(() -> GET_WORLD_NAMESPACED_KEY_METHOD
|
||||
.flatMap(method -> ReflectHelper.tryInvokeStaticMethod(method, NamespacedKey.fromString(nameOrKey)))
|
||||
.filter(World.class::isInstance)
|
||||
.map(World.class::cast)
|
||||
.toOption());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the world represented by the given {@link WorldKeyOrName} exists and return it if present.
|
||||
*
|
||||
* <p>This first attempts to resolve the world by name using {@link WorldKeyOrName#usableName()} (which
|
||||
* delegates to the traditional Bukkit world name lookup). If that fails it will attempt to resolve a
|
||||
* {@link NamespacedKey} using {@link WorldKeyOrName#usableKey()} (which mirrors the newer Bukkit API
|
||||
* that accepts namespaced keys, e.g. "minecraft:overworld").
|
||||
*
|
||||
* @param keyOrName The key-or-name wrapper providing either a world name or a {@link NamespacedKey}.
|
||||
* @return The world if it exists.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static Option<World> getWorldByNameOrKey(@NotNull WorldKeyOrName keyOrName) {
|
||||
return Option.of(Bukkit.getWorld(keyOrName.usableName()))
|
||||
.orElse(() -> GET_WORLD_NAMESPACED_KEY_METHOD
|
||||
.flatMap(method -> ReflectHelper.tryInvokeStaticMethod(method, keyOrName.usableKey()))
|
||||
.filter(World.class::isInstance)
|
||||
.map(World.class::cast)
|
||||
.toOption());
|
||||
}
|
||||
|
||||
private BukkitCompatibility() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.UnsafeValues;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle API changes in {@link UnsafeValues} class.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class UnsafeValuesCompatibility {
|
||||
|
||||
private static final Try<Method> GET_MAIN_LEVEL_NAME_METHOD;
|
||||
|
||||
static {
|
||||
GET_MAIN_LEVEL_NAME_METHOD = ReflectHelper.tryGetMethod(UnsafeValues.class, "getMainLevelName");
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to call the getMainLevelName method in UnsafeValues, which is used to get level-name configured in
|
||||
* server.properties file.
|
||||
*
|
||||
* @return The level-name configured in server.properties file, or empty if the method is not available.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static Option<String> getMainLevelName() {
|
||||
return GET_MAIN_LEVEL_NAME_METHOD
|
||||
.flatMap(method -> ReflectHelper.tryInvokeMethod(Bukkit.getUnsafe(), method))
|
||||
.map(String.class::cast)
|
||||
.toOption();
|
||||
}
|
||||
|
||||
private UnsafeValuesCompatibility() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import org.bukkit.WorldBorder;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
import org.mvplugins.multiverse.core.utils.tick.TickDuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle API changes in {@link WorldBorder} class.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@SuppressWarnings("removal")
|
||||
public final class WorldBorderCompatibility {
|
||||
|
||||
private static final boolean HAS_GET_WARNING_TIME_TICKS_METHOD;
|
||||
private static final boolean HAS_SET_WARNING_TIME_TICKS_METHOD;
|
||||
private static final boolean HAS_SET_WARNING_TIME_METHOD;
|
||||
private static final boolean HAS_GET_WARNING_TIME_METHOD;
|
||||
|
||||
private static final boolean HAS_CHANGE_SIZE_METHOD;
|
||||
private static final boolean HAS_SET_SIZE_METHOD;
|
||||
|
||||
static {
|
||||
HAS_GET_WARNING_TIME_TICKS_METHOD = ReflectHelper.hasMethod(WorldBorder.class, "getWarningTimeTicks");
|
||||
HAS_SET_WARNING_TIME_TICKS_METHOD = ReflectHelper.hasMethod(WorldBorder.class, "setWarningTimeTicks", int.class);
|
||||
HAS_GET_WARNING_TIME_METHOD = ReflectHelper.hasMethod(WorldBorder.class, "getWarningTime");
|
||||
HAS_SET_WARNING_TIME_METHOD = ReflectHelper.hasMethod(WorldBorder.class, "setWarningTime", int.class);
|
||||
|
||||
HAS_CHANGE_SIZE_METHOD = ReflectHelper.hasMethod(WorldBorder.class, "changeSize", double.class, long.class);
|
||||
HAS_SET_SIZE_METHOD = ReflectHelper.hasMethod(WorldBorder.class, "setSize", double.class, long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world border warning time in ticks, using the most precise API available.
|
||||
* <br />
|
||||
* If the server exposes {@code getWarningTimeTicks()}, that value is returned directly. Otherwise, the older
|
||||
* {@code getWarningTime()} API is used and converted from seconds to ticks.
|
||||
*
|
||||
* @param worldBorder The world border to query.
|
||||
* @return The warning time in ticks.
|
||||
*
|
||||
* @since 5.7
|
||||
* @throws IllegalStateException If neither warning-time getter is available.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static int getWarningTimeTicks(WorldBorder worldBorder) {
|
||||
if (HAS_GET_WARNING_TIME_TICKS_METHOD) {
|
||||
return worldBorder.getWarningTimeTicks();
|
||||
} else if (HAS_GET_WARNING_TIME_METHOD) {
|
||||
return worldBorder.getWarningTime() * 20;
|
||||
}
|
||||
throw new IllegalStateException("Neither getWarningTimeTicks nor getWarningTime method is available in WorldBorder class.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world border warning time in seconds, using the most appropriate API available.
|
||||
* <br />
|
||||
* If the server exposes {@code getWarningTimeTicks()}, that value is converted from ticks to seconds. Otherwise,
|
||||
* the older {@code getWarningTime()} API is used directly.
|
||||
*
|
||||
* @param worldBorder The world border to query.
|
||||
* @return The warning time in seconds.
|
||||
*
|
||||
* @since 5.7
|
||||
* @throws IllegalStateException If neither warning-time getter is available.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static double getWarningTime(WorldBorder worldBorder) {
|
||||
if (HAS_GET_WARNING_TIME_TICKS_METHOD) {
|
||||
return (double) worldBorder.getWarningTimeTicks() / 20.0;
|
||||
} else if (HAS_GET_WARNING_TIME_METHOD) {
|
||||
return worldBorder.getWarningTime();
|
||||
}
|
||||
throw new IllegalStateException("Neither getWarningTimeTicks nor getWarningTime method is available in WorldBorder class.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current server supports setting the world border warning time in ticks.
|
||||
*
|
||||
* @return True if {@code setWarningTimeTicks(int)} is available, else false.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean supportsSetWarningTimeInTicks() {
|
||||
return HAS_SET_WARNING_TIME_TICKS_METHOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the world border warning time in ticks.
|
||||
* <br />
|
||||
* This is a convenience wrapper around {@link #setWarningTimeDuration(WorldBorder, TickDuration)} that converts
|
||||
* the given tick value into a {@link TickDuration} first.
|
||||
*
|
||||
* @param worldBorder The world border to update.
|
||||
* @param ticks The warning time in ticks.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
public static void setWarningTimeTicks(WorldBorder worldBorder, int ticks) {
|
||||
setWarningTimeDuration(worldBorder, TickDuration.ofTicks(ticks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the world border warning time, preferring tick-based APIs when available.
|
||||
* <br />
|
||||
* If the server supports {@code setWarningTimeTicks(int)}, the provided duration is applied in ticks. Otherwise,
|
||||
* the older {@code setWarningTime(int)} API is used and the duration is converted to seconds.
|
||||
*
|
||||
* @param worldBorder The world border to update.
|
||||
* @param duration The warning duration to apply.
|
||||
*
|
||||
* @since 5.7
|
||||
* @throws IllegalStateException If neither warning-time setter is available.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static void setWarningTimeDuration(WorldBorder worldBorder, TickDuration duration) {
|
||||
if (HAS_SET_WARNING_TIME_TICKS_METHOD) {
|
||||
worldBorder.setWarningTimeTicks((int) duration.toTicks());
|
||||
} else if (HAS_SET_WARNING_TIME_METHOD) {
|
||||
worldBorder.setWarningTime((int) Math.round(duration.to(TimeUnit.SECONDS)));
|
||||
} else {
|
||||
throw new IllegalStateException("Neither setWarningTimeTicks nor setWarningTime method is available in WorldBorder class.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current server supports changing world border size over a duration in ticks.
|
||||
*
|
||||
* @return True if {@code changeSize(double, long)} is available, else false.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean supportsChangeSizeInTicks() {
|
||||
return HAS_CHANGE_SIZE_METHOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the world border size, preferring the tick-based API when available.
|
||||
* <br />
|
||||
* If the server supports {@code changeSize(double, long)}, the size change is applied in ticks. Otherwise, the
|
||||
* older {@code setSize(double, long)} API is used and the duration is converted to seconds.
|
||||
*
|
||||
* @param worldBorder The world border to update.
|
||||
* @param newSize The new border size.
|
||||
* @param duration The transition duration to apply.
|
||||
*
|
||||
* @since 5.7
|
||||
* @throws IllegalStateException If neither size-changing API is available.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static void changeSizeDuration(WorldBorder worldBorder, double newSize, TickDuration duration) {
|
||||
if (HAS_CHANGE_SIZE_METHOD) {
|
||||
worldBorder.changeSize(newSize, duration.toTicks());
|
||||
} else if (HAS_SET_SIZE_METHOD) {
|
||||
worldBorder.setSize(newSize, Math.round(duration.to(TimeUnit.SECONDS)));
|
||||
} else {
|
||||
throw new IllegalStateException("Neither changeSize nor setSize method is available in WorldBorder class.");
|
||||
}
|
||||
}
|
||||
|
||||
private WorldBorderCompatibility() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import io.papermc.lib.PaperLib;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle API changes in {@link World} class.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class WorldCompatibility {
|
||||
|
||||
private static final Try<Method> SAVE_WITH_FLUSH_METHOD;
|
||||
private static final boolean HAS_GET_COORDINATE_SCALE_METHOD;
|
||||
private static final boolean HAS_HAS_BONUS_CHEST_METHOD;
|
||||
|
||||
static {
|
||||
SAVE_WITH_FLUSH_METHOD = ReflectHelper.tryGetMethod(World.class, "save", boolean.class);
|
||||
HAS_GET_COORDINATE_SCALE_METHOD = ReflectHelper.hasMethod(World.class, "getCoordinateScale");
|
||||
HAS_HAS_BONUS_CHEST_METHOD = ReflectHelper.hasMethod(World.class, "hasBonusChest");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the world, with an option to wait for chunk writers to finish if the method is available. Saving with
|
||||
* flush is only supported on PaperMC 1.21+.
|
||||
* <br />
|
||||
* If the method is not available, it will fall back to the normal save method, which may not wait for chunk writers
|
||||
* to finish, but is the best we can do for older versions of Minecraft.
|
||||
*
|
||||
* @param world The world to save
|
||||
* @param flush Waits for chunk writers to finish
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static void saveWithFlush(World world, boolean flush) {
|
||||
SAVE_WITH_FLUSH_METHOD
|
||||
.flatMap(method -> ReflectHelper.tryInvokeMethod(world, method, flush))
|
||||
.orElseRun(ignore -> world.save());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the coordinate scale for the world, which is used to convert between overworld and nether coordinates.
|
||||
* On PaperMC, this can be obtained directly from the API. On older versions, it is manually determined based on the
|
||||
* world environment (overworld = 1, nether = 8, end = 1).
|
||||
*
|
||||
* @param world The world to get the coordinate scale for
|
||||
* @return The scale
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static double getCoordinateScale(World world) {
|
||||
if (HAS_GET_COORDINATE_SCALE_METHOD) {
|
||||
return world.getCoordinateScale();
|
||||
}
|
||||
return switch (world.getEnvironment()) {
|
||||
case NORMAL -> 1;
|
||||
case NETHER -> 8;
|
||||
default -> 1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the world has a bonus chest.
|
||||
* <br />
|
||||
* On PaperMC 1.21.5+, this can be obtained directly from the API. On older versions, it always returns false as
|
||||
* bonus chest is not supported.
|
||||
*
|
||||
* @param world The world to check
|
||||
* @return True if the world has a bonus chest, false otherwise
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean hasBonusChest(World world) {
|
||||
if (HAS_HAS_BONUS_CHEST_METHOD) {
|
||||
return world.hasBonusChest();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private WorldCompatibility() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle {@link WorldCreator} API changes across different server versions.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class WorldCreatorCompatibility {
|
||||
|
||||
private static final Try<Class<?>> POSITION_CLASS;
|
||||
private static final Try<Method> FORCED_SPAWN_POSITION_METHOD;
|
||||
private static final Try<Method> OF_KEY_METHOD;
|
||||
private static final Try<Method> OF_NAME_AND_KEY_METHOD;
|
||||
private static final Try<Method> BONUS_CHEST_METHOD;
|
||||
|
||||
static {
|
||||
POSITION_CLASS = ReflectHelper.tryGetClass("io.papermc.paper.math.Position");
|
||||
FORCED_SPAWN_POSITION_METHOD = POSITION_CLASS.flatMap(positionClass ->
|
||||
ReflectHelper.tryGetMethod(WorldCreator.class, "forcedSpawnPosition", positionClass, float.class, float.class));
|
||||
BONUS_CHEST_METHOD = ReflectHelper.tryGetMethod(WorldCreator.class, "bonusChest", boolean.class);
|
||||
OF_KEY_METHOD = ReflectHelper.tryGetMethod(WorldCreator.class, "ofKey", NamespacedKey.class);
|
||||
OF_NAME_AND_KEY_METHOD = ReflectHelper.tryGetMethod(WorldCreator.class, "ofNameAndKey", String.class, NamespacedKey.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current server version supports creating worlds with a {@link NamespacedKey}.
|
||||
* <br />
|
||||
* This is based on whether the {@code ofKey(NamespacedKey)} method exists in the {@link WorldCreator} class,
|
||||
* which was introduced in later server versions.
|
||||
*
|
||||
* @return True if the server supports world creation with NamespacedKey, else false.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean canCreateWorldWithKey() {
|
||||
return OF_KEY_METHOD.isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link WorldCreator} from a {@link WorldKeyOrName}, using the most appropriate method
|
||||
* based on the server version and the key/name state.
|
||||
* <br />
|
||||
* If the server supports NamespacedKey-based creation and the provided keyOrName is a key,
|
||||
* this method will use {@code ofKey()}. Otherwise, it falls back to {@code name()}.
|
||||
*
|
||||
* @param keyOrName Either a world key or name to create from.
|
||||
* @return The {@link WorldCreator} initialized for the given key or name.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static WorldCreator ofKeyOrName(@NotNull WorldKeyOrName keyOrName) {
|
||||
return ofNameAndKey(keyOrName.usableKey(), keyOrName.usableName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link WorldCreator} from a {@link NamespacedKey} and world name, using the most
|
||||
* appropriate method based on the server version and dimension storage configuration.
|
||||
* <br />
|
||||
* This method attempts to use the most feature-rich creation method available:
|
||||
* if {@code ofNameAndKey()} is available and the parameters are compatible, use it;
|
||||
* otherwise, if {@code ofKey()} is available, use it; as a fallback, use {@code name()}.
|
||||
*
|
||||
* @param worldKey The namespaced key for the world.
|
||||
* @param worldName The display name for the world.
|
||||
* @return The {@link WorldCreator} initialized with the given key and name.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static WorldCreator ofNameAndKey(@NotNull NamespacedKey worldKey, String worldName) {
|
||||
if (OF_NAME_AND_KEY_METHOD.isSuccess() && canPassIntoNameAndKey(worldKey)) {
|
||||
return WorldCreator.ofNameAndKey(worldName, worldKey);
|
||||
}
|
||||
if (OF_KEY_METHOD.isSuccess()) {
|
||||
return WorldCreator.ofKey(worldKey);
|
||||
}
|
||||
return WorldCreator.name(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given world key and name can be passed into the {@code ofNameAndKey()} method.
|
||||
* <br />
|
||||
* The parameters are compatible only if the server is not using new dimension storage,
|
||||
* or if the namespace is the Minecraft namespace and the world name (lowercased) matches the key.
|
||||
*
|
||||
* @param worldKey The namespaced key for the world.
|
||||
* @return True if the parameters can be safely passed to {@code ofNameAndKey()}, else false.
|
||||
*/
|
||||
private static boolean canPassIntoNameAndKey(@NotNull NamespacedKey worldKey) {
|
||||
return !BukkitCompatibility.isUsingNewDimensionStorage()
|
||||
|| worldKey.getNamespace().equals(NamespacedKey.MINECRAFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server supports configuring a forced spawn position via the WorldCreator API.
|
||||
* <br />
|
||||
* The force spawn position API is generally only available on PaperMC 26.1+
|
||||
*
|
||||
* @return Whether force spawn position API is supported on the current server version.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean supportsForcedSpawnPosition() {
|
||||
return FORCED_SPAWN_POSITION_METHOD.isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to set the forced spawn position if the server implements the API. This call will do nothing if server
|
||||
* software does not implement the required APIs.
|
||||
*
|
||||
* @param worldCreator Target creator instance to set on.
|
||||
* @param position The position to set as the forced spawn point for the world.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static void setForcedSpawnPosition(WorldCreator worldCreator, EntityPosition position) {
|
||||
if (!supportsForcedSpawnPosition()) {
|
||||
Logging.fine("Server does not support forced spawn position configuration via WorldCreator API.");
|
||||
return;
|
||||
}
|
||||
ReflectHelper.tryInvokeMethod(
|
||||
worldCreator,
|
||||
FORCED_SPAWN_POSITION_METHOD.get(),
|
||||
io.papermc.paper.math.Position.fine(
|
||||
position.getVector().getX().getRawValue(),
|
||||
position.getVector().getY().getRawValue(),
|
||||
position.getVector().getZ().getRawValue()
|
||||
),
|
||||
(float) position.getDirection().getYaw().getRawValue(),
|
||||
(float) position.getDirection().getPitch().getRawValue()
|
||||
).onFailure(ex ->
|
||||
Logging.warning("Failed to set forced spawn position on WorldCreator: %s", ex.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server supports configuring bonus chest generation via the WorldCreator API.
|
||||
* <br />
|
||||
* The bonus chest API is generally only available on PaperMC 1.21.5+
|
||||
*
|
||||
* @return Whether bonus chest API is supported on the current server version.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static boolean supportsBonusChest() {
|
||||
return BONUS_CHEST_METHOD.isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to set bonus chest if the server implements the API. This call will do nothing if server software does not
|
||||
* implement the required APIs.
|
||||
*
|
||||
* @param worldCreator Target creator instance to set on.
|
||||
* @param generateBonusChest Whether to generate a bonus chest at the world spawn point.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static void setBonusChest(WorldCreator worldCreator, boolean generateBonusChest) {
|
||||
if (!supportsBonusChest()) {
|
||||
Logging.fine("Server does not support bonus chest generation via WorldCreator API.");
|
||||
return;
|
||||
}
|
||||
worldCreator.bonusChest(generateBonusChest);
|
||||
}
|
||||
|
||||
private WorldCreatorCompatibility() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Contains classes to handle compatibility due to differing API methods across different server versions and the
|
||||
* API gaps between PaperMC and Spigot.
|
||||
*/
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
@@ -2,7 +2,6 @@ package org.mvplugins.multiverse.core.utils.result;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.vavr.control.Either;
|
||||
@@ -20,38 +19,6 @@ import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
*/
|
||||
public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Success, Attempt.Failure {
|
||||
|
||||
/**
|
||||
* Creates a new success attempt.
|
||||
*
|
||||
* @param value The value.
|
||||
* @param <T> The type of the value.
|
||||
* @param <F> The type of failure reason.
|
||||
* @return The new success attempt.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
static <T, F extends FailureReason> Attempt.Success<T, F> successRef(T value) {
|
||||
return new Success<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new failure attempt.
|
||||
*
|
||||
* @param failureReason The reason for failure.
|
||||
* @param messageReplacements The replacements for the failure message.
|
||||
* @param <T> The type of the value.
|
||||
* @param <F> The type of failure reason.
|
||||
* @return The new failure attempt.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new success attempt.
|
||||
*
|
||||
@@ -61,7 +28,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
* @return The new success attempt.
|
||||
*/
|
||||
static <T, F extends FailureReason> Attempt<T, F> success(T value) {
|
||||
return successRef(value);
|
||||
return new Success<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +42,7 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
*/
|
||||
static <T, F extends FailureReason> Attempt<T, F> failure(
|
||||
F failureReason, MessageReplacement... messageReplacements) {
|
||||
return failureRef(failureReason, messageReplacements);
|
||||
return new Failure<>(failureReason, Message.of(failureReason, "Failed!", messageReplacements));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,33 +203,6 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
*/
|
||||
<U> Attempt<U, F> mapAttempt(Supplier<Attempt<U, F>> mapper);
|
||||
|
||||
/**
|
||||
* Fails this attempt with the given failure attempt if the value matches the given predicate.This will do nothing
|
||||
* if this is already a failure attempt.
|
||||
*
|
||||
* @param predicate The predicate to test the value.
|
||||
* @param failureAttempt The failure attempt to return if the predicate matches.
|
||||
* @return The new attempt
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
Attempt<T, F> failIf(Predicate<? super T> predicate, Supplier<Attempt.Failure<T, F>> failureAttempt);
|
||||
|
||||
/**
|
||||
* Fails this attempt with the given failure attempt if the value matches the given predicate.This will do nothing
|
||||
* if this is already a failure attempt.
|
||||
*
|
||||
* @param predicate The predicate to test the value.
|
||||
* @param failureAttempt The failure attempt to return if the predicate matches, which can be generated based
|
||||
* on the value.
|
||||
* @return The new attempt
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
Attempt<T, F> failIf(Predicate<? super T> predicate, Function<? super T, Attempt.Failure<T, F>> failureAttempt);
|
||||
|
||||
/**
|
||||
* Maps to another attempt with a different fail reason.
|
||||
*
|
||||
@@ -430,16 +370,6 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
return mapper.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> failIf(Predicate<? super T> predicate, Supplier<Attempt.Failure<T, F>> failureAttempt) {
|
||||
return predicate.test(value) ? failureAttempt.get() : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> failIf(Predicate<? super T> predicate, Function<? super T, Failure<T, F>> failureAttempt) {
|
||||
return predicate.test(value) ? failureAttempt.apply(value) : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <UF extends FailureReason> Attempt<T, UF> transform(UF failureReason) {
|
||||
return changeFailureType();
|
||||
@@ -602,16 +532,6 @@ public sealed interface Attempt<T, F extends FailureReason> permits Attempt.Succ
|
||||
return changeValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> failIf(Predicate<? super T> predicate, Supplier<Attempt.Failure<T, F>> failureAttempt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attempt<T, F> failIf(Predicate<? super T> predicate, Function<? super T, Failure<T, F>> failureAttempt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <UF extends FailureReason> Attempt<T, UF> transform(UF failureReason) {
|
||||
return new Failure<>(failureReason, getFailureMessage(), this);
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils.tick;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Represents a duration in Minecraft game ticks.
|
||||
* <p>
|
||||
* This class wraps a {@link Duration} and provides convenient conversion methods
|
||||
* between game ticks (50ms per tick), game days (24,000 ticks), and other time units.
|
||||
* Game time is mapped to real-world seconds via: realSeconds = (ticks * 3.6) + 6*3600 (mod 24h),
|
||||
* which means 00:00 maps to 06:00 in-game time.
|
||||
* </p>
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class TickDuration {
|
||||
|
||||
/**
|
||||
* Parses a time string into a {@link TickDuration}.
|
||||
* <br />
|
||||
* Formats supported:
|
||||
* <ul>
|
||||
* <li>{@code "123"} - raw tick count</li>
|
||||
* <li>{@code "10s"} - seconds</li>
|
||||
* <li>{@code "5d"} - game days</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param timeString the time string to parse
|
||||
* @return a {@link Try} containing the parsed {@link TickDuration}, or a failure if parsing fails
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static Try<TickDuration> parseString(@NotNull String timeString) {
|
||||
return Try.of(() -> {
|
||||
if (timeString.endsWith("s")) {
|
||||
String subString = timeString.substring(0, timeString.length() - 1);
|
||||
return of(Long.parseLong(subString), TimeUnit.SECONDS);
|
||||
} else if (timeString.endsWith("d")) {
|
||||
String subString = timeString.substring(0, timeString.length() - 1);
|
||||
return ofGameDays(Long.parseLong(subString));
|
||||
}
|
||||
return ofTicks(Long.parseLong(timeString));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TickDuration} from a {@link Duration}.
|
||||
*
|
||||
* @param duration the duration
|
||||
* @return a new {@link TickDuration}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public static @NotNull TickDuration ofDuration(@NotNull Duration duration) {
|
||||
return new TickDuration(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TickDuration} from seconds (as a {@link Duration}).
|
||||
*
|
||||
* @param duration the duration representing seconds
|
||||
* @return a new {@link TickDuration}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public static @NotNull TickDuration ofSeconds(@NotNull Duration duration) {
|
||||
return new TickDuration(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TickDuration} from a tick count.
|
||||
*
|
||||
* @param ticks the number of game ticks
|
||||
* @return a new {@link TickDuration}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public static @NotNull TickDuration ofTicks(long ticks) {
|
||||
return of(ticks, TickUnit.TICK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TickDuration} from a game day count.
|
||||
*
|
||||
* @param gameDays the number of game days (24,000 ticks each)
|
||||
* @return a new {@link TickDuration}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public static @NotNull TickDuration ofGameDays(long gameDays) {
|
||||
return of(gameDays, TickUnit.GAME_DAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TickDuration} from a time value and {@link TimeUnit}.
|
||||
*
|
||||
* @param time the time value
|
||||
* @param unit the time unit
|
||||
* @return a new {@link TickDuration}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Contract(value = "_, _ -> new", pure = true)
|
||||
public static @NotNull TickDuration of(long time, @NotNull TimeUnit unit) {
|
||||
return of(time, unit.toChronoUnit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TickDuration} from a time value and {@link TemporalUnit}.
|
||||
*
|
||||
* @param time the time value
|
||||
* @param unit the temporal unit
|
||||
* @return a new {@link TickDuration}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Contract(value = "_, _ -> new", pure = true)
|
||||
public static @NotNull TickDuration of(long time, @NotNull TemporalUnit unit) {
|
||||
return new TickDuration(Duration.of(time, unit));
|
||||
}
|
||||
|
||||
private static final double TIME_MULTIPLIER = 3.6;
|
||||
private static final long DAY_SECONDS = 24 * 60 * 60;
|
||||
private static final long START_OFFSET = 6 * 60 * 60;
|
||||
|
||||
private final Duration duration;
|
||||
|
||||
/**
|
||||
* Creates a {@link TickDuration} from a {@link Duration}.
|
||||
*
|
||||
* @param duration the underlying duration
|
||||
*/
|
||||
private TickDuration(@NotNull Duration duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this duration to Minecraft local time.
|
||||
* <p>
|
||||
* Maps the duration to real-world seconds using the formula:
|
||||
* realSeconds = (ticks * 3.6) + 6*3600 (mod 24h)
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link LocalTime} in Minecraft time (where 00:00 = 06:00 in-game)
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull LocalTime toLocalTime() {
|
||||
// Minecraft maps ticks -> seconds by: realSeconds = (ticks * 3.6) + 6*3600 (mod 24h)
|
||||
long realSeconds = (long) ((toTicks() * TIME_MULTIPLIER) + START_OFFSET) % DAY_SECONDS;
|
||||
return LocalTime.ofSecondOfDay(realSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this duration to game ticks.
|
||||
*
|
||||
* @return the number of game ticks (1 tick = 50ms)
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public long toTicks() {
|
||||
return duration.toMillis() / TickUnit.TICK.inMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this duration to game days.
|
||||
*
|
||||
* @return the number of game days (1 day = 24,000 ticks)
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public double toGameDays() {
|
||||
return (double) duration.toMillis() / (double) TickUnit.GAME_DAY.inMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this duration to seconds.
|
||||
*
|
||||
* @return the duration in seconds
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public double toSeconds() {
|
||||
return to(TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this duration to a specified {@link TimeUnit}.
|
||||
*
|
||||
* @param unit the target time unit
|
||||
* @return the duration in the specified unit
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public double to(@NotNull TimeUnit unit) {
|
||||
return to(unit.toChronoUnit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this duration to a specified {@link TemporalUnit}.
|
||||
*
|
||||
* @param unit the target temporal unit
|
||||
* @return the duration in the specified unit
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public double to(@NotNull TemporalUnit unit) {
|
||||
return (double) duration.toMillis() / (double) unit.getDuration().toMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this duration is an exact multiple of the given {@link TimeUnit}.
|
||||
*
|
||||
* @param unit the time unit to test against
|
||||
* @return {@code true} if this duration divides evenly by the unit, otherwise {@code false}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public boolean isExactTo(@NotNull TimeUnit unit) {
|
||||
return isExactTo(unit.toChronoUnit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this duration is an exact multiple of the given {@link TemporalUnit}.
|
||||
*
|
||||
* @param unit the temporal unit to test against
|
||||
* @return {@code true} if this duration divides evenly by the unit, otherwise {@code false}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public boolean isExactTo(@NotNull TemporalUnit unit) {
|
||||
return duration.toMillis() % unit.getDuration().toMillis() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link Duration}.
|
||||
*
|
||||
* @return the duration
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public Duration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.utils.tick;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
|
||||
/**
|
||||
* A {@link TemporalUnit} implementation for Minecraft game ticks.
|
||||
* <br />
|
||||
* Provides two standard units:
|
||||
* <ul>
|
||||
* <li>{@link #TICK} - A single game tick (50 milliseconds)</li>
|
||||
* <li>{@link #GAME_DAY} - A full game day (24,000 ticks)</li>
|
||||
* </ul>
|
||||
* This unit is not date-based and is precisely defined without estimation.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class TickUnit implements TemporalUnit {
|
||||
|
||||
private static final long SINGLE_TICK_DURATION_MS = 50L;
|
||||
private static final long TICKS_PER_GAME_DAY = 24_000L;
|
||||
|
||||
/**
|
||||
* A single game tick (50 milliseconds).
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static final TickUnit TICK = new TickUnit(SINGLE_TICK_DURATION_MS);
|
||||
|
||||
/**
|
||||
* A full game day (24,000 ticks = 20 minutes in real time).
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static final TickUnit GAME_DAY = new TickUnit(SINGLE_TICK_DURATION_MS * TICKS_PER_GAME_DAY);
|
||||
|
||||
private final long milliseconds;
|
||||
private final Duration duration;
|
||||
|
||||
private TickUnit(long milliseconds) {
|
||||
this.milliseconds = milliseconds;
|
||||
this.duration = Duration.ofMillis(milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the duration of this unit in milliseconds.
|
||||
*
|
||||
* @return the duration in milliseconds
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public long inMillis() {
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Duration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isDurationEstimated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isDateBased() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isTimeBased() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // following ChronoUnit#addTo
|
||||
@Override
|
||||
public <R extends Temporal> R addTo(R temporal, long amount) {
|
||||
return (R) temporal.plus(duration.multipliedBy(amount));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long between(final Temporal start, final Temporal end) {
|
||||
return start.until(end, ChronoUnit.MILLIS) / milliseconds;
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@ import javax.net.ssl.HttpsURLConnection;
|
||||
* HTTP API-client.
|
||||
*/
|
||||
abstract sealed class HttpAPIClient permits PasteService, URLShortener {
|
||||
|
||||
static final String DUMPS_VIEWER_URL = "https://mvdumps.c0ding.party/";
|
||||
|
||||
/**
|
||||
* The URL for this API-request, and if necessary, the access token.
|
||||
* If an access token is not necessary, it should be set to null.
|
||||
|
||||
@@ -34,9 +34,7 @@ final class McloGsPasteService extends PasteService {
|
||||
public String postData(String data) throws PasteFailedException {
|
||||
try {
|
||||
String stringJSON = this.exec(encodeData(data), ContentType.URLENCODED); // Execute request
|
||||
return DUMPS_VIEWER_URL +
|
||||
((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(stringJSON)).get("id") +
|
||||
"?service=mclogs";
|
||||
return String.valueOf(((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(stringJSON)).get("url")); // Interpret result
|
||||
} catch (IOException | ParseException e) {
|
||||
throw new PasteFailedException(e);
|
||||
}
|
||||
@@ -46,9 +44,7 @@ final class McloGsPasteService extends PasteService {
|
||||
public String postData(Map<String, String> data) throws PasteFailedException {
|
||||
try {
|
||||
String stringJSON = this.exec(encodeData(data), ContentType.JSON); // Execute request
|
||||
return DUMPS_VIEWER_URL +
|
||||
((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(stringJSON)).get("id") +
|
||||
"?service=mclogs";
|
||||
return String.valueOf(((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(stringJSON)).get("url")); // Interpret result
|
||||
} catch (IOException | ParseException e) {
|
||||
throw new PasteFailedException(e);
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ final class PastebinPasteService extends PasteService {
|
||||
"&" + URLEncoder.encode("api_option", StandardCharsets.UTF_8) + "=" + URLEncoder.encode("paste", StandardCharsets.UTF_8) +
|
||||
"&" + URLEncoder.encode("api_paste_code", StandardCharsets.UTF_8) + "=" + URLEncoder.encode(data, StandardCharsets.UTF_8) +
|
||||
"&" + URLEncoder.encode("api_paste_private", StandardCharsets.UTF_8) + "=" + URLEncoder.encode(this.isPrivate ? "1" : "0", StandardCharsets.UTF_8) +
|
||||
"&" + URLEncoder.encode("api_paste_format", StandardCharsets.UTF_8) + "=" + URLEncoder.encode("json", StandardCharsets.UTF_8) +
|
||||
"&" + URLEncoder.encode("api_paste_format", StandardCharsets.UTF_8) + "=" + URLEncoder.encode("yaml", StandardCharsets.UTF_8) +
|
||||
"&" + URLEncoder.encode("api_paste_name", StandardCharsets.UTF_8) + "=" + URLEncoder.encode("Multiverse-Core Debug Info", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -39,9 +39,8 @@ final class PastesDevPasteService extends PasteService {
|
||||
@Override
|
||||
public String postData(String data) throws PasteFailedException {
|
||||
try {
|
||||
String stringJSON = this.exec(encodeData(data), ContentType.JSON);
|
||||
return DUMPS_VIEWER_URL +
|
||||
((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(stringJSON)).get("key");
|
||||
String stringJSON = this.exec(encodeData(data), ContentType.PLAINTEXT_YAML);
|
||||
return "https://pastes.dev/" + ((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(stringJSON)).get("key");
|
||||
} catch (IOException | ParseException e) {
|
||||
throw new PasteFailedException(e);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
@NotNull LocationManipulation locationManipulation,
|
||||
@NotNull EntityPurger entityPurger
|
||||
) {
|
||||
super(worldConfig, config);
|
||||
super(world.getName(), worldConfig, config);
|
||||
this.worldUid = world.getUID();
|
||||
this.blockSafety = blockSafety;
|
||||
this.locationManipulation = locationManipulation;
|
||||
@@ -77,7 +77,7 @@ public final class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
Logging.fine("Spawn location from world.dat file was unsafe!!");
|
||||
Logging.fine("NOT adjusting spawn for '" + this.getAliasOrName() + "' because you told me not to.");
|
||||
Logging.fine("To turn on spawn adjustment for this world simply type:");
|
||||
Logging.fine("/mv modify %s set adjust-spawn true", getName());
|
||||
Logging.fine("/mvm set adjustspawn true " + this.getAliasOrName());
|
||||
return location;
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public final class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void setWorldConfig(@NotNull WorldConfig worldConfig) {
|
||||
void setWorldConfig(WorldConfig worldConfig) {
|
||||
super.setWorldConfig(worldConfig);
|
||||
setupWorldConfig(getBukkitWorld().get());
|
||||
}
|
||||
@@ -184,8 +184,7 @@ public final class LoadedMultiverseWorld extends MultiverseWorld {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoadedMultiverseWorld{"
|
||||
+ "key='" + getKey() + "', "
|
||||
+ "name='" + getName() + "', "
|
||||
+ "name='" + worldName + "', "
|
||||
+ "env='" + getEnvironment() + "', "
|
||||
+ "type='" + getWorldType().getOrNull() + "', "
|
||||
+ "gen='" + getGenerator() + "'"
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Difficulty;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.helpers.WorldFolderResolver;
|
||||
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
|
||||
|
||||
@@ -31,6 +23,11 @@ import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
|
||||
* Represents a world handled by Multiverse which has all the custom properties provided by Multiverse.
|
||||
*/
|
||||
public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
/**
|
||||
* This world's name.
|
||||
*/
|
||||
protected final String worldName;
|
||||
|
||||
/**
|
||||
* This world's configuration.
|
||||
*/
|
||||
@@ -39,7 +36,8 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
protected final CoreConfig config;
|
||||
private String colourlessAlias = "";
|
||||
|
||||
MultiverseWorld(WorldConfig worldConfig, CoreConfig config) {
|
||||
MultiverseWorld(String worldName, WorldConfig worldConfig, CoreConfig config) {
|
||||
this.worldName = worldName;
|
||||
this.worldConfig = worldConfig;
|
||||
this.config = config;
|
||||
this.worldConfig.setMVWorld(this);
|
||||
@@ -47,46 +45,15 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
}
|
||||
|
||||
/**
|
||||
* The key that represents this world. This should now be used as the unique key for the world instead
|
||||
* of the world name. Also note that the key cannot be modified.
|
||||
*
|
||||
* @return The world key
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public NamespacedKey getKey() {
|
||||
return worldConfig.getWorldKeyOrName().usableKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this world. Prefer to use {@link #getKey()} as unique id instead of this name.
|
||||
* Also note that the name cannot be modified.
|
||||
* Gets the name of this world. The name cannot be changed.
|
||||
* <br/>
|
||||
* Note for plugin developers: Usually {@link #getAliasOrName()}is what you want to use instead of this method.
|
||||
* Note for plugin developers: Usually {@link #getAliasOrName()}
|
||||
* is what you want to use instead of this method.
|
||||
*
|
||||
* @return The name of the world as a String.
|
||||
*/
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return worldConfig.getLegacyWorldName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder where all the world contents are stored in. Generally, the location of the folder is at the server
|
||||
* roots directory for pre-26.1 servers, and at "[level]/dimensions" folder under the world level directory for
|
||||
* 26.1+ PaperMC servers.
|
||||
* <br />
|
||||
* Note this folder location is based on Multiverse's understanding of Paper and Spigot's folder structure. If the
|
||||
* server software does something weird, this folder will not reflect the actual world folder location.
|
||||
* <br />
|
||||
* If the world is loaded, you should use {@link World#getWorldFolder()} instead as it is more accurate.
|
||||
* This method is more of a fallback for when the world is not loaded.
|
||||
*
|
||||
* @return The world folder.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public File getOfflineWorldFolder() {
|
||||
return WorldFolderResolver.resolve(this);
|
||||
return worldName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,8 +66,7 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this world is loaded. Note that this may return false even if this is a stored instance of
|
||||
* {@link LoadedMultiverseWorld} as worlds can be unloaded anytime.
|
||||
* Gets whether this world is loaded.
|
||||
*
|
||||
* @return True if the world is loaded, else false.
|
||||
*/
|
||||
@@ -108,23 +74,6 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
return worldConfig.isLoadedWorld();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get loaded world instance. Only returns a value if the world is loaded at the time of calling, even
|
||||
* if this is a stored instance of {@link LoadedMultiverseWorld}.
|
||||
* <br/>
|
||||
* You may use {@link #isLoaded()} to check if the world is loaded first.
|
||||
*
|
||||
* @return Loaded world instance wrapped in an Option, or None if the world is not loaded.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Option<LoadedMultiverseWorld> asLoadedWorld() {
|
||||
return Option.of(worldConfig.getMVWorld())
|
||||
.filter(world -> world instanceof LoadedMultiverseWorld)
|
||||
.map(world -> (LoadedMultiverseWorld) world);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the properties handler of this world.
|
||||
*
|
||||
@@ -175,7 +124,7 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
* @return The alias of the world as a String.
|
||||
*/
|
||||
public String getAliasOrName() {
|
||||
return Strings.isNullOrEmpty(worldConfig.getAlias()) ? getName() : worldConfig.getAlias();
|
||||
return Strings.isNullOrEmpty(worldConfig.getAlias()) ? worldName : worldConfig.getAlias();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -424,19 +373,6 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
return worldConfig.getGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the generator settings used to create this world. You cannot change this after world creation.
|
||||
* Changing this manually in config on existing world will have no effect on the world generation.
|
||||
*
|
||||
* @return The generator settings string.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public String getGeneratorSettings() {
|
||||
return worldConfig.getGeneratorSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether or not this world will display in chat, mvw and mvl regardless if a user has the
|
||||
* access permissions to go to this world.
|
||||
@@ -498,59 +434,6 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
return worldConfig.setKeepSpawnInMemory(keepSpawnInMemory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all meta key-value pairs for this world. This is an unmodifiable view of the internal map, so changes to
|
||||
* the world meta will be reflected in this map, but attempts to modify this map will throw an exception.
|
||||
*
|
||||
* @return An unmodifiable view of all meta key-value pairs for this world.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @UnmodifiableView Map<String, String> getAllMeta() {
|
||||
return Collections.unmodifiableMap(worldConfig.getMeta());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the meta value for the given key if it exists.
|
||||
*
|
||||
* @param key The meta key to look up, may be {@code null} in which case an empty result is returned.
|
||||
* @return An {@link Option} containing the meta value if present, otherwise an empty {@link Option}.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Option<String> getMeta(@Nullable String key) {
|
||||
return Option.of(worldConfig.getMeta().get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a meta key-value pair for this world. Existing value for the key will be replaced.
|
||||
*
|
||||
* @param key The meta key to set, must not be {@code null}.
|
||||
* @param value The meta value to set.
|
||||
* @return Result of setting the property.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Try<Void> setMeta(@NotNull String key, @Nullable String value) {
|
||||
return worldConfig.setMeta(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the meta entry associated with the given key.
|
||||
*
|
||||
* @param key The meta key to remove.
|
||||
* @return Result of removing the property.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Try<Void> removeMeta(@NotNull String key) {
|
||||
return worldConfig.removeMeta(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player limit for this world after which players without an override
|
||||
* permission node will not be allowed in. A value of -1 or less signifies no limit
|
||||
@@ -786,7 +669,7 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
*
|
||||
* @return The world config.
|
||||
*/
|
||||
@NotNull WorldConfig getWorldConfig() {
|
||||
WorldConfig getWorldConfig() {
|
||||
return worldConfig;
|
||||
}
|
||||
|
||||
@@ -795,7 +678,7 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
*
|
||||
* @param worldConfig The world config.
|
||||
*/
|
||||
void setWorldConfig(@NotNull WorldConfig worldConfig) {
|
||||
void setWorldConfig(WorldConfig worldConfig) {
|
||||
this.worldConfig = worldConfig;
|
||||
}
|
||||
|
||||
@@ -805,8 +688,7 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MultiverseWorld{"
|
||||
+ "key='" + getKey() + "', "
|
||||
+ "name='" + getName() + "', "
|
||||
+ "name='" + worldName + "', "
|
||||
+ "env='" + getEnvironment() + "', "
|
||||
+ "gen='" + getGenerator() + "'"
|
||||
+ '}';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -10,5 +8,5 @@ import java.util.List;
|
||||
* @param newWorlds List of the new WorldConfigs added
|
||||
* @param removedWorlds List of the worlds removed from the config
|
||||
*/
|
||||
record NewAndRemovedWorlds(List<WorldConfig> newWorlds, List<WorldKeyOrName> removedWorlds) {
|
||||
record NewAndRemovedWorlds(List<WorldConfig> newWorlds, List<String> removedWorlds) {
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -30,7 +29,6 @@ import org.mvplugins.multiverse.core.config.migration.action.MoveMigratorAction;
|
||||
import org.mvplugins.multiverse.core.config.migration.action.NullStringMigratorAction;
|
||||
import org.mvplugins.multiverse.core.config.migration.VersionMigrator;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
|
||||
|
||||
@@ -39,16 +37,16 @@ import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
|
||||
*/
|
||||
final class WorldConfig {
|
||||
|
||||
private final WorldKeyOrName keyOrName;
|
||||
private final String worldName;
|
||||
private final WorldConfigNodes configNodes;
|
||||
private final MemoryConfigurationHandle configHandle;
|
||||
private final StringPropertyHandle stringPropertyHandle;
|
||||
|
||||
WorldConfig(
|
||||
@NotNull WorldKeyOrName keyOrName,
|
||||
@NotNull String worldName,
|
||||
@NotNull ConfigurationSection configSection,
|
||||
@NotNull MultiverseCore multiverseCore) {
|
||||
this.keyOrName = keyOrName;
|
||||
this.worldName = worldName;
|
||||
this.configNodes = new WorldConfigNodes(multiverseCore);
|
||||
this.configHandle = MemoryConfigurationHandle.builder(configSection, configNodes.getNodes())
|
||||
.logger(Logging.getLogger())
|
||||
@@ -120,11 +118,6 @@ final class WorldConfig {
|
||||
.addAction(MoveMigratorAction.of("spawning.animals", "spawning.animal"))
|
||||
.addAction(MoveMigratorAction.of("spawning.monsters", "spawning.monster"))
|
||||
.build())
|
||||
.addVersionMigrator(VersionMigrator.builder(1.3)
|
||||
.addAction(config -> config.set("read-only.legacy-world-name", keyOrName.usableName()))
|
||||
.addAction(MoveMigratorAction.of("environment", "read-only.environment"))
|
||||
.addAction(MoveMigratorAction.of("seed", "read-only.seed"))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -139,7 +132,7 @@ final class WorldConfig {
|
||||
Try<Void> save() {
|
||||
return configHandle.save().onFailure(ex ->
|
||||
Logging.warning("Failed to save world config for world '%s'. %s",
|
||||
keyOrName, ex.getLocalizedMessage()));
|
||||
worldName, ex.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
ConfigurationSection getConfigurationSection() {
|
||||
@@ -150,8 +143,8 @@ final class WorldConfig {
|
||||
return stringPropertyHandle;
|
||||
}
|
||||
|
||||
WorldKeyOrName getWorldKeyOrName() {
|
||||
return keyOrName;
|
||||
String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
boolean getAdjustSpawn() {
|
||||
@@ -265,6 +258,14 @@ final class WorldConfig {
|
||||
return configHandle.set(configNodes.entryFeeCurrency, entryFeeCurrency);
|
||||
}
|
||||
|
||||
World.Environment getEnvironment() {
|
||||
return configHandle.get(configNodes.environment);
|
||||
}
|
||||
|
||||
Try<Void> setEnvironment(World.Environment environment) {
|
||||
return configHandle.set(configNodes.environment, environment);
|
||||
}
|
||||
|
||||
GameMode getGameMode() {
|
||||
return configHandle.get(configNodes.gamemode);
|
||||
}
|
||||
@@ -304,18 +305,6 @@ final class WorldConfig {
|
||||
return configHandle.set(configNodes.keepSpawnInMemory, keepSpawnInMemory);
|
||||
}
|
||||
|
||||
Map<String, String> getMeta() {
|
||||
return configHandle.get(configNodes.meta);
|
||||
}
|
||||
|
||||
Try<Void> setMeta(String key, String value) {
|
||||
return configHandle.set(configNodes.meta, key, value);
|
||||
}
|
||||
|
||||
Try<Void> removeMeta(String key) {
|
||||
return configHandle.remove(configNodes.meta, key);
|
||||
}
|
||||
|
||||
int getPlayerLimit() {
|
||||
return configHandle.get(configNodes.playerLimit);
|
||||
}
|
||||
@@ -356,6 +345,14 @@ final class WorldConfig {
|
||||
return configHandle.set(configNodes.scale, scale);
|
||||
}
|
||||
|
||||
long getSeed() {
|
||||
return configHandle.get(configNodes.seed);
|
||||
}
|
||||
|
||||
Try<Void> setSeed(long seed) {
|
||||
return configHandle.set(configNodes.seed, seed);
|
||||
}
|
||||
|
||||
SpawnLocation getSpawnLocation() {
|
||||
return configHandle.get(configNodes.spawnLocation);
|
||||
}
|
||||
@@ -380,46 +377,10 @@ final class WorldConfig {
|
||||
return configHandle.set(configNodes.worldBlacklist, worldBlacklist);
|
||||
}
|
||||
|
||||
World.Environment getEnvironment() {
|
||||
return configHandle.get(configNodes.environment);
|
||||
}
|
||||
|
||||
Try<Void> setEnvironment(World.Environment environment) {
|
||||
return configHandle.set(configNodes.environment, environment);
|
||||
}
|
||||
|
||||
String getGeneratorSettings() {
|
||||
return configHandle.get(configNodes.generatorSettings);
|
||||
}
|
||||
|
||||
Try<Void> setGeneratorSettings(String generatorSettings) {
|
||||
return configHandle.set(configNodes.generatorSettings, generatorSettings);
|
||||
}
|
||||
|
||||
String getLegacyWorldName() {
|
||||
return configHandle.get(configNodes.legacyWorldName);
|
||||
}
|
||||
|
||||
Try<Void> setLegacyWorldName(String legacyWorldName) {
|
||||
return configHandle.set(configNodes.legacyWorldName, legacyWorldName);
|
||||
}
|
||||
|
||||
long getSeed() {
|
||||
return configHandle.get(configNodes.seed);
|
||||
}
|
||||
|
||||
Try<Void> setSeed(long seed) {
|
||||
return configHandle.set(configNodes.seed, seed);
|
||||
}
|
||||
|
||||
void setMVWorld(@NotNull MultiverseWorld world) {
|
||||
configNodes.setWorld(world);
|
||||
}
|
||||
|
||||
MultiverseWorld getMVWorld() {
|
||||
return configNodes.getWorld();
|
||||
}
|
||||
|
||||
boolean isLoadedWorld() {
|
||||
return configNodes.getWorld() instanceof LoadedMultiverseWorld;
|
||||
}
|
||||
@@ -431,7 +392,7 @@ final class WorldConfig {
|
||||
/**
|
||||
* Migrates the entry fee settings. Assumes entry fee is disabled if currency is not set.
|
||||
*/
|
||||
private static final class EntryFeeMigrator implements MigratorAction {
|
||||
static final class EntryFeeMigrator implements MigratorAction {
|
||||
@Override
|
||||
public void migrate(ConfigurationSection config) {
|
||||
String currency = config.getString("entry-fee.currency", "");
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.util.Objects;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Difficulty;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
@@ -18,7 +17,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.config.node.MapConfigNode;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.event.world.MVWorldPropertyChangedEvent;
|
||||
import org.mvplugins.multiverse.core.config.node.ConfigNode;
|
||||
@@ -26,7 +24,6 @@ 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.utils.MaterialConverter;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
|
||||
import org.mvplugins.multiverse.core.world.location.NullSpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
|
||||
@@ -93,8 +90,6 @@ final class WorldConfigNodes {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (world == null) return;
|
||||
world.updateColourlessAlias();
|
||||
worldManager.getWorldStore().changeAlias(
|
||||
ChatTextFormatter.removeColor(oldValue), ChatTextFormatter.removeColor(newValue), world);
|
||||
}));
|
||||
|
||||
final ConfigNode<Boolean> allowAdvancementGrant = node(ConfigNode.builder("allow-advancement-grant", Boolean.class)
|
||||
@@ -177,6 +172,11 @@ final class WorldConfigNodes {
|
||||
}
|
||||
}));
|
||||
|
||||
final ConfigNode<World.Environment> environment = node(ConfigNode
|
||||
.builder("environment", World.Environment.class)
|
||||
.defaultValue(World.Environment.NORMAL)
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<GameMode> gamemode = node(ConfigNode.builder("gamemode", GameMode.class)
|
||||
.defaultValue(GameMode.SURVIVAL)
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
@@ -198,21 +198,11 @@ final class WorldConfigNodes {
|
||||
final ConfigNode<Boolean> keepSpawnInMemory = node(ConfigNode
|
||||
.builder("keep-spawn-in-memory", Boolean.class)
|
||||
.defaultValue(true)
|
||||
.onLoadAndChange((sender, oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (!(world instanceof LoadedMultiverseWorld loadedWorld)) return;
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
bukkitWorld.setKeepSpawnInMemory(newValue);
|
||||
if (bukkitWorld.getKeepSpawnInMemory() != newValue) {
|
||||
sender.sendMessage(ChatColor.RED + "Keep spawn in memory feature has been removed by " +
|
||||
"Minecraft in 1.21.9+ and will no longer have any effect when set to true.");
|
||||
}
|
||||
});
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> bukkitWorld.setKeepSpawnInMemory(newValue));
|
||||
}));
|
||||
|
||||
final MapConfigNode<String, String> meta = (MapConfigNode<String, String>) node(MapConfigNode
|
||||
.mapBuilder("meta", String.class, String.class)
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<Integer> playerLimit = node(ConfigNode.builder("player-limit", Integer.class)
|
||||
.defaultValue(-1));
|
||||
|
||||
@@ -244,6 +234,10 @@ final class WorldConfigNodes {
|
||||
};
|
||||
}));
|
||||
|
||||
final ConfigNode<Long> seed = node(ConfigNode.builder("seed", Long.class)
|
||||
.defaultValue(Long.MIN_VALUE)
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<SpawnLocation> spawnLocation = node(ConfigNode.builder("spawn-location", SpawnLocation.class)
|
||||
.defaultValue(NullSpawnLocation.get())
|
||||
.hidden()
|
||||
@@ -280,22 +274,6 @@ final class WorldConfigNodes {
|
||||
|
||||
final ConfigNode<List<String>> worldBlacklist = node(ListConfigNode.listBuilder("world-blacklist", String.class));
|
||||
|
||||
final ConfigNode<World.Environment> environment = node(ConfigNode
|
||||
.builder("read-only.environment", World.Environment.class)
|
||||
.defaultValue(World.Environment.NORMAL)
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<String> generatorSettings = node(ConfigNode.builder("read-only.generator-settings", String.class)
|
||||
.defaultValue("")
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<String> legacyWorldName = node(ConfigNode.builder("read-only.legacy-world-name", String.class)
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<Long> seed = node(ConfigNode.builder("read-only.seed", Long.class)
|
||||
.defaultValue(Long.MIN_VALUE)
|
||||
.hidden());
|
||||
|
||||
final ConfigNode<Double> version = node(ConfigNode.builder("version", Double.class)
|
||||
.defaultValue(CONFIG_VERSION)
|
||||
.hidden());
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -13,22 +17,19 @@ import java.util.stream.Collectors;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import com.google.common.base.Strings;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldBorder;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
@@ -47,23 +48,19 @@ import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissions;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
import org.mvplugins.multiverse.core.utils.ServerProperties;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.WorldCompatibility;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.WorldCreatorCompatibility;
|
||||
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;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DataStore.GameRulesStore;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DataTransfer;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PotentialWorldFinder;
|
||||
import org.mvplugins.multiverse.core.world.helpers.WorldFolderResolver;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder.DimensionFormat;
|
||||
import org.mvplugins.multiverse.core.world.helpers.WorldNameChecker;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;
|
||||
@@ -90,12 +87,12 @@ import static org.mvplugins.multiverse.core.world.helpers.DataStore.WorldConfigS
|
||||
@Service // SUPPRESS CHECKSTYLE: ClassFanOutComplexity This is the world manager, it's going to be complex.
|
||||
public final class WorldManager {
|
||||
|
||||
private static final List<String> CLONE_IGNORE_FILES = List.of(
|
||||
"uid.dat", "session.lock", // All pre 26.1 format
|
||||
"data/paper/metadata.dat" // New papermc format for 26.1+
|
||||
);
|
||||
private static final List<String> CLONE_IGNORE_FILES = Arrays.asList("uid.dat", "session.lock");
|
||||
private static final DimensionFormat DEFAULT_NETHER_FORMAT = new DimensionFormat("%overworld%_nether");
|
||||
private static final DimensionFormat DEFAULT_END_FORMAT = new DimensionFormat("%overworld%_the_end");
|
||||
|
||||
private final WorldStore worldStore;
|
||||
private final Map<String, MultiverseWorld> worldsMap;
|
||||
private final Map<String, LoadedMultiverseWorld> loadedWorldsMap;
|
||||
private final List<String> unloadTracker;
|
||||
private final List<String> loadTracker;
|
||||
private final WorldsConfigManager worldsConfigManager;
|
||||
@@ -110,11 +107,9 @@ public final class WorldManager {
|
||||
private final ServerProperties serverProperties;
|
||||
private final CoreConfig config;
|
||||
private final EntityPurger entityPurger;
|
||||
private final Provider<PotentialWorldFinder> potentialWorldFinder;
|
||||
|
||||
@Inject
|
||||
WorldManager(
|
||||
@NotNull WorldStore worldStore,
|
||||
@NotNull WorldsConfigManager worldsConfigManager,
|
||||
@NotNull WorldNameChecker worldNameChecker,
|
||||
@NotNull BiomeProviderFactory biomeProviderFactory,
|
||||
@@ -126,9 +121,7 @@ public final class WorldManager {
|
||||
@NotNull CorePermissions corePermissions,
|
||||
@NotNull ServerProperties serverProperties,
|
||||
@NotNull CoreConfig config,
|
||||
@NotNull EntityPurger entityPurger,
|
||||
@NotNull Provider<PotentialWorldFinder> potentialWorldFinder) {
|
||||
this.worldStore = worldStore;
|
||||
@NotNull EntityPurger entityPurger) {
|
||||
this.worldsConfigManager = worldsConfigManager;
|
||||
this.worldNameChecker = worldNameChecker;
|
||||
this.biomeProviderFactory = biomeProviderFactory;
|
||||
@@ -141,8 +134,9 @@ public final class WorldManager {
|
||||
this.serverProperties = serverProperties;
|
||||
this.config = config;
|
||||
this.entityPurger = entityPurger;
|
||||
this.potentialWorldFinder = potentialWorldFinder;
|
||||
|
||||
this.worldsMap = new HashMap<>();
|
||||
this.loadedWorldsMap = new HashMap<>();
|
||||
this.unloadTracker = new ArrayList<>();
|
||||
this.loadTracker = new ArrayList<>();
|
||||
}
|
||||
@@ -174,23 +168,19 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private void loadNewWorldConfigs(Collection<WorldConfig> newWorldConfigs) {
|
||||
newWorldConfigs.forEach(worldConfig -> {
|
||||
worldStore.getUnloadedWorldRef(worldConfig.getWorldKeyOrName().usableName())
|
||||
.peek(unloadedWorld -> unloadedWorld.setWorldConfig(worldConfig))
|
||||
.onEmpty(() -> newMultiverseWorld(worldConfig));
|
||||
worldStore.getLoadedWorld(worldConfig.getWorldKeyOrName().usableName())
|
||||
.peek(loadedWorld -> loadedWorld.setWorldConfig(worldConfig));
|
||||
});
|
||||
newWorldConfigs.forEach(worldConfig -> Option.of(worldsMap.get(worldConfig.getWorldName()))
|
||||
.peek(unloadedWorld -> unloadedWorld.setWorldConfig(worldConfig))
|
||||
.onEmpty(() -> newMultiverseWorld(worldConfig.getWorldName(), worldConfig)));
|
||||
}
|
||||
|
||||
private void removeWorldsNotInConfigs(Collection<WorldKeyOrName> removedWorlds) {
|
||||
removedWorlds.forEach(keyOrName -> getWorld(keyOrName.usableName())
|
||||
private void removeWorldsNotInConfigs(Collection<String> removedWorlds) {
|
||||
removedWorlds.forEach(worldName -> getWorld(worldName)
|
||||
.map(world -> removeWorld(RemoveWorldOptions.world(world)))
|
||||
.getOrElse(() -> worldActionResult(RemoveFailureReason.WORLD_NON_EXISTENT, keyOrName.toString()))
|
||||
.getOrElse(() -> worldActionResult(RemoveFailureReason.UNLOAD_FAILED, worldName))
|
||||
.onFailure(failure ->
|
||||
Logging.severe("Failed to unload world %s: %s", keyOrName, failure))
|
||||
Logging.severe("Failed to unload world %s: %s", worldName, failure))
|
||||
.onSuccess(success ->
|
||||
Logging.fine("Unloaded world %s as it was removed from config", keyOrName)));
|
||||
Logging.fine("Unloaded world %s as it was removed from config", worldName)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,10 +192,9 @@ public final class WorldManager {
|
||||
.collect(Collectors.toMap(World::getName, Function.identity()));
|
||||
|
||||
serverProperties.getLevelName().peek(overworldName -> {
|
||||
//TODO: check by namespaced key instead
|
||||
World overworld = bukkitWorlds.remove(overworldName);
|
||||
World nether = bukkitWorlds.remove(DimensionFinder.DEFAULT_NETHER_FORMAT.replaceOverworld(overworldName));
|
||||
World end = bukkitWorlds.remove(DimensionFinder.DEFAULT_END_FORMAT.replaceOverworld(overworldName));
|
||||
World nether = bukkitWorlds.remove(DEFAULT_NETHER_FORMAT.replaceOverworld(overworldName));
|
||||
World end = bukkitWorlds.remove(DEFAULT_END_FORMAT.replaceOverworld(overworldName));
|
||||
|
||||
if (config.getAutoImportDefaultWorlds()) {
|
||||
importExistingBukkitWorld(overworld);
|
||||
@@ -252,60 +241,43 @@ public final class WorldManager {
|
||||
* @return The result of the creation.
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, CreateFailureReason> createWorld(CreateWorldOptions options) {
|
||||
return parseCreateWorldOptionsKeyOrName(options).mapAttempt(this::doCreateWorld);
|
||||
return validateCreateWorldOptions(options).mapAttempt(this::doCreateWorld);
|
||||
}
|
||||
|
||||
private Attempt<KeyOrNameWithOptions<CreateWorldOptions>, CreateFailureReason> parseCreateWorldOptionsKeyOrName(
|
||||
private Attempt<CreateWorldOptions, CreateFailureReason> validateCreateWorldOptions(
|
||||
CreateWorldOptions options) {
|
||||
return options.keyOrName()
|
||||
.fold(WorldKeyOrName::parse, Attempt::success)
|
||||
.transform(CreateFailureReason.INVALID_WORLDNAME)
|
||||
.failIf(keyOrName -> !worldNameChecker.isValidWorldName(keyOrName.usableName()),
|
||||
keyOrName -> worldActionResult(CreateFailureReason.INVALID_WORLDNAME, keyOrName))
|
||||
.failIf(keyOrName -> keyOrName.isKey() && !WorldCreatorCompatibility.canCreateWorldWithKey(),
|
||||
keyOrName -> worldActionResult(CreateFailureReason.NAMESPACEDKEY_UNSUPPORTED, keyOrName))
|
||||
.failIf(worldStore::isLoadedWorld,
|
||||
keyOrName -> worldActionResult(CreateFailureReason.WORLD_EXIST_LOADED, keyOrName))
|
||||
.failIf(worldStore::isUnloadedWorld,
|
||||
keyOrName -> worldActionResult(CreateFailureReason.WORLD_EXIST_UNLOADED, keyOrName))
|
||||
.failIf(keyOrName -> options.doFolderCheck() && worldNameChecker.hasWorldFolder(keyOrName),
|
||||
keyOrName -> worldActionResult(CreateFailureReason.WORLD_EXIST_FOLDER, keyOrName))
|
||||
.map(keyOrName -> new KeyOrNameWithOptions<>(keyOrName, options));
|
||||
if (!worldNameChecker.isValidWorldName(options.worldName())) {
|
||||
return worldActionResult(CreateFailureReason.INVALID_WORLDNAME, options.worldName());
|
||||
} else if (getLoadedWorld(options.worldName()).isDefined()) {
|
||||
return worldActionResult(CreateFailureReason.WORLD_EXIST_LOADED, options.worldName());
|
||||
} else if (getWorld(options.worldName()).isDefined()) {
|
||||
return worldActionResult(CreateFailureReason.WORLD_EXIST_UNLOADED, options.worldName());
|
||||
} else if (options.doFolderCheck() && worldNameChecker.hasWorldFolder(options.worldName())) {
|
||||
return worldActionResult(CreateFailureReason.WORLD_EXIST_FOLDER, options.worldName());
|
||||
}
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, CreateFailureReason> doCreateWorld(
|
||||
KeyOrNameWithOptions<CreateWorldOptions> keyOrNameWithOptions) {
|
||||
WorldKeyOrName keyOrName = keyOrNameWithOptions.keyOrName();
|
||||
CreateWorldOptions options = keyOrNameWithOptions.options();
|
||||
String generatorString = generatorProvider.parseGeneratorString(keyOrName.usableName(), options.generator());
|
||||
WorldCreator worldCreator = WorldCreatorCompatibility.ofKeyOrName(keyOrName)
|
||||
CreateWorldOptions options) {
|
||||
String generatorString = generatorProvider.parseGeneratorString(options.worldName(), options.generator());
|
||||
WorldCreator worldCreator = WorldCreator.name(options.worldName())
|
||||
.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 addBiomeProviderToCreator(worldCreator, options.worldName(), options.biome())
|
||||
.mapAttempt(creator -> addGeneratorToCreator(creator, generatorString))
|
||||
.mapAttempt(this::createBukkitWorld)
|
||||
.transform(CreateFailureReason.WORLD_CREATOR_FAILED)
|
||||
.map(bukkitWorld -> newLoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
generatorString,
|
||||
options.biome(),
|
||||
options.generatorSettings(),
|
||||
options.useSpawnAdjust()))
|
||||
.peek(loadedWorld -> postCreateWorld(loadedWorld, options));
|
||||
}
|
||||
|
||||
private void postCreateWorld(LoadedMultiverseWorld loadedWorld, CreateWorldOptions options) {
|
||||
options.worldPropertyStrings().forEach((key, value) -> loadedWorld.getStringPropertyHandle()
|
||||
.setPropertyString(key, value)
|
||||
.onFailure(failure -> Logging.warning("Failed to set property '%s' to '%s' for world %s: %s",
|
||||
key, value, loadedWorld.getName(), failure.getMessage())));
|
||||
pluginManager.callEvent(new MVWorldCreatedEvent(loadedWorld));
|
||||
saveWorldsConfig();
|
||||
bukkitWorld,
|
||||
generatorString,
|
||||
options.biome(),
|
||||
options.useSpawnAdjust()))
|
||||
.peek(loadedWorld -> pluginManager.callEvent(new MVWorldCreatedEvent(loadedWorld)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,51 +287,34 @@ public final class WorldManager {
|
||||
* @return The result of the import.
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, ImportFailureReason> importWorld(ImportWorldOptions options) {
|
||||
return options.keyOrName()
|
||||
.fold(WorldKeyOrName::parse, Attempt::success)
|
||||
.transform(ImportFailureReason.INVALID_WORLDNAME)
|
||||
.failIf(keyOrName -> !worldNameChecker.isValidWorldName(keyOrName.usableName()),
|
||||
keyOrName -> worldActionResult(ImportFailureReason.INVALID_WORLDNAME, keyOrName))
|
||||
.failIf(keyOrName -> keyOrName.isKey() && !WorldCreatorCompatibility.canCreateWorldWithKey(),
|
||||
keyOrName -> worldActionResult(ImportFailureReason.NAMESPACEDKEY_UNSUPPORTED, keyOrName))
|
||||
.failIf(worldStore::isLoadedWorld,
|
||||
keyOrName -> worldActionResult(ImportFailureReason.WORLD_EXIST_LOADED, keyOrName))
|
||||
.failIf(worldStore::isUnloadedWorld,
|
||||
keyOrName -> worldActionResult(ImportFailureReason.WORLD_EXIST_UNLOADED, keyOrName))
|
||||
.map(keyOrName -> new KeyOrNameWithOptions<>(keyOrName, options))
|
||||
.mapAttempt(pair -> BukkitCompatibility.getWorldByNameOrKey(pair.keyOrName())
|
||||
.map(bukkitWorld -> doImportBukkitWorld(pair, bukkitWorld))
|
||||
.getOrElse(() -> validateImportWorldOptions(pair).mapAttempt(this::doImportWorld)));
|
||||
String worldName = options.worldName();
|
||||
if (isLoadedWorld(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_EXIST_LOADED, worldName);
|
||||
} else if (isWorld(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_EXIST_UNLOADED, worldName);
|
||||
}
|
||||
return Option.of(Bukkit.getWorld(worldName))
|
||||
.map(bukkitWorld -> doImportBukkitWorld(options, bukkitWorld))
|
||||
.getOrElse(() -> validateImportWorldOptions(options).mapAttempt(this::doImportWorld));
|
||||
}
|
||||
|
||||
private Attempt<KeyOrNameWithOptions<ImportWorldOptions>, ImportFailureReason> validateImportWorldOptions(
|
||||
KeyOrNameWithOptions<ImportWorldOptions> keyOrNameWithOptions) {
|
||||
WorldKeyOrName keyOrName = keyOrNameWithOptions.keyOrName();
|
||||
if (keyOrNameWithOptions.options().doFolderCheck()) {
|
||||
//todo This is a duplicate of folder check in load world
|
||||
WorldNameChecker.FolderStatus folderStatus = worldNameChecker.checkFolder(keyOrName);
|
||||
if (!folderStatus.isLoadable()) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, keyOrName);
|
||||
}
|
||||
if (folderStatus == WorldNameChecker.FolderStatus.REQUIRES_MIGRATION) {
|
||||
Logging.info("World '%s' will be automatically migrated by PaperMC to the new dimension " +
|
||||
"location. If you face any issue with migration, please contact PaperMC support!",
|
||||
keyOrName);
|
||||
}
|
||||
private Attempt<ImportWorldOptions, ImportFailureReason> validateImportWorldOptions(ImportWorldOptions options) {
|
||||
String worldName = options.worldName();
|
||||
if (!worldNameChecker.isValidWorldName(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.INVALID_WORLDNAME, worldName);
|
||||
} else if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(worldName)) {
|
||||
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, worldName);
|
||||
}
|
||||
return worldActionResult(keyOrNameWithOptions);
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportWorld(
|
||||
KeyOrNameWithOptions<ImportWorldOptions> keyOrNameWithOptions) {
|
||||
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());
|
||||
ImportWorldOptions options) {
|
||||
String generatorString = generatorProvider.parseGeneratorString(options.worldName(), options.generator());
|
||||
WorldCreator worldCreator = WorldCreator.name(options.worldName())
|
||||
.environment(options.environment());
|
||||
|
||||
return addBiomeProviderToCreator(worldCreator, keyOrName.usableName(), options.biome())
|
||||
return addBiomeProviderToCreator(worldCreator, options.worldName(), options.biome())
|
||||
.mapAttempt(creator -> addGeneratorToCreator(creator, generatorString))
|
||||
.mapAttempt(this::createBukkitWorld)
|
||||
.transform(ImportFailureReason.WORLD_CREATOR_FAILED)
|
||||
@@ -367,15 +322,11 @@ public final class WorldManager {
|
||||
bukkitWorld,
|
||||
generatorString,
|
||||
options.biome(),
|
||||
options.generatorSettings(),
|
||||
options.useSpawnAdjust()))
|
||||
.peek(loadedWorld -> pluginManager.callEvent(new MVWorldImportedEvent(loadedWorld)));
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportBukkitWorld(
|
||||
KeyOrNameWithOptions<ImportWorldOptions> keyOrNameWithOptions, World bukkitWorld) {
|
||||
WorldKeyOrName keyOrName = keyOrNameWithOptions.keyOrName();
|
||||
ImportWorldOptions options = keyOrNameWithOptions.options();
|
||||
private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportBukkitWorld(ImportWorldOptions options, World bukkitWorld) {
|
||||
if (options.environment() != bukkitWorld.getEnvironment()) {
|
||||
return Attempt.failure(ImportFailureReason.BUKKIT_ENVIRONMENT_MISMATCH,
|
||||
Replace.WORLD.with(bukkitWorld.getName()),
|
||||
@@ -383,21 +334,18 @@ public final class WorldManager {
|
||||
replace("{mvEnvironment}").with(options.environment().name()));
|
||||
}
|
||||
|
||||
//todo: check for key mismatch?
|
||||
|
||||
LoadedMultiverseWorld loadedWorld = newLoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
generatorProvider.parseGeneratorString(keyOrName.usableName(), options.generator()),
|
||||
generatorProvider.parseGeneratorString(options.worldName(), options.generator()),
|
||||
options.biome(),
|
||||
options.generatorSettings(),
|
||||
options.useSpawnAdjust());
|
||||
pluginManager.callEvent(new MVWorldImportedEvent(loadedWorld));
|
||||
return Attempt.success(loadedWorld);
|
||||
}
|
||||
|
||||
private MultiverseWorld newMultiverseWorld(WorldConfig worldConfig) {
|
||||
MultiverseWorld mvWorld = new MultiverseWorld(worldConfig, config);
|
||||
worldStore.putUnloadedWorld(mvWorld);
|
||||
private MultiverseWorld newMultiverseWorld(String worldName, WorldConfig worldConfig) {
|
||||
MultiverseWorld mvWorld = new MultiverseWorld(worldName, worldConfig, config);
|
||||
worldsMap.put(mvWorld.getName().toLowerCase(Locale.ENGLISH), mvWorld);
|
||||
corePermissions.addWorldPermissions(mvWorld);
|
||||
return mvWorld;
|
||||
}
|
||||
@@ -410,28 +358,22 @@ public final class WorldManager {
|
||||
* @param adjustSpawn Whether to adjust spawn.
|
||||
*/
|
||||
private LoadedMultiverseWorld newLoadedMultiverseWorld(
|
||||
@NotNull World world,
|
||||
@Nullable String generator,
|
||||
@Nullable String biome,
|
||||
@Nullable String generatorSettings,
|
||||
boolean adjustSpawn) {
|
||||
WorldConfig worldConfig = worldsConfigManager.addWorldConfig(world.getKey());
|
||||
@NotNull World world, @Nullable String generator, @Nullable String biome, boolean adjustSpawn) {
|
||||
WorldConfig worldConfig = worldsConfigManager.addWorldConfig(world.getName());
|
||||
|
||||
// Properties from multiverse input
|
||||
worldConfig.setAdjustSpawn(adjustSpawn);
|
||||
worldConfig.setGenerator(generator == null ? "" : generator);
|
||||
worldConfig.setBiome(biome == null ? "" : biome);
|
||||
worldConfig.setGeneratorSettings(generatorSettings == null ? "" : generatorSettings);
|
||||
|
||||
// Properties from the bukkit world
|
||||
worldConfig.setLegacyWorldName(world.getName());
|
||||
worldConfig.setDifficulty(world.getDifficulty());
|
||||
worldConfig.setKeepSpawnInMemory(world.getKeepSpawnInMemory());
|
||||
worldConfig.setScale(WorldCompatibility.getCoordinateScale(world));
|
||||
worldConfig.setScale(getCoordinateScale(world));
|
||||
|
||||
worldConfig.save();
|
||||
|
||||
newMultiverseWorld(worldConfig);
|
||||
newMultiverseWorld(world.getName(), worldConfig);
|
||||
LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld(
|
||||
world,
|
||||
worldConfig,
|
||||
@@ -440,12 +382,23 @@ public final class WorldManager {
|
||||
locationManipulation,
|
||||
entityPurger
|
||||
);
|
||||
worldStore.putLoadedWorld(loadedWorld);
|
||||
loadedWorldsMap.put(loadedWorld.getName().toLowerCase(Locale.ENGLISH), loadedWorld);
|
||||
saveWorldsConfig();
|
||||
pluginManager.callEvent(new MVWorldLoadedEvent(loadedWorld));
|
||||
return loadedWorld;
|
||||
}
|
||||
|
||||
private double getCoordinateScale(World world) {
|
||||
if (PaperLib.isPaper()) {
|
||||
return world.getCoordinateScale();
|
||||
}
|
||||
return switch (world.getEnvironment()) {
|
||||
case NORMAL -> 1;
|
||||
case NETHER -> 8;
|
||||
default -> 1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an existing world in config.
|
||||
*
|
||||
@@ -512,19 +465,11 @@ public final class WorldManager {
|
||||
return doLoadBukkitWorld(bukkitWorld, mvWorld);
|
||||
}
|
||||
|
||||
if (options.doFolderCheck()) {
|
||||
WorldNameChecker.FolderStatus folderStatus = worldNameChecker.checkFolder(mvWorld.getOfflineWorldFolder());
|
||||
if (!folderStatus.isLoadable()) {
|
||||
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
|
||||
}
|
||||
if (folderStatus == WorldNameChecker.FolderStatus.REQUIRES_MIGRATION) {
|
||||
Logging.info("World '%s' will be automatically migrated by PaperMC to the new dimension " +
|
||||
"location. If you face any issue with migration, please contact PaperMC support!",
|
||||
mvWorld.getName());
|
||||
}
|
||||
if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(mvWorld.getName())) {
|
||||
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
|
||||
}
|
||||
|
||||
WorldCreator worldCreator = WorldCreatorCompatibility.ofNameAndKey(mvWorld.getKey(), mvWorld.getName())
|
||||
WorldCreator worldCreator = WorldCreator.name(mvWorld.getName())
|
||||
.environment(mvWorld.getEnvironment())
|
||||
.seed(mvWorld.getSeed());
|
||||
return addBiomeProviderToCreator(worldCreator, mvWorld.getName(), mvWorld.getBiome())
|
||||
@@ -547,23 +492,7 @@ public final class WorldManager {
|
||||
}
|
||||
|
||||
private Attempt<LoadedMultiverseWorld, LoadFailureReason> newLoadedMultiverseWorld(MultiverseWorld mvWorld, World bukkitWorld) {
|
||||
WorldConfig worldConfig = mvWorld.getWorldConfig();
|
||||
|
||||
if (worldConfig.getWorldKeyOrName().isName() && mvWorld.getName().equalsIgnoreCase(bukkitWorld.getName())) {
|
||||
// do migration of namespaced key
|
||||
Logging.info("Migrating world config for '%s' to use namespaced key '%s'...",
|
||||
mvWorld.getName(), bukkitWorld.getKey());
|
||||
worldConfig = worldsConfigManager.migrateWorldConfigKey(worldConfig, bukkitWorld.getKey());
|
||||
mvWorld.setWorldConfig(worldConfig);
|
||||
}
|
||||
|
||||
if (!bukkitWorld.getKey().equals(mvWorld.getKey())) {
|
||||
return Attempt.failure(LoadFailureReason.BUKKIT_NAMESPACED_KEY_MISMATCH,
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
replace("{bukkitNamespace}").with(bukkitWorld.getKey()),
|
||||
replace("{mvNamespace}").with(mvWorld.getKey()));
|
||||
}
|
||||
|
||||
WorldConfig worldConfig = worldsConfigManager.getWorldConfig(mvWorld.getName()).get(); //TODO: null check here, but logically it should never be null.
|
||||
LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld(
|
||||
bukkitWorld,
|
||||
worldConfig,
|
||||
@@ -572,7 +501,7 @@ public final class WorldManager {
|
||||
locationManipulation,
|
||||
entityPurger
|
||||
);
|
||||
worldStore.putLoadedWorld(loadedWorld);
|
||||
loadedWorldsMap.put(loadedWorld.getName().toLowerCase(Locale.ENGLISH), loadedWorld);
|
||||
saveWorldsConfig();
|
||||
pluginManager.callEvent(new MVWorldLoadedEvent(loadedWorld));
|
||||
return Attempt.success(loadedWorld);
|
||||
@@ -606,10 +535,12 @@ public final class WorldManager {
|
||||
success -> removeLoadedMultiverseWorld(world));
|
||||
}
|
||||
|
||||
private Attempt<MultiverseWorld, UnloadFailureReason> removeLoadedMultiverseWorld(@NotNull LoadedMultiverseWorld mvWorld) {
|
||||
MultiverseWorld unloadedWorld = worldStore.getUnloadedWorldRef(mvWorld.getKey().toString()).getOrElseThrow(
|
||||
() -> new IllegalStateException("Unloaded ref of world not found: " + mvWorld));
|
||||
worldStore.removeLoadedWorld(mvWorld);
|
||||
private Attempt<MultiverseWorld, UnloadFailureReason> removeLoadedMultiverseWorld(@NotNull LoadedMultiverseWorld world) {
|
||||
MultiverseWorld mvWorld = Objects.requireNonNull(loadedWorldsMap.remove(world.getName()),
|
||||
"For some reason, the loaded world isn't in the map... BUGGG");
|
||||
Logging.fine("Removed MultiverseWorld from map: " + world.getName());
|
||||
var unloadedWorld = Objects.requireNonNull(worldsMap.get(world.getName()),
|
||||
"For some reason, the unloaded world isn't in the map... BUGGG");
|
||||
mvWorld.getWorldConfig().setMVWorld(unloadedWorld);
|
||||
pluginManager.callEvent(new MVWorldUnloadedEvent(mvWorld));
|
||||
return worldActionResult(unloadedWorld);
|
||||
@@ -699,15 +630,11 @@ public final class WorldManager {
|
||||
*/
|
||||
private Attempt<String, RemoveFailureReason> removeWorldFromConfig(@NotNull MultiverseWorld world) {
|
||||
// Remove world from config
|
||||
worldStore.removeWorld(world);
|
||||
|
||||
WorldConfig worldConfig = world.getWorldConfig();
|
||||
worldConfig.deferenceMVWorld();
|
||||
worldsConfigManager.deleteWorldConfig(worldConfig);
|
||||
worldsMap.remove(world.getName());
|
||||
world.getWorldConfig().deferenceMVWorld();
|
||||
worldsConfigManager.deleteWorldConfig(world.getName());
|
||||
saveWorldsConfig();
|
||||
|
||||
corePermissions.removeWorldPermissions(world);
|
||||
|
||||
pluginManager.callEvent(new MVWorldRemovedEvent(world));
|
||||
return worldActionResult(world.getName());
|
||||
}
|
||||
@@ -764,7 +691,6 @@ public final class WorldManager {
|
||||
private Attempt<File, DeleteFailureReason> validateWorldToDelete(
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
return world.getBukkitWorld().map(World::getWorldFolder)
|
||||
.peek(folder -> Logging.finer("World folder for world %s is at: %s", world.getName(), folder.getPath()))
|
||||
.filter(worldNameChecker::isValidWorldFolder)
|
||||
.map(this::<File, DeleteFailureReason>worldActionResult)
|
||||
.getOrElse(() -> {
|
||||
@@ -780,91 +706,91 @@ public final class WorldManager {
|
||||
* @return The result of the clone.
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, CloneFailureReason> cloneWorld(@NotNull CloneWorldOptions options) {
|
||||
return parseCloneWorldOptionsNewWorld(options)
|
||||
return cloneWorldValidateWorld(options)
|
||||
.mapAttempt(this::cloneWorldCopyFolder)
|
||||
.mapAttempt(keyOrNameWithOptions -> {
|
||||
.mapAttempt(validatedOptions -> {
|
||||
ImportWorldOptions importWorldOptions = ImportWorldOptions
|
||||
.worldKeyOrName(keyOrNameWithOptions.keyOrName())
|
||||
.biome(options.fromWorld().getBiome())
|
||||
.environment(options.fromWorld().getEnvironment())
|
||||
.generator(options.fromWorld().getGenerator());
|
||||
.worldName(validatedOptions.newWorldName())
|
||||
.biome(validatedOptions.world().getBiome())
|
||||
.environment(validatedOptions.world().getEnvironment())
|
||||
.generator(validatedOptions.world().getGenerator());
|
||||
return importWorld(importWorldOptions).transform(CloneFailureReason.IMPORT_FAILED);
|
||||
})
|
||||
.onSuccess(newWorld -> {
|
||||
cloneWorldTransferData(options, newWorld);
|
||||
if (options.keepWorldConfig()) {
|
||||
// Special case for spawn location as it's considered a hidden config
|
||||
newWorld.setSpawnLocation(options.fromWorld().getSpawnLocation());
|
||||
newWorld.setSpawnLocation(options.world().getSpawnLocation());
|
||||
}
|
||||
saveWorldsConfig();
|
||||
pluginManager.callEvent(new MVWorldClonedEvent(newWorld, options.fromWorld()));
|
||||
pluginManager.callEvent(new MVWorldClonedEvent(newWorld, options.world()));
|
||||
});
|
||||
}
|
||||
|
||||
private Attempt<KeyOrNameWithOptions<CloneWorldOptions>, CloneFailureReason> parseCloneWorldOptionsNewWorld(
|
||||
private Attempt<CloneWorldOptions, CloneFailureReason> cloneWorldValidateWorld(
|
||||
@NotNull CloneWorldOptions options) {
|
||||
return options.newWorldKeyOrName()
|
||||
.fold(WorldKeyOrName::parse, Attempt::success)
|
||||
.transform(CloneFailureReason.INVALID_WORLDNAME)
|
||||
.failIf(keyOrName -> !worldNameChecker.isValidWorldName(keyOrName.usableName()),
|
||||
keyOrName -> worldActionResult(CloneFailureReason.INVALID_WORLDNAME, keyOrName))
|
||||
.failIf(keyOrName -> keyOrName.isKey() && !WorldCreatorCompatibility.canCreateWorldWithKey(),
|
||||
keyOrName -> worldActionResult(CloneFailureReason.NAMESPACEDKEY_UNSUPPORTED, keyOrName))
|
||||
.failIf(worldStore::isLoadedWorld,
|
||||
keyOrName -> worldActionResult(CloneFailureReason.WORLD_EXIST_LOADED, keyOrName))
|
||||
.failIf(worldStore::isWorld,
|
||||
keyOrName -> worldActionResult(CloneFailureReason.WORLD_EXIST_UNLOADED, keyOrName))
|
||||
.failIf(worldNameChecker::hasWorldFolder,
|
||||
keyOrName -> worldActionResult(CloneFailureReason.WORLD_EXIST_FOLDER, keyOrName))
|
||||
.failIf(keyOrName -> !worldNameChecker.isValidWorldFolder(options.fromWorld().getOfflineWorldFolder()),
|
||||
keyOrName -> worldActionResult(CloneFailureReason.FROM_WORLD_FOLDER_INVALID,
|
||||
options.fromWorld().getName()))
|
||||
.map(keyOrName -> new KeyOrNameWithOptions<>(keyOrName, options));
|
||||
String newWorldName = options.newWorldName();
|
||||
if (!worldNameChecker.isValidWorldName(newWorldName)) {
|
||||
Logging.severe("Invalid world name: " + newWorldName);
|
||||
return worldActionResult(CloneFailureReason.INVALID_WORLDNAME, newWorldName);
|
||||
}
|
||||
if (isLoadedWorld(newWorldName)) {
|
||||
Logging.severe("World already loaded when attempting to clone: " + newWorldName);
|
||||
return worldActionResult(CloneFailureReason.WORLD_EXIST_LOADED, newWorldName);
|
||||
}
|
||||
if (isWorld(newWorldName)) {
|
||||
Logging.severe("World already exist unloaded: " + newWorldName);
|
||||
return worldActionResult(CloneFailureReason.WORLD_EXIST_UNLOADED, newWorldName);
|
||||
}
|
||||
if (worldNameChecker.hasWorldFolder(newWorldName)) {
|
||||
return worldActionResult(CloneFailureReason.WORLD_EXIST_FOLDER, newWorldName);
|
||||
}
|
||||
return worldActionResult(options);
|
||||
}
|
||||
|
||||
private Attempt<KeyOrNameWithOptions<CloneWorldOptions>, CloneFailureReason> cloneWorldCopyFolder(
|
||||
@NotNull KeyOrNameWithOptions<CloneWorldOptions> keyOrNameWithOptions) {
|
||||
WorldKeyOrName newWorldkeyOrName = keyOrNameWithOptions.keyOrName();
|
||||
CloneWorldOptions options = keyOrNameWithOptions.options();
|
||||
private Attempt<CloneWorldOptions, CloneFailureReason> cloneWorldCopyFolder(@NotNull CloneWorldOptions options) {
|
||||
if (options.saveBukkitWorld()) {
|
||||
options.fromWorld().asLoadedWorld().peek(loadedWorld -> {
|
||||
Logging.finer("Saving world before cloning: " + loadedWorld.getName());
|
||||
loadedWorld.getBukkitWorld().peek(bukkitWorld -> WorldCompatibility.saveWithFlush(bukkitWorld, true));
|
||||
});
|
||||
Logging.finer("Saving bukkit world before cloning: " + options.world().getName());
|
||||
options.world().getBukkitWorld().peek(this::saveWorldWithFlush);
|
||||
}
|
||||
File worldFolder = WorldFolderResolver.resolve(options.fromWorld());
|
||||
File newWorldFolder = WorldFolderResolver.resolve(newWorldkeyOrName);
|
||||
File worldFolder = options.world().getBukkitWorld().map(World::getWorldFolder).get();
|
||||
File newWorldFolder = new File(Bukkit.getWorldContainer(), options.newWorldName());
|
||||
return fileUtils.copyFolder(worldFolder, newWorldFolder, CLONE_IGNORE_FILES).fold(
|
||||
exception -> worldActionResult(CloneFailureReason.COPY_FAILED,
|
||||
options.fromWorld().getName(), exception),
|
||||
success -> worldActionResult(keyOrNameWithOptions));
|
||||
options.world().getName(), exception),
|
||||
success -> worldActionResult(options));
|
||||
}
|
||||
|
||||
// This method is only available since 1.21
|
||||
private final Method saveWithFlush = ReflectHelper.getMethod(World.class, "save", boolean.class);
|
||||
private void saveWorldWithFlush(World world) {
|
||||
if (saveWithFlush != null) {
|
||||
Logging.fine("Using world save method with flush...");
|
||||
ReflectHelper.invokeMethod(world, saveWithFlush, true);
|
||||
} else {
|
||||
world.save();
|
||||
}
|
||||
}
|
||||
|
||||
private void cloneWorldTransferData(@NotNull CloneWorldOptions options, @NotNull LoadedMultiverseWorld newWorld) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = transferData(options, options.world());
|
||||
dataTransfer.pasteAllTo(newWorld);
|
||||
}
|
||||
|
||||
private DataTransfer<LoadedMultiverseWorld> transferData(
|
||||
@NotNull KeepWorldSettingsOptions options, @NotNull LoadedMultiverseWorld world) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = new DataTransfer<>();
|
||||
|
||||
if (options.keepWorldConfig()) {
|
||||
new DataTransfer<MultiverseWorld>()
|
||||
.addDataStore(new WorldConfigStore<>(), options.fromWorld())
|
||||
.pasteAllTo(newWorld);
|
||||
dataTransfer.addDataStore(new WorldConfigStore(), world);
|
||||
}
|
||||
if (options.keepGameRule()) {
|
||||
dataTransfer.addDataStore(new GameRulesStore(), world);
|
||||
}
|
||||
if (options.keepWorldBorder()) {
|
||||
dataTransfer.addDataStore(new WorldBorderStore(), world);
|
||||
}
|
||||
|
||||
newWorld.getBukkitWorld().peek(bukkitWorld -> {
|
||||
if (!options.keepWorldBorder()) {
|
||||
WorldBorder worldBorder = bukkitWorld.getWorldBorder();
|
||||
worldBorder.reset();
|
||||
}
|
||||
|
||||
if (!options.keepGameRule()) {
|
||||
Arrays.stream(bukkitWorld.getGameRules())
|
||||
.map(gameRuleName -> GameRule.getByName(gameRuleName))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(gameRule -> {
|
||||
Object gameRuleDefault = bukkitWorld.getGameRuleDefault(gameRule);
|
||||
if (gameRuleDefault != null) {
|
||||
bukkitWorld.setGameRule(gameRule, gameRuleDefault);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return dataTransfer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -875,16 +801,14 @@ public final class WorldManager {
|
||||
*/
|
||||
public Attempt<LoadedMultiverseWorld, RegenFailureReason> regenWorld(@NotNull RegenWorldOptions options) {
|
||||
LoadedMultiverseWorld world = options.world();
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = regenWorldTransferData(options, world);
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = transferData(options, world);
|
||||
boolean shouldKeepSpawnLocation = options.keepWorldConfig() && options.seed() == world.getSeed();
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
CreateWorldOptions createWorldOptions = CreateWorldOptions.worldKeyOrName(world.worldConfig.getWorldKeyOrName())
|
||||
.biome(world.getBiome())
|
||||
.bonusChest(world.getBukkitWorld().map(WorldCompatibility::hasBonusChest).getOrElse(false))
|
||||
|
||||
CreateWorldOptions createWorldOptions = CreateWorldOptions.worldName(world.getName())
|
||||
.environment(world.getEnvironment())
|
||||
.generateStructures(world.canGenerateStructures().getOrElse(true))
|
||||
.generator(world.getGenerator())
|
||||
.generatorSettings(world.getGeneratorSettings())
|
||||
.seed(options.seed())
|
||||
.useSpawnAdjust(!shouldKeepSpawnLocation && world.getAdjustSpawn())
|
||||
.worldType(world.getWorldType().getOrElse(WorldType.NORMAL))
|
||||
@@ -905,35 +829,18 @@ public final class WorldManager {
|
||||
});
|
||||
}
|
||||
|
||||
private DataTransfer<LoadedMultiverseWorld> regenWorldTransferData(
|
||||
@NotNull KeepWorldSettingsOptions options, @NotNull LoadedMultiverseWorld world) {
|
||||
DataTransfer<LoadedMultiverseWorld> dataTransfer = new DataTransfer<>();
|
||||
|
||||
if (options.keepWorldConfig()) {
|
||||
dataTransfer.addDataStore(new WorldConfigStore<>(), world);
|
||||
}
|
||||
if (options.keepGameRule()) {
|
||||
dataTransfer.addDataStore(new GameRulesStore(), world);
|
||||
}
|
||||
if (options.keepWorldBorder()) {
|
||||
dataTransfer.addDataStore(new WorldBorderStore(), world);
|
||||
}
|
||||
|
||||
return dataTransfer;
|
||||
}
|
||||
|
||||
private <T, F extends FailureReason> Attempt<T, F> worldActionResult(@NotNull T value) {
|
||||
return Attempt.success(value);
|
||||
}
|
||||
|
||||
private <T, F extends FailureReason> Attempt.Failure<T, F> worldActionResult(
|
||||
@NotNull F failureReason, @NotNull Object worldName) {
|
||||
return Attempt.failureRef(failureReason, Replace.WORLD.with(worldName));
|
||||
private <T, F extends FailureReason> Attempt<T, F> worldActionResult(
|
||||
@NotNull F failureReason, @NotNull String worldName) {
|
||||
return Attempt.failure(failureReason, Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
private <T, F extends FailureReason> Attempt.Failure<T, F> worldActionResult(
|
||||
private <T, F extends FailureReason> Attempt<T, F> worldActionResult(
|
||||
@NotNull F failureReason, @NotNull String worldName, @NotNull Throwable error) {
|
||||
return Attempt.failureRef(failureReason, Replace.WORLD.with(worldName), Replace.ERROR.with(error));
|
||||
return Attempt.failure(failureReason, Replace.WORLD.with(worldName), Replace.ERROR.with(error));
|
||||
}
|
||||
|
||||
private Attempt<WorldCreator, WorldCreatorFailureReason> addBiomeProviderToCreator(
|
||||
@@ -984,9 +891,7 @@ public final class WorldManager {
|
||||
}).onFailure(exception -> {
|
||||
Logging.severe("Failed to create bukkit world: " + worldCreator.name());
|
||||
exception.printStackTrace();
|
||||
}).andFinally(() -> {
|
||||
this.loadTracker.remove(worldCreator.name());
|
||||
}).fold(throwable -> Attempt.failure(WorldCreatorFailureReason.BUKKIT_CREATION_FAILED,
|
||||
}).andFinally(() -> this.loadTracker.remove(worldCreator.name())).fold(throwable -> Attempt.failure(WorldCreatorFailureReason.BUKKIT_CREATION_FAILED,
|
||||
Replace.WORLD.with(worldCreator.name()),
|
||||
Replace.ERROR.with(throwable)),
|
||||
Attempt::success);
|
||||
@@ -1029,13 +934,17 @@ public final class WorldManager {
|
||||
* Checks based on folder contents and name.
|
||||
*
|
||||
* @return A list of all potential worlds.
|
||||
*
|
||||
* @deprecated Use {@link PotentialWorldFinder#findPotentialWorlds()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public List<String> getPotentialWorlds() {
|
||||
return potentialWorldFinder.get().findPotentialWorlds();
|
||||
File[] files = Bukkit.getWorldContainer().listFiles();
|
||||
if (files == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(files)
|
||||
.filter(file -> !isWorld(file.getName()))
|
||||
.filter(worldNameChecker::isValidWorldFolder)
|
||||
.map(File::getName)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1057,7 +966,9 @@ public final class WorldManager {
|
||||
* @return The world if it exists.
|
||||
*/
|
||||
public Option<MultiverseWorld> getWorld(@Nullable String worldName) {
|
||||
return worldStore.getWorld(worldName);
|
||||
return getLoadedWorld(worldName)
|
||||
.map(world -> (MultiverseWorld) world)
|
||||
.orElse(() -> getUnloadedWorld(worldName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1068,7 +979,9 @@ public final class WorldManager {
|
||||
* @return The world if it exists.
|
||||
*/
|
||||
public Option<MultiverseWorld> getWorldByNameOrAlias(@Nullable String worldNameOrAlias) {
|
||||
return getWorld(worldStore.translateAlias(worldNameOrAlias));
|
||||
return getLoadedWorldByNameOrAlias(worldNameOrAlias)
|
||||
.map(world -> (MultiverseWorld) world)
|
||||
.orElse(() -> getUnloadedWorldByNameOrAlias(worldNameOrAlias));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1078,15 +991,14 @@ public final class WorldManager {
|
||||
* <p>If you want only unloaded worlds, use {@link #getUnloadedWorlds()}. If you want only loaded worlds, use
|
||||
* {@link #getLoadedWorlds()}.</p>
|
||||
*
|
||||
* <p>Note that this is an unmodifiable copy of the current worlds. It will not update as worlds are added/removed.
|
||||
* Call it everytime you need the most updated list of worlds.</p>
|
||||
*
|
||||
* @return A list of all worlds that may or may not be loaded.
|
||||
*/
|
||||
@Unmodifiable
|
||||
@NotNull
|
||||
public Collection<MultiverseWorld> getWorlds() {
|
||||
return worldStore.getWorlds();
|
||||
return worldsMap.values().stream()
|
||||
.map(world -> getLoadedWorld(world)
|
||||
.map(loadedWorld -> (MultiverseWorld) loadedWorld)
|
||||
.getOrElse(world))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1096,7 +1008,7 @@ public final class WorldManager {
|
||||
* @return True if the world is a world is known to multiverse, but may or may not be loaded.
|
||||
*/
|
||||
public boolean isWorld(@Nullable String worldName) {
|
||||
return worldName != null && worldStore.getWorld(worldName).isDefined();
|
||||
return worldName != null && worldsMap.containsKey(worldName.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1106,7 +1018,9 @@ public final class WorldManager {
|
||||
* @return The world if it exists.
|
||||
*/
|
||||
public Option<MultiverseWorld> getUnloadedWorld(@Nullable String worldName) {
|
||||
return worldStore.getUnloadedWorld(worldName);
|
||||
return isLoadedWorld(worldName)
|
||||
? Option.none()
|
||||
: Option.of(worldName).flatMap(name -> Option.of(worldsMap.get(name.toLowerCase(Locale.ENGLISH))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1116,7 +1030,18 @@ public final class WorldManager {
|
||||
* @return The world if it exists.
|
||||
*/
|
||||
public Option<MultiverseWorld> getUnloadedWorldByNameOrAlias(@Nullable String worldNameOrAlias) {
|
||||
return getUnloadedWorld(worldStore.translateAlias(worldNameOrAlias));
|
||||
return getUnloadedWorld(worldNameOrAlias).orElse(() -> getUnloadedWorldByAlias(worldNameOrAlias));
|
||||
}
|
||||
|
||||
private Option<MultiverseWorld> getUnloadedWorldByAlias(@Nullable String alias) {
|
||||
if (alias == null || alias.isEmpty()) {
|
||||
return Option.none();
|
||||
}
|
||||
String colourlessAlias = ChatTextFormatter.removeColor(alias);
|
||||
return Option.ofOptional(worldsMap.values().stream()
|
||||
.filter(world -> !world.isLoaded())
|
||||
.filter(world -> world.getColourlessAlias().equalsIgnoreCase(colourlessAlias))
|
||||
.findFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1124,10 +1049,10 @@ public final class WorldManager {
|
||||
*
|
||||
* @return A list of all worlds that are not loaded.
|
||||
*/
|
||||
@Unmodifiable
|
||||
@NotNull
|
||||
public Collection<MultiverseWorld> getUnloadedWorlds() {
|
||||
return worldStore.getUnloadedWorlds();
|
||||
return worldsMap.values().stream()
|
||||
.filter(world -> !world.isLoaded())
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1167,7 +1092,8 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable String worldName) {
|
||||
return Option.of(worldName).flatMap(worldStore::getLoadedWorld);
|
||||
return Option.of(worldName)
|
||||
.flatMap(name -> Option.of(loadedWorldsMap.get(name.toLowerCase(Locale.ENGLISH))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1177,21 +1103,28 @@ public final class WorldManager {
|
||||
* @return The multiverse world if it exists.
|
||||
*/
|
||||
public Option<LoadedMultiverseWorld> getLoadedWorldByNameOrAlias(@Nullable String worldNameOrAlias) {
|
||||
return getLoadedWorld(worldStore.translateAlias(worldNameOrAlias));
|
||||
return getLoadedWorld(worldNameOrAlias)
|
||||
.orElse(() -> getLoadedWorldByAlias(worldNameOrAlias));
|
||||
}
|
||||
|
||||
private Option<LoadedMultiverseWorld> getLoadedWorldByAlias(@Nullable String alias) {
|
||||
if (alias == null || alias.isEmpty()) {
|
||||
return Option.none();
|
||||
}
|
||||
return Option.ofOptional(loadedWorldsMap.values().stream()
|
||||
.filter(world -> world.getColourlessAlias()
|
||||
.equalsIgnoreCase(ChatTextFormatter.removeColor(alias)))
|
||||
.findFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a read-only list of all multiverse worlds that are loaded.
|
||||
*
|
||||
* <p>Note that this is an unmodifiable copy of the current worlds. It will not update as worlds are added/removed.
|
||||
* Call it everytime you need the most updated list of worlds.</p>
|
||||
*
|
||||
* @return A list of all multiverse worlds that are loaded.
|
||||
*/
|
||||
@Unmodifiable
|
||||
@NotNull
|
||||
public Collection<LoadedMultiverseWorld> getLoadedWorlds() {
|
||||
return worldStore.getLoadedWorlds();
|
||||
return loadedWorldsMap.values().stream()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1221,7 +1154,7 @@ public final class WorldManager {
|
||||
* @return True if the world is a multiverse world that is loaded.
|
||||
*/
|
||||
public boolean isLoadedWorld(@Nullable String worldName) {
|
||||
return worldName != null && worldStore.getLoadedWorld(worldName).isDefined();
|
||||
return worldName != null && loadedWorldsMap.containsKey(worldName.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1249,18 +1182,4 @@ public final class WorldManager {
|
||||
failure.printStackTrace();
|
||||
});
|
||||
}
|
||||
|
||||
WorldStore getWorldStore() {
|
||||
return worldStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple pair wrapper for convenience to pass both the world key or name and the options together between methods
|
||||
* when parsing world options.
|
||||
*
|
||||
* @param keyOrName The world key or name.
|
||||
* @param options The world options.
|
||||
* @param <T> The world options type.
|
||||
*/
|
||||
private record KeyOrNameWithOptions<T>(WorldKeyOrName keyOrName, T options) { }
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.world;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.utils.CaseInsensitiveStringMap;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
final class WorldStore {
|
||||
|
||||
/**
|
||||
* Only loaded worlds, i.e. will be empty if all worlds are unloaded.
|
||||
*/
|
||||
private final List<LoadedMultiverseWorld> loadedList;
|
||||
|
||||
/**
|
||||
* Only unloaded worlds, i.e. will be empty if all worlds are loaded.
|
||||
*/
|
||||
private final List<MultiverseWorld> unloadedList;
|
||||
|
||||
/**
|
||||
* Contains single reference to all worlds, either loaded or unloaded ref depending on the world's current status.
|
||||
*/
|
||||
private final List<MultiverseWorld> worldList;
|
||||
|
||||
/**
|
||||
* Maps all key and key to loaded ref.
|
||||
*/
|
||||
private final Map<String, LoadedMultiverseWorld> loadedMap;
|
||||
|
||||
/**
|
||||
* Maps all key and key to unloaded ref.
|
||||
*/
|
||||
private final Map<String, MultiverseWorld> unloadedMap;
|
||||
|
||||
/**
|
||||
* Maps colorless alias to world key. As alias may not be unique, this is a multimap.
|
||||
*/
|
||||
private final Multimap<String, String> aliasMap;
|
||||
|
||||
@Inject
|
||||
private WorldStore() {
|
||||
this.loadedList = new ArrayList<>();
|
||||
this.unloadedList = new ArrayList<>();
|
||||
this.worldList = new ArrayList<>();
|
||||
|
||||
this.loadedMap = new CaseInsensitiveStringMap<>();
|
||||
this.unloadedMap = new CaseInsensitiveStringMap<>();
|
||||
this.aliasMap = HashMultimap.create();
|
||||
}
|
||||
|
||||
void putUnloadedWorld(MultiverseWorld world) {
|
||||
if (world instanceof LoadedMultiverseWorld) {
|
||||
throw new IllegalArgumentException("Loaded world cannot be put in unloaded map");
|
||||
}
|
||||
|
||||
unloadedMap.put(world.getKey().toString(), world);
|
||||
unloadedMap.put(world.getName(), world);
|
||||
|
||||
unloadedList.add(world);
|
||||
worldList.add(world);
|
||||
}
|
||||
|
||||
void putLoadedWorld(LoadedMultiverseWorld world) {
|
||||
loadedMap.put(world.getKey().toString(), world);
|
||||
loadedMap.put(world.getName(), world);
|
||||
|
||||
unloadedList.remove(unloadedMap.get(world.getKey().toString()));
|
||||
worldList.remove(unloadedMap.get(world.getKey().toString()));
|
||||
|
||||
loadedList.add(world);
|
||||
worldList.add(world);
|
||||
}
|
||||
|
||||
void removeWorld(MultiverseWorld world) {
|
||||
LoadedMultiverseWorld loadedRef = loadedMap.get(world.getKey().toString());
|
||||
MultiverseWorld unloadedRef = unloadedMap.get(world.getKey().toString());
|
||||
|
||||
// remove from list
|
||||
unloadedList.remove(unloadedRef);
|
||||
loadedList.remove(loadedRef);
|
||||
worldList.remove(unloadedRef);
|
||||
worldList.remove(loadedRef);
|
||||
|
||||
// remove from maps
|
||||
loadedMap.remove(world.getKey().toString());
|
||||
unloadedMap.remove(world.getName());
|
||||
|
||||
unloadedMap.remove(world.getKey().toString());
|
||||
unloadedMap.remove(world.getName());
|
||||
|
||||
// remove alias
|
||||
aliasMap.remove(world.getColourlessAlias(), world.getKey().toString());
|
||||
}
|
||||
|
||||
void removeLoadedWorld(LoadedMultiverseWorld world) {
|
||||
// remove from list
|
||||
loadedList.remove(world);
|
||||
worldList.remove(world);
|
||||
|
||||
// remove from maps
|
||||
loadedMap.remove(world.getKey().toString());
|
||||
loadedMap.remove(world.getName());
|
||||
|
||||
// Add back unloaded
|
||||
unloadedList.add(unloadedMap.get(world.getKey().toString()));
|
||||
}
|
||||
|
||||
void changeAlias(@Nullable String oldAlias, @Nullable String newAlias, @NotNull MultiverseWorld world) {
|
||||
if (Objects.equals(oldAlias, newAlias)) {
|
||||
// nothing changed, ignore
|
||||
return;
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(oldAlias)) {
|
||||
aliasMap.remove(oldAlias, world.getKey().toString());
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(newAlias)) {
|
||||
aliasMap.put(newAlias, world.getKey().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Unmodifiable
|
||||
@NotNull
|
||||
List<MultiverseWorld> getWorlds() {
|
||||
return List.copyOf(worldList);
|
||||
}
|
||||
|
||||
@Unmodifiable
|
||||
@NotNull
|
||||
List<LoadedMultiverseWorld> getLoadedWorlds() {
|
||||
return List.copyOf(loadedList);
|
||||
}
|
||||
|
||||
@Unmodifiable
|
||||
@NotNull
|
||||
List<MultiverseWorld> getUnloadedWorlds() {
|
||||
return List.copyOf(unloadedList);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getWorld(@Nullable WorldKeyOrName keyOrName) {
|
||||
return keyOrName == null
|
||||
? Option.none()
|
||||
: getWorld(keyOrName.usableKey()).orElse(() -> getWorld(keyOrName.usableName()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getWorld(@Nullable NamespacedKey worldKey) {
|
||||
return Option.of(worldKey).map(NamespacedKey::toString).flatMap(this::getWorld);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getWorld(@Nullable String worldKeyString) {
|
||||
return Option.of((MultiverseWorld) loadedMap.get(worldKeyString))
|
||||
.orElse(() -> Option.of(unloadedMap.get(worldKeyString)));
|
||||
}
|
||||
|
||||
boolean isWorld(@Nullable WorldKeyOrName keyOrName) {
|
||||
return keyOrName != null && (isWorld(keyOrName.usableKey()) || isWorld(keyOrName.usableName()));
|
||||
}
|
||||
|
||||
boolean isWorld(@Nullable NamespacedKey worldKey) {
|
||||
return getWorld(worldKey).isDefined();
|
||||
}
|
||||
|
||||
boolean isWorld(@Nullable String worldKeyString) {
|
||||
return getWorld(worldKeyString).isDefined();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable WorldKeyOrName keyOrName) {
|
||||
return keyOrName == null
|
||||
? Option.none()
|
||||
: getLoadedWorld(keyOrName.usableKey()).orElse(() -> getLoadedWorld(keyOrName.usableName()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable NamespacedKey worldKey) {
|
||||
return Option.of(worldKey).map(NamespacedKey::toString).flatMap(this::getLoadedWorld);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable String worldKeyString) {
|
||||
return Option.of(loadedMap.get(worldKeyString))
|
||||
.filter(MultiverseWorld::isLoaded);
|
||||
}
|
||||
|
||||
boolean isLoadedWorld(@Nullable WorldKeyOrName keyOrName) {
|
||||
return keyOrName != null && (isLoadedWorld(keyOrName.usableKey()) || isLoadedWorld(keyOrName.usableName()));
|
||||
}
|
||||
|
||||
boolean isLoadedWorld(@Nullable NamespacedKey worldKey) {
|
||||
return getLoadedWorld(worldKey).isDefined();
|
||||
}
|
||||
|
||||
boolean isLoadedWorld(@Nullable String worldKeyString) {
|
||||
return getLoadedWorld(worldKeyString).isDefined();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getUnloadedWorld(@Nullable WorldKeyOrName keyOrName) {
|
||||
return keyOrName == null
|
||||
? Option.none()
|
||||
: getUnloadedWorld(keyOrName.usableKey()).orElse(() -> getUnloadedWorld(keyOrName.usableName()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getUnloadedWorld(@Nullable NamespacedKey worldKey) {
|
||||
return Option.of(worldKey).map(NamespacedKey::toString).flatMap(this::getUnloadedWorld);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getUnloadedWorld(@Nullable String worldKeyString) {
|
||||
return Option.of(unloadedMap.get(worldKeyString))
|
||||
.filter(world -> !world.isLoaded());
|
||||
}
|
||||
|
||||
boolean isUnloadedWorld(@Nullable WorldKeyOrName keyOrName) {
|
||||
return keyOrName != null && (isUnloadedWorld(keyOrName.usableKey()) || isUnloadedWorld(keyOrName.usableName()));
|
||||
}
|
||||
|
||||
boolean isUnloadedWorld(@Nullable NamespacedKey worldKey) {
|
||||
return getUnloadedWorld(worldKey).isDefined();
|
||||
}
|
||||
|
||||
boolean isUnloadedWorld(@Nullable String worldKeyString) {
|
||||
return getUnloadedWorld(worldKeyString).isDefined();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getUnloadedWorldRef(@Nullable WorldKeyOrName keyOrName) {
|
||||
return keyOrName == null
|
||||
? Option.none()
|
||||
: getUnloadedWorldRef(keyOrName.usableKey()).orElse(() -> getUnloadedWorldRef(keyOrName.usableName()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getUnloadedWorldRef(@Nullable NamespacedKey worldKey) {
|
||||
return Option.of(worldKey).map(NamespacedKey::toString).flatMap(this::getUnloadedWorldRef);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Option<MultiverseWorld> getUnloadedWorldRef(@Nullable String worldKeyString) {
|
||||
return Option.of(unloadedMap.get(worldKeyString));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String translateAlias(@Nullable String worldNameOrAlias) {
|
||||
if (Strings.isNullOrEmpty(worldNameOrAlias)) {
|
||||
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()
|
||||
.findFirst()
|
||||
.orElse(worldNameOrAlias);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
@@ -18,9 +17,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyParseFailReason;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
@@ -32,7 +28,7 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
final class WorldsConfigManager {
|
||||
private static final String CONFIG_FILENAME = "worlds.yml";
|
||||
|
||||
private final SortedMap<WorldKeyOrName, WorldConfig> worldConfigMap;
|
||||
private final SortedMap<String, WorldConfig> worldConfigMap;
|
||||
private final File worldConfigFile;
|
||||
private YamlConfiguration worldsConfig;
|
||||
|
||||
@@ -51,7 +47,7 @@ final class WorldsConfigManager {
|
||||
*
|
||||
* @return A {@link NewAndRemovedWorlds} record.
|
||||
*/
|
||||
Try<NewAndRemovedWorlds> load() {
|
||||
public Try<NewAndRemovedWorlds> load() {
|
||||
return Try.of(() -> {
|
||||
loadWorldYmlFile();
|
||||
return parseNewAndRemovedWorlds();
|
||||
@@ -117,7 +113,7 @@ final class WorldsConfigManager {
|
||||
config.set("worlds", null);
|
||||
|
||||
for (Map.Entry<String, ConfigurationSection> entry : worldConfigMap.entrySet()) {
|
||||
config.set(encodeConfigKey(entry.getKey()), entry.getValue());
|
||||
config.set(encodeWorldName(entry.getKey()), entry.getValue());
|
||||
}
|
||||
config.save(worldConfigFile);
|
||||
})
|
||||
@@ -166,39 +162,33 @@ final class WorldsConfigManager {
|
||||
* @return A tuple containing a list of the new WorldConfigs added and a list of the worlds removed from the config.
|
||||
*/
|
||||
private NewAndRemovedWorlds parseNewAndRemovedWorlds() {
|
||||
List<WorldKeyOrName> allWorldsInConfig = worldsConfig.getKeys(false)
|
||||
List<String> allWorldsInConfig = worldsConfig.getKeys(false)
|
||||
.stream()
|
||||
.map(keyStr -> decodeConfigKey(keyStr)
|
||||
.onFailure(reason ->
|
||||
Logging.warning("Failed to parse world key '%s' in config: %s", keyStr, reason))
|
||||
.getOrNull())
|
||||
.filter(Objects::nonNull)
|
||||
.map(this::decodeWorldName)
|
||||
.toList();
|
||||
|
||||
List<WorldConfig> newWorldsAdded = new ArrayList<>();
|
||||
|
||||
for (WorldKeyOrName worldKeyStr : allWorldsInConfig) {
|
||||
getWorldConfig(worldKeyStr)
|
||||
.peek(config -> config.load(getWorldConfigSection(worldKeyStr))
|
||||
.onFailure(e -> Logging.warning("Failed to load world config for world '%s': %s",
|
||||
worldKeyStr, e.getMessage())))
|
||||
for (String worldName : allWorldsInConfig) {
|
||||
getWorldConfig(worldName)
|
||||
.peek(config -> config.load(getWorldConfigSection(worldName)))
|
||||
.orElse(() -> {
|
||||
WorldConfig newWorldConfig = new WorldConfig(
|
||||
worldKeyStr,
|
||||
getWorldConfigSection(worldKeyStr),
|
||||
worldName,
|
||||
getWorldConfigSection(worldName),
|
||||
multiverseCore);
|
||||
worldConfigMap.put(worldKeyStr, newWorldConfig);
|
||||
worldConfigMap.put(worldName, newWorldConfig);
|
||||
newWorldsAdded.add(newWorldConfig);
|
||||
return Option.of(newWorldConfig);
|
||||
})
|
||||
.peek(WorldConfig::save);
|
||||
}
|
||||
|
||||
List<WorldKeyOrName> worldsRemoved = worldConfigMap.keySet().stream()
|
||||
List<String> worldsRemoved = worldConfigMap.keySet().stream()
|
||||
.filter(worldName -> !allWorldsInConfig.contains(worldName))
|
||||
.toList();
|
||||
|
||||
for (WorldKeyOrName s : worldsRemoved) {
|
||||
for (String s : worldsRemoved) {
|
||||
worldConfigMap.remove(s);
|
||||
}
|
||||
|
||||
@@ -210,7 +200,7 @@ final class WorldsConfigManager {
|
||||
*
|
||||
* @return Whether the worlds.yml file has been loaded.
|
||||
*/
|
||||
boolean isLoaded() {
|
||||
public boolean isLoaded() {
|
||||
return worldsConfig != null;
|
||||
}
|
||||
|
||||
@@ -219,17 +209,17 @@ final class WorldsConfigManager {
|
||||
*
|
||||
* @return Whether the save was successful or the error that occurred.
|
||||
*/
|
||||
Try<Void> save() {
|
||||
public Try<Void> save() {
|
||||
return Try.run(() -> {
|
||||
if (!isLoaded()) {
|
||||
throw new IllegalStateException("WorldsConfigManager is not loaded!");
|
||||
}
|
||||
worldsConfig = new YamlConfiguration();
|
||||
worldConfigMap.forEach((keyOrName, worldConfig) -> {
|
||||
worldConfigMap.forEach((worldName, worldConfig) -> {
|
||||
worldConfig.save().onFailure(e -> {
|
||||
throw new RuntimeException("Failed to save world %s in config: " + keyOrName, e);
|
||||
throw new RuntimeException("Failed to save world config: " + worldName, e);
|
||||
});
|
||||
worldsConfig.set(encodeConfigKey(keyOrName), worldConfig.getConfigurationSection());
|
||||
worldsConfig.set(encodeWorldName(worldName), worldConfig.getConfigurationSection());
|
||||
});
|
||||
worldsConfig.save(worldConfigFile);
|
||||
}).onFailure(e -> {
|
||||
@@ -238,81 +228,63 @@ final class WorldsConfigManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link WorldConfig} instance of a world in the worlds.yml file.
|
||||
* Gets the {@link WorldConfig} instance of all worlds in the worlds.yml file.
|
||||
*
|
||||
* @param keyOrName The target key to get
|
||||
* @param worldName The name of the world to check.
|
||||
* @return The {@link WorldConfig} instance of the world, or empty option if it doesn't exist.
|
||||
*/
|
||||
@NotNull Option<WorldConfig> getWorldConfig(@NotNull WorldKeyOrName keyOrName) {
|
||||
return Option.of(worldConfigMap.get(keyOrName));
|
||||
}
|
||||
|
||||
@NotNull WorldConfig migrateWorldConfigKey(@NotNull WorldConfig worldConfig, @NotNull NamespacedKey toKey) {
|
||||
worldConfig.save();
|
||||
WorldKeyOrName newKeyOrName = WorldKeyOrName.parseKey(toKey);
|
||||
WorldConfig migratedWorldConfig = new WorldConfig(
|
||||
newKeyOrName,
|
||||
worldConfig.getConfigurationSection(),
|
||||
multiverseCore
|
||||
);
|
||||
deleteWorldConfig(worldConfig);
|
||||
worldConfigMap.put(newKeyOrName, migratedWorldConfig);
|
||||
return migratedWorldConfig;
|
||||
public @NotNull Option<WorldConfig> getWorldConfig(@NotNull String worldName) {
|
||||
return Option.of(worldConfigMap.get(worldName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new world to the worlds.yml file. If a world with the given name already exists, an exception is thrown.
|
||||
*
|
||||
* @param namespacedKey The target key to add
|
||||
* @param worldName The name of the world to add.
|
||||
* @return The newly created {@link WorldConfig} instance.
|
||||
*/
|
||||
@NotNull WorldConfig addWorldConfig(@NotNull NamespacedKey namespacedKey) {
|
||||
WorldKeyOrName keyOrName = WorldKeyOrName.parseKey(namespacedKey);
|
||||
if (worldConfigMap.containsKey(keyOrName)) {
|
||||
throw new IllegalStateException("WorldConfig for world " + namespacedKey + " already exists.");
|
||||
public @NotNull WorldConfig addWorldConfig(@NotNull String worldName) {
|
||||
if (worldConfigMap.containsKey(worldName)) {
|
||||
throw new IllegalArgumentException("WorldConfig for world " + worldName + " already exists.");
|
||||
}
|
||||
WorldConfig worldConfig = new WorldConfig(keyOrName, getWorldConfigSection(keyOrName), multiverseCore);
|
||||
worldConfigMap.put(keyOrName, worldConfig);
|
||||
WorldConfig worldConfig = new WorldConfig(worldName, getWorldConfigSection(worldName), multiverseCore);
|
||||
worldConfigMap.put(worldName, worldConfig);
|
||||
return worldConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the world config for the given world.
|
||||
*
|
||||
* @param worldConfig The target world config to delete
|
||||
* @param worldName The name of the world to delete.
|
||||
*/
|
||||
void deleteWorldConfig(@NotNull WorldConfig worldConfig) {
|
||||
worldConfigMap.remove(worldConfig.getWorldKeyOrName());
|
||||
worldsConfig.set(encodeConfigKey(worldConfig.getWorldKeyOrName()), null);
|
||||
public void deleteWorldConfig(@NotNull String worldName) {
|
||||
worldConfigMap.remove(worldName);
|
||||
worldsConfig.set(encodeWorldName(worldName), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ConfigurationSection} for the given world in the worlds.yml file. If the section doesn't exist,
|
||||
* it is created.
|
||||
*
|
||||
* @param keyOrName the config key of the world to get the configuration section for.
|
||||
* @param worldName The name of the world.
|
||||
* @return The {@link ConfigurationSection} for the given world.
|
||||
*/
|
||||
@NotNull
|
||||
private ConfigurationSection getWorldConfigSection(@NotNull WorldKeyOrName keyOrName) {
|
||||
String encodeWorldKey = encodeConfigKey(keyOrName);
|
||||
ConfigurationSection section = worldsConfig.getConfigurationSection(encodeWorldKey);
|
||||
return section == null ? worldsConfig.createSection(encodeWorldKey) : section;
|
||||
}
|
||||
|
||||
private String encodeConfigKey(@NotNull WorldKeyOrName worldKeyOrName) {
|
||||
return encodeConfigKey(worldKeyOrName.serialise());
|
||||
private ConfigurationSection getWorldConfigSection(String worldName) {
|
||||
worldName = encodeWorldName(worldName);
|
||||
return worldsConfig.isConfigurationSection(worldName)
|
||||
? worldsConfig.getConfigurationSection(worldName)
|
||||
: worldsConfig.createSection(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove dot . with [dot] as it is a special character in YAML that causes sub-path issues.
|
||||
* Dot is a special character in YAML that causes sub-path issues.
|
||||
*/
|
||||
private String encodeConfigKey(@NotNull String worldName) {
|
||||
private String encodeWorldName(String worldName) {
|
||||
return worldName.replace(".", "[dot]");
|
||||
}
|
||||
|
||||
private Attempt<WorldKeyOrName, WorldKeyParseFailReason> decodeConfigKey(@NotNull String worldKeyStr) {
|
||||
return WorldKeyOrName.parse(worldKeyStr.replace("[dot]", "."));
|
||||
private String decodeWorldName(String worldName) {
|
||||
return worldName.replace("[dot]", ".");
|
||||
}
|
||||
|
||||
private static final class ConfigMigratedException extends RuntimeException {
|
||||
|
||||
+25
-21
@@ -1,36 +1,40 @@
|
||||
package org.mvplugins.multiverse.core.world.biomeprovider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A parser for {@link SingleBiomeProvider}
|
||||
*/
|
||||
final class SingleBiomeProviderParser implements BiomeProviderParser {
|
||||
|
||||
private List<String> biomes;
|
||||
private List<String> biomes;
|
||||
|
||||
@Override
|
||||
public BiomeProvider parseBiomeProvider(@NotNull String worldName, @NotNull String params) {
|
||||
NamespacedKey biomeKey = NamespacedKey.fromString(params.toLowerCase(Locale.ENGLISH));
|
||||
return new SingleBiomeProvider(Registry.BIOME.get(biomeKey));
|
||||
}
|
||||
@Override
|
||||
public BiomeProvider parseBiomeProvider(@NotNull String worldName, @NotNull String params) {
|
||||
final NamespacedKey biomeKey = NamespacedKey.fromString(StringUtils.lowerCase(params, Locale.ENGLISH));
|
||||
return new SingleBiomeProvider(Registry.BIOME.get(biomeKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> suggestParams(@NotNull String currentInput) {
|
||||
if (biomes == null) {
|
||||
biomes = Registry.BIOME.stream()
|
||||
.map(biome -> biome.getKey().getKey().toLowerCase(Locale.ENGLISH))
|
||||
.toList();
|
||||
}
|
||||
return biomes;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Collection<String> suggestParams(@NotNull String currentInput) {
|
||||
if (biomes == null) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
for (Biome biome : Registry.BIOME) {
|
||||
result.add(StringUtils.lowerCase(biome.getKey().getKey(), Locale.ENGLISH));
|
||||
}
|
||||
biomes = result;
|
||||
}
|
||||
return biomes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.world.entity;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
@@ -41,24 +40,25 @@ public final class EntityPurger {
|
||||
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 -> {
|
||||
world.getBukkitWorld().peek(bukkitWorld -> {
|
||||
for (Entity entity : bukkitWorld.getEntities()) {
|
||||
if (spawnCategoriesSet.contains(entity.getSpawnCategory())) {
|
||||
entity.remove();
|
||||
purgeCount.incrementAndGet();
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
return purgeCount.get();
|
||||
}
|
||||
|
||||
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();
|
||||
}));
|
||||
world.getBukkitWorld().peek(bukkitWorld -> {
|
||||
for (Entity entity : bukkitWorld.getEntities()) {
|
||||
entity.remove();
|
||||
purgeCount.incrementAndGet();
|
||||
}
|
||||
});
|
||||
return purgeCount.get();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
@@ -34,11 +33,6 @@ public final class EntitySpawnConfig {
|
||||
));
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public boolean shouldAllowSpawn(EntityType entityType) {
|
||||
return getSpawnCategoryConfig(SpawnCategoryMapper.getSpawnCategory(entityType)).shouldAllowSpawn(entityType);
|
||||
}
|
||||
|
||||
public boolean shouldAllowSpawn(Entity entity) {
|
||||
return getSpawnCategoryConfig(entity.getSpawnCategory()).shouldAllowSpawn(entity);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -19,27 +21,26 @@ import java.util.Map;
|
||||
*/
|
||||
final class SpawnCategoryMapper {
|
||||
|
||||
private static final Map<EntityType, SpawnCategory> entityTypeToSpawnCategoryMap;
|
||||
private static final Map<SpawnCategory, List<EntityType>> spawnCategoryMap;
|
||||
|
||||
static {
|
||||
entityTypeToSpawnCategoryMap = new HashMap<>();
|
||||
spawnCategoryMap = new HashMap<>();
|
||||
buildSpawnCategoryMap();
|
||||
}
|
||||
|
||||
private static Map<SpawnCategory, List<EntityType>> spawnCategoryMap;
|
||||
|
||||
private static void buildSpawnCategoryMap() {
|
||||
Class<?> entityTypeClass = ReflectHelper.tryGetClass("net.minecraft.world.entity.EntityType").getOrNull();
|
||||
spawnCategoryMap = new HashMap<>();
|
||||
|
||||
Class<?> entityTypeClass = ReflectHelper.getClass("net.minecraft.world.entity.EntityType");
|
||||
if (entityTypeClass == null) {
|
||||
Logging.warning("Failed to find EntityType class. SpawnCategoryMapper will not work.");
|
||||
return;
|
||||
}
|
||||
Method getCategoryMethod = ReflectHelper.tryGetMethod(entityTypeClass, "getCategory").getOrNull();
|
||||
Method getCategoryMethod = ReflectHelper.getMethod(entityTypeClass, "getCategory");
|
||||
if (getCategoryMethod == null) {
|
||||
Logging.warning("Failed to find getCategory method. SpawnCategoryMapper will not work.");
|
||||
return;
|
||||
}
|
||||
Class<?> craftSpawnCategoryClass = ReflectHelper.tryGetClass("org.bukkit.craftbukkit.util.CraftSpawnCategory").getOrNull();
|
||||
Class<?> craftSpawnCategoryClass = ReflectHelper.getClass("org.bukkit.craftbukkit.util.CraftSpawnCategory");
|
||||
if (craftSpawnCategoryClass == null) {
|
||||
Logging.warning("Failed to find CraftSpawnCategory class. SpawnCategoryMapper will not work.");
|
||||
return;
|
||||
@@ -52,26 +53,25 @@ final class SpawnCategoryMapper {
|
||||
Logging.warning("Failed to find toBukkit method. SpawnCategoryMapper will not work.");
|
||||
return;
|
||||
}
|
||||
Arrays.stream(entityTypeClass.getFields()).forEach(entityTypeField -> Try.of(() -> EntityType.valueOf(entityTypeField.getName()))
|
||||
.peek(entityType -> ReflectHelper.tryGetStaticFieldValue(entityTypeField, entityTypeClass)
|
||||
.flatMap(nmsEntityType -> ReflectHelper.tryInvokeMethod(nmsEntityType, getCategoryMethod))
|
||||
.flatMap(nsmMobCategory -> ReflectHelper.tryInvokeStaticMethod(toBukkitMethod, nsmMobCategory))
|
||||
.filter(bukkitSpawnCategory -> bukkitSpawnCategory instanceof SpawnCategory)
|
||||
.map(bukkitSpawnCategory -> (SpawnCategory) bukkitSpawnCategory)
|
||||
.peek(bukkitSpawnCategory -> entityTypeToSpawnCategoryMap.put(entityType, bukkitSpawnCategory))
|
||||
.peek(bukkitSpawnCategory -> spawnCategoryMap
|
||||
.computeIfAbsent(bukkitSpawnCategory, ignore -> new ArrayList<>())
|
||||
.add(entityType))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spawn category that the given entity type is in.
|
||||
*
|
||||
* @param entityType The entity type
|
||||
* @return The spawn category that the given entity type is in, or null if it cannot be determined
|
||||
*/
|
||||
static SpawnCategory getSpawnCategory(EntityType entityType) {
|
||||
return entityTypeToSpawnCategoryMap.get(entityType);
|
||||
Field[] entityTypeFields = entityTypeClass.getFields();
|
||||
for (Field entityTypeField : entityTypeFields) {
|
||||
String entityName = entityTypeField.getName();
|
||||
EntityType entityType = Try.of(() -> EntityType.valueOf(entityName)).getOrNull();
|
||||
if (entityType == null) {
|
||||
continue;
|
||||
}
|
||||
Object nmsEntityType = ReflectHelper.getFieldValue(null, entityTypeField, entityTypeClass);
|
||||
Object nsmMobCategory = ReflectHelper.invokeMethod(nmsEntityType, getCategoryMethod);
|
||||
if (nsmMobCategory == null) {
|
||||
continue;
|
||||
}
|
||||
Object bukkitSpawnCategory = ReflectHelper.invokeMethod(null, toBukkitMethod, nsmMobCategory);
|
||||
if (!(bukkitSpawnCategory instanceof SpawnCategory spawnCategory)) {
|
||||
continue;
|
||||
}
|
||||
spawnCategoryMap.computeIfAbsent(spawnCategory, ignore -> new ArrayList<>())
|
||||
.add(entityType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,9 +9,7 @@ import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.WorldBorderCompatibility;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* A data store for storing and restoring data from an object.
|
||||
@@ -92,14 +90,14 @@ public interface DataStore<T> {
|
||||
/**
|
||||
* A {@link DataStore} for storing and restoring world properties for a multiverse world.
|
||||
*/
|
||||
class WorldConfigStore<T extends MultiverseWorld> implements DataStore<T> {
|
||||
class WorldConfigStore implements DataStore<LoadedMultiverseWorld> {
|
||||
private Map<String, Object> configMap;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public WorldConfigStore<T> copyFrom(T world) {
|
||||
public WorldConfigStore copyFrom(LoadedMultiverseWorld world) {
|
||||
this.configMap = new HashMap<>();
|
||||
StringPropertyHandle worldPropertyHandler = world.getStringPropertyHandle();
|
||||
worldPropertyHandler.getAllPropertyNames().forEach(name -> worldPropertyHandler.getProperty(name)
|
||||
@@ -113,7 +111,7 @@ public interface DataStore<T> {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public WorldConfigStore<T> pasteTo(T world) {
|
||||
public WorldConfigStore pasteTo(LoadedMultiverseWorld world) {
|
||||
if (configMap == null) {
|
||||
return this;
|
||||
}
|
||||
@@ -141,16 +139,14 @@ public interface DataStore<T> {
|
||||
*/
|
||||
@Override
|
||||
public WorldBorderStore copyFrom(LoadedMultiverseWorld world) {
|
||||
world.getBukkitWorld()
|
||||
.map(World::getWorldBorder)
|
||||
.peek(worldBorder -> {
|
||||
borderCenterX = worldBorder.getCenter().getX();
|
||||
borderCenterZ = worldBorder.getCenter().getZ();
|
||||
borderDamageAmount = worldBorder.getDamageAmount();
|
||||
borderDamageBuffer = worldBorder.getDamageBuffer();
|
||||
borderSize = worldBorder.getSize();
|
||||
borderTimeRemaining = WorldBorderCompatibility.getWarningTimeTicks(worldBorder);
|
||||
});
|
||||
world.getBukkitWorld().peek(bukkitWorld -> {
|
||||
borderCenterX = bukkitWorld.getWorldBorder().getCenter().getX();
|
||||
borderCenterZ = bukkitWorld.getWorldBorder().getCenter().getZ();
|
||||
borderDamageAmount = bukkitWorld.getWorldBorder().getDamageAmount();
|
||||
borderDamageBuffer = bukkitWorld.getWorldBorder().getDamageBuffer();
|
||||
borderSize = bukkitWorld.getWorldBorder().getSize();
|
||||
borderTimeRemaining = bukkitWorld.getWorldBorder().getWarningTime();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -159,15 +155,13 @@ public interface DataStore<T> {
|
||||
*/
|
||||
@Override
|
||||
public WorldBorderStore pasteTo(LoadedMultiverseWorld world) {
|
||||
world.getBukkitWorld()
|
||||
.map(World::getWorldBorder)
|
||||
.peek(worldBorder -> {
|
||||
worldBorder.setCenter(borderCenterX, borderCenterZ);
|
||||
worldBorder.setDamageAmount(borderDamageAmount);
|
||||
worldBorder.setDamageBuffer(borderDamageBuffer);
|
||||
worldBorder.setSize(borderSize);
|
||||
WorldBorderCompatibility.setWarningTimeTicks(worldBorder, borderTimeRemaining);
|
||||
});
|
||||
world.getBukkitWorld().peek(bukkitWorld -> {
|
||||
bukkitWorld.getWorldBorder().setCenter(borderCenterX, borderCenterZ);
|
||||
bukkitWorld.getWorldBorder().setDamageAmount(borderDamageAmount);
|
||||
bukkitWorld.getWorldBorder().setDamageBuffer(borderDamageBuffer);
|
||||
bukkitWorld.getWorldBorder().setSize(borderSize);
|
||||
bukkitWorld.getWorldBorder().setWarningTime(borderTimeRemaining);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
@@ -20,24 +19,6 @@ import java.util.regex.Pattern;
|
||||
@Service
|
||||
public final class DimensionFinder {
|
||||
|
||||
/**
|
||||
* Default world for nether worlds, The default is "%overworld_nether%", which means that the nether world of
|
||||
* "world" will be named "world_nether".
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static final DimensionFormat DEFAULT_NETHER_FORMAT = new DimensionFormat("%overworld%_nether");
|
||||
|
||||
/**
|
||||
* Default world name format for end worlds. The default is "%overworld%_the_end", which means that the end world
|
||||
* of "world" will be named "world_the_end".
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static final DimensionFormat DEFAULT_END_FORMAT = new DimensionFormat("%overworld%_the_end");
|
||||
|
||||
private final CoreConfig config;
|
||||
private final WorldManager worldManager;
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportFailureReason;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
@@ -86,24 +84,6 @@ public final class PlayerWorldTeleporter {
|
||||
.getOrElse(AsyncAttemptsAggregate::emptySuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers all players from the given world to the given destination.
|
||||
*
|
||||
* @param world The world to transfer players from.
|
||||
* @param destinationInstance The destination instance to transfer players to.
|
||||
* @return A list of async futures that represent the teleportation result of each player.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public AsyncAttemptsAggregate<Void, TeleportFailureReason> transferAllFromWorldToDestination(
|
||||
@NotNull LoadedMultiverseWorld world,
|
||||
@NotNull DestinationInstance<?, ?> destinationInstance) {
|
||||
return world.getPlayers()
|
||||
.map(players -> safetyTeleporter.to(destinationInstance).teleport(players))
|
||||
.getOrElse(AsyncAttemptsAggregate::emptySuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports all players to the given world's spawn location.
|
||||
*
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Tool that traversals the server folders to find all potential worlds that can be imported, based on folder contents
|
||||
* and name.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Service
|
||||
public final class PotentialWorldFinder {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final WorldNameChecker worldNameChecker;
|
||||
|
||||
@Inject
|
||||
private PotentialWorldFinder(@NotNull WorldManager worldManager, @NotNull WorldNameChecker worldNameChecker) {
|
||||
this.worldManager = worldManager;
|
||||
this.worldNameChecker = worldNameChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all potential worlds that can be loaded from the server folders.
|
||||
* Checks based on folder contents and name.
|
||||
*
|
||||
* @return A list of all potential worlds.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Unmodifiable
|
||||
@NotNull
|
||||
public List<String> findPotentialWorlds() {
|
||||
return BukkitCompatibility.isUsingNewDimensionStorage()
|
||||
? Stream.concat(findFromDimensionsFolder(), findFromRootFolder()).toList()
|
||||
: findFromRootFolder().toList();
|
||||
}
|
||||
|
||||
private Stream<String> findFromRootFolder() {
|
||||
Path worldContainer = Bukkit.getWorldContainer().toPath();
|
||||
return Arrays.stream(listFolders(worldContainer))
|
||||
.filter(worldNameChecker::isValidWorldFolder)
|
||||
.map(File::getName)
|
||||
.filter(worldNameChecker::isValidWorldName)
|
||||
.filter(worldName -> !worldManager.isWorld(worldName));
|
||||
}
|
||||
|
||||
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))
|
||||
.filter(namespacedKey -> NamespacedKey.fromString(namespacedKey) != null)
|
||||
.filter(namespacedKey -> !worldManager.isWorld(namespacedKey));
|
||||
}
|
||||
|
||||
private @NotNull File[] listFolders(Path folder) {
|
||||
return Option.of(folder.toFile().listFiles(File::isDirectory))
|
||||
.getOrElse(() -> new File[0]);
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* <p>Utility class for resolving the file system location of a world folder.</p>
|
||||
*
|
||||
* <p>This class handles the differences in world folder locations between server versions,
|
||||
* particularly the new dimension storage system introduced in PaperMC 26.1 which stores worlds
|
||||
* in a dimensions folder under the world level directory.</p>
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class WorldFolderResolver {
|
||||
|
||||
/**
|
||||
* Resolves the world folder file path for the given world key or name.
|
||||
* <br />
|
||||
* This method automatically selects the appropriate resolution method based on whether the server
|
||||
* is using the new dimension storage system introduced in PaperMC 26.1.
|
||||
*
|
||||
* @param keyOrName The world key or name to resolve.
|
||||
* @return The resolved world folder file.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static File resolve(@NotNull WorldKeyOrName keyOrName) {
|
||||
return BukkitCompatibility.isUsingNewDimensionStorage()
|
||||
? resolveAsDimensionKey(keyOrName.usableKey())
|
||||
: resolveAsLegacyWorldName(keyOrName.usableName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the world folder file path for the given Multiverse world.
|
||||
* <br />
|
||||
* This method automatically selects the appropriate resolution method based on whether the server
|
||||
* is using the new dimension storage system introduced in PaperMC 26.1.
|
||||
*
|
||||
* @param multiverseWorld The Multiverse world to resolve.
|
||||
* @return The resolved world folder file.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static File resolve(@NotNull MultiverseWorld multiverseWorld) {
|
||||
File dimensionKey = resolveAsDimensionKey(multiverseWorld.getKey());
|
||||
File legacy = resolveAsLegacyWorldName(multiverseWorld.getName());
|
||||
if (BukkitCompatibility.isUsingNewDimensionStorage()) {
|
||||
return !dimensionKey.isDirectory() && legacy.isDirectory() ? legacy : dimensionKey;
|
||||
}
|
||||
return legacy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world folder using the new dimension storage path format of {@code [namespace]/[key]}.
|
||||
* <br />
|
||||
* This method is only valid for PaperMC 26.1 and above. For earlier server versions,
|
||||
* use {@link #resolveAsLegacyWorldName(String)} instead.
|
||||
*
|
||||
* @param namespacedKey The namespaced key of the world.
|
||||
* @return The resolved world folder file using the new dimension storage format.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static File resolveAsDimensionKey(@NotNull NamespacedKey namespacedKey) {
|
||||
return BukkitCompatibility.getWorldFoldersDirectory()
|
||||
.resolve(namespacedKey.getNamespace())
|
||||
.resolve(namespacedKey.getKey())
|
||||
.toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world folder using the legacy world name format from the server root directory.
|
||||
* <br />
|
||||
* This method is the default for server versions before PaperMC 26.1.
|
||||
*
|
||||
* @param worldName The name of the world.
|
||||
* @return The resolved world folder file using the legacy world name format.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public static File resolveAsLegacyWorldName(@NotNull String worldName) {
|
||||
return Bukkit.getWorldContainer()
|
||||
.toPath()
|
||||
.resolve(worldName)
|
||||
.toFile();
|
||||
}
|
||||
|
||||
private WorldFolderResolver() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,15 @@
|
||||
package org.mvplugins.multiverse.core.world.helpers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
/**
|
||||
* <p>Utility class in helping to check the status of a world name and it's associated world folder.</p>
|
||||
@@ -32,18 +29,6 @@ public final class WorldNameChecker {
|
||||
"plugins",
|
||||
"versions");
|
||||
|
||||
private static final List<WorldFolderSchema> WORLD_FOLDER_SCHEMA = List.of(
|
||||
// OLD
|
||||
WorldFolderSchema.file("level.dat"),
|
||||
WorldFolderSchema.folder("DIM1"),
|
||||
WorldFolderSchema.folder("DIM-1"),
|
||||
// NEW
|
||||
WorldFolderSchema.file("paper-world.yml"),
|
||||
WorldFolderSchema.folder("data"),
|
||||
WorldFolderSchema.folder("entities"),
|
||||
WorldFolderSchema.folder("poi"),
|
||||
WorldFolderSchema.folder("region"));
|
||||
|
||||
/**
|
||||
* Checks if a world name is valid.
|
||||
*
|
||||
@@ -68,7 +53,7 @@ public final class WorldNameChecker {
|
||||
if (name.isEmpty()) {
|
||||
return NameStatus.EMPTY;
|
||||
}
|
||||
if (!BukkitCompatibility.isUsingNewDimensionStorage() && BLACKLIST_NAMES.contains(name)) {
|
||||
if (BLACKLIST_NAMES.contains(name)) {
|
||||
return NameStatus.BLACKLISTED;
|
||||
}
|
||||
if (!REPatterns.NAMESPACE_KEY.matcher(name).matches()) {
|
||||
@@ -84,54 +69,19 @@ public final class WorldNameChecker {
|
||||
*
|
||||
* @param worldName The world name to check on.
|
||||
* @return True if the folder exists, else false.
|
||||
*
|
||||
* @deprecated Use {@link #hasWorldFolder(WorldKeyOrName)} instead, which is more robust and supports namespaced keys.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public boolean hasWorldFolder(@Nullable String worldName) {
|
||||
return checkFolder(worldName) != FolderStatus.DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a world name has a world folder directory. It may not contain valid world data.
|
||||
*
|
||||
* @param worldKey The world key to check on.
|
||||
* @return True if the folder exists, else false.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public boolean hasWorldFolder(@Nullable WorldKeyOrName worldKey) {
|
||||
return checkFolder(worldKey) != FolderStatus.DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a world name has a valid world folder with basic world data.
|
||||
*
|
||||
* @param worldName The world name to check on.
|
||||
* @return True if check result is valid, else false.
|
||||
*
|
||||
* @deprecated Use {@link #isValidWorldFolder(WorldKeyOrName)} instead, which is more robust and supports
|
||||
* namespaced keys.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public boolean isValidWorldFolder(@Nullable String worldName) {
|
||||
return checkFolder(worldName).loadable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a world name has a valid world folder with basic world data.
|
||||
*
|
||||
* @param nameOrKey The world key to check on.
|
||||
* @return True if check result is valid, else false.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public boolean isValidWorldFolder(@Nullable WorldKeyOrName nameOrKey) {
|
||||
return checkFolder(nameOrKey).loadable;
|
||||
return checkFolder(worldName) == FolderStatus.VALID;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +91,7 @@ public final class WorldNameChecker {
|
||||
* @return True if check result is valid, else false.
|
||||
*/
|
||||
public boolean isValidWorldFolder(@Nullable File worldFolder) {
|
||||
return checkFolder(worldFolder).loadable;
|
||||
return checkFolder(worldFolder) == FolderStatus.VALID;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,45 +99,14 @@ public final class WorldNameChecker {
|
||||
*
|
||||
* @param worldName The world name to check on.
|
||||
* @return The resulting folder status.
|
||||
*
|
||||
* @deprecated Use {@link #checkFolder(WorldKeyOrName)} instead, which is more robust and supports namespaced keys.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@NotNull
|
||||
public FolderStatus checkFolder(@Nullable String worldName) {
|
||||
if (worldName == null) {
|
||||
return FolderStatus.DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
return WorldKeyOrName.parse(worldName)
|
||||
.map(this::checkFolder)
|
||||
.getOrElse(FolderStatus.NOT_A_WORLD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current folder status for a world key or name.
|
||||
*
|
||||
* @param keyOrName The world key or name to check on.
|
||||
* @return The resulting folder status.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull
|
||||
public FolderStatus checkFolder(@Nullable WorldKeyOrName keyOrName) {
|
||||
if (keyOrName == null) {
|
||||
return FolderStatus.DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
if (BukkitCompatibility.isUsingNewDimensionStorage()) {
|
||||
File oldWorldFolder = WorldFolderResolver.resolveAsLegacyWorldName(keyOrName.usableName());
|
||||
if (checkFolder(oldWorldFolder) == FolderStatus.VALID) {
|
||||
return FolderStatus.REQUIRES_MIGRATION;
|
||||
}
|
||||
}
|
||||
|
||||
return checkFolder(WorldFolderResolver.resolve(keyOrName));
|
||||
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
|
||||
return checkFolder(worldFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +120,7 @@ public final class WorldNameChecker {
|
||||
if (worldFolder == null || !worldFolder.exists() || !worldFolder.isDirectory()) {
|
||||
return FolderStatus.DOES_NOT_EXIST;
|
||||
}
|
||||
if (!folderWorldSchemaCheck(worldFolder)) {
|
||||
if (!folderHasDat(worldFolder)) {
|
||||
return FolderStatus.NOT_A_WORLD;
|
||||
}
|
||||
return FolderStatus.VALID;
|
||||
@@ -214,54 +133,9 @@ public final class WorldNameChecker {
|
||||
* @param worldFolder The File that may be a world.
|
||||
* @return True if it looks like a world, else false.
|
||||
*/
|
||||
private boolean folderWorldSchemaCheck(@NotNull File worldFolder) {
|
||||
return WORLD_FOLDER_SCHEMA.stream()
|
||||
.filter(schema -> schema.check(worldFolder))
|
||||
.count() >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to check if a file or folder exist.
|
||||
*/
|
||||
private interface WorldFolderSchema {
|
||||
|
||||
static WorldFolderSchema file(String path) {
|
||||
return new WorldFile(path);
|
||||
}
|
||||
|
||||
static WorldFolderSchema folder(String path) {
|
||||
return new WorldFolder(path);
|
||||
}
|
||||
|
||||
boolean check(File worldFolder);
|
||||
|
||||
final class WorldFile implements WorldFolderSchema {
|
||||
private final String path;
|
||||
|
||||
private WorldFile(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(File worldFolder) {
|
||||
File thisFolder = worldFolder.toPath().resolve(path).toFile();
|
||||
return thisFolder.exists() && thisFolder.isFile();
|
||||
}
|
||||
}
|
||||
|
||||
final class WorldFolder implements WorldFolderSchema {
|
||||
private final String path;
|
||||
|
||||
private WorldFolder(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(File worldFolder) {
|
||||
File thisFolder = worldFolder.toPath().resolve(path).toFile();
|
||||
return thisFolder.exists() && thisFolder.isDirectory();
|
||||
}
|
||||
}
|
||||
private boolean folderHasDat(@NotNull File worldFolder) {
|
||||
File[] files = worldFolder.listFiles((file, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(".dat"));
|
||||
return files != null && files.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,39 +170,16 @@ public final class WorldNameChecker {
|
||||
/**
|
||||
* Folder is valid.
|
||||
*/
|
||||
VALID(true),
|
||||
|
||||
/**
|
||||
* This folder will cause PaperMC to migrate to new dimension world folder in 26.1+
|
||||
*/
|
||||
REQUIRES_MIGRATION(true),
|
||||
VALID,
|
||||
|
||||
/**
|
||||
* Folder exist, but contents in it doesnt look like a world.
|
||||
*/
|
||||
NOT_A_WORLD(false),
|
||||
NOT_A_WORLD,
|
||||
|
||||
/**
|
||||
* Folder does not exist.
|
||||
*/
|
||||
DOES_NOT_EXIST(false),
|
||||
;
|
||||
|
||||
private final boolean loadable;
|
||||
|
||||
FolderStatus(boolean loadable) {
|
||||
this.loadable = loadable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this folder status is loadable, meaning it has the basic world data and can be loaded as a world.
|
||||
* Note that this does not guarantee the server will definitely load the world with no errors.
|
||||
*
|
||||
* @return True if folder probably is loadable by the server, else false.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public boolean isLoadable() {
|
||||
return loadable;
|
||||
}
|
||||
DOES_NOT_EXIST
|
||||
}
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.world.key;
|
||||
|
||||
import io.vavr.control.Either;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizableMessage;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.utils.ServerProperties;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.UnsafeValuesCompatibility;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents either a world key or a world name, with methods to parse from strings and retrieve the key or name.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public sealed abstract class WorldKeyOrName implements Comparable<WorldKeyOrName>, LocalizableMessage permits WorldKeyOrName.Key, WorldKeyOrName.Name {
|
||||
|
||||
private static final String DEFAULT_OVERWORLD_KEY = "overworld";
|
||||
private static final String DEFAULT_NETHER_KEY = "the_nether";
|
||||
private static final String DEFAULT_END_KEY = "the_end";
|
||||
|
||||
/**
|
||||
* Parse a string into a {@link WorldKeyOrName} instance.
|
||||
* <p>
|
||||
* The provided input will be interpreted as a namespaced key if it contains a ':' character,
|
||||
* otherwise it will be treated as a world name.
|
||||
*
|
||||
* @param nameOrKey The input string to parse, may be a world name or a namespaced key string
|
||||
* @return An {@link Attempt} containing either a parsed {@link WorldKeyOrName} on success or a
|
||||
* {@link WorldKeyParseFailReason} describing the failure
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static Attempt<WorldKeyOrName, WorldKeyParseFailReason> parse(@Nullable String nameOrKey) {
|
||||
if (nameOrKey == null || nameOrKey.isEmpty()) {
|
||||
return Attempt.failure(WorldKeyParseFailReason.EMPTY);
|
||||
}
|
||||
return nameOrKey.contains(":") ? parseKey(nameOrKey) : parseName(nameOrKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a world name into a {@link WorldKeyOrName} instance.
|
||||
* <p>
|
||||
* This will attempt to create a usable {@link NamespacedKey} from the given name using
|
||||
* {@link NamespacedKey#minecraft(String)}. If parsing fails, a failure {@link Attempt} will be
|
||||
* returned describing an invalid world name.
|
||||
*
|
||||
* @param name The world name to parse
|
||||
* @return An {@link Attempt} containing a {@link WorldKeyOrName.Name} on success or a
|
||||
* {@link WorldKeyParseFailReason#INVALID_WORLD_NAME} failure on error
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static Attempt<WorldKeyOrName, WorldKeyParseFailReason> parseName(@NotNull String name) {
|
||||
return Try.of(() -> NamespacedKey.minecraft(mapWorldNameToMinecraftKey(name)))
|
||||
.map(usableKey -> Attempt.<WorldKeyOrName, WorldKeyParseFailReason>success(new Name(name, usableKey)))
|
||||
.recover(throwable -> Attempt.failure(WorldKeyParseFailReason.INVALID_WORLD_NAME,
|
||||
MessageReplacement.Replace.WORLD.with(name)))
|
||||
.getOrElse(() -> Attempt.failure(WorldKeyParseFailReason.INVALID_WORLD_NAME,
|
||||
MessageReplacement.Replace.WORLD.with(name)));
|
||||
}
|
||||
|
||||
private static String mapWorldNameToMinecraftKey(@NotNull String nameOrKey) {
|
||||
String defaultLevelName = getMostAccurateLevelName();
|
||||
String lowerCaseName = nameOrKey.toLowerCase(Locale.ROOT);
|
||||
if (defaultLevelName.equalsIgnoreCase(lowerCaseName)) {
|
||||
lowerCaseName = DEFAULT_OVERWORLD_KEY;
|
||||
} else if (DimensionFinder.DEFAULT_NETHER_FORMAT.replaceOverworld(defaultLevelName).equalsIgnoreCase(lowerCaseName)) {
|
||||
lowerCaseName = DEFAULT_NETHER_KEY;
|
||||
} else if (DimensionFinder.DEFAULT_END_FORMAT.replaceOverworld(defaultLevelName).equalsIgnoreCase(lowerCaseName)) {
|
||||
lowerCaseName = DEFAULT_END_KEY;
|
||||
}
|
||||
return lowerCaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a namespaced key string into a {@link WorldKeyOrName} instance.
|
||||
*
|
||||
* @param nameOrKey The namespaced key string to parse (e.g. "minecraft:world")
|
||||
* @return An {@link Attempt} containing a {@link WorldKeyOrName.Key} on success or a
|
||||
* {@link WorldKeyParseFailReason#INVALID_NAMESPACED_KEY} failure on error
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static Attempt<WorldKeyOrName, WorldKeyParseFailReason> parseKey(@NotNull String nameOrKey) {
|
||||
return Option.of(NamespacedKey.fromString(nameOrKey))
|
||||
.filter(Objects::nonNull)
|
||||
.map(key -> Attempt.<WorldKeyOrName, WorldKeyParseFailReason>success(new Key(key, usableNameFromKey(key))))
|
||||
.getOrElse(() -> Attempt.failure(WorldKeyParseFailReason.INVALID_NAMESPACED_KEY,
|
||||
MessageReplacement.Replace.NAMESPACE.with(nameOrKey)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WorldKeyOrName} representing a namespaced key.
|
||||
*
|
||||
* @param key The {@link NamespacedKey} to wrap
|
||||
* @return A {@link WorldKeyOrName} containing the provided key
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static WorldKeyOrName parseKey(@NotNull NamespacedKey key) {
|
||||
return new Key(key, usableNameFromKey(key));
|
||||
}
|
||||
|
||||
private static String usableNameFromKey(@NotNull NamespacedKey key) {
|
||||
return key.getNamespace().equals(NamespacedKey.MINECRAFT)
|
||||
? mapMinecraftKeyToWorldName(key)
|
||||
: mapCustomKeyToWorldName(key);
|
||||
}
|
||||
|
||||
private static String mapMinecraftKeyToWorldName(@NotNull NamespacedKey key) {
|
||||
String defaultLevelName = getMostAccurateLevelName();
|
||||
return switch (key.getKey()) {
|
||||
case DEFAULT_OVERWORLD_KEY -> defaultLevelName;
|
||||
case DEFAULT_NETHER_KEY -> DimensionFinder.DEFAULT_NETHER_FORMAT.replaceOverworld(defaultLevelName);
|
||||
case DEFAULT_END_KEY -> DimensionFinder.DEFAULT_END_FORMAT.replaceOverworld(defaultLevelName);
|
||||
default -> key.getKey();
|
||||
};
|
||||
}
|
||||
|
||||
private static String mapCustomKeyToWorldName(@NotNull NamespacedKey key) {
|
||||
return key.getNamespace() + "_" + key.getKey();
|
||||
}
|
||||
|
||||
private static String getMostAccurateLevelName() {
|
||||
return UnsafeValuesCompatibility.getMainLevelName()
|
||||
.orElse(ServerProperties::getStaticLevelName)
|
||||
.getOrElse("world"); // Worse case assume its world
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance represents a world name (not a namespaced key).
|
||||
*
|
||||
* @return true if this is a name variant
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract boolean isName();
|
||||
|
||||
/**
|
||||
* Returns true if this instance represents a namespaced key (not a world name).
|
||||
*
|
||||
* @return true if this is a key variant
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract boolean isKey();
|
||||
|
||||
/**
|
||||
* Get the underlying {@link NamespacedKey} if this instance is a key variant.
|
||||
*
|
||||
* @return An {@link Option} containing the key when present, otherwise {@link Option#none()}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract @NotNull Option<NamespacedKey> getKey();
|
||||
|
||||
/**
|
||||
* Get the underlying world name if this instance is a name variant.
|
||||
*
|
||||
* @return An {@link Option} containing the name when present, otherwise {@link Option#none()}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract @NotNull Option<String> getName();
|
||||
|
||||
/**
|
||||
* Represent this value as an {@link Either} where the left side is the {@link NamespacedKey}
|
||||
* and the right side is the world name.
|
||||
*
|
||||
* @return An {@link Either} containing either the key (left) or the name (right)
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract @NotNull Either<NamespacedKey, String> asEither();
|
||||
|
||||
/**
|
||||
* Get a usable {@link NamespacedKey} for this instance.
|
||||
* <p>
|
||||
* If this instance is a key variant, the underlying key is returned. If it is a name variant,
|
||||
* an implementation-provided usable key (derived from the name) is returned.
|
||||
*
|
||||
* @return A non-null {@link NamespacedKey} that can be used where a key is required
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract @NotNull NamespacedKey usableKey();
|
||||
|
||||
/**
|
||||
* Get a usable name for this instance.
|
||||
* <p>
|
||||
* If this instance is a name variant, the underlying name is returned. If it is a key variant,
|
||||
* a reasonable string representation derived from the key is returned.
|
||||
*
|
||||
* @return A non-null {@link String} representing a usable name
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract @NotNull String usableName();
|
||||
|
||||
/**
|
||||
* Serialize this instance to a string form suitable for storage or comparison.
|
||||
*
|
||||
* @return The serialized representation of this value
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public abstract @NotNull String serialise();
|
||||
|
||||
/**
|
||||
* Sorts the instances based on their serialized form, which ensures that keys and names are sorted in an
|
||||
* alphabetical manner by default.
|
||||
*
|
||||
* @param o the object to be compared.
|
||||
* @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(WorldKeyOrName o) {
|
||||
return serialise().compareTo(o.serialise());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Message getLocalizableMessage() {
|
||||
return Message.of(serialise());
|
||||
}
|
||||
|
||||
public static final class Key extends WorldKeyOrName {
|
||||
|
||||
private final NamespacedKey key;
|
||||
private final String usableName;
|
||||
|
||||
private Key(@NotNull NamespacedKey key, @NotNull String usableName) {
|
||||
this.key = key;
|
||||
this.usableName = usableName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isName() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKey() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Option<NamespacedKey> getKey() {
|
||||
return Option.of(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Option<String> getName() {
|
||||
return Option.none();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Either<NamespacedKey, String> asEither() {
|
||||
return Either.left(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NamespacedKey usableKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String usableName() {
|
||||
return usableName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String serialise() {
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Key key1 = (Key) o;
|
||||
return Objects.equals(key, key1.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Key{" +
|
||||
"key=" + key +
|
||||
", usableName='" + usableName + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Name extends WorldKeyOrName {
|
||||
|
||||
private final String name;
|
||||
private final NamespacedKey usableKey;
|
||||
|
||||
private Name(String name, NamespacedKey usableKey) {
|
||||
this.name = name;
|
||||
this.usableKey = usableKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isName() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Option<NamespacedKey> getKey() {
|
||||
return Option.none();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Option<String> getName() {
|
||||
return Option.of(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Either<NamespacedKey, String> asEither() {
|
||||
return Either.right(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NamespacedKey usableKey() {
|
||||
return usableKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String usableName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String serialise() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Name name1 = (Name) o;
|
||||
return Objects.equals(name, name1.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Name{" +
|
||||
"name='" + name + '\'' +
|
||||
", usableKey=" + usableKey +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package org.mvplugins.multiverse.core.world.key;
|
||||
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
/**
|
||||
* Reasons why parsing a world key or name failed.
|
||||
* <br />
|
||||
* These values are returned by parsing utilities such as {@link org.mvplugins.multiverse.core.world.key.WorldKeyOrName#parse}
|
||||
* to indicate the specific cause of failure so callers can present an appropriate localized message.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public enum WorldKeyParseFailReason implements FailureReason {
|
||||
/**
|
||||
* The provided input was null or empty.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
EMPTY(MVCorei18n.WORLDKEYPARSE_EMPTY),
|
||||
|
||||
/**
|
||||
* The provided world name contains invalid characters or format.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
INVALID_WORLD_NAME(MVCorei18n.WORLDKEYPARSE_INVALIDWORLDNAME),
|
||||
|
||||
/**
|
||||
* The provided string was intended to be a namespaced key but did not parse as a valid
|
||||
* {@link org.bukkit.NamespacedKey} (malformed namespace/key or missing parts).
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
INVALID_NAMESPACED_KEY(MVCorei18n.WORLDKEYPARSE_INVALIDNAMESPACEDKEY),
|
||||
;
|
||||
|
||||
private final MessageKeyProvider message;
|
||||
|
||||
WorldKeyParseFailReason(MessageKeyProvider message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the localized message key associated with this failure reason.
|
||||
* <p>
|
||||
* Implementations of {@link FailureReason} can use this to provide a user-facing localized
|
||||
* message for the failure.
|
||||
*
|
||||
* @return The {@link MessageKey} that corresponds to this failure reason
|
||||
* @since 5.7
|
||||
*/
|
||||
@Override
|
||||
public MessageKey getMessageKey() {
|
||||
return message.getMessageKey();
|
||||
}
|
||||
}
|
||||
@@ -1,125 +1,44 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import io.vavr.control.Either;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
/**
|
||||
* Options for customizing the cloning of a world.
|
||||
*/
|
||||
public final class CloneWorldOptions implements KeepWorldSettingsOptions {
|
||||
|
||||
/**
|
||||
* Creates a new {@link CloneWorldOptions} instance with the given loaded world.
|
||||
*
|
||||
* @param fromWorld The loaded world to clone.
|
||||
* @param newWorldName The name of the new world.
|
||||
* @return A new {@link CloneWorldOptions} instance.
|
||||
*
|
||||
* @deprecated Cloning can be done from unloaded worlds as well. Use {@link #fromTo(MultiverseWorld, String)} instead.
|
||||
* To ensure you use the non-deprecated method, you need to downcast the loaded world to a {@link MultiverseWorld}
|
||||
* before passing it in, for example: {@code CloneWorldOptions.fromTo((MultiverseWorld) loadedWorld, newWorldName)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.6")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public static @NotNull CloneWorldOptions fromTo(@NotNull LoadedMultiverseWorld fromWorld, @NotNull String newWorldName) {
|
||||
return fromTo((MultiverseWorld) fromWorld, newWorldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CloneWorldOptions} instance with the given world.
|
||||
*
|
||||
* @param fromWorld The world to clone.
|
||||
* @param world The world to clone.
|
||||
* @param newWorldName The name of the new world.
|
||||
* @return A new {@link CloneWorldOptions} instance.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public static @NotNull CloneWorldOptions fromTo(@NotNull MultiverseWorld fromWorld, @NotNull String newWorldName) {
|
||||
return new CloneWorldOptions(fromWorld, Either.left(newWorldName));
|
||||
public static CloneWorldOptions fromTo(LoadedMultiverseWorld world, String newWorldName) {
|
||||
return new CloneWorldOptions(world, newWorldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CloneWorldOptions} instance with the given world and namespaced key.
|
||||
*
|
||||
* @param fromWorld The world to clone.
|
||||
* @param key The namespaced key for the new world.
|
||||
* @return A new {@link CloneWorldOptions} instance.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static @NotNull CloneWorldOptions fromTo(@NotNull MultiverseWorld fromWorld, @NotNull NamespacedKey key) {
|
||||
return new CloneWorldOptions(fromWorld, Either.right(WorldKeyOrName.parseKey(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CloneWorldOptions} instance with the given world and world key or name.
|
||||
*
|
||||
* @param fromWorld The world to clone.
|
||||
* @param keyOrName The key or name for the new world.
|
||||
* @return A new {@link CloneWorldOptions} instance.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static @NotNull CloneWorldOptions fromTo(@NotNull MultiverseWorld fromWorld, @NotNull WorldKeyOrName keyOrName) {
|
||||
return new CloneWorldOptions(fromWorld, Either.right(keyOrName));
|
||||
}
|
||||
|
||||
private final MultiverseWorld fromWorld;
|
||||
private final Either<String, WorldKeyOrName> newWorldKeyOrName;
|
||||
private final LoadedMultiverseWorld world;
|
||||
private final String newWorldName;
|
||||
private boolean keepGameRule = true;
|
||||
private boolean keepWorldConfig = true;
|
||||
private boolean saveBukkitWorld = true;
|
||||
|
||||
private boolean keepWorldBorder = true;
|
||||
|
||||
CloneWorldOptions(MultiverseWorld fromWorld, Either<String, WorldKeyOrName> newWorldKeyOrName) {
|
||||
this.fromWorld = fromWorld;
|
||||
this.newWorldKeyOrName = newWorldKeyOrName;
|
||||
CloneWorldOptions(LoadedMultiverseWorld world, String newWorldName) {
|
||||
this.world = world;
|
||||
this.newWorldName = newWorldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the loaded world to clone.
|
||||
* Gets the world to clone.
|
||||
*
|
||||
* @return The world to clone.
|
||||
*
|
||||
* @deprecated Cloning can be done from unloaded worlds as well. Use {@link #fromWorld()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.6")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public LoadedMultiverseWorld world() {
|
||||
return fromWorld.asLoadedWorld().getOrNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the loaded world to clone.
|
||||
*
|
||||
* @return The world to clone.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public @NotNull MultiverseWorld fromWorld() {
|
||||
return fromWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new world key or name, either unparsed as string or the {@link WorldKeyOrName} instance.
|
||||
*
|
||||
* @return The new world key or name.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Either<String, WorldKeyOrName> newWorldKeyOrName() {
|
||||
return newWorldKeyOrName;
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,10 +46,8 @@ public final class CloneWorldOptions implements KeepWorldSettingsOptions {
|
||||
*
|
||||
* @return The name of the new world.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public @NotNull String newWorldName() {
|
||||
return newWorldKeyOrName.fold(name -> name, WorldKeyOrName::usableName);
|
||||
public String newWorldName() {
|
||||
return newWorldName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
package org.mvplugins.multiverse.core.world.options;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
import io.vavr.control.Either;
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Options for customizing the creation of a new world.
|
||||
@@ -29,43 +18,12 @@ public final class CreateWorldOptions {
|
||||
* @return A new {@link CreateWorldOptions} instance.
|
||||
*/
|
||||
public static @NotNull CreateWorldOptions worldName(@NotNull String worldName) {
|
||||
return new CreateWorldOptions(Either.left(worldName));
|
||||
return new CreateWorldOptions(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CreateWorldOptions} instance with the given namespaced key. Note that creating world with
|
||||
* namespace requires PaperMC. This will not work on Spigot.
|
||||
*
|
||||
* @param key The namespaced key for the world to create.
|
||||
* @return A new {@link CreateWorldOptions} instance.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static @NotNull CreateWorldOptions worldKey(@NotNull NamespacedKey key) {
|
||||
return new CreateWorldOptions(Either.right(WorldKeyOrName.parseKey(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CreateWorldOptions} instance with the given world key or name. Note that creating world with
|
||||
* namespace requires PaperMC. WorldKeyOrName parsed as namespaced key (i.e. {@link WorldKeyOrName#isKey()} is true)
|
||||
* will not work on Spigot.
|
||||
*
|
||||
* @param keyOrName The key or name for the world to create.
|
||||
* @return A new {@link CreateWorldOptions} instance.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static @NotNull CreateWorldOptions worldKeyOrName(@NotNull WorldKeyOrName keyOrName) {
|
||||
return new CreateWorldOptions(Either.right(keyOrName));
|
||||
}
|
||||
|
||||
private final Either<String, WorldKeyOrName> keyOrName;
|
||||
private final String worldName;
|
||||
private String biome = "";
|
||||
private boolean bonusChest = false;
|
||||
private World.Environment environment = World.Environment.NORMAL;
|
||||
private EntityPosition forcedSpawnPosition = null;
|
||||
private boolean generateStructures = true;
|
||||
private String generator = null;
|
||||
private String generatorSettings = "";
|
||||
@@ -73,38 +31,24 @@ public final class CreateWorldOptions {
|
||||
private boolean useSpawnAdjust = true;
|
||||
private WorldType worldType = WorldType.NORMAL;
|
||||
private boolean doFolderCheck = true;
|
||||
private final Map<String, String> worldPropertyStrings = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link CreateWorldOptions} instance with either a world name or world key or name.
|
||||
* Creates a new {@link CreateWorldOptions} instance with the given world name.
|
||||
*
|
||||
* @param keyOrName Either the world name or the world key/name instance.
|
||||
* @param worldName The name of the world to create.
|
||||
*/
|
||||
CreateWorldOptions(@NotNull Either<String, WorldKeyOrName> keyOrName) {
|
||||
this.keyOrName = keyOrName;
|
||||
CreateWorldOptions(@NotNull String worldName) {
|
||||
this.worldName = worldName;
|
||||
this.seed = ACFUtil.RANDOM.nextLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new world key or name, either unparsed as string or the {@link WorldKeyOrName} instance.
|
||||
*
|
||||
* @return The new world key or name.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Either<String, WorldKeyOrName> keyOrName() {
|
||||
return keyOrName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the world to create.
|
||||
*
|
||||
* @return The name of the world to create.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
public @NotNull String worldName() {
|
||||
return keyOrName.fold(name -> name ,WorldKeyOrName::usableName);
|
||||
return worldName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,32 +73,6 @@ public final class CreateWorldOptions {
|
||||
return biome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether bonus chest should generate at spawn upon world creation.
|
||||
* <br />
|
||||
* This feature only works on PaperMC 1.21.5+
|
||||
*
|
||||
* @param bonusChest Whether bonus chest should generate at spawn upon world creation.
|
||||
* @return This {@link CreateWorldOptions} instance.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull CreateWorldOptions bonusChest(boolean bonusChest) {
|
||||
this.bonusChest = bonusChest;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether bonus chest should generate at spawn upon world creation.
|
||||
* <br />
|
||||
* This feature only works on PaperMC 1.21.5+
|
||||
*
|
||||
* @return true if bonus chest should generate, else false.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public boolean bonusChest() {
|
||||
return bonusChest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the environment of the world to create.
|
||||
*
|
||||
@@ -175,37 +93,6 @@ public final class CreateWorldOptions {
|
||||
return environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the forced spawn position of the world to apply. This may be null, in which case the spawn position will
|
||||
* be determined by the default generator. Setting spawn position and {@link #useSpawnAdjust(boolean)} to false
|
||||
* will improve world creation speed on PaperMC as chunks will not be loaded to search for spawn point.
|
||||
* <br />
|
||||
* This feature only works on PaperMC 26.1+
|
||||
*
|
||||
* @param forcedSpawnPosition The forced spawn position of the world to create.
|
||||
* @return This {@link CreateWorldOptions} instance.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull CreateWorldOptions forcedSpawnPosition(@Nullable EntityPosition forcedSpawnPosition) {
|
||||
this.forcedSpawnPosition = forcedSpawnPosition;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the forced spawn position of the world to apply. This may be null, in which case the spawn position will
|
||||
* be determined by the default generator.
|
||||
* <br />
|
||||
* This feature only works on PaperMC 26.1+
|
||||
*
|
||||
* @return The force spawn position to apply if available.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Option<EntityPosition> forcedSpawnPosition() {
|
||||
return Option.of(this.forcedSpawnPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether structures such as NPC villages should be generated.
|
||||
*
|
||||
@@ -373,46 +260,4 @@ public final class CreateWorldOptions {
|
||||
public boolean doFolderCheck() {
|
||||
return doFolderCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a world property string key-value pair. Overwrites any existing key.
|
||||
*
|
||||
* @param key The key of the world property string.
|
||||
* @param value The value of the world property string.
|
||||
* @return This {@link CreateWorldOptions} instance.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public @NotNull CreateWorldOptions worldPropertyString(@NotNull String key, @Nullable String value) {
|
||||
this.worldPropertyStrings.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple world property string key-value pairs. Overwrites any existing keys.
|
||||
*
|
||||
* @param worldProperties A map of world property string key-value pairs.
|
||||
* @return This {@link CreateWorldOptions} instance.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public @NotNull CreateWorldOptions worldPropertyStrings(@NotNull Map<@NotNull String, @Nullable String> worldProperties) {
|
||||
this.worldPropertyStrings.putAll(worldProperties);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an unmodifiable view of the world property strings. Use {@link #worldPropertyString(String, String)} and
|
||||
* {@link #worldPropertyStrings(Map)} to modify the world property strings.
|
||||
*
|
||||
* @return An unmodifiable view of the world property strings.
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public @UnmodifiableView @NotNull Map<String, String> worldPropertyStrings() {
|
||||
return Collections.unmodifiableMap(worldPropertyStrings);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 152 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user