Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-19 17:30:08 +01:00
Merge branch 'main' into feat/improved-entity-operations
Dieser Commit ist enthalten in:
Commit
d987643378
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -27,7 +27,7 @@ body:
|
|||||||
description: Which server version version you using? If your server version is not listed, it is not supported. Update to a supported version first.
|
description: Which server version version you using? If your server version is not listed, it is not supported. Update to a supported version first.
|
||||||
multiple: false
|
multiple: false
|
||||||
options:
|
options:
|
||||||
- '1.20.1'
|
- '1.20.2'
|
||||||
- '1.20'
|
- '1.20'
|
||||||
- '1.19.4'
|
- '1.19.4'
|
||||||
- '1.18.2'
|
- '1.18.2'
|
||||||
|
11
.github/renovate.json
vendored
11
.github/renovate.json
vendored
@ -19,10 +19,15 @@
|
|||||||
"net.fabricmc:fabric-loader",
|
"net.fabricmc:fabric-loader",
|
||||||
"net.fabricmc.fabric-api:fabric-api",
|
"net.fabricmc.fabric-api:fabric-api",
|
||||||
"com.github.luben:zstd-jni",
|
"com.github.luben:zstd-jni",
|
||||||
"net.kyori",
|
|
||||||
"net.kyori:adventure-nbt",
|
|
||||||
"org.jetbrains.kotlin.jvm",
|
"org.jetbrains.kotlin.jvm",
|
||||||
"log4j"
|
"log4j",
|
||||||
|
"org.apache.logging.log4j:log4j-api",
|
||||||
|
"org.apache.logging.log4j:log4j-bom",
|
||||||
|
"org.apache.logging.log4j:log4j-slf4j-impl",
|
||||||
|
"org.apache.logging.log4j:log4j-core",
|
||||||
|
"org.bstats:bstats-sponge",
|
||||||
|
"org.spongepowered:spongeapi",
|
||||||
|
"org.yaml:snakeyaml"
|
||||||
],
|
],
|
||||||
"labels": ["Renovate"],
|
"labels": ["Renovate"],
|
||||||
"rebaseWhen": "conflicted",
|
"rebaseWhen": "conflicted",
|
||||||
|
2
.github/workflows/build-pr.yml
vendored
2
.github/workflows/build-pr.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
language: ['java']
|
language: ['java']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/upload-release-assets.yml
vendored
2
.github/workflows/upload-release-assets.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
|
@ -7,7 +7,7 @@ import xyz.jpenilla.runpaper.task.RunServer
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
|
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
|
||||||
id("xyz.jpenilla.run-paper") version "2.1.0"
|
id("xyz.jpenilla.run-paper") version "2.2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File("$rootDir/.git").exists()) {
|
if (!File("$rootDir/.git").exists()) {
|
||||||
@ -34,7 +34,7 @@ logger.lifecycle("""
|
|||||||
*******************************************
|
*******************************************
|
||||||
""")
|
""")
|
||||||
|
|
||||||
var rootVersion by extra("2.6.5")
|
var rootVersion by extra("2.8.1")
|
||||||
var snapshot by extra("SNAPSHOT")
|
var snapshot by extra("SNAPSHOT")
|
||||||
var revision: String by extra("")
|
var revision: String by extra("")
|
||||||
var buildNumber by extra("")
|
var buildNumber by extra("")
|
||||||
@ -83,7 +83,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyCommonConfiguration()
|
applyCommonConfiguration()
|
||||||
val supportedVersions = listOf("1.17.1", "1.18.2", "1.19.4", "1.20", "1.20.1")
|
val supportedVersions = listOf("1.17.1", "1.18.2", "1.19.4", "1.20", "1.20.2")
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
supportedVersions.forEach {
|
supportedVersions.forEach {
|
||||||
@ -97,7 +97,7 @@ tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
runServer {
|
runServer {
|
||||||
minecraftVersion("1.20.1")
|
minecraftVersion("1.20.2")
|
||||||
pluginJars(*project(":worldedit-bukkit").getTasksByName("shadowJar", false).map { (it as Jar).archiveFile }
|
pluginJars(*project(":worldedit-bukkit").getTasksByName("shadowJar", false).map { (it as Jar).archiveFile }
|
||||||
.toTypedArray())
|
.toTypedArray())
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ fun Project.applyPaperweightAdapterConfiguration() {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
"implementation"(project(":worldedit-bukkit"))
|
"implementation"(project(":worldedit-bukkit"))
|
||||||
"implementation"(platform("com.intellectualsites.bom:bom-1.18.x:1.31"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("assemble") {
|
tasks.named("assemble") {
|
||||||
|
@ -40,12 +40,11 @@ fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, banSlf4j: Boolean
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
"compileOnly"("com.google.code.findbugs:jsr305:3.0.2")
|
"compileOnly"("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
"testImplementation"("org.junit.jupiter:junit-jupiter-api:5.9.2")
|
"testImplementation"("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||||
"testImplementation"("org.junit.jupiter:junit-jupiter-params:5.9.2")
|
"testImplementation"("org.junit.jupiter:junit-jupiter-params:5.10.0")
|
||||||
"testImplementation"("org.mockito:mockito-core:5.1.1")
|
"testImplementation"("org.mockito:mockito-core:5.4.0")
|
||||||
"testImplementation"("org.mockito:mockito-junit-jupiter:5.1.1")
|
"testImplementation"("org.mockito:mockito-junit-jupiter:5.4.0")
|
||||||
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.9.2")
|
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.10.0")
|
||||||
"implementation"(platform("com.intellectualsites.bom:bom-1.18.x:1.31"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Java 8 turns on doclint which we fail
|
// Java 8 turns on doclint which we fail
|
||||||
|
@ -1,51 +1,66 @@
|
|||||||
[versions]
|
[versions]
|
||||||
# Minecraft expectations
|
# Minecraft expectations
|
||||||
|
paper = "1.20.2-R0.1-SNAPSHOT"
|
||||||
fastutil = "8.5.9"
|
fastutil = "8.5.9"
|
||||||
guava = "31.1-jre"
|
guava = "31.1-jre"
|
||||||
log4j = "2.19.0"
|
log4j = "2.19.0"
|
||||||
|
gson = "2.10"
|
||||||
|
snakeyaml = "2.0"
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
dummypermscompat = "1.10"
|
dummypermscompat = "1.10"
|
||||||
worldguard-bukkit = "7.0.8"
|
worldguard-bukkit = "7.0.9"
|
||||||
mapmanager = "1.8.0-SNAPSHOT"
|
mapmanager = "1.8.0-SNAPSHOT"
|
||||||
griefprevention = "16.18.1"
|
griefprevention = "16.18.1"
|
||||||
griefdefender = "2.1.0-SNAPSHOT"
|
griefdefender = "2.1.0-SNAPSHOT"
|
||||||
residence = "4.5._13.1"
|
residence = "4.5._13.1"
|
||||||
towny = "0.99.2.7"
|
towny = "0.99.5.20"
|
||||||
|
plotsquared = "7.0.0"
|
||||||
|
|
||||||
# Third party
|
# Third party
|
||||||
bstats = "3.0.2"
|
bstats = "3.0.2"
|
||||||
sparsebitset = "1.2"
|
sparsebitset = "1.3"
|
||||||
parallelgzip = "1.0.5"
|
parallelgzip = "1.0.5"
|
||||||
adventure = "4.9.3"
|
adventure = "4.14.0"
|
||||||
|
adventure-bukkit = "4.3.1"
|
||||||
|
checkerqual = "3.38.0"
|
||||||
truezip = "6.8.4"
|
truezip = "6.8.4"
|
||||||
auto-value = "1.10.2"
|
auto-value = "1.10.2"
|
||||||
findbugs = "3.0.2"
|
findbugs = "3.0.2"
|
||||||
rhino-runtime = "1.7.14"
|
rhino-runtime = "1.7.14"
|
||||||
zstd-jni = "1.4.8-1" # Not latest as it can be difficult to obtain latest ZSTD libs
|
zstd-jni = "1.4.8-1" # Not latest as it can be difficult to obtain latest ZSTD libs
|
||||||
antlr4 = "4.13.0"
|
antlr4 = "4.13.1"
|
||||||
json-simple = "1.1.1"
|
json-simple = "1.1.1"
|
||||||
jlibnoise = "1.0.0"
|
jlibnoise = "1.0.0"
|
||||||
jchronic = "0.2.4a"
|
jchronic = "0.2.4a"
|
||||||
lz4-java = "1.8.0"
|
lz4-java = "1.8.0"
|
||||||
lz4-stream = "1.0.0"
|
lz4-stream = "1.0.0"
|
||||||
|
commons-cli = "1.5.0"
|
||||||
|
paperlib = "1.0.8"
|
||||||
|
paster = "1.1.5"
|
||||||
|
vault = "1.7.1"
|
||||||
|
serverlib = "2.3.4"
|
||||||
## Internal
|
## Internal
|
||||||
adventure-text-minimessage = "4.2.0-SNAPSHOT"
|
|
||||||
text-adapter = "3.0.6"
|
text-adapter = "3.0.6"
|
||||||
text = "3.0.4"
|
text = "3.0.4"
|
||||||
piston = "0.5.7"
|
piston = "0.5.7"
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
mockito = "5.4.0"
|
mockito = "5.5.0"
|
||||||
|
|
||||||
# Gradle plugins
|
# Gradle plugins
|
||||||
pluginyml = "0.6.0"
|
pluginyml = "0.6.0"
|
||||||
|
minotaur = "2.8.4"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
# Minecraft expectations
|
# Minecraft expectations
|
||||||
|
paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
|
||||||
fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil" }
|
fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil" }
|
||||||
log4jBom = { group = "org.apache.logging.log4j", name = "log4j-bom", version.ref = "log4j" }
|
log4jBom = { group = "org.apache.logging.log4j", name = "log4j-bom", version.ref = "log4j" }
|
||||||
|
log4jApi = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" }
|
||||||
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
|
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
|
||||||
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
|
snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "snakeyaml" }
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
dummypermscompat = { group = "com.sk89q", name = "dummypermscompat", version.ref = "dummypermscompat" }
|
dummypermscompat = { group = "com.sk89q", name = "dummypermscompat", version.ref = "dummypermscompat" }
|
||||||
@ -55,9 +70,12 @@ griefprevention = { group = "com.github.TechFortress", name = "GriefPrevention",
|
|||||||
griefdefender = { group = "com.griefdefender", name = "api", version.ref = "griefdefender" }
|
griefdefender = { group = "com.griefdefender", name = "api", version.ref = "griefdefender" }
|
||||||
residence = { group = "com.bekvon.bukkit.residence", name = "Residence", version.ref = "residence" }
|
residence = { group = "com.bekvon.bukkit.residence", name = "Residence", version.ref = "residence" }
|
||||||
towny = { group = "com.palmergames.bukkit.towny", name = "towny", version.ref = "towny" }
|
towny = { group = "com.palmergames.bukkit.towny", name = "towny", version.ref = "towny" }
|
||||||
|
plotSquaredCore = { group = "com.intellectualsites.plotsquared", name = "plotsquared-core", version.ref = "plotsquared" }
|
||||||
|
plotSquaredBukkit = { group = "com.intellectualsites.plotsquared", name = "plotsquared-bukkit", version.ref = "plotsquared" }
|
||||||
|
|
||||||
# Third Party
|
# Third Party
|
||||||
bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" }
|
bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" }
|
||||||
|
bstatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" }
|
||||||
sparsebitset = { group = "com.zaxxer", name = "SparseBitSet", version.ref = "sparsebitset" }
|
sparsebitset = { group = "com.zaxxer", name = "SparseBitSet", version.ref = "sparsebitset" }
|
||||||
parallelgzip = { group = "org.anarres", name = "parallelgzip", version.ref = "parallelgzip" }
|
parallelgzip = { group = "org.anarres", name = "parallelgzip", version.ref = "parallelgzip" }
|
||||||
adventureNbt = { group = "net.kyori", name = "adventure-nbt", version.ref = "adventure" }
|
adventureNbt = { group = "net.kyori", name = "adventure-nbt", version.ref = "adventure" }
|
||||||
@ -74,6 +92,15 @@ jlibnoise = { group = "com.sk89q.lib", name = "jlibnoise", version.ref = "jlibno
|
|||||||
jchronic = { group = "com.sk89q", name = "jchronic", version.ref = "jchronic" }
|
jchronic = { group = "com.sk89q", name = "jchronic", version.ref = "jchronic" }
|
||||||
lz4Java = { group = "org.lz4", name = "lz4-java", version.ref = "lz4-java" }
|
lz4Java = { group = "org.lz4", name = "lz4-java", version.ref = "lz4-java" }
|
||||||
lz4JavaStream = { group = "net.jpountz", name = "lz4-java-stream", version.ref = "lz4-stream" }
|
lz4JavaStream = { group = "net.jpountz", name = "lz4-java-stream", version.ref = "lz4-stream" }
|
||||||
|
commonsCli = { group = "commons-cli", name = "commons-cli", version.ref = "commons-cli" }
|
||||||
|
paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" }
|
||||||
|
adventureApi = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" }
|
||||||
|
adventureMiniMessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" }
|
||||||
|
adventureBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventure-bukkit" }
|
||||||
|
paster = { group = "com.intellectualsites.paster", name = "Paster", version.ref = "paster" }
|
||||||
|
vault = { group = "com.github.MilkBowl", name = "VaultAPI", version.ref = "vault" }
|
||||||
|
serverlib = { group = "dev.notmyfault.serverlib", name = "ServerLib", version.ref = "serverlib" }
|
||||||
|
checkerqual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerqual" }
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
## Text
|
## Text
|
||||||
@ -95,3 +122,4 @@ log4jCore = { group = "org.apache.logging.log4j", name = "log4j-core", version.r
|
|||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
pluginyml = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
|
pluginyml = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
|
||||||
|
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binäre Datei nicht angezeigt.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
3
gradlew
vendored
3
gradlew
vendored
@ -83,7 +83,8 @@ done
|
|||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
@ -2,7 +2,7 @@ rootProject.name = "FastAsyncWorldEdit"
|
|||||||
|
|
||||||
include("worldedit-libs")
|
include("worldedit-libs")
|
||||||
|
|
||||||
listOf("legacy", "1_17_1", "1_18_2", "1_19_4", "1_20").forEach {
|
listOf("legacy", "1_17_1", "1_18_2", "1_19_4", "1_20", "1_20_2").forEach {
|
||||||
include("worldedit-bukkit:adapters:adapter-$it")
|
include("worldedit-bukkit:adapters:adapter-$it")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,5 +22,5 @@ configurations.all {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.17.1-R0.1-20220414.034903-210")
|
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.17.1-R0.1-20220414.034903-210")
|
||||||
compileOnly("io.papermc:paperlib")
|
compileOnly(libs.paperlib)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import com.fastasyncworldedit.core.util.ReflectionUtils;
|
|||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
import com.mojang.datafixers.util.Either;
|
import com.mojang.datafixers.util.Either;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
@ -48,6 +49,8 @@ import net.minecraft.world.level.chunk.Palette;
|
|||||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
import net.minecraft.world.level.gameevent.GameEventDispatcher;
|
import net.minecraft.world.level.gameevent.GameEventDispatcher;
|
||||||
import net.minecraft.world.level.gameevent.GameEventListener;
|
import net.minecraft.world.level.gameevent.GameEventListener;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.craftbukkit.v1_17_R1.CraftChunk;
|
import org.bukkit.craftbukkit.v1_17_R1.CraftChunk;
|
||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
@ -61,6 +64,8 @@ import java.util.Locale;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -80,17 +85,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
|
|
||||||
private static final MethodHandle methodGetVisibleChunk;
|
private static final MethodHandle methodGetVisibleChunk;
|
||||||
|
|
||||||
private static final int CHUNKSECTION_BASE;
|
|
||||||
private static final int CHUNKSECTION_SHIFT;
|
|
||||||
|
|
||||||
private static final Field fieldLock;
|
private static final Field fieldLock;
|
||||||
private static final long fieldLockOffset;
|
|
||||||
|
|
||||||
private static final Field fieldGameEventDispatcherSections;
|
private static final Field fieldGameEventDispatcherSections;
|
||||||
private static final MethodHandle methodremoveBlockEntityTicker;
|
private static final MethodHandle methodremoveBlockEntityTicker;
|
||||||
|
|
||||||
private static final Field fieldRemove;
|
private static final Field fieldRemove;
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
fieldBits = PalettedContainer.class.getDeclaredField(Refraction.pickName("bits", "l"));
|
fieldBits = PalettedContainer.class.getDeclaredField(Refraction.pickName("bits", "l"));
|
||||||
@ -120,15 +123,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
getVisibleChunkIfPresent.setAccessible(true);
|
getVisibleChunkIfPresent.setAccessible(true);
|
||||||
methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent);
|
methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent);
|
||||||
|
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
if (!PaperLib.isPaper()) {
|
if (!PaperLib.isPaper()) {
|
||||||
|
|
||||||
fieldLock = PalettedContainer.class.getDeclaredField(Refraction.pickName("lock", "m"));
|
fieldLock = PalettedContainer.class.getDeclaredField(Refraction.pickName("lock", "m"));
|
||||||
fieldLockOffset = unsafe.objectFieldOffset(fieldLock);
|
fieldLock.setAccessible(true);
|
||||||
} else {
|
} else {
|
||||||
// in paper, the used methods are synchronized properly
|
// in paper, the used methods are synchronized properly
|
||||||
fieldLock = null;
|
fieldLock = null;
|
||||||
fieldLockOffset = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldGameEventDispatcherSections = LevelChunk.class.getDeclaredField(Refraction.pickName(
|
fieldGameEventDispatcherSections = LevelChunk.class.getDeclaredField(Refraction.pickName(
|
||||||
@ -145,13 +145,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
|
|
||||||
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p"));
|
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p"));
|
||||||
fieldRemove.setAccessible(true);
|
fieldRemove.setAccessible(true);
|
||||||
|
|
||||||
CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class);
|
|
||||||
int scale = unsafe.arrayIndexScale(LevelChunkSection[].class);
|
|
||||||
if ((scale & (scale - 1)) != 0) {
|
|
||||||
throw new Error("data type scale not a power of two");
|
|
||||||
}
|
|
||||||
CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale);
|
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Throwable rethrow) {
|
} catch (Throwable rethrow) {
|
||||||
@ -166,9 +159,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
LevelChunkSection value,
|
LevelChunkSection value,
|
||||||
int layer
|
int layer
|
||||||
) {
|
) {
|
||||||
long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE;
|
|
||||||
if (layer >= 0 && layer < sections.length) {
|
if (layer >= 0 && layer < sections.length) {
|
||||||
return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value);
|
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -183,14 +175,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
synchronized (section) {
|
synchronized (section) {
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
||||||
Semaphore currentLock = (Semaphore) unsafe.getObject(blocks, fieldLockOffset);
|
Semaphore currentLock = (Semaphore) fieldLock.get(blocks);
|
||||||
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
||||||
return delegateSemaphore;
|
return delegateSemaphore;
|
||||||
}
|
}
|
||||||
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
||||||
unsafe.putObject(blocks, fieldLockOffset, newLock);
|
fieldLock.set(blocks, newLock);
|
||||||
return newLock;
|
return newLock;
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@ -225,7 +216,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
||||||
try {
|
try {
|
||||||
CraftChunk chunk = (CraftChunk) future.get();
|
CraftChunk chunk;
|
||||||
|
try {
|
||||||
|
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
String world = serverLevel.getWorld().getName();
|
||||||
|
// We've already taken 10 seconds we can afford to wait a little here.
|
||||||
|
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
|
||||||
|
if (loaded) {
|
||||||
|
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
|
||||||
|
// Retry chunk load
|
||||||
|
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
|
||||||
|
}
|
||||||
|
}
|
||||||
return chunk.getHandle();
|
return chunk.getHandle();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -184,9 +184,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
originalChunkProvider = originalServerWorld.getChunkSource();
|
||||||
if (!(originalChunkProvider instanceof ServerChunkCache)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
//flat bedrock? (only on paper)
|
||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
@ -197,7 +194,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
|
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -332,7 +329,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ReflectionUtils.unsafeSet(chunkSourceField, freshWorld, freshChunkProvider);
|
chunkSourceField.set(freshWorld, freshChunkProvider);
|
||||||
//let's start then
|
//let's start then
|
||||||
structureManager = server.getStructureManager();
|
structureManager = server.getStructureManager();
|
||||||
threadedLevelLightEngine = freshChunkProvider.getLightEngine();
|
threadedLevelLightEngine = freshChunkProvider.getLightEngine();
|
||||||
@ -680,7 +677,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> processChunk(Long xz, List<ChunkAccess> accessibleChunks) {
|
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
||||||
return chunkStatus.generate(
|
return chunkStatus.generate(
|
||||||
Runnable::run, // TODO revisit, we might profit from this somehow?
|
Runnable::run, // TODO revisit, we might profit from this somehow?
|
||||||
freshWorld,
|
freshWorld,
|
||||||
|
@ -13,5 +13,5 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
// https://papermc.io/repo/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
|
// https://papermc.io/repo/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
|
||||||
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.18.2-R0.1-20220920.010157-167")
|
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.18.2-R0.1-20220920.010157-167")
|
||||||
compileOnly("io.papermc:paperlib")
|
compileOnly(libs.paperlib)
|
||||||
}
|
}
|
||||||
|
@ -13,17 +13,16 @@ import com.mojang.datafixers.util.Either;
|
|||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
import io.papermc.lib.PaperLib;
|
import io.papermc.lib.PaperLib;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.IdMap;
|
import net.minecraft.core.IdMap;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.SectionPos;
|
|
||||||
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||||
import net.minecraft.server.level.ChunkHolder;
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
import net.minecraft.server.level.ChunkMap;
|
import net.minecraft.server.level.ChunkMap;
|
||||||
@ -41,7 +40,6 @@ import net.minecraft.world.level.LevelAccessor;
|
|||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.chunk.GlobalPalette;
|
import net.minecraft.world.level.chunk.GlobalPalette;
|
||||||
import net.minecraft.world.level.chunk.HashMapPalette;
|
import net.minecraft.world.level.chunk.HashMapPalette;
|
||||||
@ -51,8 +49,8 @@ import net.minecraft.world.level.chunk.LinearPalette;
|
|||||||
import net.minecraft.world.level.chunk.Palette;
|
import net.minecraft.world.level.chunk.Palette;
|
||||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
import net.minecraft.world.level.chunk.SingleValuePalette;
|
import net.minecraft.world.level.chunk.SingleValuePalette;
|
||||||
import net.minecraft.world.level.gameevent.GameEventDispatcher;
|
import org.apache.logging.log4j.Logger;
|
||||||
import net.minecraft.world.level.gameevent.GameEventListener;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.craftbukkit.v1_18_R2.CraftChunk;
|
import org.bukkit.craftbukkit.v1_18_R2.CraftChunk;
|
||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
@ -75,6 +73,8 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public final class PaperweightPlatformAdapter extends NMSAdapter {
|
public final class PaperweightPlatformAdapter extends NMSAdapter {
|
||||||
@ -92,20 +92,16 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
|
|
||||||
private static final MethodHandle methodGetVisibleChunk;
|
private static final MethodHandle methodGetVisibleChunk;
|
||||||
|
|
||||||
private static final int CHUNKSECTION_BASE;
|
|
||||||
private static final int CHUNKSECTION_SHIFT;
|
|
||||||
|
|
||||||
private static final Field fieldThreadingDetector;
|
private static final Field fieldThreadingDetector;
|
||||||
private static final long fieldThreadingDetectorOffset;
|
|
||||||
|
|
||||||
private static final Field fieldLock;
|
private static final Field fieldLock;
|
||||||
private static final long fieldLockOffset;
|
|
||||||
|
|
||||||
private static final MethodHandle methodRemoveGameEventListener;
|
private static final MethodHandle methodRemoveGameEventListener;
|
||||||
private static final MethodHandle methodremoveTickingBlockEntity;
|
private static final MethodHandle methodremoveTickingBlockEntity;
|
||||||
|
|
||||||
private static final Field fieldRemove;
|
private static final Field fieldRemove;
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "d"));
|
fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "d"));
|
||||||
@ -134,20 +130,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
getVisibleChunkIfPresent.setAccessible(true);
|
getVisibleChunkIfPresent.setAccessible(true);
|
||||||
methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent);
|
methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent);
|
||||||
|
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
if (!PaperLib.isPaper()) {
|
if (!PaperLib.isPaper()) {
|
||||||
fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f"));
|
fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f"));
|
||||||
fieldThreadingDetectorOffset = unsafe.objectFieldOffset(fieldThreadingDetector);
|
fieldThreadingDetector.setAccessible(true);
|
||||||
|
|
||||||
fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c"));
|
fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c"));
|
||||||
fieldLockOffset = unsafe.objectFieldOffset(fieldLock);
|
fieldLock.setAccessible(true);
|
||||||
} else {
|
} else {
|
||||||
// in paper, the used methods are synchronized properly
|
// in paper, the used methods are synchronized properly
|
||||||
fieldThreadingDetector = null;
|
fieldThreadingDetector = null;
|
||||||
fieldThreadingDetectorOffset = -1;
|
|
||||||
|
|
||||||
fieldLock = null;
|
fieldLock = null;
|
||||||
fieldLockOffset = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Method removeGameEventListener = LevelChunk.class.getDeclaredMethod(
|
Method removeGameEventListener = LevelChunk.class.getDeclaredMethod(
|
||||||
@ -167,13 +158,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
|
|
||||||
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p"));
|
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p"));
|
||||||
fieldRemove.setAccessible(true);
|
fieldRemove.setAccessible(true);
|
||||||
|
|
||||||
CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class);
|
|
||||||
int scale = unsafe.arrayIndexScale(LevelChunkSection[].class);
|
|
||||||
if ((scale & (scale - 1)) != 0) {
|
|
||||||
throw new Error("data type scale not a power of two");
|
|
||||||
}
|
|
||||||
CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale);
|
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Throwable rethrow) {
|
} catch (Throwable rethrow) {
|
||||||
@ -188,9 +172,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
LevelChunkSection value,
|
LevelChunkSection value,
|
||||||
int layer
|
int layer
|
||||||
) {
|
) {
|
||||||
long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE;
|
|
||||||
if (layer >= 0 && layer < sections.length) {
|
if (layer >= 0 && layer < sections.length) {
|
||||||
return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value);
|
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -205,19 +188,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
synchronized (section) {
|
synchronized (section) {
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
||||||
ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject(
|
ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks);
|
||||||
blocks,
|
|
||||||
fieldThreadingDetectorOffset
|
|
||||||
);
|
|
||||||
synchronized (currentThreadingDetector) {
|
synchronized (currentThreadingDetector) {
|
||||||
Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset);
|
Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector);
|
||||||
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
||||||
return delegateSemaphore;
|
return delegateSemaphore;
|
||||||
}
|
}
|
||||||
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
||||||
unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock);
|
fieldLock.set(currentThreadingDetector, newLock);
|
||||||
return newLock;
|
return newLock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,7 +232,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
||||||
try {
|
try {
|
||||||
CraftChunk chunk = (CraftChunk) future.get();
|
CraftChunk chunk;
|
||||||
|
try {
|
||||||
|
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
String world = serverLevel.getWorld().getName();
|
||||||
|
// We've already taken 10 seconds we can afford to wait a little here.
|
||||||
|
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
|
||||||
|
if (loaded) {
|
||||||
|
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
|
||||||
|
// Retry chunk load
|
||||||
|
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
|
||||||
|
}
|
||||||
|
}
|
||||||
return chunk.getHandle();
|
return chunk.getHandle();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -178,9 +178,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
originalChunkProvider = originalServerWorld.getChunkSource();
|
||||||
if (!(originalChunkProvider instanceof ServerChunkCache)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
//flat bedrock? (only on paper)
|
||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
@ -191,7 +188,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
|
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -345,7 +342,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ReflectionUtils.unsafeSet(chunkSourceField, freshWorld, freshChunkProvider);
|
chunkSourceField.set(freshWorld, freshChunkProvider);
|
||||||
//let's start then
|
//let's start then
|
||||||
structureManager = server.getStructureManager();
|
structureManager = server.getStructureManager();
|
||||||
threadedLevelLightEngine = freshChunkProvider.getLightEngine();
|
threadedLevelLightEngine = freshChunkProvider.getLightEngine();
|
||||||
@ -523,7 +520,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> processChunk(Long xz, List<ChunkAccess> accessibleChunks) {
|
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
||||||
return chunkStatus.generate(
|
return chunkStatus.generate(
|
||||||
Runnable::run, // TODO revisit, we might profit from this somehow?
|
Runnable::run, // TODO revisit, we might profit from this somehow?
|
||||||
freshWorld,
|
freshWorld,
|
||||||
|
@ -12,5 +12,5 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.19.4-R0.1-20230608.201059-104")
|
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.19.4-R0.1-20230608.201059-104")
|
||||||
compileOnly("io.papermc:paperlib")
|
compileOnly(libs.paperlib)
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import com.mojang.datafixers.util.Either;
|
|||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
@ -43,7 +44,6 @@ import net.minecraft.world.level.biome.Biome;
|
|||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.GlobalPalette;
|
import net.minecraft.world.level.chunk.GlobalPalette;
|
||||||
import net.minecraft.world.level.chunk.HashMapPalette;
|
import net.minecraft.world.level.chunk.HashMapPalette;
|
||||||
@ -54,6 +54,8 @@ import net.minecraft.world.level.chunk.Palette;
|
|||||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
import net.minecraft.world.level.chunk.SingleValuePalette;
|
import net.minecraft.world.level.chunk.SingleValuePalette;
|
||||||
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
|
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
|
||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
@ -61,7 +63,6 @@ import javax.annotation.Nonnull;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@ -77,9 +78,10 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static java.lang.invoke.MethodType.methodType;
|
|
||||||
import static net.minecraft.core.registries.Registries.BIOME;
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
public final class PaperweightPlatformAdapter extends NMSAdapter {
|
public final class PaperweightPlatformAdapter extends NMSAdapter {
|
||||||
@ -97,20 +99,16 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
|
|
||||||
private static final MethodHandle methodGetVisibleChunk;
|
private static final MethodHandle methodGetVisibleChunk;
|
||||||
|
|
||||||
private static final int CHUNKSECTION_BASE;
|
|
||||||
private static final int CHUNKSECTION_SHIFT;
|
|
||||||
|
|
||||||
private static final Field fieldThreadingDetector;
|
private static final Field fieldThreadingDetector;
|
||||||
private static final long fieldThreadingDetectorOffset;
|
|
||||||
|
|
||||||
private static final Field fieldLock;
|
private static final Field fieldLock;
|
||||||
private static final long fieldLockOffset;
|
|
||||||
|
|
||||||
private static final MethodHandle methodRemoveGameEventListener;
|
private static final MethodHandle methodRemoveGameEventListener;
|
||||||
private static final MethodHandle methodremoveTickingBlockEntity;
|
private static final MethodHandle methodremoveTickingBlockEntity;
|
||||||
|
|
||||||
private static final Field fieldRemove;
|
private static final Field fieldRemove;
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
static final boolean POST_CHUNK_REWRITE;
|
static final boolean POST_CHUNK_REWRITE;
|
||||||
private static Method PAPER_CHUNK_GEN_ALL_ENTITIES;
|
private static Method PAPER_CHUNK_GEN_ALL_ENTITIES;
|
||||||
private static Field LEVEL_CHUNK_ENTITIES;
|
private static Field LEVEL_CHUNK_ENTITIES;
|
||||||
@ -145,20 +143,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
getVisibleChunkIfPresent.setAccessible(true);
|
getVisibleChunkIfPresent.setAccessible(true);
|
||||||
methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent);
|
methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent);
|
||||||
|
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
if (!PaperLib.isPaper()) {
|
if (!PaperLib.isPaper()) {
|
||||||
fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f"));
|
fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f"));
|
||||||
fieldThreadingDetectorOffset = unsafe.objectFieldOffset(fieldThreadingDetector);
|
fieldThreadingDetector.setAccessible(true);
|
||||||
|
|
||||||
fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c"));
|
fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c"));
|
||||||
fieldLockOffset = unsafe.objectFieldOffset(fieldLock);
|
fieldLock.setAccessible(true);
|
||||||
} else {
|
} else {
|
||||||
// in paper, the used methods are synchronized properly
|
// in paper, the used methods are synchronized properly
|
||||||
fieldThreadingDetector = null;
|
fieldThreadingDetector = null;
|
||||||
fieldThreadingDetectorOffset = -1;
|
|
||||||
|
|
||||||
fieldLock = null;
|
fieldLock = null;
|
||||||
fieldLockOffset = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Method removeGameEventListener = LevelChunk.class.getDeclaredMethod(
|
Method removeGameEventListener = LevelChunk.class.getDeclaredMethod(
|
||||||
@ -181,12 +174,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q"));
|
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q"));
|
||||||
fieldRemove.setAccessible(true);
|
fieldRemove.setAccessible(true);
|
||||||
|
|
||||||
CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class);
|
|
||||||
int scale = unsafe.arrayIndexScale(LevelChunkSection[].class);
|
|
||||||
if ((scale & (scale - 1)) != 0) {
|
|
||||||
throw new Error("data type scale not a power of two");
|
|
||||||
}
|
|
||||||
CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale);
|
|
||||||
boolean chunkRewrite;
|
boolean chunkRewrite;
|
||||||
try {
|
try {
|
||||||
ServerLevel.class.getDeclaredMethod("getEntityLookup");
|
ServerLevel.class.getDeclaredMethod("getEntityLookup");
|
||||||
@ -222,9 +209,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
LevelChunkSection value,
|
LevelChunkSection value,
|
||||||
int layer
|
int layer
|
||||||
) {
|
) {
|
||||||
long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE;
|
|
||||||
if (layer >= 0 && layer < sections.length) {
|
if (layer >= 0 && layer < sections.length) {
|
||||||
return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value);
|
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -239,19 +225,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
synchronized (section) {
|
synchronized (section) {
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
||||||
ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject(
|
ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks);
|
||||||
blocks,
|
|
||||||
fieldThreadingDetectorOffset
|
|
||||||
);
|
|
||||||
synchronized (currentThreadingDetector) {
|
synchronized (currentThreadingDetector) {
|
||||||
Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset);
|
Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector);
|
||||||
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
||||||
return delegateSemaphore;
|
return delegateSemaphore;
|
||||||
}
|
}
|
||||||
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
||||||
unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock);
|
fieldLock.set(currentThreadingDetector, newLock);
|
||||||
return newLock;
|
return newLock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,7 +269,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
||||||
try {
|
try {
|
||||||
CraftChunk chunk = (CraftChunk) future.get();
|
CraftChunk chunk;
|
||||||
|
try {
|
||||||
|
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
String world = serverLevel.getWorld().getName();
|
||||||
|
// We've already taken 10 seconds we can afford to wait a little here.
|
||||||
|
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
|
||||||
|
if (loaded) {
|
||||||
|
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
|
||||||
|
// Retry chunk load
|
||||||
|
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
|
||||||
|
}
|
||||||
|
}
|
||||||
addTicket(serverLevel, chunkX, chunkZ);
|
addTicket(serverLevel, chunkX, chunkZ);
|
||||||
return (LevelChunk) chunk.getHandle(ChunkStatus.FULL);
|
return (LevelChunk) chunk.getHandle(ChunkStatus.FULL);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -192,9 +192,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
originalChunkProvider = originalServerWorld.getChunkSource();
|
||||||
if (!(originalChunkProvider instanceof ServerChunkCache)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
//flat bedrock? (only on paper)
|
||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
@ -205,7 +202,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
|
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -372,7 +369,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ReflectionUtils.unsafeSet(chunkSourceField, freshWorld, freshChunkProvider);
|
chunkSourceField.set(freshWorld, freshChunkProvider);
|
||||||
//let's start then
|
//let's start then
|
||||||
structureTemplateManager = server.getStructureManager();
|
structureTemplateManager = server.getStructureManager();
|
||||||
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
||||||
@ -554,7 +551,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> processChunk(Long xz, List<ChunkAccess> accessibleChunks) {
|
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
||||||
return chunkStatus.generate(
|
return chunkStatus.generate(
|
||||||
Runnable::run, // TODO revisit, we might profit from this somehow?
|
Runnable::run, // TODO revisit, we might profit from this somehow?
|
||||||
freshWorld,
|
freshWorld,
|
||||||
|
@ -12,6 +12,6 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
|
// https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
|
||||||
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.1-R0.1-20230623.105806-29")
|
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.1-R0.1-20230916.212543-167")
|
||||||
compileOnly("io.papermc:paperlib")
|
compileOnly(libs.paperlib)
|
||||||
}
|
}
|
||||||
|
@ -637,7 +637,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
|
|||||||
final net.minecraft.core.Direction enumFacing = adapt(face);
|
final net.minecraft.core.Direction enumFacing = adapt(face);
|
||||||
BlockHitResult rayTrace = new BlockHitResult(blockVec, enumFacing, blockPos, false);
|
BlockHitResult rayTrace = new BlockHitResult(blockVec, enumFacing, blockPos, false);
|
||||||
UseOnContext context = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTrace);
|
UseOnContext context = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTrace);
|
||||||
InteractionResult result = stack.useOn(context, InteractionHand.MAIN_HAND);
|
InteractionResult result = stack.useOn(context);
|
||||||
if (result != InteractionResult.SUCCESS) {
|
if (result != InteractionResult.SUCCESS) {
|
||||||
if (worldServer.getBlockState(blockPos).use(worldServer, fakePlayer, InteractionHand.MAIN_HAND, rayTrace).consumesAction()) {
|
if (worldServer.getBlockState(blockPos).use(worldServer, fakePlayer, InteractionHand.MAIN_HAND, rayTrace).consumesAction()) {
|
||||||
result = InteractionResult.SUCCESS;
|
result = InteractionResult.SUCCESS;
|
||||||
|
@ -14,6 +14,7 @@ import com.mojang.datafixers.util.Either;
|
|||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
@ -54,6 +55,8 @@ import net.minecraft.world.level.chunk.Palette;
|
|||||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
import net.minecraft.world.level.chunk.SingleValuePalette;
|
import net.minecraft.world.level.chunk.SingleValuePalette;
|
||||||
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.CraftChunk;
|
import org.bukkit.craftbukkit.v1_20_R1.CraftChunk;
|
||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
@ -77,6 +80,8 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static java.lang.invoke.MethodType.methodType;
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
@ -97,14 +102,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
|
|
||||||
private static final MethodHandle methodGetVisibleChunk;
|
private static final MethodHandle methodGetVisibleChunk;
|
||||||
|
|
||||||
private static final int CHUNKSECTION_BASE;
|
|
||||||
private static final int CHUNKSECTION_SHIFT;
|
|
||||||
|
|
||||||
private static final Field fieldThreadingDetector;
|
private static final Field fieldThreadingDetector;
|
||||||
private static final long fieldThreadingDetectorOffset;
|
|
||||||
|
|
||||||
private static final Field fieldLock;
|
private static final Field fieldLock;
|
||||||
private static final long fieldLockOffset;
|
|
||||||
|
|
||||||
private static final MethodHandle methodRemoveGameEventListener;
|
private static final MethodHandle methodRemoveGameEventListener;
|
||||||
private static final MethodHandle methodremoveTickingBlockEntity;
|
private static final MethodHandle methodremoveTickingBlockEntity;
|
||||||
@ -117,6 +116,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
|
|
||||||
private static final Field fieldRemove;
|
private static final Field fieldRemove;
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
static final boolean POST_CHUNK_REWRITE;
|
static final boolean POST_CHUNK_REWRITE;
|
||||||
private static Method PAPER_CHUNK_GEN_ALL_ENTITIES;
|
private static Method PAPER_CHUNK_GEN_ALL_ENTITIES;
|
||||||
private static Field LEVEL_CHUNK_ENTITIES;
|
private static Field LEVEL_CHUNK_ENTITIES;
|
||||||
@ -151,20 +152,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
getVisibleChunkIfPresent.setAccessible(true);
|
getVisibleChunkIfPresent.setAccessible(true);
|
||||||
methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent);
|
methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent);
|
||||||
|
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
if (!PaperLib.isPaper()) {
|
if (!PaperLib.isPaper()) {
|
||||||
fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f"));
|
fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f"));
|
||||||
fieldThreadingDetectorOffset = unsafe.objectFieldOffset(fieldThreadingDetector);
|
fieldThreadingDetector.setAccessible(true);
|
||||||
|
|
||||||
fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c"));
|
fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c"));
|
||||||
fieldLockOffset = unsafe.objectFieldOffset(fieldLock);
|
fieldLock.setAccessible(true);
|
||||||
} else {
|
} else {
|
||||||
// in paper, the used methods are synchronized properly
|
// in paper, the used methods are synchronized properly
|
||||||
fieldThreadingDetector = null;
|
fieldThreadingDetector = null;
|
||||||
fieldThreadingDetectorOffset = -1;
|
|
||||||
|
|
||||||
fieldLock = null;
|
fieldLock = null;
|
||||||
fieldLockOffset = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Method removeGameEventListener = LevelChunk.class.getDeclaredMethod(
|
Method removeGameEventListener = LevelChunk.class.getDeclaredMethod(
|
||||||
@ -187,12 +183,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q"));
|
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q"));
|
||||||
fieldRemove.setAccessible(true);
|
fieldRemove.setAccessible(true);
|
||||||
|
|
||||||
CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class);
|
|
||||||
int scale = unsafe.arrayIndexScale(LevelChunkSection[].class);
|
|
||||||
if ((scale & (scale - 1)) != 0) {
|
|
||||||
throw new Error("data type scale not a power of two");
|
|
||||||
}
|
|
||||||
CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale);
|
|
||||||
boolean chunkRewrite;
|
boolean chunkRewrite;
|
||||||
try {
|
try {
|
||||||
ServerLevel.class.getDeclaredMethod("getEntityLookup");
|
ServerLevel.class.getDeclaredMethod("getEntityLookup");
|
||||||
@ -242,9 +232,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
LevelChunkSection value,
|
LevelChunkSection value,
|
||||||
int layer
|
int layer
|
||||||
) {
|
) {
|
||||||
long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE;
|
|
||||||
if (layer >= 0 && layer < sections.length) {
|
if (layer >= 0 && layer < sections.length) {
|
||||||
return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value);
|
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -259,19 +248,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
synchronized (section) {
|
synchronized (section) {
|
||||||
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
|
||||||
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
||||||
ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject(
|
ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks);
|
||||||
blocks,
|
|
||||||
fieldThreadingDetectorOffset
|
|
||||||
);
|
|
||||||
synchronized (currentThreadingDetector) {
|
synchronized (currentThreadingDetector) {
|
||||||
Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset);
|
Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector);
|
||||||
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
||||||
return delegateSemaphore;
|
return delegateSemaphore;
|
||||||
}
|
}
|
||||||
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
||||||
unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock);
|
fieldLock.set(currentThreadingDetector, newLock);
|
||||||
return newLock;
|
return newLock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,7 +292,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
|
|||||||
}
|
}
|
||||||
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
||||||
try {
|
try {
|
||||||
CraftChunk chunk = (CraftChunk) future.get();
|
CraftChunk chunk;
|
||||||
|
try {
|
||||||
|
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
String world = serverLevel.getWorld().getName();
|
||||||
|
// We've already taken 10 seconds we can afford to wait a little here.
|
||||||
|
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
|
||||||
|
if (loaded) {
|
||||||
|
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
|
||||||
|
// Retry chunk load
|
||||||
|
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
|
||||||
|
}
|
||||||
|
}
|
||||||
addTicket(serverLevel, chunkX, chunkZ);
|
addTicket(serverLevel, chunkX, chunkZ);
|
||||||
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
|
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -192,9 +192,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
originalChunkProvider = originalServerWorld.getChunkSource();
|
||||||
if (!(originalChunkProvider instanceof ServerChunkCache)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
//flat bedrock? (only on paper)
|
||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
@ -205,7 +202,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
|
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -373,7 +370,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ReflectionUtils.unsafeSet(chunkSourceField, freshWorld, freshChunkProvider);
|
chunkSourceField.set(freshWorld, freshChunkProvider);
|
||||||
//let's start then
|
//let's start then
|
||||||
structureTemplateManager = server.getStructureManager();
|
structureTemplateManager = server.getStructureManager();
|
||||||
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
||||||
@ -555,7 +552,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> processChunk(Long xz, List<ChunkAccess> accessibleChunks) {
|
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
||||||
return chunkStatus.generate(
|
return chunkStatus.generate(
|
||||||
Runnable::run, // TODO revisit, we might profit from this somehow?
|
Runnable::run, // TODO revisit, we might profit from this somehow?
|
||||||
freshWorld,
|
freshWorld,
|
||||||
|
17
worldedit-bukkit/adapters/adapter-1_20_2/build.gradle.kts
Normale Datei
17
worldedit-bukkit/adapters/adapter-1_20_2/build.gradle.kts
Normale Datei
@ -0,0 +1,17 @@
|
|||||||
|
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
applyPaperweightAdapterConfiguration()
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
|
||||||
|
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.2-R0.1-20230929.031919-14")
|
||||||
|
compileOnly(libs.paperlib)
|
||||||
|
}
|
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2;
|
||||||
|
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ClientInformation;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.stats.Stat;
|
||||||
|
import net.minecraft.world.MenuProvider;
|
||||||
|
import net.minecraft.world.damagesource.DamageSource;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.HumanoidArm;
|
||||||
|
import net.minecraft.world.entity.player.ChatVisiblity;
|
||||||
|
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
|
||||||
|
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
class PaperweightFakePlayer extends ServerPlayer {
|
||||||
|
private static final GameProfile FAKE_WORLDEDIT_PROFILE = new GameProfile(UUID.nameUUIDFromBytes("worldedit".getBytes()), "[WorldEdit]");
|
||||||
|
private static final Vec3 ORIGIN = new Vec3(0.0D, 0.0D, 0.0D);
|
||||||
|
private static final ClientInformation FAKE_CLIENT_INFO = new ClientInformation(
|
||||||
|
"en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false
|
||||||
|
);
|
||||||
|
|
||||||
|
PaperweightFakePlayer(ServerLevel world) {
|
||||||
|
super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE, FAKE_CLIENT_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vec3 position() {
|
||||||
|
return ORIGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void die(DamageSource damagesource) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entity changeDimension(ServerLevel worldserver, TeleportCause cause) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionalInt openMenu(MenuProvider factory) {
|
||||||
|
return OptionalInt.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateOptions(ClientInformation clientOptions) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void displayClientMessage(Component message, boolean actionBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void awardStat(Stat<?> stat, int amount) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void awardStat(Stat<?> stat) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInvulnerableTo(DamageSource damageSource) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openTextEdit(SignBlockEntity sign, boolean front) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
|
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
|
||||||
|
import com.sk89q.worldedit.util.SideEffect;
|
||||||
|
import com.sk89q.worldedit.util.SideEffectSet;
|
||||||
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.FullChunkStatus;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.event.block.BlockPhysicsEvent;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChunk, net.minecraft.world.level.block.state.BlockState, BlockPos> {
|
||||||
|
private static final int UPDATE = 1;
|
||||||
|
private static final int NOTIFY = 2;
|
||||||
|
|
||||||
|
private final com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter;
|
||||||
|
private final WeakReference<ServerLevel> world;
|
||||||
|
private SideEffectSet sideEffectSet;
|
||||||
|
|
||||||
|
public PaperweightWorldNativeAccess(com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter, WeakReference<ServerLevel> world) {
|
||||||
|
this.adapter = adapter;
|
||||||
|
this.world = world;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerLevel getWorld() {
|
||||||
|
return Objects.requireNonNull(world.get(), "The reference to the world was lost");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) {
|
||||||
|
this.sideEffectSet = sideEffectSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LevelChunk getChunk(int x, int z) {
|
||||||
|
return getWorld().getChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState toNative(BlockState state) {
|
||||||
|
int stateId = BlockStateIdAccess.getBlockStateId(state);
|
||||||
|
return BlockStateIdAccess.isValidInternalId(stateId)
|
||||||
|
? Block.stateById(stateId)
|
||||||
|
: ((CraftBlockData) BukkitAdapter.adapt(state)).getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk chunk, BlockPos position) {
|
||||||
|
return chunk.getBlockState(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState setBlockState(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState state) {
|
||||||
|
return chunk.setBlockState(position, state, false, this.sideEffectSet.shouldApply(SideEffect.UPDATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(net.minecraft.world.level.block.state.BlockState block, BlockPos position) {
|
||||||
|
return Block.updateFromNeighbourShapes(block, getWorld(), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockPos getPosition(int x, int y, int z) {
|
||||||
|
return new BlockPos(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLightingForBlock(BlockPos position) {
|
||||||
|
getWorld().getChunkSource().getLightEngine().checkBlock(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateTileEntity(final BlockPos position, final CompoundBinaryTag tag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
|
||||||
|
if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) {
|
||||||
|
getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChunkTicking(LevelChunk chunk) {
|
||||||
|
return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markBlockChanged(LevelChunk chunk, BlockPos position) {
|
||||||
|
if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) {
|
||||||
|
getWorld().getChunkSource().blockChanged(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
|
||||||
|
ServerLevel world = getWorld();
|
||||||
|
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
|
||||||
|
world.updateNeighborsAt(pos, oldState.getBlock());
|
||||||
|
} else {
|
||||||
|
// When we don't want events, manually run the physics without them.
|
||||||
|
Block block = oldState.getBlock();
|
||||||
|
fireNeighborChanged(pos, world, block, pos.west());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.east());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.below());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.above());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.north());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.south());
|
||||||
|
}
|
||||||
|
if (newState.hasAnalogOutputSignal()) {
|
||||||
|
world.updateNeighbourForOutputSignal(pos, newState.getBlock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not sure why neighborChanged is deprecated
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) {
|
||||||
|
world.getBlockState(neighborPos).neighborChanged(world, neighborPos, block, pos, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, int recursionLimit) {
|
||||||
|
ServerLevel world = getWorld();
|
||||||
|
// a == updateNeighbors
|
||||||
|
// b == updateDiagonalNeighbors
|
||||||
|
oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit);
|
||||||
|
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
|
||||||
|
CraftWorld craftWorld = world.getWorld();
|
||||||
|
BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState));
|
||||||
|
world.getCraftServer().getPluginManager().callEvent(event);
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit);
|
||||||
|
newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlockStateChange(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
|
||||||
|
getWorld().onBlockStateChange(pos, oldState, newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,185 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.util.ReflectionUtil;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
|
||||||
|
import com.sk89q.worldedit.world.registry.BlockMaterial;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.EmptyBlockGetter;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
import net.minecraft.world.level.block.LiquidBlock;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.material.PushReaction;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||||
|
|
||||||
|
public class PaperweightBlockMaterial implements BlockMaterial {
|
||||||
|
|
||||||
|
private final Block block;
|
||||||
|
private final BlockState blockState;
|
||||||
|
private final boolean isTranslucent;
|
||||||
|
private final CraftBlockData craftBlockData;
|
||||||
|
private final org.bukkit.Material craftMaterial;
|
||||||
|
private final int opacity;
|
||||||
|
private final CompoundTag tile;
|
||||||
|
|
||||||
|
public PaperweightBlockMaterial(Block block) {
|
||||||
|
this(block, block.defaultBlockState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaperweightBlockMaterial(Block block, BlockState blockState) {
|
||||||
|
this.block = block;
|
||||||
|
this.blockState = blockState;
|
||||||
|
this.craftBlockData = CraftBlockData.fromData(blockState);
|
||||||
|
this.craftMaterial = craftBlockData.getMaterial();
|
||||||
|
BlockBehaviour.Properties blockInfo = ReflectionUtil.getField(BlockBehaviour.class, block,
|
||||||
|
Refraction.pickName("properties", "aN"));
|
||||||
|
this.isTranslucent = !(boolean) ReflectionUtil.getField(BlockBehaviour.Properties.class, blockInfo,
|
||||||
|
Refraction.pickName("canOcclude", "m")
|
||||||
|
);
|
||||||
|
opacity = blockState.getLightBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
|
||||||
|
BlockEntity tileEntity = !(block instanceof EntityBlock) ? null : ((EntityBlock) block).newBlockEntity(
|
||||||
|
BlockPos.ZERO,
|
||||||
|
blockState
|
||||||
|
);
|
||||||
|
tile = tileEntity == null
|
||||||
|
? null
|
||||||
|
: new PaperweightLazyCompoundTag(Suppliers.memoize(tileEntity::saveWithId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block getBlock() {
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getState() {
|
||||||
|
return blockState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CraftBlockData getCraftBlockData() {
|
||||||
|
return craftBlockData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAir() {
|
||||||
|
return blockState.isAir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullCube() {
|
||||||
|
return craftMaterial.isOccluding();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpaque() {
|
||||||
|
return blockState.isOpaque();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPowerSource() {
|
||||||
|
return blockState.isSignalSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLiquid() {
|
||||||
|
// TODO: Better check ?
|
||||||
|
return block instanceof LiquidBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSolid() {
|
||||||
|
// TODO: Replace
|
||||||
|
return blockState.isSolid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getHardness() {
|
||||||
|
return craftBlockData.getState().destroySpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getResistance() {
|
||||||
|
return block.getExplosionResistance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getSlipperiness() {
|
||||||
|
return block.getFriction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLightValue() {
|
||||||
|
return blockState.getLightEmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLightOpacity() {
|
||||||
|
return opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFragileWhenPushed() {
|
||||||
|
return blockState.getPistonPushReaction() == PushReaction.DESTROY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUnpushable() {
|
||||||
|
return blockState.getPistonPushReaction() == PushReaction.BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTicksRandomly() {
|
||||||
|
return block.isRandomlyTicking(blockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMovementBlocker() {
|
||||||
|
return craftMaterial.isSolid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBurnable() {
|
||||||
|
return craftMaterial.isBurnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isToolRequired() {
|
||||||
|
// Removed in 1.16.1, this is not present in higher versions
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReplacedDuringPlacement() {
|
||||||
|
return blockState.canBeReplaced();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTranslucent() {
|
||||||
|
return isTranslucent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasContainer() {
|
||||||
|
return block instanceof EntityBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTile() {
|
||||||
|
return block instanceof EntityBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getDefaultTile() {
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMapColor() {
|
||||||
|
// rgb field
|
||||||
|
return block.defaultMapColor().col;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,652 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.IDelegateBukkitImplAdapter;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory;
|
||||||
|
import com.fastasyncworldedit.core.FaweCache;
|
||||||
|
import com.fastasyncworldedit.core.entity.LazyBaseEntity;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
|
import com.fastasyncworldedit.core.queue.IBatchProcessor;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
|
||||||
|
import com.fastasyncworldedit.core.util.NbtUtils;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
|
import com.sk89q.worldedit.EditSession;
|
||||||
|
import com.sk89q.worldedit.blocks.BaseItemStack;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitWorld;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen;
|
||||||
|
import com.sk89q.worldedit.entity.BaseEntity;
|
||||||
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.registry.state.BooleanProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.DirectionalProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.EnumProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.IntegerProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.Property;
|
||||||
|
import com.sk89q.worldedit.util.Direction;
|
||||||
|
import com.sk89q.worldedit.util.SideEffect;
|
||||||
|
import com.sk89q.worldedit.util.SideEffectSet;
|
||||||
|
import com.sk89q.worldedit.util.TreeGenerator;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||||
|
import com.sk89q.worldedit.util.nbt.BinaryTag;
|
||||||
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
||||||
|
import com.sk89q.worldedit.util.nbt.StringBinaryTag;
|
||||||
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockType;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import com.sk89q.worldedit.world.entity.EntityType;
|
||||||
|
import com.sk89q.worldedit.world.item.ItemType;
|
||||||
|
import com.sk89q.worldedit.world.registry.BlockMaterial;
|
||||||
|
import io.papermc.lib.PaperLib;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.WritableRegistry;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.util.StringRepresentable;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.TreeType;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockState;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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.OptionalInt;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
|
public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
|
||||||
|
IDelegateBukkitImplAdapter<net.minecraft.nbt.Tag> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
private static Method CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE = ChunkHolder.class.getDeclaredMethod("wasAccessibleSinceLastSave");
|
||||||
|
} catch (NoSuchMethodException ignored) { // may not be present in newer paper versions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PaperweightAdapter parent;
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Code that may break between versions of Minecraft
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
private final PaperweightMapChunkUtil mapUtil = new PaperweightMapChunkUtil();
|
||||||
|
private char[] ibdToStateOrdinal = null;
|
||||||
|
private int[] ordinalToIbdID = null;
|
||||||
|
private boolean initialised = false;
|
||||||
|
private Map<String, List<Property<?>>> allBlockProperties = null;
|
||||||
|
|
||||||
|
public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException {
|
||||||
|
this.parent = new PaperweightAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String getEntityId(Entity entity) {
|
||||||
|
ResourceLocation resourceLocation = net.minecraft.world.entity.EntityType.getKey(entity.getType());
|
||||||
|
return resourceLocation == null ? null : resourceLocation.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag compoundTag) {
|
||||||
|
entity.save(compoundTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BukkitImplAdapter<net.minecraft.nbt.Tag> getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean init() {
|
||||||
|
if (ibdToStateOrdinal != null && ibdToStateOrdinal[1] != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ibdToStateOrdinal = new char[BlockTypesCache.states.length]; // size
|
||||||
|
ordinalToIbdID = new int[ibdToStateOrdinal.length]; // size
|
||||||
|
for (int i = 0; i < ibdToStateOrdinal.length; i++) {
|
||||||
|
BlockState blockState = BlockTypesCache.states[i];
|
||||||
|
PaperweightBlockMaterial material = (PaperweightBlockMaterial) blockState.getMaterial();
|
||||||
|
int id = Block.BLOCK_STATE_REGISTRY.getId(material.getState());
|
||||||
|
char ordinal = blockState.getOrdinalChar();
|
||||||
|
ibdToStateOrdinal[id] = ordinal;
|
||||||
|
ordinalToIbdID[ordinal] = id;
|
||||||
|
}
|
||||||
|
Map<String, List<Property<?>>> properties = new HashMap<>();
|
||||||
|
try {
|
||||||
|
for (Field field : BlockStateProperties.class.getDeclaredFields()) {
|
||||||
|
Object obj = field.get(null);
|
||||||
|
if (!(obj instanceof net.minecraft.world.level.block.state.properties.Property<?> state)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Property<?> property;
|
||||||
|
if (state instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) {
|
||||||
|
property = new BooleanProperty(
|
||||||
|
state.getName(),
|
||||||
|
(List<Boolean>) ImmutableList.copyOf(state.getPossibleValues())
|
||||||
|
);
|
||||||
|
} else if (state instanceof DirectionProperty) {
|
||||||
|
property = new DirectionalProperty(
|
||||||
|
state.getName(),
|
||||||
|
state
|
||||||
|
.getPossibleValues()
|
||||||
|
.stream()
|
||||||
|
.map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase()))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
} else if (state instanceof net.minecraft.world.level.block.state.properties.EnumProperty) {
|
||||||
|
property = new EnumProperty(
|
||||||
|
state.getName(),
|
||||||
|
state
|
||||||
|
.getPossibleValues()
|
||||||
|
.stream()
|
||||||
|
.map(e -> ((StringRepresentable) e).getSerializedName())
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
} else if (state instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) {
|
||||||
|
property = new IntegerProperty(
|
||||||
|
state.getName(),
|
||||||
|
(List<Integer>) ImmutableList.copyOf(state.getPossibleValues())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("FastAsyncWorldEdit needs an update to support " + state
|
||||||
|
.getClass()
|
||||||
|
.getSimpleName());
|
||||||
|
}
|
||||||
|
properties.compute(property.getName().toLowerCase(Locale.ROOT), (k, v) -> {
|
||||||
|
if (v == null) {
|
||||||
|
v = new ArrayList<>(Collections.singletonList(property));
|
||||||
|
} else {
|
||||||
|
v.add(property);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
allBlockProperties = ImmutableMap.copyOf(properties);
|
||||||
|
}
|
||||||
|
initialised = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockMaterial getMaterial(BlockType blockType) {
|
||||||
|
Block block = getBlock(blockType);
|
||||||
|
return new PaperweightBlockMaterial(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized BlockMaterial getMaterial(BlockState state) {
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState();
|
||||||
|
return new PaperweightBlockMaterial(blockState.getBlock(), blockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block getBlock(BlockType blockType) {
|
||||||
|
return DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.BLOCK)
|
||||||
|
.get(new ResourceLocation(blockType.getNamespace(), blockType.getResource()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public BlockState getBlock(Location location) {
|
||||||
|
Preconditions.checkNotNull(location);
|
||||||
|
|
||||||
|
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
|
||||||
|
int x = location.getBlockX();
|
||||||
|
int y = location.getBlockY();
|
||||||
|
int z = location.getBlockZ();
|
||||||
|
final ServerLevel handle = craftWorld.getHandle();
|
||||||
|
LevelChunk chunk = handle.getChunk(x >> 4, z >> 4);
|
||||||
|
final BlockPos blockPos = new BlockPos(x, y, z);
|
||||||
|
final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos);
|
||||||
|
BlockState state = adapt(blockData);
|
||||||
|
if (state == null) {
|
||||||
|
org.bukkit.block.Block bukkitBlock = location.getBlock();
|
||||||
|
state = BukkitAdapter.adapt(bukkitBlock.getBlockData());
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseBlock getFullBlock(final Location location) {
|
||||||
|
Preconditions.checkNotNull(location);
|
||||||
|
|
||||||
|
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
|
||||||
|
int x = location.getBlockX();
|
||||||
|
int y = location.getBlockY();
|
||||||
|
int z = location.getBlockZ();
|
||||||
|
|
||||||
|
final ServerLevel handle = craftWorld.getHandle();
|
||||||
|
LevelChunk chunk = handle.getChunk(x >> 4, z >> 4);
|
||||||
|
final BlockPos blockPos = new BlockPos(x, y, z);
|
||||||
|
final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos);
|
||||||
|
BlockState state = adapt(blockData);
|
||||||
|
if (state == null) {
|
||||||
|
org.bukkit.block.Block bukkitBlock = location.getBlock();
|
||||||
|
state = BukkitAdapter.adapt(bukkitBlock.getBlockData());
|
||||||
|
}
|
||||||
|
if (state.getBlockType().getMaterial().hasContainer()) {
|
||||||
|
|
||||||
|
// Read the NBT data
|
||||||
|
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
|
||||||
|
if (blockEntity != null) {
|
||||||
|
net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId();
|
||||||
|
return state.toBaseBlock((CompoundBinaryTag) toNativeBinary(tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.toBaseBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SideEffect> getSupportedSideEffects() {
|
||||||
|
return SideEffectSet.defaults().getSideEffectsToApply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) {
|
||||||
|
return new PaperweightFaweWorldNativeAccess(
|
||||||
|
this,
|
||||||
|
new WeakReference<>(((CraftWorld) world).getHandle())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
|
||||||
|
Preconditions.checkNotNull(entity);
|
||||||
|
|
||||||
|
CraftEntity craftEntity = ((CraftEntity) entity);
|
||||||
|
Entity mcEntity = craftEntity.getHandle();
|
||||||
|
|
||||||
|
String id = getEntityId(mcEntity);
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id);
|
||||||
|
Supplier<CompoundBinaryTag> saveTag = () -> {
|
||||||
|
final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag();
|
||||||
|
readEntityIntoTag(mcEntity, minecraftTag);
|
||||||
|
//add Id for AbstractChangeSet to work
|
||||||
|
final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag);
|
||||||
|
final Map<String, BinaryTag> tags = NbtUtils.getCompoundBinaryTagValues(tag);
|
||||||
|
tags.put("Id", StringBinaryTag.of(id));
|
||||||
|
return CompoundBinaryTag.from(tags);
|
||||||
|
};
|
||||||
|
return new LazyBaseEntity(type, saveTag);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getRichBlockName(BlockType blockType) {
|
||||||
|
return parent.getRichBlockName(blockType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getRichItemName(ItemType itemType) {
|
||||||
|
return parent.getRichItemName(itemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getRichItemName(BaseItemStack itemStack) {
|
||||||
|
return parent.getRichItemName(itemStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionalInt getInternalBlockStateId(BlockState state) {
|
||||||
|
PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial();
|
||||||
|
net.minecraft.world.level.block.state.BlockState mcState = material.getCraftBlockData().getState();
|
||||||
|
return OptionalInt.of(Block.BLOCK_STATE_REGISTRY.getId(mcState));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState adapt(BlockData blockData) {
|
||||||
|
CraftBlockData cbd = ((CraftBlockData) blockData);
|
||||||
|
net.minecraft.world.level.block.state.BlockState ibd = cbd.getState();
|
||||||
|
return adapt(ibd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState adapt(net.minecraft.world.level.block.state.BlockState blockState) {
|
||||||
|
return BlockTypesCache.states[adaptToChar(blockState)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public char adaptToChar(net.minecraft.world.level.block.state.BlockState blockState) {
|
||||||
|
int id = Block.BLOCK_STATE_REGISTRY.getId(blockState);
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
} catch (ArrayIndexOutOfBoundsException e1) {
|
||||||
|
LOGGER.error("Attempted to convert {} with ID {} to char. ibdToStateOrdinal length: {}. Defaulting to air!",
|
||||||
|
blockState.getBlock(), Block.BLOCK_STATE_REGISTRY.getId(blockState), ibdToStateOrdinal.length, e1
|
||||||
|
);
|
||||||
|
return BlockTypesCache.ReservedIDs.AIR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public char ibdIDToOrdinal(int id) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getIbdToStateOrdinal() {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ibdToStateOrdinal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ordinalToIbdID(char ordinal) {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID[ordinal];
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID[ordinal];
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ordinalToIbdID[ordinal];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getOrdinalToIbdID() {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ordinalToIbdID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <B extends BlockStateHolder<B>> BlockData adapt(B state) {
|
||||||
|
PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial();
|
||||||
|
return material.getCraftBlockData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket chunkPacket) {
|
||||||
|
ServerLevel nmsWorld = ((CraftWorld) world).getHandle();
|
||||||
|
ChunkHolder map = PaperweightPlatformAdapter.getPlayerChunk(nmsWorld, chunkPacket.getChunkX(), chunkPacket.getChunkZ());
|
||||||
|
if (map != null && wasAccessibleSinceLastSave(map)) {
|
||||||
|
boolean flag = false;
|
||||||
|
// PlayerChunk.d players = map.players;
|
||||||
|
Stream<ServerPlayer> stream = /*players.a(new ChunkCoordIntPair(packet.getChunkX(), packet.getChunkZ()), flag)
|
||||||
|
*/ Stream.empty();
|
||||||
|
|
||||||
|
ServerPlayer checkPlayer = player == null ? null : ((CraftPlayer) player).getHandle();
|
||||||
|
stream.filter(entityPlayer -> checkPlayer == null || entityPlayer == checkPlayer)
|
||||||
|
.forEach(entityPlayer -> {
|
||||||
|
synchronized (chunkPacket) {
|
||||||
|
ClientboundLevelChunkWithLightPacket nmsPacket = (ClientboundLevelChunkWithLightPacket) chunkPacket.getNativePacket();
|
||||||
|
if (nmsPacket == null) {
|
||||||
|
nmsPacket = mapUtil.create(this, chunkPacket);
|
||||||
|
chunkPacket.setNativePacket(nmsPacket);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FaweCache.INSTANCE.CHUNK_FLAG.get().set(true);
|
||||||
|
entityPlayer.connection.send(nmsPacket);
|
||||||
|
} finally {
|
||||||
|
FaweCache.INSTANCE.CHUNK_FLAG.get().set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ? extends Property<?>> getProperties(BlockType blockType) {
|
||||||
|
return getParent().getProperties(blockType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canPlaceAt(org.bukkit.World world, BlockVector3 blockVector3, BlockState blockState) {
|
||||||
|
int internalId = BlockStateIdAccess.getBlockStateId(blockState);
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState1 = Block.stateById(internalId);
|
||||||
|
return blockState1.hasPostProcess(
|
||||||
|
((CraftWorld) world).getHandle(),
|
||||||
|
new BlockPos(blockVector3.getX(), blockVector3.getY(), blockVector3.getZ())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) {
|
||||||
|
ItemStack stack = new ItemStack(
|
||||||
|
DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.ITEM)
|
||||||
|
.get(ResourceLocation.tryParse(baseItemStack.getType().getId())),
|
||||||
|
baseItemStack.getAmount()
|
||||||
|
);
|
||||||
|
stack.setTag(((net.minecraft.nbt.CompoundTag) fromNative(baseItemStack.getNbtData())));
|
||||||
|
return CraftItemStack.asCraftMirror(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean generateTree(
|
||||||
|
TreeGenerator.TreeType treeType, EditSession editSession, BlockVector3 blockVector3,
|
||||||
|
org.bukkit.World bukkitWorld
|
||||||
|
) {
|
||||||
|
TreeType bukkitType = BukkitWorld.toBukkitTreeType(treeType);
|
||||||
|
if (bukkitType == TreeType.CHORUS_PLANT) {
|
||||||
|
blockVector3 = blockVector3.add(
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
); // bukkit skips the feature gen which does this offset normally, so we have to add it back
|
||||||
|
}
|
||||||
|
ServerLevel serverLevel = ((CraftWorld) bukkitWorld).getHandle();
|
||||||
|
final BlockVector3 finalBlockVector = blockVector3;
|
||||||
|
// Sync to main thread to ensure no clashes occur
|
||||||
|
Map<BlockPos, CraftBlockState> placed = TaskManager.taskManager().sync(() -> {
|
||||||
|
serverLevel.captureTreeGeneration = true;
|
||||||
|
serverLevel.captureBlockStates = true;
|
||||||
|
try {
|
||||||
|
if (!bukkitWorld.generateTree(BukkitAdapter.adapt(bukkitWorld, finalBlockVector), bukkitType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ImmutableMap.copyOf(serverLevel.capturedBlockStates);
|
||||||
|
} finally {
|
||||||
|
serverLevel.captureBlockStates = false;
|
||||||
|
serverLevel.captureTreeGeneration = false;
|
||||||
|
serverLevel.capturedBlockStates.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (placed == null || placed.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (CraftBlockState craftBlockState : placed.values()) {
|
||||||
|
if (craftBlockState == null || craftBlockState.getType() == Material.AIR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
editSession.setBlock(craftBlockState.getX(), craftBlockState.getY(), craftBlockState.getZ(),
|
||||||
|
BukkitAdapter.adapt(((org.bukkit.block.BlockState) craftBlockState).getBlockData())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
||||||
|
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);
|
||||||
|
final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount());
|
||||||
|
weStack.setNbt(((CompoundBinaryTag) toNativeBinary(nmsStack.getTag())));
|
||||||
|
return weStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tag toNative(net.minecraft.nbt.Tag foreign) {
|
||||||
|
return parent.toNative(foreign);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.nbt.Tag fromNative(Tag foreign) {
|
||||||
|
if (foreign instanceof PaperweightLazyCompoundTag) {
|
||||||
|
return ((PaperweightLazyCompoundTag) foreign).get();
|
||||||
|
}
|
||||||
|
return parent.fromNative(foreign);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
|
||||||
|
return new PaperweightRegen(bukkitWorld, region, target, options).regenerate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) {
|
||||||
|
return new PaperweightGetBlocks(world, chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInternalBiomeId(BiomeType biomeType) {
|
||||||
|
final Registry<Biome> registry = MinecraftServer
|
||||||
|
.getServer()
|
||||||
|
.registryAccess()
|
||||||
|
.registryOrThrow(BIOME);
|
||||||
|
ResourceLocation resourceLocation = ResourceLocation.tryParse(biomeType.getId());
|
||||||
|
Biome biome = registry.get(resourceLocation);
|
||||||
|
return registry.getId(biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<NamespacedKey> getRegisteredBiomes() {
|
||||||
|
WritableRegistry<Biome> biomeRegistry = (WritableRegistry<Biome>) ((CraftServer) Bukkit.getServer())
|
||||||
|
.getServer()
|
||||||
|
.registryAccess()
|
||||||
|
.registryOrThrow(BIOME);
|
||||||
|
List<ResourceLocation> keys = biomeRegistry.stream()
|
||||||
|
.map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
|
||||||
|
List<NamespacedKey> namespacedKeys = new ArrayList<>();
|
||||||
|
for (ResourceLocation key : keys) {
|
||||||
|
try {
|
||||||
|
namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOGGER.error("Error converting biome key {}", key.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namespacedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RelighterFactory getRelighterFactory() {
|
||||||
|
if (PaperLib.isPaper()) {
|
||||||
|
return new PaperweightStarlightRelighterFactory();
|
||||||
|
} else {
|
||||||
|
return new NMSRelighterFactory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<Property<?>>> getAllProperties() {
|
||||||
|
if (initialised) {
|
||||||
|
return allBlockProperties;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return allBlockProperties;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return allBlockProperties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBatchProcessor getTickingPostProcessor() {
|
||||||
|
return new PaperweightPostProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean wasAccessibleSinceLastSave(ChunkHolder holder) {
|
||||||
|
if (!PaperLib.isPaper() || !PaperweightPlatformAdapter.POST_CHUNK_REWRITE) {
|
||||||
|
try {
|
||||||
|
return (boolean) CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE.invoke(holder);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException ignored) {
|
||||||
|
// fall-through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Papers new chunk system has no related replacement - therefor we assume true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,287 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.math.IntPair;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.fastasyncworldedit.core.util.task.RunnableVal;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
|
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
|
||||||
|
import com.sk89q.worldedit.util.SideEffect;
|
||||||
|
import com.sk89q.worldedit.util.SideEffectSet;
|
||||||
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import net.minecraft.server.level.FullChunkStatus;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.event.block.BlockPhysicsEvent;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<LevelChunk,
|
||||||
|
net.minecraft.world.level.block.state.BlockState, BlockPos> {
|
||||||
|
|
||||||
|
private static final int UPDATE = 1;
|
||||||
|
private static final int NOTIFY = 2;
|
||||||
|
private static final Direction[] NEIGHBOUR_ORDER = {
|
||||||
|
Direction.EAST,
|
||||||
|
Direction.WEST,
|
||||||
|
Direction.DOWN,
|
||||||
|
Direction.UP,
|
||||||
|
Direction.NORTH,
|
||||||
|
Direction.SOUTH
|
||||||
|
};
|
||||||
|
private final PaperweightFaweAdapter paperweightFaweAdapter;
|
||||||
|
private final WeakReference<Level> level;
|
||||||
|
private final AtomicInteger lastTick;
|
||||||
|
private final Set<CachedChange> cachedChanges = new HashSet<>();
|
||||||
|
private final Set<IntPair> cachedChunksToSend = new HashSet<>();
|
||||||
|
private SideEffectSet sideEffectSet;
|
||||||
|
|
||||||
|
public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference<Level> level) {
|
||||||
|
this.paperweightFaweAdapter = paperweightFaweAdapter;
|
||||||
|
this.level = level;
|
||||||
|
// Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging.
|
||||||
|
// - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway.
|
||||||
|
this.lastTick = new AtomicInteger(MinecraftServer.currentTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Level getLevel() {
|
||||||
|
return Objects.requireNonNull(level.get(), "The reference to the world was lost");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) {
|
||||||
|
this.sideEffectSet = sideEffectSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LevelChunk getChunk(int x, int z) {
|
||||||
|
return getLevel().getChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState toNative(BlockState blockState) {
|
||||||
|
int stateId = paperweightFaweAdapter.ordinalToIbdID(blockState.getOrdinalChar());
|
||||||
|
return BlockStateIdAccess.isValidInternalId(stateId)
|
||||||
|
? Block.stateById(stateId)
|
||||||
|
: ((CraftBlockData) BukkitAdapter.adapt(blockState)).getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk levelChunk, BlockPos blockPos) {
|
||||||
|
return levelChunk.getBlockState(blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public synchronized net.minecraft.world.level.block.state.BlockState setBlockState(
|
||||||
|
LevelChunk levelChunk, BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState
|
||||||
|
) {
|
||||||
|
int currentTick = MinecraftServer.currentTick;
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
return levelChunk.setBlockState(blockPos, blockState,
|
||||||
|
this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( )
|
||||||
|
cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState));
|
||||||
|
cachedChunksToSend.add(new IntPair(levelChunk.locX, levelChunk.locZ));
|
||||||
|
boolean nextTick = lastTick.get() > currentTick;
|
||||||
|
if (nextTick || cachedChanges.size() >= 1024) {
|
||||||
|
if (nextTick) {
|
||||||
|
lastTick.set(currentTick);
|
||||||
|
}
|
||||||
|
flushAsync(nextTick);
|
||||||
|
}
|
||||||
|
return blockState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState,
|
||||||
|
BlockPos blockPos
|
||||||
|
) {
|
||||||
|
return Block.updateFromNeighbourShapes(blockState, getLevel(), blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockPos getPosition(int x, int y, int z) {
|
||||||
|
return new BlockPos(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLightingForBlock(BlockPos blockPos) {
|
||||||
|
getLevel().getChunkSource().getLightEngine().checkBlock(blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateTileEntity(BlockPos blockPos, CompoundBinaryTag tag) {
|
||||||
|
// We will assume that the tile entity was created for us,
|
||||||
|
// though we do not do this on the other versions
|
||||||
|
BlockEntity blockEntity = getLevel().getBlockEntity(blockPos);
|
||||||
|
if (blockEntity == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
net.minecraft.nbt.Tag nativeTag = paperweightFaweAdapter.fromNativeBinary(tag);
|
||||||
|
blockEntity.load((CompoundTag) nativeTag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyBlockUpdate(
|
||||||
|
LevelChunk levelChunk, BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState
|
||||||
|
) {
|
||||||
|
if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) {
|
||||||
|
getLevel().sendBlockUpdated(blockPos, oldState, newState, UPDATE | NOTIFY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChunkTicking(LevelChunk levelChunk) {
|
||||||
|
return levelChunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markBlockChanged(LevelChunk levelChunk, BlockPos blockPos) {
|
||||||
|
if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) {
|
||||||
|
((ServerChunkCache) getLevel().getChunkSource()).blockChanged(blockPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyNeighbors(
|
||||||
|
BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState
|
||||||
|
) {
|
||||||
|
Level level = getLevel();
|
||||||
|
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
|
||||||
|
level.blockUpdated(blockPos, oldState.getBlock());
|
||||||
|
} else {
|
||||||
|
// When we don't want events, manually run the physics without them.
|
||||||
|
// Un-nest neighbour updating
|
||||||
|
for (Direction direction : NEIGHBOUR_ORDER) {
|
||||||
|
BlockPos shifted = blockPos.relative(direction);
|
||||||
|
level.getBlockState(shifted).neighborChanged(level, shifted, oldState.getBlock(), blockPos, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newState.hasAnalogOutputSignal()) {
|
||||||
|
level.updateNeighbourForOutputSignal(blockPos, newState.getBlock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateNeighbors(
|
||||||
|
BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState,
|
||||||
|
int recursionLimit
|
||||||
|
) {
|
||||||
|
Level level = getLevel();
|
||||||
|
// a == updateNeighbors
|
||||||
|
// b == updateDiagonalNeighbors
|
||||||
|
oldState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit);
|
||||||
|
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
|
||||||
|
CraftWorld craftWorld = level.getWorld();
|
||||||
|
if (craftWorld != null) {
|
||||||
|
BlockPhysicsEvent event = new BlockPhysicsEvent(
|
||||||
|
craftWorld.getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()),
|
||||||
|
CraftBlockData.fromData(newState)
|
||||||
|
);
|
||||||
|
level.getCraftServer().getPluginManager().callEvent(event);
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newState.triggerEvent(level, blockPos, NOTIFY, recursionLimit);
|
||||||
|
newState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlockStateChange(
|
||||||
|
BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState
|
||||||
|
) {
|
||||||
|
getLevel().onBlockStateChange(blockPos, oldState, newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void flushAsync(final boolean sendChunks) {
|
||||||
|
final Set<CachedChange> changes = Set.copyOf(cachedChanges);
|
||||||
|
cachedChanges.clear();
|
||||||
|
final Set<IntPair> toSend;
|
||||||
|
if (sendChunks) {
|
||||||
|
toSend = Set.copyOf(cachedChunksToSend);
|
||||||
|
cachedChunksToSend.clear();
|
||||||
|
} else {
|
||||||
|
toSend = Collections.emptySet();
|
||||||
|
}
|
||||||
|
RunnableVal<Object> runnableVal = new RunnableVal<>() {
|
||||||
|
@Override
|
||||||
|
public void run(Object value) {
|
||||||
|
changes.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState,
|
||||||
|
sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE)
|
||||||
|
));
|
||||||
|
if (!sendChunks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (IntPair chunk : toSend) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TaskManager.taskManager().async(() -> TaskManager.taskManager().sync(runnableVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void flush() {
|
||||||
|
RunnableVal<Object> runnableVal = new RunnableVal<>() {
|
||||||
|
@Override
|
||||||
|
public void run(Object value) {
|
||||||
|
cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState,
|
||||||
|
sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE)
|
||||||
|
));
|
||||||
|
for (IntPair chunk : cachedChunksToSend) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
runnableVal.run();
|
||||||
|
} else {
|
||||||
|
TaskManager.taskManager().sync(runnableVal);
|
||||||
|
}
|
||||||
|
cachedChanges.clear();
|
||||||
|
cachedChunksToSend.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record CachedChange(
|
||||||
|
LevelChunk levelChunk,
|
||||||
|
BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -0,0 +1,248 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType;
|
||||||
|
import com.fastasyncworldedit.core.queue.IBlocks;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkSet;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
|
import net.minecraft.world.level.chunk.PalettedContainerRO;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
public class PaperweightGetBlocks_Copy implements IChunkGet {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private final Map<BlockVector3, CompoundTag> tiles = new HashMap<>();
|
||||||
|
private final Set<CompoundTag> entities = new HashSet<>();
|
||||||
|
private final char[][] blocks;
|
||||||
|
private final int minHeight;
|
||||||
|
private final int maxHeight;
|
||||||
|
final ServerLevel serverLevel;
|
||||||
|
final LevelChunk levelChunk;
|
||||||
|
private PalettedContainer<Holder<Biome>>[] biomes = null;
|
||||||
|
|
||||||
|
protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) {
|
||||||
|
this.levelChunk = levelChunk;
|
||||||
|
this.serverLevel = levelChunk.level;
|
||||||
|
this.minHeight = serverLevel.getMinBuildHeight();
|
||||||
|
this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive.
|
||||||
|
this.blocks = new char[getSectionCount()][];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void storeTile(BlockEntity blockEntity) {
|
||||||
|
tiles.put(
|
||||||
|
BlockVector3.at(
|
||||||
|
blockEntity.getBlockPos().getX(),
|
||||||
|
blockEntity.getBlockPos().getY(),
|
||||||
|
blockEntity.getBlockPos().getZ()
|
||||||
|
),
|
||||||
|
new PaperweightLazyCompoundTag(Suppliers.memoize(blockEntity::saveWithId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<BlockVector3, CompoundTag> getTiles() {
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public CompoundTag getTile(int x, int y, int z) {
|
||||||
|
return tiles.get(BlockVector3.at(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
protected void storeEntity(Entity entity) {
|
||||||
|
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
|
||||||
|
net.minecraft.nbt.CompoundTag compoundTag = new net.minecraft.nbt.CompoundTag();
|
||||||
|
entity.save(compoundTag);
|
||||||
|
entities.add((CompoundTag) adapter.toNative(compoundTag));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<CompoundTag> getEntities() {
|
||||||
|
return this.entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getEntity(UUID uuid) {
|
||||||
|
for (CompoundTag tag : entities) {
|
||||||
|
if (uuid.equals(tag.getUUID())) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCreateCopy() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreateCopy(boolean createCopy) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeightmapToGet(HeightMapType type, int[] data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxY() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinY() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxSectionPosition() {
|
||||||
|
return maxHeight >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinSectionPosition() {
|
||||||
|
return minHeight >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiomeType getBiomeType(int x, int y, int z) {
|
||||||
|
Holder<Biome> biome = biomes[(y >> 4) - getMinSectionPosition()].get(x >> 2, (y & 15) >> 2, z >> 2);
|
||||||
|
return PaperweightPlatformAdapter.adapt(biome, serverLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSectionLighting(int layer, boolean sky) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBlocks reset() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectionCount() {
|
||||||
|
return serverLevel.getSectionsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void storeSection(int layer, char[] data) {
|
||||||
|
blocks[layer] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void storeBiomes(int layer, PalettedContainerRO<Holder<Biome>> biomeData) {
|
||||||
|
if (biomes == null) {
|
||||||
|
biomes = new PalettedContainer[getSectionCount()];
|
||||||
|
}
|
||||||
|
if (biomeData instanceof PalettedContainer<Holder<Biome>> palettedContainer) {
|
||||||
|
biomes[layer] = palettedContainer.copy();
|
||||||
|
} else {
|
||||||
|
LOGGER.error(
|
||||||
|
"Cannot correctly save biomes to history. Expected class type {} but got {}",
|
||||||
|
PalettedContainer.class.getSimpleName(),
|
||||||
|
biomeData.getClass().getSimpleName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseBlock getFullBlock(int x, int y, int z) {
|
||||||
|
BlockState state = BlockTypesCache.states[get(x, y, z)];
|
||||||
|
return state.toBaseBlock(this, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSection(int layer) {
|
||||||
|
layer -= getMinSectionPosition();
|
||||||
|
return blocks[layer] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] load(int layer) {
|
||||||
|
layer -= getMinSectionPosition();
|
||||||
|
return blocks[layer];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] loadIfPresent(int layer) {
|
||||||
|
layer -= getMinSectionPosition();
|
||||||
|
return blocks[layer];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getBlock(int x, int y, int z) {
|
||||||
|
return BlockTypesCache.states[get(x, y, z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSkyLight(int x, int y, int z) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEmittedLight(int x, int y, int z) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getHeightMap(HeightMapType type) {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends Future<T>> T call(IChunkSet set, Runnable finalize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char get(int x, int y, int z) {
|
||||||
|
final int layer = (y >> 4) - getMinSectionPosition();
|
||||||
|
final int index = (y & 15) << 8 | z << 4 | x;
|
||||||
|
return blocks[layer][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.MapChunkUtil;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||||
|
|
||||||
|
//TODO un-very-break-this
|
||||||
|
public class PaperweightMapChunkUtil extends MapChunkUtil<ClientboundLevelChunkWithLightPacket> {
|
||||||
|
|
||||||
|
public PaperweightMapChunkUtil() throws NoSuchFieldException {
|
||||||
|
fieldX = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("TWO_MEGABYTES", "a"));
|
||||||
|
fieldZ = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("x", "a"));
|
||||||
|
fieldBitMask = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("z", "b"));
|
||||||
|
fieldHeightMap = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("heightmaps", "b"));
|
||||||
|
fieldChunkData = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("chunkData", "c"));
|
||||||
|
fieldBlockEntities = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("buffer", "c"));
|
||||||
|
fieldFull = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("blockEntitiesData", "d"));
|
||||||
|
fieldX.setAccessible(true);
|
||||||
|
fieldZ.setAccessible(true);
|
||||||
|
fieldBitMask.setAccessible(true);
|
||||||
|
fieldHeightMap.setAccessible(true);
|
||||||
|
fieldChunkData.setAccessible(true);
|
||||||
|
fieldBlockEntities.setAccessible(true);
|
||||||
|
fieldFull.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientboundLevelChunkWithLightPacket createPacket() {
|
||||||
|
// TODO ??? return new ClientboundLevelChunkPacket();
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,719 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.util.maplist.EntityList;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.FaweCache;
|
||||||
|
import com.fastasyncworldedit.core.math.BitArrayUnstretched;
|
||||||
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
|
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.mojang.datafixers.util.Either;
|
||||||
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import io.papermc.lib.PaperLib;
|
||||||
|
import io.papermc.paper.world.ChunkEntitySlices;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.IdMap;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.ChunkMap;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.server.level.TicketType;
|
||||||
|
import net.minecraft.util.BitStorage;
|
||||||
|
import net.minecraft.util.ExceptionCollector;
|
||||||
|
import net.minecraft.util.SimpleBitStorage;
|
||||||
|
import net.minecraft.util.ThreadingDetector;
|
||||||
|
import net.minecraft.util.Unit;
|
||||||
|
import net.minecraft.util.ZeroBitStorage;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.LevelAccessor;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
|
import net.minecraft.world.level.chunk.GlobalPalette;
|
||||||
|
import net.minecraft.world.level.chunk.HashMapPalette;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||||
|
import net.minecraft.world.level.chunk.LinearPalette;
|
||||||
|
import net.minecraft.world.level.chunk.Palette;
|
||||||
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
|
import net.minecraft.world.level.chunk.SingleValuePalette;
|
||||||
|
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftChunk;
|
||||||
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
|
public final class PaperweightPlatformAdapter extends NMSAdapter {
|
||||||
|
|
||||||
|
public static final Field fieldData;
|
||||||
|
|
||||||
|
public static final Constructor<?> dataConstructor;
|
||||||
|
|
||||||
|
public static final Field fieldStorage;
|
||||||
|
public static final Field fieldPalette;
|
||||||
|
|
||||||
|
private static final Field fieldTickingFluidCount;
|
||||||
|
private static final Field fieldTickingBlockCount;
|
||||||
|
private static final Field fieldNonEmptyBlockCount;
|
||||||
|
|
||||||
|
private static final MethodHandle methodGetVisibleChunk;
|
||||||
|
|
||||||
|
private static final Field fieldThreadingDetector;
|
||||||
|
private static final Field fieldLock;
|
||||||
|
|
||||||
|
private static final MethodHandle methodRemoveGameEventListener;
|
||||||
|
private static final MethodHandle methodremoveTickingBlockEntity;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a workaround for the changes from https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/1fddefce1cdce44010927b888432bf70c0e88cde#src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
||||||
|
* and is only needed to support 1.19.4 versions before *and* after this change.
|
||||||
|
*/
|
||||||
|
private static final MethodHandle CRAFT_CHUNK_GET_HANDLE;
|
||||||
|
|
||||||
|
private static final Field fieldRemove;
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
static final boolean POST_CHUNK_REWRITE;
|
||||||
|
private static Method PAPER_CHUNK_GEN_ALL_ENTITIES;
|
||||||
|
private static Field LEVEL_CHUNK_ENTITIES;
|
||||||
|
private static Field SERVER_LEVEL_ENTITY_MANAGER;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||||
|
try {
|
||||||
|
fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "d"));
|
||||||
|
fieldData.setAccessible(true);
|
||||||
|
|
||||||
|
Class<?> dataClazz = fieldData.getType();
|
||||||
|
dataConstructor = dataClazz.getDeclaredConstructors()[0];
|
||||||
|
dataConstructor.setAccessible(true);
|
||||||
|
|
||||||
|
fieldStorage = dataClazz.getDeclaredField(Refraction.pickName("storage", "b"));
|
||||||
|
fieldStorage.setAccessible(true);
|
||||||
|
fieldPalette = dataClazz.getDeclaredField(Refraction.pickName("palette", "c"));
|
||||||
|
fieldPalette.setAccessible(true);
|
||||||
|
|
||||||
|
fieldTickingFluidCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingFluidCount", "g"));
|
||||||
|
fieldTickingFluidCount.setAccessible(true);
|
||||||
|
fieldTickingBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingBlockCount", "f"));
|
||||||
|
fieldTickingBlockCount.setAccessible(true);
|
||||||
|
fieldNonEmptyBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("nonEmptyBlockCount", "e"));
|
||||||
|
fieldNonEmptyBlockCount.setAccessible(true);
|
||||||
|
|
||||||
|
Method getVisibleChunkIfPresent = ChunkMap.class.getDeclaredMethod(Refraction.pickName(
|
||||||
|
"getVisibleChunkIfPresent",
|
||||||
|
"b"
|
||||||
|
), long.class);
|
||||||
|
getVisibleChunkIfPresent.setAccessible(true);
|
||||||
|
methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent);
|
||||||
|
|
||||||
|
if (!PaperLib.isPaper()) {
|
||||||
|
fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f"));
|
||||||
|
fieldThreadingDetector.setAccessible(true);
|
||||||
|
fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c"));
|
||||||
|
fieldLock.setAccessible(true);
|
||||||
|
} else {
|
||||||
|
// in paper, the used methods are synchronized properly
|
||||||
|
fieldThreadingDetector = null;
|
||||||
|
fieldLock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Method removeGameEventListener = LevelChunk.class.getDeclaredMethod(
|
||||||
|
Refraction.pickName("removeGameEventListener", "a"),
|
||||||
|
BlockEntity.class,
|
||||||
|
ServerLevel.class
|
||||||
|
);
|
||||||
|
removeGameEventListener.setAccessible(true);
|
||||||
|
methodRemoveGameEventListener = lookup.unreflect(removeGameEventListener);
|
||||||
|
|
||||||
|
Method removeBlockEntityTicker = LevelChunk.class.getDeclaredMethod(
|
||||||
|
Refraction.pickName(
|
||||||
|
"removeBlockEntityTicker",
|
||||||
|
"l"
|
||||||
|
), BlockPos.class
|
||||||
|
);
|
||||||
|
removeBlockEntityTicker.setAccessible(true);
|
||||||
|
methodremoveTickingBlockEntity = lookup.unreflect(removeBlockEntityTicker);
|
||||||
|
|
||||||
|
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q"));
|
||||||
|
fieldRemove.setAccessible(true);
|
||||||
|
|
||||||
|
boolean chunkRewrite;
|
||||||
|
try {
|
||||||
|
ServerLevel.class.getDeclaredMethod("getEntityLookup");
|
||||||
|
chunkRewrite = true;
|
||||||
|
PAPER_CHUNK_GEN_ALL_ENTITIES = ChunkEntitySlices.class.getDeclaredMethod("getAllEntities");
|
||||||
|
PAPER_CHUNK_GEN_ALL_ENTITIES.setAccessible(true);
|
||||||
|
} catch (NoSuchMethodException ignored) {
|
||||||
|
chunkRewrite = false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Paper - Pre-Chunk-Update
|
||||||
|
LEVEL_CHUNK_ENTITIES = LevelChunk.class.getDeclaredField("entities");
|
||||||
|
LEVEL_CHUNK_ENTITIES.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Non-Paper
|
||||||
|
SERVER_LEVEL_ENTITY_MANAGER = ServerLevel.class.getDeclaredField(Refraction.pickName("entityManager", "M"));
|
||||||
|
SERVER_LEVEL_ENTITY_MANAGER.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
POST_CHUNK_REWRITE = chunkRewrite;
|
||||||
|
} catch (RuntimeException | Error e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
MethodHandle craftChunkGetHandle;
|
||||||
|
final MethodType type = methodType(LevelChunk.class);
|
||||||
|
try {
|
||||||
|
craftChunkGetHandle = lookup.findVirtual(CraftChunk.class, "getHandle", type);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
try {
|
||||||
|
final MethodType newType = methodType(ChunkAccess.class, ChunkStatus.class);
|
||||||
|
craftChunkGetHandle = lookup.findVirtual(CraftChunk.class, "getHandle", newType);
|
||||||
|
craftChunkGetHandle = MethodHandles.insertArguments(craftChunkGetHandle, 1, ChunkStatus.FULL);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CRAFT_CHUNK_GET_HANDLE = craftChunkGetHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean setSectionAtomic(
|
||||||
|
LevelChunkSection[] sections,
|
||||||
|
LevelChunkSection expected,
|
||||||
|
LevelChunkSection value,
|
||||||
|
int layer
|
||||||
|
) {
|
||||||
|
if (layer >= 0 && layer < sections.length) {
|
||||||
|
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no point in having a functional semaphore for paper servers.
|
||||||
|
private static final ThreadLocal<DelegateSemaphore> SEMAPHORE_THREAD_LOCAL =
|
||||||
|
ThreadLocal.withInitial(() -> new DelegateSemaphore(1, null));
|
||||||
|
|
||||||
|
static DelegateSemaphore applyLock(LevelChunkSection section) {
|
||||||
|
if (PaperLib.isPaper()) {
|
||||||
|
return SEMAPHORE_THREAD_LOCAL.get();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
synchronized (section) {
|
||||||
|
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
||||||
|
ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks);
|
||||||
|
synchronized (currentThreadingDetector) {
|
||||||
|
Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector);
|
||||||
|
if (currentLock instanceof DelegateSemaphore delegateSemaphore) {
|
||||||
|
return delegateSemaphore;
|
||||||
|
}
|
||||||
|
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
||||||
|
fieldLock.set(currentThreadingDetector, newLock);
|
||||||
|
return newLock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) {
|
||||||
|
if (!PaperLib.isPaper()) {
|
||||||
|
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false);
|
||||||
|
if (nmsChunk != null) {
|
||||||
|
return nmsChunk;
|
||||||
|
}
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
return serverLevel.getChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
|
||||||
|
if (nmsChunk != null) {
|
||||||
|
addTicket(serverLevel, chunkX, chunkZ);
|
||||||
|
return nmsChunk;
|
||||||
|
}
|
||||||
|
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
|
||||||
|
if (nmsChunk != null) {
|
||||||
|
addTicket(serverLevel, chunkX, chunkZ);
|
||||||
|
return nmsChunk;
|
||||||
|
}
|
||||||
|
// Avoid "async" methods from the main thread.
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
return serverLevel.getChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
||||||
|
try {
|
||||||
|
CraftChunk chunk;
|
||||||
|
try {
|
||||||
|
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
String world = serverLevel.getWorld().getName();
|
||||||
|
// We've already taken 10 seconds we can afford to wait a little here.
|
||||||
|
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
|
||||||
|
if (loaded) {
|
||||||
|
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
|
||||||
|
// Retry chunk load
|
||||||
|
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addTicket(serverLevel, chunkX, chunkZ);
|
||||||
|
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) {
|
||||||
|
// Ensure chunk is definitely loaded before applying a ticket
|
||||||
|
io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel
|
||||||
|
.getChunkSource()
|
||||||
|
.addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
|
||||||
|
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
|
||||||
|
try {
|
||||||
|
return (ChunkHolder) methodGetVisibleChunk.invoke(chunkMap, ChunkPos.asLong(chunkX, chunkZ));
|
||||||
|
} catch (Throwable thr) {
|
||||||
|
throw new RuntimeException(thr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static void sendChunk(ServerLevel nmsWorld, int chunkX, int chunkZ, boolean lighting) {
|
||||||
|
ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ);
|
||||||
|
if (chunkHolder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ);
|
||||||
|
LevelChunk levelChunk;
|
||||||
|
if (PaperLib.isPaper()) {
|
||||||
|
// getChunkAtIfLoadedImmediately is paper only
|
||||||
|
levelChunk = nmsWorld
|
||||||
|
.getChunkSource()
|
||||||
|
.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
|
||||||
|
} else {
|
||||||
|
levelChunk = ((Optional<LevelChunk>) ((Either) chunkHolder
|
||||||
|
.getTickingChunkFuture() // method is not present with new paper chunk system
|
||||||
|
.getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left())
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
if (levelChunk == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TaskManager.taskManager().task(() -> {
|
||||||
|
ClientboundLevelChunkWithLightPacket packet;
|
||||||
|
if (PaperLib.isPaper()) {
|
||||||
|
packet = new ClientboundLevelChunkWithLightPacket(
|
||||||
|
levelChunk,
|
||||||
|
nmsWorld.getChunkSource().getLightEngine(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
// last false is to not bother with x-ray
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// deprecated on paper - deprecation suppressed
|
||||||
|
packet = new ClientboundLevelChunkWithLightPacket(
|
||||||
|
levelChunk,
|
||||||
|
nmsWorld.getChunkSource().getLightEngine(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ServerPlayer> nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) {
|
||||||
|
return serverLevel.getChunkSource().chunkMap.getPlayers(coordIntPair, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NMS conversion
|
||||||
|
*/
|
||||||
|
public static LevelChunkSection newChunkSection(
|
||||||
|
final int layer,
|
||||||
|
final char[] blocks,
|
||||||
|
CachedBukkitAdapter adapter,
|
||||||
|
Registry<Biome> biomeRegistry,
|
||||||
|
@Nullable PalettedContainer<Holder<Biome>> biomes
|
||||||
|
) {
|
||||||
|
return newChunkSection(layer, null, blocks, adapter, biomeRegistry, biomes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelChunkSection newChunkSection(
|
||||||
|
final int layer,
|
||||||
|
final Function<Integer, char[]> get,
|
||||||
|
char[] set,
|
||||||
|
CachedBukkitAdapter adapter,
|
||||||
|
Registry<Biome> biomeRegistry,
|
||||||
|
@Nullable PalettedContainer<Holder<Biome>> biomes
|
||||||
|
) {
|
||||||
|
if (set == null) {
|
||||||
|
return newChunkSection(layer, biomeRegistry, biomes);
|
||||||
|
}
|
||||||
|
final int[] blockToPalette = FaweCache.INSTANCE.BLOCK_TO_PALETTE.get();
|
||||||
|
final int[] paletteToBlock = FaweCache.INSTANCE.PALETTE_TO_BLOCK.get();
|
||||||
|
final long[] blockStates = FaweCache.INSTANCE.BLOCK_STATES.get();
|
||||||
|
final int[] blocksCopy = FaweCache.INSTANCE.SECTION_BLOCKS.get();
|
||||||
|
try {
|
||||||
|
int num_palette;
|
||||||
|
if (get == null) {
|
||||||
|
num_palette = createPalette(blockToPalette, paletteToBlock, blocksCopy, set, adapter, null);
|
||||||
|
} else {
|
||||||
|
num_palette = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, get, set, adapter, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
|
||||||
|
if (bitsPerEntry > 0 && bitsPerEntry < 5) {
|
||||||
|
bitsPerEntry = 4;
|
||||||
|
} else if (bitsPerEntry > 8) {
|
||||||
|
bitsPerEntry = MathMan.log2nlz(Block.BLOCK_STATE_REGISTRY.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitsPerEntryNonZero = Math.max(bitsPerEntry, 1); // We do want to use zero sometimes
|
||||||
|
final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntryNonZero);
|
||||||
|
final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong);
|
||||||
|
|
||||||
|
if (num_palette == 1) {
|
||||||
|
for (int i = 0; i < blockBitArrayEnd; i++) {
|
||||||
|
blockStates[i] = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntryNonZero, 4096, blockStates);
|
||||||
|
bitArray.fromRaw(blocksCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
final long[] bits = Arrays.copyOfRange(blockStates, 0, blockBitArrayEnd);
|
||||||
|
final BitStorage nmsBits;
|
||||||
|
if (bitsPerEntry == 0) {
|
||||||
|
nmsBits = new ZeroBitStorage(4096);
|
||||||
|
} else {
|
||||||
|
nmsBits = new SimpleBitStorage(bitsPerEntry, 4096, bits);
|
||||||
|
}
|
||||||
|
List<net.minecraft.world.level.block.state.BlockState> palette;
|
||||||
|
if (bitsPerEntry < 9) {
|
||||||
|
palette = new ArrayList<>();
|
||||||
|
for (int i = 0; i < num_palette; i++) {
|
||||||
|
int ordinal = paletteToBlock[i];
|
||||||
|
blockToPalette[ordinal] = Integer.MAX_VALUE;
|
||||||
|
final BlockState state = BlockTypesCache.states[ordinal];
|
||||||
|
palette.add(((PaperweightBlockMaterial) state.getMaterial()).getState());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
palette = List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create palette with data
|
||||||
|
@SuppressWarnings("deprecation") // constructor is deprecated on paper, but needed to keep compatibility with spigot
|
||||||
|
final PalettedContainer<net.minecraft.world.level.block.state.BlockState> blockStatePalettedContainer =
|
||||||
|
new PalettedContainer<>(
|
||||||
|
Block.BLOCK_STATE_REGISTRY,
|
||||||
|
PalettedContainer.Strategy.SECTION_STATES,
|
||||||
|
PalettedContainer.Strategy.SECTION_STATES.getConfiguration(Block.BLOCK_STATE_REGISTRY, bitsPerEntry),
|
||||||
|
nmsBits,
|
||||||
|
palette
|
||||||
|
);
|
||||||
|
if (biomes == null) {
|
||||||
|
IdMap<Holder<Biome>> biomeHolderIdMap = biomeRegistry.asHolderIdMap();
|
||||||
|
biomes = new PalettedContainer<>(
|
||||||
|
biomeHolderIdMap,
|
||||||
|
biomeHolderIdMap.byIdOrThrow(WorldEditPlugin
|
||||||
|
.getInstance()
|
||||||
|
.getBukkitImplAdapter()
|
||||||
|
.getInternalBiomeId(
|
||||||
|
BiomeTypes.PLAINS)),
|
||||||
|
PalettedContainer.Strategy.SECTION_BIOMES
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LevelChunkSection(blockStatePalettedContainer, biomes);
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
Arrays.fill(blockToPalette, Integer.MAX_VALUE);
|
||||||
|
Arrays.fill(paletteToBlock, Integer.MAX_VALUE);
|
||||||
|
Arrays.fill(blockStates, 0);
|
||||||
|
Arrays.fill(blocksCopy, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Only deprecated in paper
|
||||||
|
private static LevelChunkSection newChunkSection(
|
||||||
|
int layer,
|
||||||
|
Registry<Biome> biomeRegistry,
|
||||||
|
@Nullable PalettedContainer<Holder<Biome>> biomes
|
||||||
|
) {
|
||||||
|
if (biomes == null) {
|
||||||
|
return new LevelChunkSection(biomeRegistry);
|
||||||
|
}
|
||||||
|
PalettedContainer<net.minecraft.world.level.block.state.BlockState> dataPaletteBlocks = new PalettedContainer<>(
|
||||||
|
Block.BLOCK_STATE_REGISTRY,
|
||||||
|
Blocks.AIR.defaultBlockState(),
|
||||||
|
PalettedContainer.Strategy.SECTION_STATES
|
||||||
|
);
|
||||||
|
return new LevelChunkSection(dataPaletteBlocks, biomes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link PalettedContainer<Biome>}. Should only be used if no biome container existed beforehand.
|
||||||
|
*/
|
||||||
|
public static PalettedContainer<Holder<Biome>> getBiomePalettedContainer(
|
||||||
|
BiomeType[] biomes,
|
||||||
|
IdMap<Holder<Biome>> biomeRegistry
|
||||||
|
) {
|
||||||
|
if (biomes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BukkitImplAdapter<?> adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
|
||||||
|
// Don't stream this as typically will see 1-4 biomes; stream overhead is large for the small length
|
||||||
|
Map<BiomeType, Holder<Biome>> palette = new HashMap<>();
|
||||||
|
for (BiomeType biomeType : new LinkedList<>(Arrays.asList(biomes))) {
|
||||||
|
Holder<Biome> biome;
|
||||||
|
if (biomeType == null) {
|
||||||
|
biome = biomeRegistry.byId(adapter.getInternalBiomeId(BiomeTypes.PLAINS));
|
||||||
|
} else {
|
||||||
|
biome = biomeRegistry.byId(adapter.getInternalBiomeId(biomeType));
|
||||||
|
}
|
||||||
|
palette.put(biomeType, biome);
|
||||||
|
}
|
||||||
|
int biomeCount = palette.size();
|
||||||
|
int bitsPerEntry = MathMan.log2nlz(biomeCount - 1);
|
||||||
|
Object configuration = PalettedContainer.Strategy.SECTION_STATES.getConfiguration(
|
||||||
|
new FakeIdMapBiome(biomeCount),
|
||||||
|
bitsPerEntry
|
||||||
|
);
|
||||||
|
if (bitsPerEntry > 3) {
|
||||||
|
bitsPerEntry = MathMan.log2nlz(biomeRegistry.size() - 1);
|
||||||
|
}
|
||||||
|
PalettedContainer<Holder<Biome>> biomePalettedContainer = new PalettedContainer<>(
|
||||||
|
biomeRegistry,
|
||||||
|
biomeRegistry.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)),
|
||||||
|
PalettedContainer.Strategy.SECTION_BIOMES
|
||||||
|
);
|
||||||
|
|
||||||
|
final Palette<Holder<Biome>> biomePalette;
|
||||||
|
if (bitsPerEntry == 0) {
|
||||||
|
biomePalette = new SingleValuePalette<>(
|
||||||
|
biomePalettedContainer.registry,
|
||||||
|
biomePalettedContainer,
|
||||||
|
new ArrayList<>(palette.values()) // Must be modifiable
|
||||||
|
);
|
||||||
|
} else if (bitsPerEntry == 4) {
|
||||||
|
biomePalette = LinearPalette.create(
|
||||||
|
4,
|
||||||
|
biomePalettedContainer.registry,
|
||||||
|
biomePalettedContainer,
|
||||||
|
new ArrayList<>(palette.values()) // Must be modifiable
|
||||||
|
);
|
||||||
|
} else if (bitsPerEntry < 9) {
|
||||||
|
biomePalette = HashMapPalette.create(
|
||||||
|
bitsPerEntry,
|
||||||
|
biomePalettedContainer.registry,
|
||||||
|
biomePalettedContainer,
|
||||||
|
new ArrayList<>(palette.values()) // Must be modifiable
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
biomePalette = GlobalPalette.create(
|
||||||
|
bitsPerEntry,
|
||||||
|
biomePalettedContainer.registry,
|
||||||
|
biomePalettedContainer,
|
||||||
|
null // unused
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitsPerEntryNonZero = Math.max(bitsPerEntry, 1); // We do want to use zero sometimes
|
||||||
|
final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntryNonZero);
|
||||||
|
final int arrayLength = MathMan.ceilZero(64f / blocksPerLong);
|
||||||
|
|
||||||
|
|
||||||
|
BitStorage bitStorage = bitsPerEntry == 0 ? new ZeroBitStorage(64) : new SimpleBitStorage(
|
||||||
|
bitsPerEntry,
|
||||||
|
64,
|
||||||
|
new long[arrayLength]
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object data = dataConstructor.newInstance(configuration, bitStorage, biomePalette);
|
||||||
|
fieldData.set(biomePalettedContainer, data);
|
||||||
|
int index = 0;
|
||||||
|
for (int y = 0; y < 4; y++) {
|
||||||
|
for (int z = 0; z < 4; z++) {
|
||||||
|
for (int x = 0; x < 4; x++, index++) {
|
||||||
|
BiomeType biomeType = biomes[index];
|
||||||
|
if (biomeType == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Holder<Biome> biome = biomeRegistry.byId(WorldEditPlugin
|
||||||
|
.getInstance()
|
||||||
|
.getBukkitImplAdapter()
|
||||||
|
.getInternalBiomeId(biomeType));
|
||||||
|
if (biome == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
biomePalettedContainer.set(x, y, z, biome);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return biomePalettedContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearCounts(final LevelChunkSection section) throws IllegalAccessException {
|
||||||
|
fieldTickingFluidCount.setShort(section, (short) 0);
|
||||||
|
fieldTickingBlockCount.setShort(section, (short) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BiomeType adapt(Holder<Biome> biome, LevelAccessor levelAccessor) {
|
||||||
|
final Registry<Biome> biomeRegistry = levelAccessor.registryAccess().registryOrThrow(BIOME);
|
||||||
|
if (biomeRegistry.getKey(biome.value()) == null) {
|
||||||
|
return biomeRegistry.asHolderIdMap().getId(biome) == -1 ? BiomeTypes.OCEAN
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
return BiomeTypes.get(biome.unwrapKey().orElseThrow().location().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) {
|
||||||
|
try {
|
||||||
|
if (levelChunk.loaded || levelChunk.level.isClientSide()) {
|
||||||
|
BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos());
|
||||||
|
if (blockEntity != null) {
|
||||||
|
if (!levelChunk.level.isClientSide) {
|
||||||
|
methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level);
|
||||||
|
}
|
||||||
|
fieldRemove.set(beacon, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos());
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Entity> getEntities(LevelChunk chunk) {
|
||||||
|
ExceptionCollector<RuntimeException> collector = new ExceptionCollector<>();
|
||||||
|
if (PaperLib.isPaper()) {
|
||||||
|
if (POST_CHUNK_REWRITE) {
|
||||||
|
try {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (List<Entity>) PAPER_CHUNK_GEN_ALL_ENTITIES.invoke(chunk.level.getEntityLookup().getChunk(chunk.locX, chunk.locZ));
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Failed to lookup entities [POST_CHUNK_REWRITE=true]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
EntityList entityList = (EntityList) LEVEL_CHUNK_ENTITIES.get(chunk);
|
||||||
|
return List.of(entityList.getRawData());
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
collector.add(new RuntimeException("Failed to lookup entities [POST_CHUNK_REWRITE=false]", e));
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
//noinspection unchecked
|
||||||
|
return ((PersistentEntitySectionManager<Entity>) (SERVER_LEVEL_ENTITY_MANAGER.get(chunk.level))).getEntities(chunk.getPos());
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
collector.add(new RuntimeException("Failed to lookup entities [PAPER=false]", e));
|
||||||
|
}
|
||||||
|
collector.throwIfPresent();
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
record FakeIdMapBlock(int size) implements IdMap<net.minecraft.world.level.block.state.BlockState> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId(final net.minecraft.world.level.block.state.BlockState entry) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState byId(final int index) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Iterator<net.minecraft.world.level.block.state.BlockState> iterator() {
|
||||||
|
return Collections.emptyIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
record FakeIdMapBiome(int size) implements IdMap<Biome> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId(final Biome entry) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Biome byId(final int index) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Iterator<Biome> iterator() {
|
||||||
|
return Collections.emptyIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
|
||||||
|
import com.fastasyncworldedit.core.queue.IBatchProcessor;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunk;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkSet;
|
||||||
|
import com.fastasyncworldedit.core.registry.state.PropertyKey;
|
||||||
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.material.Fluid;
|
||||||
|
import net.minecraft.world.level.material.Fluids;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class PaperweightPostProcessor implements IBatchProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
public void postProcess(final IChunk chunk, final IChunkGet iChunkGet, final IChunkSet iChunkSet) {
|
||||||
|
boolean tickFluid = Settings.settings().EXPERIMENTAL.ALLOW_TICK_FLUIDS;
|
||||||
|
// The PostProcessor shouldn't be added, but just in case
|
||||||
|
if (!tickFluid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PaperweightGetBlocks_Copy getBlocks = (PaperweightGetBlocks_Copy) iChunkGet;
|
||||||
|
layer:
|
||||||
|
for (int layer = iChunkSet.getMinSectionPosition(); layer <= iChunkSet.getMaxSectionPosition(); layer++) {
|
||||||
|
char[] set = iChunkSet.loadIfPresent(layer);
|
||||||
|
if (set == null) {
|
||||||
|
// No edit means no need to process
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
char[] get = null;
|
||||||
|
for (int i = 0; i < 4096; i++) {
|
||||||
|
char ordinal = set[i];
|
||||||
|
char replacedOrdinal = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||||
|
boolean fromGet = false; // Used for liquids
|
||||||
|
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||||
|
if (get == null) {
|
||||||
|
get = getBlocks.load(layer);
|
||||||
|
}
|
||||||
|
// If this is null, then it's because we're loading a layer in the range of 0->15, but blocks aren't
|
||||||
|
// actually being set
|
||||||
|
if (get == null) {
|
||||||
|
continue layer;
|
||||||
|
}
|
||||||
|
fromGet = true;
|
||||||
|
ordinal = replacedOrdinal = get[i];
|
||||||
|
}
|
||||||
|
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||||
|
continue;
|
||||||
|
} else if (!fromGet) { // if fromGet, don't do the same again
|
||||||
|
if (get == null) {
|
||||||
|
get = getBlocks.load(layer);
|
||||||
|
}
|
||||||
|
replacedOrdinal = get[i];
|
||||||
|
}
|
||||||
|
boolean ticking = BlockTypesCache.ticking[ordinal];
|
||||||
|
boolean replacedWasTicking = BlockTypesCache.ticking[replacedOrdinal];
|
||||||
|
boolean replacedWasLiquid = false;
|
||||||
|
BlockState replacedState = null;
|
||||||
|
if (!ticking) {
|
||||||
|
// If the block being replaced was not ticking, it cannot be a liquid
|
||||||
|
if (!replacedWasTicking) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the block being replaced is not fluid, we do not need to worry
|
||||||
|
if (!(replacedWasLiquid =
|
||||||
|
(replacedState = BlockState.getFromOrdinal(replacedOrdinal)).getMaterial().isLiquid())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockState state = BlockState.getFromOrdinal(ordinal);
|
||||||
|
boolean liquid = state.getMaterial().isLiquid();
|
||||||
|
int x = i & 15;
|
||||||
|
int y = (i >> 8) & 15;
|
||||||
|
int z = (i >> 4) & 15;
|
||||||
|
BlockPos position = new BlockPos((chunk.getX() << 4) + x, (layer << 4) + y, (chunk.getZ() << 4) + z);
|
||||||
|
if (liquid || replacedWasLiquid) {
|
||||||
|
if (liquid) {
|
||||||
|
addFluid(getBlocks.serverLevel, state, position);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the replaced fluid (is?) adjacent to water. Do not bother to check adjacent chunks(sections) as this
|
||||||
|
// may be time consuming. Chances are any fluid blocks in adjacent chunks are being replaced or will end up
|
||||||
|
// being ticked anyway. We only need it to be "hit" once.
|
||||||
|
if (!wasAdjacentToWater(get, set, i, x, y, z)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addFluid(getBlocks.serverLevel, replacedState, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Extent construct(final Extent child) {
|
||||||
|
throw new UnsupportedOperationException("Processing only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessorScope getScope() {
|
||||||
|
return ProcessorScope.READING_SET_BLOCKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) {
|
||||||
|
if (set == null || get == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char ordinal;
|
||||||
|
char reserved = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||||
|
if (x > 0 && set[i - 1] != reserved) {
|
||||||
|
if (BlockTypesCache.ticking[(ordinal = get[i - 1])] && isFluid(ordinal)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (x < 15 && set[i + 1] != reserved) {
|
||||||
|
if (BlockTypesCache.ticking[(ordinal = get[i + 1])] && isFluid(ordinal)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (z > 0 && set[i - 16] != reserved) {
|
||||||
|
if (BlockTypesCache.ticking[(ordinal = get[i - 16])] && isFluid(ordinal)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (z < 15 && set[i + 16] != reserved) {
|
||||||
|
if (BlockTypesCache.ticking[(ordinal = get[i + 16])] && isFluid(ordinal)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (y > 0 && set[i - 256] != reserved) {
|
||||||
|
if (BlockTypesCache.ticking[(ordinal = get[i - 256])] && isFluid(ordinal)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (y < 15 && set[i + 256] != reserved) {
|
||||||
|
return BlockTypesCache.ticking[(ordinal = get[i + 256])] && isFluid(ordinal);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private boolean isFluid(char ordinal) {
|
||||||
|
return BlockState.getFromOrdinal(ordinal).getMaterial().isLiquid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void addFluid(final ServerLevel serverLevel, final BlockState replacedState, final BlockPos position) {
|
||||||
|
Fluid type;
|
||||||
|
if (replacedState.getBlockType() == BlockTypes.LAVA) {
|
||||||
|
type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.LAVA : Fluids.FLOWING_LAVA;
|
||||||
|
} else {
|
||||||
|
type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.WATER : Fluids.FLOWING_WATER;
|
||||||
|
}
|
||||||
|
serverLevel.scheduleTick(
|
||||||
|
position,
|
||||||
|
type,
|
||||||
|
type.getTickDelay(serverLevel)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,205 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongArraySet;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||||
|
import net.minecraft.server.level.ChunkMap;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.TicketType;
|
||||||
|
import net.minecraft.util.Unit;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
public class PaperweightStarlightRelighter implements Relighter {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
|
||||||
|
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
|
||||||
|
|
||||||
|
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
||||||
|
private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT);
|
||||||
|
|
||||||
|
|
||||||
|
private final ServerLevel serverLevel;
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
||||||
|
private final ReentrantLock areaLock = new ReentrantLock();
|
||||||
|
private final NMSRelighter delegate;
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
|
||||||
|
this.serverLevel = serverLevel;
|
||||||
|
this.delegate = new NMSRelighter(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
|
||||||
|
areaLock.lock();
|
||||||
|
try {
|
||||||
|
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
|
||||||
|
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
|
||||||
|
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
|
||||||
|
chunks.add(ChunkPos.asLong(cx, cz));
|
||||||
|
} finally {
|
||||||
|
areaLock.unlock();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLightUpdate(int x, int y, int z) {
|
||||||
|
delegate.addLightUpdate(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This method is called "recursively", iterating and removing elements
|
||||||
|
* from the regions linked map. This way, chunks are loaded in batches to avoid
|
||||||
|
* OOMEs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fixLightingSafe(boolean sky) {
|
||||||
|
this.areaLock.lock();
|
||||||
|
try {
|
||||||
|
if (regions.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LongSet first = regions.removeFirst();
|
||||||
|
fixLighting(first, () -> fixLightingSafe(true));
|
||||||
|
} finally {
|
||||||
|
this.areaLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Processes a set of chunks and runs an action afterwards.
|
||||||
|
* The action is run async, the chunks are partly processed on the main thread
|
||||||
|
* (as required by the server).
|
||||||
|
*/
|
||||||
|
private void fixLighting(LongSet chunks, Runnable andThen) {
|
||||||
|
// convert from long keys to ChunkPos
|
||||||
|
Set<ChunkPos> coords = new HashSet<>();
|
||||||
|
LongIterator iterator = chunks.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
coords.add(new ChunkPos(iterator.nextLong()));
|
||||||
|
}
|
||||||
|
TaskManager.taskManager().task(() -> {
|
||||||
|
// trigger chunk load and apply ticket on main thread
|
||||||
|
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||||
|
for (ChunkPos pos : coords) {
|
||||||
|
futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z)
|
||||||
|
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
|
||||||
|
FAWE_TICKET,
|
||||||
|
pos,
|
||||||
|
LIGHT_LEVEL,
|
||||||
|
Unit.INSTANCE
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// collect futures and trigger relight once all chunks are loaded
|
||||||
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
|
||||||
|
invokeRelight(
|
||||||
|
coords,
|
||||||
|
c -> {
|
||||||
|
}, // no callback for single chunks required
|
||||||
|
i -> {
|
||||||
|
if (i != coords.size()) {
|
||||||
|
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
|
||||||
|
}
|
||||||
|
// post process chunks on main thread
|
||||||
|
TaskManager.taskManager().task(() -> postProcessChunks(coords));
|
||||||
|
// call callback on our own threads
|
||||||
|
TaskManager.taskManager().async(andThen);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeRelight(
|
||||||
|
Set<ChunkPos> coords,
|
||||||
|
Consumer<ChunkPos> chunkCallback,
|
||||||
|
IntConsumer processCallback
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
serverLevel.getChunkSource().getLightEngine().relight(coords, chunkCallback, processCallback);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Error occurred on relighting", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow the server to unload the chunks again.
|
||||||
|
* Also, if chunk packets are sent delayed, we need to do that here
|
||||||
|
*/
|
||||||
|
private void postProcessChunks(Set<ChunkPos> coords) {
|
||||||
|
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
||||||
|
for (ChunkPos pos : coords) {
|
||||||
|
int x = pos.x;
|
||||||
|
int z = pos.z;
|
||||||
|
if (delay) { // we still need to send the block changes of that chunk
|
||||||
|
PaperweightPlatformAdapter.sendChunk(serverLevel, x, z, false);
|
||||||
|
}
|
||||||
|
serverLevel.getChunkSource().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL, Unit.INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeLighting() {
|
||||||
|
this.delegate.removeLighting();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fixBlockLighting() {
|
||||||
|
fixLightingSafe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fixSkyLighting() {
|
||||||
|
fixLightingSafe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReentrantLock getLock() {
|
||||||
|
return this.lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
fixLightingSafe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
|
import com.sk89q.worldedit.world.World;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nonnull
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
||||||
|
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
||||||
|
if (w == null) {
|
||||||
|
return NullRelighter.INSTANCE;
|
||||||
|
}
|
||||||
|
return new PaperweightStarlightRelighter(((CraftWorld) w).getHandle(), queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,161 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt;
|
||||||
|
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.jnbt.LazyCompoundTag;
|
||||||
|
import com.sk89q.jnbt.ListTag;
|
||||||
|
import com.sk89q.jnbt.StringTag;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
||||||
|
import net.minecraft.nbt.NumericTag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class PaperweightLazyCompoundTag extends LazyCompoundTag {
|
||||||
|
|
||||||
|
private final Supplier<net.minecraft.nbt.CompoundTag> compoundTagSupplier;
|
||||||
|
private CompoundTag compoundTag;
|
||||||
|
|
||||||
|
public PaperweightLazyCompoundTag(Supplier<net.minecraft.nbt.CompoundTag> compoundTagSupplier) {
|
||||||
|
super(new HashMap<>());
|
||||||
|
this.compoundTagSupplier = compoundTagSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaperweightLazyCompoundTag(net.minecraft.nbt.CompoundTag compoundTag) {
|
||||||
|
this(() -> compoundTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public net.minecraft.nbt.CompoundTag get() {
|
||||||
|
return compoundTagSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, Tag> getValue() {
|
||||||
|
if (compoundTag == null) {
|
||||||
|
compoundTag = (CompoundTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(compoundTagSupplier.get());
|
||||||
|
}
|
||||||
|
return compoundTag.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundBinaryTag asBinaryTag() {
|
||||||
|
getValue();
|
||||||
|
return compoundTag.asBinaryTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(String key) {
|
||||||
|
return compoundTagSupplier.get().contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getByteArray(String key) {
|
||||||
|
return compoundTagSupplier.get().getByteArray(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getByte(String key) {
|
||||||
|
return compoundTagSupplier.get().getByte(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDouble(String key) {
|
||||||
|
return compoundTagSupplier.get().getDouble(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double asDouble(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof NumericTag numTag) {
|
||||||
|
return numTag.getAsDouble();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getFloat(String key) {
|
||||||
|
return compoundTagSupplier.get().getFloat(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getIntArray(String key) {
|
||||||
|
return compoundTagSupplier.get().getIntArray(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInt(String key) {
|
||||||
|
return compoundTagSupplier.get().getInt(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int asInt(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof NumericTag numTag) {
|
||||||
|
return numTag.getAsInt();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<Tag> getList(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof net.minecraft.nbt.ListTag nbtList) {
|
||||||
|
ArrayList<Tag> list = new ArrayList<>();
|
||||||
|
for (net.minecraft.nbt.Tag elem : nbtList) {
|
||||||
|
if (elem instanceof net.minecraft.nbt.CompoundTag compoundTag) {
|
||||||
|
list.add(new PaperweightLazyCompoundTag(compoundTag));
|
||||||
|
} else {
|
||||||
|
list.add(WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(elem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public ListTag getListTag(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof net.minecraft.nbt.ListTag) {
|
||||||
|
return (ListTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(tag);
|
||||||
|
}
|
||||||
|
return new ListTag(StringTag.class, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends Tag> List<T> getList(String key, Class<T> listType) {
|
||||||
|
ListTag listTag = getListTag(key);
|
||||||
|
if (listTag.getType().equals(listType)) {
|
||||||
|
return (List<T>) listTag.getValue();
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long[] getLongArray(String key) {
|
||||||
|
return compoundTagSupplier.get().getLongArray(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLong(String key) {
|
||||||
|
return compoundTagSupplier.get().getLong(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long asLong(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof NumericTag numTag) {
|
||||||
|
return numTag.getAsLong();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getShort(String key) {
|
||||||
|
return compoundTagSupplier.get().getShort(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String key) {
|
||||||
|
return compoundTagSupplier.get().getString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return compoundTagSupplier.get().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,591 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.Regenerator;
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkCache;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.datafixers.util.Either;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.PaperweightGetBlocks;
|
||||||
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
||||||
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
import net.minecraft.server.level.ChunkMap;
|
||||||
|
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter.Message;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||||
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||||
|
import net.minecraft.util.thread.ProcessorHandle;
|
||||||
|
import net.minecraft.util.thread.ProcessorMailbox;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.LevelHeightAccessor;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
|
import net.minecraft.world.level.biome.FixedBiomeSource;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||||
|
import net.minecraft.world.level.chunk.UpgradeData;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||||
|
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
|
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator;
|
||||||
|
import org.bukkit.generator.BiomeProvider;
|
||||||
|
import org.bukkit.generator.BlockPopulator;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
|
public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, LevelChunk, PaperweightRegen.ChunkStatusWrap> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private static final Field serverWorldsField;
|
||||||
|
private static final Field paperConfigField;
|
||||||
|
private static final Field flatBedrockField;
|
||||||
|
private static final Field generatorSettingFlatField;
|
||||||
|
private static final Field generatorSettingBaseSupplierField;
|
||||||
|
private static final Field delegateField;
|
||||||
|
private static final Field chunkSourceField;
|
||||||
|
private static final Field generatorStructureStateField;
|
||||||
|
private static final Field ringPositionsField;
|
||||||
|
private static final Field hasGeneratedPositionsField;
|
||||||
|
|
||||||
|
//list of chunk stati in correct order without FULL
|
||||||
|
private static final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing
|
||||||
|
chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps
|
||||||
|
chunkStati.put(
|
||||||
|
ChunkStatus.STRUCTURE_REFERENCES,
|
||||||
|
Concurrency.FULL
|
||||||
|
); // structure refs: radius 8, but only writes to current chunk
|
||||||
|
chunkStati.put(ChunkStatus.BIOMES, Concurrency.FULL); // biomes: radius 0
|
||||||
|
chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8
|
||||||
|
chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE
|
||||||
|
chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results
|
||||||
|
/*chunkStati.put(
|
||||||
|
ChunkStatus.LIQUID_CARVERS,
|
||||||
|
Concurrency.NONE
|
||||||
|
); // liquid carvers: radius 0, but RADIUS and FULL change results*/
|
||||||
|
chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps
|
||||||
|
chunkStati.put(
|
||||||
|
ChunkStatus.LIGHT,
|
||||||
|
Concurrency.FULL
|
||||||
|
); // light: radius 1, but no writes to other chunks, only current chunk
|
||||||
|
chunkStati.put(ChunkStatus.SPAWN, Concurrency.FULL); // spawn: radius 0
|
||||||
|
// chunkStati.put(ChunkStatus.HEIGHTMAPS, Concurrency.FULL); // heightmaps: radius 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
serverWorldsField.setAccessible(true);
|
||||||
|
|
||||||
|
Field tmpPaperConfigField;
|
||||||
|
Field tmpFlatBedrockField;
|
||||||
|
try { //only present on paper
|
||||||
|
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
||||||
|
tmpPaperConfigField.setAccessible(true);
|
||||||
|
|
||||||
|
tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
|
||||||
|
tmpFlatBedrockField.setAccessible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
tmpPaperConfigField = null;
|
||||||
|
tmpFlatBedrockField = null;
|
||||||
|
}
|
||||||
|
paperConfigField = tmpPaperConfigField;
|
||||||
|
flatBedrockField = tmpFlatBedrockField;
|
||||||
|
|
||||||
|
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
||||||
|
"settings", "e"));
|
||||||
|
generatorSettingBaseSupplierField.setAccessible(true);
|
||||||
|
|
||||||
|
generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "d"));
|
||||||
|
generatorSettingFlatField.setAccessible(true);
|
||||||
|
|
||||||
|
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
||||||
|
delegateField.setAccessible(true);
|
||||||
|
|
||||||
|
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "I"));
|
||||||
|
chunkSourceField.setAccessible(true);
|
||||||
|
|
||||||
|
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "v"));
|
||||||
|
generatorStructureStateField.setAccessible(true);
|
||||||
|
|
||||||
|
ringPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(Refraction.pickName("ringPositions", "g"));
|
||||||
|
ringPositionsField.setAccessible(true);
|
||||||
|
|
||||||
|
hasGeneratedPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(
|
||||||
|
Refraction.pickName("hasGeneratedPositions", "h")
|
||||||
|
);
|
||||||
|
hasGeneratedPositionsField.setAccessible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//runtime
|
||||||
|
private ServerLevel originalServerWorld;
|
||||||
|
private ServerChunkCache originalChunkProvider;
|
||||||
|
private ServerLevel freshWorld;
|
||||||
|
private ServerChunkCache freshChunkProvider;
|
||||||
|
private LevelStorageSource.LevelStorageAccess session;
|
||||||
|
private StructureTemplateManager structureTemplateManager;
|
||||||
|
private ThreadedLevelLightEngine threadedLevelLightEngine;
|
||||||
|
private ChunkGenerator chunkGenerator;
|
||||||
|
|
||||||
|
private Path tempDir;
|
||||||
|
|
||||||
|
private boolean generateFlatBedrock = false;
|
||||||
|
|
||||||
|
public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
|
||||||
|
super(originalBukkitWorld, region, target, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean prepare() {
|
||||||
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
|
originalChunkProvider = originalServerWorld.getChunkSource();
|
||||||
|
|
||||||
|
//flat bedrock? (only on paper)
|
||||||
|
if (paperConfigField != null) {
|
||||||
|
try {
|
||||||
|
generateFlatBedrock = flatBedrockField.getBoolean(paperConfigField.get(originalServerWorld));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
|
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean initNewWorld() throws Exception {
|
||||||
|
//world folder
|
||||||
|
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
||||||
|
|
||||||
|
//prepare for world init (see upstream implementation for reference)
|
||||||
|
org.bukkit.World.Environment environment = originalBukkitWorld.getEnvironment();
|
||||||
|
org.bukkit.generator.ChunkGenerator generator = originalBukkitWorld.getGenerator();
|
||||||
|
LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> levelStemResourceKey = getWorldDimKey(environment);
|
||||||
|
session = levelStorageSource.createAccess("faweregentempworld", levelStemResourceKey);
|
||||||
|
PrimaryLevelData originalWorldData = originalServerWorld.serverLevelData;
|
||||||
|
|
||||||
|
MinecraftServer server = originalServerWorld.getCraftServer().getServer();
|
||||||
|
WorldOptions originalOpts = originalWorldData.worldGenOptions();
|
||||||
|
WorldOptions newOpts = options.getSeed().isPresent()
|
||||||
|
? originalOpts.withSeed(OptionalLong.of(seed))
|
||||||
|
: originalOpts;
|
||||||
|
LevelSettings newWorldSettings = new LevelSettings(
|
||||||
|
"faweregentempworld",
|
||||||
|
originalWorldData.settings.gameType(),
|
||||||
|
originalWorldData.settings.hardcore(),
|
||||||
|
originalWorldData.settings.difficulty(),
|
||||||
|
originalWorldData.settings.allowCommands(),
|
||||||
|
originalWorldData.settings.gameRules(),
|
||||||
|
originalWorldData.settings.getDataConfiguration()
|
||||||
|
);
|
||||||
|
|
||||||
|
PrimaryLevelData.SpecialWorldProperty specialWorldProperty =
|
||||||
|
originalWorldData.isFlatWorld()
|
||||||
|
? PrimaryLevelData.SpecialWorldProperty.FLAT
|
||||||
|
: originalWorldData.isDebugWorld()
|
||||||
|
? PrimaryLevelData.SpecialWorldProperty.DEBUG
|
||||||
|
: PrimaryLevelData.SpecialWorldProperty.NONE;
|
||||||
|
PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, specialWorldProperty, Lifecycle.stable());
|
||||||
|
|
||||||
|
BiomeProvider biomeProvider = getBiomeProvider();
|
||||||
|
|
||||||
|
|
||||||
|
//init world
|
||||||
|
freshWorld = Fawe.instance().getQueueHandler().sync((Supplier<ServerLevel>) () -> new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
session,
|
||||||
|
newWorldData,
|
||||||
|
originalServerWorld.dimension(),
|
||||||
|
DedicatedServer.getServer().registryAccess().registry(Registries.LEVEL_STEM).orElseThrow()
|
||||||
|
.getOrThrow(levelStemResourceKey),
|
||||||
|
new RegenNoOpWorldLoadListener(),
|
||||||
|
originalServerWorld.isDebug(),
|
||||||
|
seed,
|
||||||
|
ImmutableList.of(),
|
||||||
|
false,
|
||||||
|
originalServerWorld.getRandomSequences(),
|
||||||
|
environment,
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
) {
|
||||||
|
|
||||||
|
private final Holder<Biome> singleBiome = options.hasBiomeType() ? DedicatedServer.getServer().registryAccess()
|
||||||
|
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
|
||||||
|
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(BooleanSupplier shouldKeepTicking) { //no ticking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
||||||
|
if (options.hasBiomeType()) {
|
||||||
|
return singleBiome;
|
||||||
|
}
|
||||||
|
return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(
|
||||||
|
biomeX, biomeY, biomeZ, getChunkSource().randomState().sampler()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
freshWorld.noSave = true;
|
||||||
|
removeWorldFromWorldsMap();
|
||||||
|
newWorldData.checkName(originalServerWorld.serverLevelData.getLevelName()); //rename to original world name
|
||||||
|
if (paperConfigField != null) {
|
||||||
|
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkGenerator originalGenerator = originalChunkProvider.getGenerator();
|
||||||
|
if (originalGenerator instanceof FlatLevelSource flatLevelSource) {
|
||||||
|
FlatLevelGeneratorSettings generatorSettingFlat = flatLevelSource.settings();
|
||||||
|
chunkGenerator = new FlatLevelSource(generatorSettingFlat);
|
||||||
|
} else if (originalGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
|
||||||
|
Holder<NoiseGeneratorSettings> generatorSettingBaseSupplier = (Holder<NoiseGeneratorSettings>) generatorSettingBaseSupplierField.get(
|
||||||
|
originalGenerator);
|
||||||
|
BiomeSource biomeSource;
|
||||||
|
if (options.hasBiomeType()) {
|
||||||
|
|
||||||
|
biomeSource = new FixedBiomeSource(
|
||||||
|
DedicatedServer.getServer().registryAccess()
|
||||||
|
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
|
||||||
|
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
biomeSource = originalGenerator.getBiomeSource();
|
||||||
|
}
|
||||||
|
chunkGenerator = new NoiseBasedChunkGenerator(
|
||||||
|
biomeSource,
|
||||||
|
generatorSettingBaseSupplier
|
||||||
|
);
|
||||||
|
} else if (originalGenerator instanceof CustomChunkGenerator customChunkGenerator) {
|
||||||
|
chunkGenerator = customChunkGenerator.getDelegate();
|
||||||
|
} else {
|
||||||
|
LOGGER.error("Unsupported generator type {}", originalGenerator.getClass().getName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (generator != null) {
|
||||||
|
chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator);
|
||||||
|
generateConcurrent = generator.isParallelCapable();
|
||||||
|
}
|
||||||
|
// chunkGenerator.conf = freshWorld.spigotConfig; - Does not exist anymore, may need to be re-addressed
|
||||||
|
|
||||||
|
freshChunkProvider = new ServerChunkCache(
|
||||||
|
freshWorld,
|
||||||
|
session,
|
||||||
|
server.getFixerUpper(),
|
||||||
|
server.getStructureManager(),
|
||||||
|
server.executor,
|
||||||
|
chunkGenerator,
|
||||||
|
freshWorld.spigotConfig.viewDistance,
|
||||||
|
freshWorld.spigotConfig.simulationDistance,
|
||||||
|
server.forceSynchronousWrites(),
|
||||||
|
new RegenNoOpWorldLoadListener(),
|
||||||
|
(chunkCoordIntPair, state) -> {
|
||||||
|
},
|
||||||
|
() -> server.overworld().getDataStorage()
|
||||||
|
) {
|
||||||
|
// redirect to LevelChunks created in #createChunks
|
||||||
|
@Override
|
||||||
|
public ChunkAccess getChunk(int x, int z, ChunkStatus chunkstatus, boolean create) {
|
||||||
|
ChunkAccess chunkAccess = getChunkAt(x, z);
|
||||||
|
if (chunkAccess == null && create) {
|
||||||
|
chunkAccess = createChunk(getProtoChunkAt(x, z));
|
||||||
|
}
|
||||||
|
return chunkAccess;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (seed == originalOpts.seed() && !options.hasBiomeType()) {
|
||||||
|
// Optimisation for needless ring position calculation when the seed and biome is the same.
|
||||||
|
ChunkGeneratorStructureState state = (ChunkGeneratorStructureState) generatorStructureStateField.get(originalChunkProvider.chunkMap);
|
||||||
|
boolean hasGeneratedPositions = hasGeneratedPositionsField.getBoolean(state);
|
||||||
|
if (hasGeneratedPositions) {
|
||||||
|
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> origPositions =
|
||||||
|
(Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>>) ringPositionsField.get(state);
|
||||||
|
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> copy = new Object2ObjectArrayMap<>(
|
||||||
|
origPositions);
|
||||||
|
ChunkGeneratorStructureState newState = (ChunkGeneratorStructureState) generatorStructureStateField.get(freshChunkProvider.chunkMap);
|
||||||
|
ringPositionsField.set(newState, copy);
|
||||||
|
hasGeneratedPositionsField.setBoolean(newState, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
chunkSourceField.set(freshWorld, freshChunkProvider);
|
||||||
|
//let's start then
|
||||||
|
structureTemplateManager = server.getStructureManager();
|
||||||
|
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup() {
|
||||||
|
try {
|
||||||
|
session.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//shutdown chunk provider
|
||||||
|
try {
|
||||||
|
Fawe.instance().getQueueHandler().sync(() -> {
|
||||||
|
try {
|
||||||
|
freshChunkProvider.close(false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove world from server
|
||||||
|
try {
|
||||||
|
Fawe.instance().getQueueHandler().sync(this::removeWorldFromWorldsMap);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete directory
|
||||||
|
try {
|
||||||
|
SafeFiles.tryHardToDeleteDir(tempDir);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ProtoChunk createProtoChunk(int x, int z) {
|
||||||
|
return new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld,
|
||||||
|
this.freshWorld.registryAccess().registryOrThrow(BIOME), null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LevelChunk createChunk(ProtoChunk protoChunk) {
|
||||||
|
return new LevelChunk(
|
||||||
|
freshWorld,
|
||||||
|
protoChunk,
|
||||||
|
null // we don't want to add entities
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ChunkStatusWrap getFullChunkStatus() {
|
||||||
|
return new ChunkStatusWrap(ChunkStatus.FULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<BlockPopulator> getBlockPopulators() {
|
||||||
|
return originalServerWorld.getWorld().getPopulators();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) {
|
||||||
|
// BlockPopulator#populate has to be called synchronously for TileEntity access
|
||||||
|
TaskManager.taskManager().task(() -> {
|
||||||
|
final CraftWorld world = freshWorld.getWorld();
|
||||||
|
final Chunk chunk = world.getChunkAt(levelChunk.locX, levelChunk.locZ);
|
||||||
|
blockPopulator.populate(world, random, chunk);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
||||||
|
return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) {
|
||||||
|
@Override
|
||||||
|
public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) {
|
||||||
|
return getChunkAt(x, z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//util
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void removeWorldFromWorldsMap() {
|
||||||
|
Fawe.instance().getQueueHandler().sync(() -> {
|
||||||
|
try {
|
||||||
|
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
|
||||||
|
map.remove("faweregentempworld");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceKey<LevelStem> getWorldDimKey(org.bukkit.World.Environment env) {
|
||||||
|
return switch (env) {
|
||||||
|
case NETHER -> LevelStem.NETHER;
|
||||||
|
case THE_END -> LevelStem.END;
|
||||||
|
default -> LevelStem.OVERWORLD;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RegenNoOpWorldLoadListener implements ChunkProgressListener {
|
||||||
|
|
||||||
|
private RegenNoOpWorldLoadListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSpawnPos(ChunkPos spawnPos) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Paper only(?) @Override
|
||||||
|
public void setChunkRadius(int radius) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FastProtoChunk extends ProtoChunk {
|
||||||
|
|
||||||
|
public FastProtoChunk(
|
||||||
|
final ChunkPos pos,
|
||||||
|
final UpgradeData upgradeData,
|
||||||
|
final LevelHeightAccessor world,
|
||||||
|
final Registry<Biome> biomeRegistry,
|
||||||
|
@Nullable final BlendingData blendingData
|
||||||
|
) {
|
||||||
|
super(pos, upgradeData, world, biomeRegistry, blendingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid warning on paper
|
||||||
|
|
||||||
|
// compatibility with spigot
|
||||||
|
|
||||||
|
public boolean generateFlatBedrock() {
|
||||||
|
return generateFlatBedrock;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no one will ever see the entities!
|
||||||
|
@Override
|
||||||
|
public List<CompoundTag> getEntities() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ChunkStatusWrap extends ChunkStatusWrapper<ChunkAccess> {
|
||||||
|
|
||||||
|
private final ChunkStatus chunkStatus;
|
||||||
|
|
||||||
|
public ChunkStatusWrap(ChunkStatus chunkStatus) {
|
||||||
|
this.chunkStatus = chunkStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int requiredNeighborChunkRadius() {
|
||||||
|
return chunkStatus.getRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return chunkStatus.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
||||||
|
return chunkStatus.generate(
|
||||||
|
Runnable::run, // TODO revisit, we might profit from this somehow?
|
||||||
|
freshWorld,
|
||||||
|
chunkGenerator,
|
||||||
|
structureTemplateManager,
|
||||||
|
threadedLevelLightEngine,
|
||||||
|
c -> CompletableFuture.completedFuture(Either.left(c)),
|
||||||
|
accessibleChunks
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A light engine that does nothing. As light is calculated after pasting anyway, we can avoid
|
||||||
|
* work this way.
|
||||||
|
*/
|
||||||
|
static class NoOpLightEngine extends ThreadedLevelLightEngine {
|
||||||
|
|
||||||
|
private static final ProcessorMailbox<Runnable> MAILBOX = ProcessorMailbox.create(task -> {
|
||||||
|
}, "fawe-no-op");
|
||||||
|
private static final ProcessorHandle<Message<Runnable>> HANDLE = ProcessorHandle.of("fawe-no-op", m -> {
|
||||||
|
});
|
||||||
|
|
||||||
|
public NoOpLightEngine(final ServerChunkCache chunkProvider) {
|
||||||
|
super(chunkProvider, chunkProvider.chunkMap, false, MAILBOX, HANDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<ChunkAccess> lightChunk(final ChunkAccess chunk, final boolean excludeBlocks) {
|
||||||
|
return CompletableFuture.completedFuture(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,7 +3,7 @@ import io.papermc.paperweight.userdev.attribute.Obfuscation
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
`java-library`
|
||||||
id("com.modrinth.minotaur") version "2.8.1"
|
alias(libs.plugins.minotaur)
|
||||||
}
|
}
|
||||||
|
|
||||||
project.description = "Bukkit"
|
project.description = "Bukkit"
|
||||||
@ -74,19 +74,19 @@ dependencies {
|
|||||||
implementation(libs.fastutil)
|
implementation(libs.fastutil)
|
||||||
|
|
||||||
// Platform expectations
|
// Platform expectations
|
||||||
compileOnly("io.papermc.paper:paper-api") {
|
compileOnly(libs.paper) {
|
||||||
exclude("junit", "junit")
|
exclude("junit", "junit")
|
||||||
exclude(group = "org.slf4j", module = "slf4j-api")
|
exclude(group = "org.slf4j", module = "slf4j-api")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
localImplementation("org.apache.logging.log4j:log4j-api")
|
localImplementation(libs.log4jApi)
|
||||||
localImplementation(libs.log4jBom) {
|
localImplementation(libs.log4jBom) {
|
||||||
because("Spigot provides Log4J (sort of, not in API, implicitly part of server)")
|
because("Spigot provides Log4J (sort of, not in API, implicitly part of server)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
compileOnly("com.github.MilkBowl:VaultAPI") { isTransitive = false }
|
compileOnly(libs.vault) { isTransitive = false }
|
||||||
compileOnly(libs.dummypermscompat) {
|
compileOnly(libs.dummypermscompat) {
|
||||||
exclude("com.github.MilkBowl", "VaultAPI")
|
exclude("com.github.MilkBowl", "VaultAPI")
|
||||||
}
|
}
|
||||||
@ -101,26 +101,26 @@ dependencies {
|
|||||||
compileOnly(libs.griefdefender) { isTransitive = false }
|
compileOnly(libs.griefdefender) { isTransitive = false }
|
||||||
compileOnly(libs.residence) { isTransitive = false }
|
compileOnly(libs.residence) { isTransitive = false }
|
||||||
compileOnly(libs.towny) { isTransitive = false }
|
compileOnly(libs.towny) { isTransitive = false }
|
||||||
compileOnly("com.plotsquared:PlotSquared-Bukkit") { isTransitive = false }
|
compileOnly(libs.plotSquaredBukkit) { isTransitive = false }
|
||||||
compileOnly("com.plotsquared:PlotSquared-Core") { isTransitive = false }
|
compileOnly(libs.plotSquaredCore) { isTransitive = false }
|
||||||
|
|
||||||
// Third party
|
// Third party
|
||||||
implementation("io.papermc:paperlib")
|
implementation(libs.paperlib)
|
||||||
implementation("org.bstats:bstats-bukkit") { isTransitive = false }
|
implementation(libs.bstatsBukkit) { isTransitive = false }
|
||||||
implementation(libs.bstatsBase) { isTransitive = false }
|
implementation(libs.bstatsBase) { isTransitive = false }
|
||||||
implementation("dev.notmyfault.serverlib:ServerLib")
|
implementation(libs.serverlib)
|
||||||
implementation("com.intellectualsites.paster:Paster") { isTransitive = false }
|
implementation(libs.paster) { isTransitive = false }
|
||||||
api(libs.lz4Java) { isTransitive = false }
|
api(libs.lz4Java) { isTransitive = false }
|
||||||
api(libs.sparsebitset) { isTransitive = false }
|
api(libs.sparsebitset) { isTransitive = false }
|
||||||
api(libs.parallelgzip) { isTransitive = false }
|
api(libs.parallelgzip) { isTransitive = false }
|
||||||
compileOnly("net.kyori:adventure-api")
|
compileOnly(libs.adventureApi)
|
||||||
compileOnlyApi("org.checkerframework:checker-qual")
|
compileOnlyApi(libs.checkerqual)
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testImplementation(libs.mockito)
|
testImplementation(libs.mockito)
|
||||||
testImplementation("net.kyori:adventure-api")
|
testImplementation(libs.adventureApi)
|
||||||
testImplementation("org.checkerframework:checker-qual")
|
testImplementation(libs.checkerqual)
|
||||||
testImplementation("io.papermc.paper:paper-api") { isTransitive = true }
|
testImplementation(libs.paper) { isTransitive = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named<Copy>("processResources") {
|
tasks.named<Copy>("processResources") {
|
||||||
@ -174,7 +174,7 @@ tasks.named<ShadowJar>("shadowJar") {
|
|||||||
include(dependency("it.unimi.dsi:fastutil"))
|
include(dependency("it.unimi.dsi:fastutil"))
|
||||||
}
|
}
|
||||||
relocate("org.incendo.serverlib", "com.fastasyncworldedit.serverlib") {
|
relocate("org.incendo.serverlib", "com.fastasyncworldedit.serverlib") {
|
||||||
include(dependency("dev.notmyfault.serverlib:ServerLib:2.3.1"))
|
include(dependency("dev.notmyfault.serverlib:ServerLib:2.3.4"))
|
||||||
}
|
}
|
||||||
relocate("com.intellectualsites.paster", "com.fastasyncworldedit.paster") {
|
relocate("com.intellectualsites.paster", "com.fastasyncworldedit.paster") {
|
||||||
include(dependency("com.intellectualsites.paster:Paster"))
|
include(dependency("com.intellectualsites.paster:Paster"))
|
||||||
@ -183,10 +183,10 @@ tasks.named<ShadowJar>("shadowJar") {
|
|||||||
include(dependency("org.lz4:lz4-java:1.8.0"))
|
include(dependency("org.lz4:lz4-java:1.8.0"))
|
||||||
}
|
}
|
||||||
relocate("net.kyori", "com.fastasyncworldedit.core.adventure") {
|
relocate("net.kyori", "com.fastasyncworldedit.core.adventure") {
|
||||||
include(dependency("net.kyori:adventure-nbt:4.9.3"))
|
include(dependency("net.kyori:adventure-nbt:4.14.0"))
|
||||||
}
|
}
|
||||||
relocate("com.zaxxer", "com.fastasyncworldedit.core.math") {
|
relocate("com.zaxxer", "com.fastasyncworldedit.core.math") {
|
||||||
include(dependency("com.zaxxer:SparseBitSet:1.2"))
|
include(dependency("com.zaxxer:SparseBitSet:1.3"))
|
||||||
}
|
}
|
||||||
relocate("org.anarres", "com.fastasyncworldedit.core.internal.io") {
|
relocate("org.anarres", "com.fastasyncworldedit.core.internal.io") {
|
||||||
include(dependency("org.anarres:parallelgzip:1.0.5"))
|
include(dependency("org.anarres:parallelgzip:1.0.5"))
|
||||||
@ -206,7 +206,7 @@ tasks {
|
|||||||
versionNumber.set("${project.version}")
|
versionNumber.set("${project.version}")
|
||||||
versionType.set("release")
|
versionType.set("release")
|
||||||
uploadFile.set(file("build/libs/${rootProject.name}-Bukkit-${project.version}.jar"))
|
uploadFile.set(file("build/libs/${rootProject.name}-Bukkit-${project.version}.jar"))
|
||||||
gameVersions.addAll(listOf("1.20.1", "1.20", "1.19.4", "1.18.2", "1.17.1", "1.16.5"))
|
gameVersions.addAll(listOf("1.20.2", "1.20.1", "1.20", "1.19.4", "1.18.2", "1.17.1", "1.16.5"))
|
||||||
loaders.addAll(listOf("paper", "spigot"))
|
loaders.addAll(listOf("paper", "spigot"))
|
||||||
changelog.set("The changelog is available on GitHub: https://github.com/IntellectualSites/" +
|
changelog.set("The changelog is available on GitHub: https://github.com/IntellectualSites/" +
|
||||||
"FastAsyncWorldEdit/releases/tag/${project.version}")
|
"FastAsyncWorldEdit/releases/tag/${project.version}")
|
||||||
|
@ -325,20 +325,11 @@ public class FaweBukkit implements IFawe, Listener {
|
|||||||
if (plotSquared == null) {
|
if (plotSquared == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PlotSquared.get().getVersion().version[0] == 6) {
|
if (PlotSquared.get().getVersion().version[0] == 7) {
|
||||||
WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature());
|
WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature());
|
||||||
LOGGER.info("Plugin 'PlotSquared' v6 found. Using it now.");
|
LOGGER.info("Plugin 'PlotSquared' v7 found. Using it now.");
|
||||||
} else if (PlotSquared.get().getVersion().version[0] == 7) {
|
|
||||||
WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature());
|
|
||||||
LOGGER.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
|
||||||
LOGGER.error("!! !!");
|
|
||||||
LOGGER.error("!! ERROR: PlotSquared v7 found. This FAWE version does not support PlotSquared V7 !!");
|
|
||||||
LOGGER.error("!! Follow the instructions when notified of v7 release candidates and use FAWE from !!");
|
|
||||||
LOGGER.error("!! https://ci.athion.net/job/FastAsyncWorldEdit-Pull-Requests/view/change-requests/job/PR-2075/ !!");
|
|
||||||
LOGGER.error("!! !!");
|
|
||||||
LOGGER.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("Incompatible version of PlotSquared found. Please use PlotSquared v6.");
|
LOGGER.error("Incompatible version of PlotSquared found. Please use PlotSquared v7.");
|
||||||
LOGGER.info("https://www.spigotmc.org/resources/77506/");
|
LOGGER.info("https://www.spigotmc.org/resources/77506/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,14 @@ import com.sk89q.worldedit.world.block.BaseBlock;
|
|||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongList;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.generator.BiomeProvider;
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.BlockPopulator;
|
import org.bukkit.generator.BlockPopulator;
|
||||||
import org.bukkit.generator.WorldInfo;
|
import org.bukkit.generator.WorldInfo;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -42,7 +42,6 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an abstract regeneration handler.
|
* Represents an abstract regeneration handler.
|
||||||
@ -62,7 +61,7 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
protected final RegenOptions options;
|
protected final RegenOptions options;
|
||||||
|
|
||||||
//runtime
|
//runtime
|
||||||
protected final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
|
protected final Map<ChunkStatus, Concurrency> chunkStatuses = new LinkedHashMap<>();
|
||||||
private final Long2ObjectLinkedOpenHashMap<ProtoChunk> protoChunks = new Long2ObjectLinkedOpenHashMap<>();
|
private final Long2ObjectLinkedOpenHashMap<ProtoChunk> protoChunks = new Long2ObjectLinkedOpenHashMap<>();
|
||||||
private final Long2ObjectOpenHashMap<Chunk> chunks = new Long2ObjectOpenHashMap<>();
|
private final Long2ObjectOpenHashMap<Chunk> chunks = new Long2ObjectOpenHashMap<>();
|
||||||
protected boolean generateConcurrent = true;
|
protected boolean generateConcurrent = true;
|
||||||
@ -85,19 +84,19 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Random getChunkRandom(long worldseed, int x, int z) {
|
private static Random getChunkRandom(long worldSeed, int x, int z) {
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
random.setSeed(worldseed);
|
random.setSeed(worldSeed);
|
||||||
long xRand = random.nextLong() / 2L * 2L + 1L;
|
long xRand = random.nextLong() / 2L * 2L + 1L;
|
||||||
long zRand = random.nextLong() / 2L * 2L + 1L;
|
long zRand = random.nextLong() / 2L * 2L + 1L;
|
||||||
random.setSeed((long) x * xRand + (long) z * zRand ^ worldseed);
|
random.setSeed((long) x * xRand + (long) z * zRand ^ worldSeed);
|
||||||
return random;
|
return random;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regenerates the selected {@code Region}.
|
* Regenerates the selected {@code Region}.
|
||||||
*
|
*
|
||||||
* @return whether or not the regeneration process was successful
|
* @return whether the regeneration process was successful
|
||||||
* @throws Exception when something goes terribly wrong
|
* @throws Exception when something goes terribly wrong
|
||||||
*/
|
*/
|
||||||
public boolean regenerate() throws Exception {
|
public boolean regenerate() throws Exception {
|
||||||
@ -175,8 +174,8 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
//for now it is working well and fast, if we are bored in the future we could do the research (a lot of it) to reduce the border radius
|
//for now it is working well and fast, if we are bored in the future we could do the research (a lot of it) to reduce the border radius
|
||||||
|
|
||||||
//generate chunk coords lists with a certain radius
|
//generate chunk coords lists with a certain radius
|
||||||
Int2ObjectOpenHashMap<List<Long>> chunkCoordsForRadius = new Int2ObjectOpenHashMap<>();
|
Int2ObjectOpenHashMap<long[]> chunkCoordsForRadius = new Int2ObjectOpenHashMap<>();
|
||||||
chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> {
|
chunkStatuses.keySet().stream().mapToInt(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> {
|
||||||
if (radius == -1) { //ignore ChunkStatus.EMPTY
|
if (radius == -1) { //ignore ChunkStatus.EMPTY
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -186,19 +185,19 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
});
|
});
|
||||||
|
|
||||||
//create chunks
|
//create chunks
|
||||||
for (Long xz : chunkCoordsForRadius.get(0)) {
|
for (long xz : chunkCoordsForRadius.get(0)) {
|
||||||
ProtoChunk chunk = createProtoChunk(MathMan.unpairIntX(xz), MathMan.unpairIntY(xz));
|
ProtoChunk chunk = createProtoChunk(MathMan.unpairIntX(xz), MathMan.unpairIntY(xz));
|
||||||
protoChunks.put(xz, chunk);
|
protoChunks.put(xz, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
//generate lists for RegionLimitedWorldAccess, need to be square with odd length (e.g. 17x17), 17 = 1 middle chunk + 8 border chunks * 2
|
//generate lists for RegionLimitedWorldAccess, need to be square with odd length (e.g. 17x17), 17 = 1 middle chunk + 8 border chunks * 2
|
||||||
Int2ObjectOpenHashMap<Long2ObjectOpenHashMap<List<IChunkAccess>>> worldlimits = new Int2ObjectOpenHashMap<>();
|
Int2ObjectOpenHashMap<Long2ObjectOpenHashMap<List<IChunkAccess>>> worldLimits = new Int2ObjectOpenHashMap<>();
|
||||||
chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> {
|
chunkStatuses.keySet().stream().mapToInt(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> {
|
||||||
if (radius == -1) { //ignore ChunkStatus.EMPTY
|
if (radius == -1) { //ignore ChunkStatus.EMPTY
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Long2ObjectOpenHashMap<List<IChunkAccess>> map = new Long2ObjectOpenHashMap<>();
|
Long2ObjectOpenHashMap<List<IChunkAccess>> map = new Long2ObjectOpenHashMap<>();
|
||||||
for (Long xz : chunkCoordsForRadius.get(radius)) {
|
for (long xz : chunkCoordsForRadius.get(radius)) {
|
||||||
int x = MathMan.unpairIntX(xz);
|
int x = MathMan.unpairIntX(xz);
|
||||||
int z = MathMan.unpairIntY(xz);
|
int z = MathMan.unpairIntY(xz);
|
||||||
List<IChunkAccess> l = new ArrayList<>((radius + 1 + radius) * (radius + 1 + radius));
|
List<IChunkAccess> l = new ArrayList<>((radius + 1 + radius) * (radius + 1 + radius));
|
||||||
@ -209,80 +208,63 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
}
|
}
|
||||||
map.put(xz, l);
|
map.put(xz, l);
|
||||||
}
|
}
|
||||||
worldlimits.put(radius, map);
|
worldLimits.put(radius, map);
|
||||||
});
|
});
|
||||||
|
|
||||||
//run generation tasks excluding FULL chunk status
|
//run generation tasks excluding FULL chunk status
|
||||||
for (Map.Entry<ChunkStatus, Concurrency> entry : chunkStati.entrySet()) {
|
for (Map.Entry<ChunkStatus, Concurrency> entry : chunkStatuses.entrySet()) {
|
||||||
ChunkStatus chunkStatus = entry.getKey();
|
ChunkStatus chunkStatus = entry.getKey();
|
||||||
int radius = chunkStatus.requiredNeighborChunkRadius0();
|
int radius = chunkStatus.requiredNeighborChunkRadius0();
|
||||||
|
|
||||||
List<Long> coords = chunkCoordsForRadius.get(radius);
|
long[] coords = chunkCoordsForRadius.get(radius);
|
||||||
|
Long2ObjectOpenHashMap<List<IChunkAccess>> limitsForRadius = worldLimits.get(radius);
|
||||||
if (this.generateConcurrent && entry.getValue() == Concurrency.RADIUS) {
|
if (this.generateConcurrent && entry.getValue() == Concurrency.RADIUS) {
|
||||||
SequentialTasks<ConcurrentTasks<SequentialTasks<Long>>> tasks = getChunkStatusTaskRows(coords, radius);
|
SequentialTasks<ConcurrentTasks<LongList>> tasks = getChunkStatusTaskRows(coords, radius);
|
||||||
for (ConcurrentTasks<SequentialTasks<Long>> para : tasks) {
|
for (ConcurrentTasks<LongList> para : tasks) {
|
||||||
List<Runnable> scheduled = new ArrayList<>(tasks.size());
|
List<Runnable> scheduled = new ArrayList<>(tasks.size());
|
||||||
for (SequentialTasks<Long> row : para) {
|
for (LongList row : para) {
|
||||||
scheduled.add(() -> {
|
scheduled.add(() -> {
|
||||||
for (Long xz : row) {
|
for (long xz : row) {
|
||||||
chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
|
chunkStatus.processChunkSave(xz, limitsForRadius.get(xz));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
try {
|
runAndWait(scheduled);
|
||||||
List<Future<?>> futures = new ArrayList<>();
|
|
||||||
scheduled.forEach(task -> futures.add(executor.submit(task)));
|
|
||||||
for (Future<?> future : futures) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (this.generateConcurrent && entry.getValue() == Concurrency.FULL) {
|
} else if (this.generateConcurrent && entry.getValue() == Concurrency.FULL) {
|
||||||
// every chunk can be processed individually
|
// every chunk can be processed individually
|
||||||
List<Runnable> scheduled = new ArrayList<>(coords.size());
|
List<Runnable> scheduled = new ArrayList<>(coords.length);
|
||||||
for (long xz : coords) {
|
for (long xz : coords) {
|
||||||
scheduled.add(() -> {
|
scheduled.add(() -> chunkStatus.processChunkSave(xz, limitsForRadius.get(xz)));
|
||||||
chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
List<Future<?>> futures = new ArrayList<>();
|
|
||||||
scheduled.forEach(task -> futures.add(executor.submit(task)));
|
|
||||||
for (Future<?> future : futures) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
runAndWait(scheduled);
|
||||||
} else { // Concurrency.NONE or generateConcurrent == false
|
} else { // Concurrency.NONE or generateConcurrent == false
|
||||||
// run sequential but submit to different thread
|
// run sequential but submit to different thread
|
||||||
// running regen on the main thread otherwise triggers async-only events on the main thread
|
// running regen on the main thread otherwise triggers async-only events on the main thread
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
for (long xz : coords) {
|
for (long xz : coords) {
|
||||||
chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
|
chunkStatus.processChunkSave(xz, limitsForRadius.get(xz));
|
||||||
}
|
}
|
||||||
}).get(); // wait until finished this step
|
}).get(); // wait until finished this step
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//convert to proper chunks
|
//convert to proper chunks
|
||||||
for (Long xz : chunkCoordsForRadius.get(0)) {
|
for (long xz : chunkCoordsForRadius.get(0)) {
|
||||||
ProtoChunk proto = protoChunks.get(xz);
|
ProtoChunk proto = protoChunks.get(xz);
|
||||||
chunks.put(xz, createChunk(proto));
|
chunks.put(xz, createChunk(proto));
|
||||||
}
|
}
|
||||||
|
|
||||||
//final chunkstatus
|
//final chunkstatus
|
||||||
ChunkStatus FULL = getFullChunkStatus();
|
ChunkStatus FULL = getFullChunkStatus();
|
||||||
for (Long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0!
|
for (long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0!
|
||||||
Chunk chunk = chunks.get(xz);
|
Chunk chunk = chunks.get(xz);
|
||||||
FULL.processChunkSave(xz, Arrays.asList(chunk));
|
FULL.processChunkSave(xz, List.of(chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
//populate
|
//populate
|
||||||
List<BlockPopulator> populators = getBlockPopulators();
|
List<BlockPopulator> populators = getBlockPopulators();
|
||||||
for (Long xz : chunkCoordsForRadius.get(0)) {
|
for (long xz : chunkCoordsForRadius.get(0)) {
|
||||||
int x = MathMan.unpairIntX(xz);
|
int x = MathMan.unpairIntX(xz);
|
||||||
int z = MathMan.unpairIntY(xz);
|
int z = MathMan.unpairIntY(xz);
|
||||||
|
|
||||||
@ -302,6 +284,18 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runAndWait(final List<Runnable> tasks) {
|
||||||
|
try {
|
||||||
|
List<Future<?>> futures = new ArrayList<>();
|
||||||
|
tasks.forEach(task -> futures.add(executor.submit(task)));
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.catching(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void copyToWorld() {
|
private void copyToWorld() {
|
||||||
//Setting Blocks
|
//Setting Blocks
|
||||||
boolean genbiomes = options.shouldRegenBiomes();
|
boolean genbiomes = options.shouldRegenBiomes();
|
||||||
@ -437,7 +431,7 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
protected abstract IChunkCache<IChunkGet> initSourceQueueCache();
|
protected abstract IChunkCache<IChunkGet> initSourceQueueCache();
|
||||||
|
|
||||||
//algorithms
|
//algorithms
|
||||||
private List<Long> getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks
|
private long[] getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks
|
||||||
BlockVector3 oldMin = region.getMinimumPoint();
|
BlockVector3 oldMin = region.getMinimumPoint();
|
||||||
BlockVector3 newMin = BlockVector3.at(
|
BlockVector3 newMin = BlockVector3.at(
|
||||||
(oldMin.getX() >> 4 << 4) - border * 16,
|
(oldMin.getX() >> 4 << 4) - border * 16,
|
||||||
@ -455,76 +449,79 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
.sorted(Comparator
|
.sorted(Comparator
|
||||||
.comparingInt(BlockVector2::getZ)
|
.comparingInt(BlockVector2::getZ)
|
||||||
.thenComparingInt(BlockVector2::getX)) //needed for RegionLimitedWorldAccess
|
.thenComparingInt(BlockVector2::getX)) //needed for RegionLimitedWorldAccess
|
||||||
.map(c -> MathMan.pairInt(c.getX(), c.getZ()))
|
.mapToLong(c -> MathMan.pairInt(c.getX(), c.getZ()))
|
||||||
.collect(Collectors.toList());
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a list of chunkcoord rows that may be executed concurrently
|
* Creates a list of chunkcoord rows that may be executed concurrently
|
||||||
*
|
*
|
||||||
* @param allcoords the coords that should be sorted into rows, must be sorted by z and x
|
* @param allCoords the coords that should be sorted into rows, must be sorted by z and x
|
||||||
* @param requiredNeighborChunkRadius the radius of neighbor chunks that may not be written to concurrently (ChunkStatus
|
* @param requiredNeighborChunkRadius the radius of neighbor chunks that may not be written to concurrently (ChunkStatus
|
||||||
* .requiredNeighborRadius)
|
* .requiredNeighborRadius)
|
||||||
* @return a list of chunkcoords rows that may be executed concurrently
|
* @return a list of chunkcoords rows that may be executed concurrently
|
||||||
*/
|
*/
|
||||||
private SequentialTasks<ConcurrentTasks<SequentialTasks<Long>>> getChunkStatusTaskRows(
|
private SequentialTasks<ConcurrentTasks<LongList>> getChunkStatusTaskRows(
|
||||||
List<Long> allcoords,
|
long[] allCoords,
|
||||||
int requiredNeighborChunkRadius
|
int requiredNeighborChunkRadius
|
||||||
) {
|
) {
|
||||||
int requiredneighbors = Math.max(0, requiredNeighborChunkRadius);
|
int requiredNeighbors = Math.max(0, requiredNeighborChunkRadius);
|
||||||
|
|
||||||
int minx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(0));
|
final int coordsCount = allCoords.length;
|
||||||
int maxx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(allcoords.size() - 1));
|
long first = coordsCount == 0 ? 0 : allCoords[0];
|
||||||
int minz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(0));
|
long last = coordsCount == 0 ? 0 : allCoords[coordsCount - 1];
|
||||||
int maxz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(allcoords.size() - 1));
|
int minX = MathMan.unpairIntX(first);
|
||||||
SequentialTasks<ConcurrentTasks<SequentialTasks<Long>>> tasks;
|
int maxX = MathMan.unpairIntX(last);
|
||||||
if (maxz - minz > maxx - minx) {
|
int minZ = MathMan.unpairIntY(first);
|
||||||
int numlists = Math.min(requiredneighbors * 2 + 1, maxx - minx + 1);
|
int maxZ = MathMan.unpairIntY(last);
|
||||||
|
SequentialTasks<ConcurrentTasks<LongList>> tasks;
|
||||||
|
if (maxZ - minZ > maxX - minX) {
|
||||||
|
int numlists = Math.min(requiredNeighbors * 2 + 1, maxX - minX + 1);
|
||||||
|
|
||||||
Int2ObjectOpenHashMap<SequentialTasks<Long>> byx = new Int2ObjectOpenHashMap();
|
Int2ObjectOpenHashMap<LongList> byX = new Int2ObjectOpenHashMap<>();
|
||||||
int expectedListLength = (allcoords.size() + 1) / (maxx - minx);
|
int expectedListLength = (coordsCount + 1) / (maxX - minX);
|
||||||
|
|
||||||
//init lists
|
//init lists
|
||||||
for (int i = minx; i <= maxx; i++) {
|
for (int i = minX; i <= maxX; i++) {
|
||||||
byx.put(i, new SequentialTasks(expectedListLength));
|
byX.put(i, new LongArrayList(expectedListLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
//sort into lists by x coord
|
//sort into lists by x coord
|
||||||
for (Long xz : allcoords) {
|
for (long allCoord : allCoords) {
|
||||||
byx.get(MathMan.unpairIntX(xz)).add(xz);
|
byX.get(MathMan.unpairIntX(allCoord)).add(allCoord);
|
||||||
}
|
}
|
||||||
|
|
||||||
//create parallel tasks
|
//create parallel tasks
|
||||||
tasks = new SequentialTasks(numlists);
|
tasks = new SequentialTasks<>(numlists);
|
||||||
for (int offset = 0; offset < numlists; offset++) {
|
for (int offset = 0; offset < numlists; offset++) {
|
||||||
ConcurrentTasks<SequentialTasks<Long>> para = new ConcurrentTasks((maxz - minz + 1) / numlists + 1);
|
ConcurrentTasks<LongList> para = new ConcurrentTasks<>((maxZ - minZ + 1) / numlists + 1);
|
||||||
for (int i = 0; minx + i * numlists + offset <= maxx; i++) {
|
for (int i = 0; minX + i * numlists + offset <= maxX; i++) {
|
||||||
para.add(byx.get(minx + i * numlists + offset));
|
para.add(byX.get(minX + i * numlists + offset));
|
||||||
}
|
}
|
||||||
tasks.add(para);
|
tasks.add(para);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int numlists = Math.min(requiredneighbors * 2 + 1, maxz - minz + 1);
|
int numlists = Math.min(requiredNeighbors * 2 + 1, maxZ - minZ + 1);
|
||||||
|
|
||||||
Int2ObjectOpenHashMap<SequentialTasks<Long>> byz = new Int2ObjectOpenHashMap();
|
Int2ObjectOpenHashMap<LongList> byZ = new Int2ObjectOpenHashMap<>();
|
||||||
int expectedListLength = (allcoords.size() + 1) / (maxz - minz);
|
int expectedListLength = (coordsCount + 1) / (maxZ - minZ);
|
||||||
|
|
||||||
//init lists
|
//init lists
|
||||||
for (int i = minz; i <= maxz; i++) {
|
for (int i = minZ; i <= maxZ; i++) {
|
||||||
byz.put(i, new SequentialTasks(expectedListLength));
|
byZ.put(i, new LongArrayList(expectedListLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
//sort into lists by x coord
|
//sort into lists by x coord
|
||||||
for (Long xz : allcoords) {
|
for (long allCoord : allCoords) {
|
||||||
byz.get(MathMan.unpairIntY(xz)).add(xz);
|
byZ.get(MathMan.unpairIntY(allCoord)).add(allCoord);
|
||||||
}
|
}
|
||||||
|
|
||||||
//create parallel tasks
|
//create parallel tasks
|
||||||
tasks = new SequentialTasks(numlists);
|
tasks = new SequentialTasks<>(numlists);
|
||||||
for (int offset = 0; offset < numlists; offset++) {
|
for (int offset = 0; offset < numlists; offset++) {
|
||||||
ConcurrentTasks<SequentialTasks<Long>> para = new ConcurrentTasks((maxx - minx + 1) / numlists + 1);
|
ConcurrentTasks<LongList> para = new ConcurrentTasks<>((maxX - minX + 1) / numlists + 1);
|
||||||
for (int i = 0; minz + i * numlists + offset <= maxz; i++) {
|
for (int i = 0; minZ + i * numlists + offset <= maxZ; i++) {
|
||||||
para.add(byz.get(minz + i * numlists + offset));
|
para.add(byZ.get(minZ + i * numlists + offset));
|
||||||
}
|
}
|
||||||
tasks.add(para);
|
tasks.add(para);
|
||||||
}
|
}
|
||||||
@ -576,15 +573,14 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
/**
|
/**
|
||||||
* Return the name of the wrapped {@code ChunkStatus}.
|
* Return the name of the wrapped {@code ChunkStatus}.
|
||||||
*
|
*
|
||||||
* @param xz represents the chunk coordinates of the chunk to process as denoted by {@code MathMan}
|
|
||||||
* @param accessibleChunks a list of chunks that will be used during the execution of the wrapped {@code ChunkStatus}.
|
* @param accessibleChunks a list of chunks that will be used during the execution of the wrapped {@code ChunkStatus}.
|
||||||
* This list is order in the correct order required by the {@code ChunkStatus}, unless Mojang suddenly decides to do things differently.
|
* This list is order in the correct order required by the {@code ChunkStatus}, unless Mojang suddenly decides to do things differently.
|
||||||
*/
|
*/
|
||||||
public abstract CompletableFuture<?> processChunk(Long xz, List<IChunkAccess> accessibleChunks);
|
public abstract CompletableFuture<?> processChunk(List<IChunkAccess> accessibleChunks);
|
||||||
|
|
||||||
void processChunkSave(Long xz, List<IChunkAccess> accessibleChunks) {
|
void processChunkSave(long xz, List<IChunkAccess> accessibleChunks) {
|
||||||
try {
|
try {
|
||||||
processChunk(xz, accessibleChunks).get();
|
processChunk(accessibleChunks).get();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"Error while running " + name() + " on chunk " + MathMan.unpairIntX(xz) + "/" + MathMan.unpairIntY(xz),
|
"Error while running " + name() + " on chunk " + MathMan.unpairIntX(xz) + "/" + MathMan.unpairIntY(xz),
|
||||||
@ -597,16 +593,16 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
|
|
||||||
public static class SequentialTasks<T> extends Tasks<T> {
|
public static class SequentialTasks<T> extends Tasks<T> {
|
||||||
|
|
||||||
public SequentialTasks(int expectedsize) {
|
public SequentialTasks(int expectedSize) {
|
||||||
super(expectedsize);
|
super(expectedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ConcurrentTasks<T> extends Tasks<T> {
|
public static class ConcurrentTasks<T> extends Tasks<T> {
|
||||||
|
|
||||||
public ConcurrentTasks(int expectedsize) {
|
public ConcurrentTasks(int expectedSize) {
|
||||||
super(expectedsize);
|
super(expectedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -615,8 +611,8 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
|
|
||||||
private final List<T> tasks;
|
private final List<T> tasks;
|
||||||
|
|
||||||
public Tasks(int expectedsize) {
|
public Tasks(int expectedSize) {
|
||||||
tasks = new ArrayList(expectedsize);
|
tasks = new ArrayList<>(expectedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(T task) {
|
public void add(T task) {
|
||||||
|
@ -171,13 +171,11 @@ public class FaweDelegateRegionManager {
|
|||||||
.limitUnlimited()
|
.limitUnlimited()
|
||||||
.changeSetNull()
|
.changeSetNull()
|
||||||
.build();
|
.build();
|
||||||
File schematicFile = new File(hybridPlotWorld.getRoot(), "plot.schem");
|
File schematicFile = new File(hybridPlotWorld.getSchematicRoot(), "plot.schem");
|
||||||
if (!schematicFile.exists()) {
|
if (!schematicFile.exists()) {
|
||||||
schematicFile = new File(hybridPlotWorld.getRoot(), "plot.schematic");
|
schematicFile = new File(hybridPlotWorld.getSchematicRoot(), "plot.schematic");
|
||||||
}
|
}
|
||||||
BlockVector3 to = plot.getBottomAbs().getBlockVector3().withY(Settings.Schematics.PASTE_ON_TOP
|
BlockVector3 to = plot.getBottomAbs().getBlockVector3().withY(hybridPlotWorld.getPlotYStart());
|
||||||
? hybridPlotWorld.SCHEM_Y
|
|
||||||
: hybridPlotWorld.getMinBuildHeight());
|
|
||||||
try {
|
try {
|
||||||
Clipboard clip = ClipboardFormats
|
Clipboard clip = ClipboardFormats
|
||||||
.findByFile(schematicFile)
|
.findByFile(schematicFile)
|
||||||
@ -215,7 +213,7 @@ public class FaweDelegateRegionManager {
|
|||||||
) {
|
) {
|
||||||
TaskManager.taskManager().async(() -> {
|
TaskManager.taskManager().async(() -> {
|
||||||
synchronized (FaweDelegateRegionManager.class) {
|
synchronized (FaweDelegateRegionManager.class) {
|
||||||
//todo because of the following code this should proably be in the Bukkit module
|
//todo because of the following code this should probably be in the Bukkit module
|
||||||
World pos1World = BukkitAdapter.adapt(getWorld(pos1.getWorldName()));
|
World pos1World = BukkitAdapter.adapt(getWorld(pos1.getWorldName()));
|
||||||
World pos3World = BukkitAdapter.adapt(getWorld(swapPos.getWorldName()));
|
World pos3World = BukkitAdapter.adapt(getWorld(swapPos.getWorldName()));
|
||||||
EditSession sessionA = WorldEdit.getInstance().newEditSessionBuilder().world(pos1World)
|
EditSession sessionA = WorldEdit.getInstance().newEditSessionBuilder().world(pos1World)
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package com.fastasyncworldedit.bukkit.regions.plotsquared;
|
package com.fastasyncworldedit.bukkit.regions.plotsquared;
|
||||||
|
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
import com.plotsquared.core.PlotSquared;
|
import com.plotsquared.core.PlotSquared;
|
||||||
import com.plotsquared.core.command.CommandCategory;
|
import com.plotsquared.core.command.CommandCategory;
|
||||||
import com.plotsquared.core.command.CommandDeclaration;
|
import com.plotsquared.core.command.CommandDeclaration;
|
||||||
import com.plotsquared.core.command.RequiredType;
|
import com.plotsquared.core.command.RequiredType;
|
||||||
import com.plotsquared.core.command.SubCommand;
|
import com.plotsquared.core.command.SubCommand;
|
||||||
import com.plotsquared.core.configuration.caption.StaticCaption;
|
import com.plotsquared.core.configuration.caption.StaticCaption;
|
||||||
import com.plotsquared.core.configuration.caption.Templates;
|
|
||||||
import com.plotsquared.core.configuration.caption.TranslatableCaption;
|
import com.plotsquared.core.configuration.caption.TranslatableCaption;
|
||||||
import com.plotsquared.core.player.PlotPlayer;
|
import com.plotsquared.core.player.PlotPlayer;
|
||||||
|
|
||||||
@ -33,7 +35,7 @@ public class FaweTrim extends SubCommand {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!PlotSquared.platform().worldUtil().isWorld(strings[0])) {
|
if (!PlotSquared.platform().worldUtil().isWorld(strings[0])) {
|
||||||
plotPlayer.sendMessage(TranslatableCaption.of("errors.not_valid_plot_world"), Templates.of("value", strings[0]));
|
plotPlayer.sendMessage(TranslatableCaption.of("errors.not_valid_plot_world"), TagResolver.resolver("value", Tag.inserting(Component.text(strings[0]))));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ran = true;
|
ran = true;
|
||||||
|
@ -6,11 +6,9 @@ import com.plotsquared.core.command.CommandCategory;
|
|||||||
import com.plotsquared.core.command.CommandDeclaration;
|
import com.plotsquared.core.command.CommandDeclaration;
|
||||||
import com.plotsquared.core.command.MainCommand;
|
import com.plotsquared.core.command.MainCommand;
|
||||||
import com.plotsquared.core.command.RequiredType;
|
import com.plotsquared.core.command.RequiredType;
|
||||||
import com.plotsquared.core.configuration.caption.Templates;
|
|
||||||
import com.plotsquared.core.configuration.caption.TranslatableCaption;
|
import com.plotsquared.core.configuration.caption.TranslatableCaption;
|
||||||
import com.plotsquared.core.player.PlotPlayer;
|
import com.plotsquared.core.player.PlotPlayer;
|
||||||
import com.plotsquared.core.plot.Plot;
|
import com.plotsquared.core.plot.Plot;
|
||||||
import com.plotsquared.core.util.Permissions;
|
|
||||||
import com.plotsquared.core.util.StringMan;
|
import com.plotsquared.core.util.StringMan;
|
||||||
import com.plotsquared.core.util.task.RunnableVal2;
|
import com.plotsquared.core.util.task.RunnableVal2;
|
||||||
import com.plotsquared.core.util.task.RunnableVal3;
|
import com.plotsquared.core.util.task.RunnableVal3;
|
||||||
@ -24,6 +22,9 @@ import com.sk89q.worldedit.world.biome.BiomeType;
|
|||||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
import com.sk89q.worldedit.world.biome.Biomes;
|
import com.sk89q.worldedit.world.biome.Biomes;
|
||||||
import com.sk89q.worldedit.world.registry.BiomeRegistry;
|
import com.sk89q.worldedit.world.registry.BiomeRegistry;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -56,7 +57,7 @@ public class PlotSetBiome extends Command {
|
|||||||
) throws CommandException {
|
) throws CommandException {
|
||||||
final Plot plot = check(player.getCurrentPlot(), TranslatableCaption.of("errors.not_in_plot"));
|
final Plot plot = check(player.getCurrentPlot(), TranslatableCaption.of("errors.not_in_plot"));
|
||||||
checkTrue(
|
checkTrue(
|
||||||
plot.isOwner(player.getUUID()) || Permissions.hasPermission(player, "plots.admin.command.generatebiome"),
|
plot.isOwner(player.getUUID()) || player.hasPermission("plots.admin.command.generatebiome"),
|
||||||
TranslatableCaption.of("permission.no_plot_perms")
|
TranslatableCaption.of("permission.no_plot_perms")
|
||||||
);
|
);
|
||||||
if (plot.getRunning() != 0) {
|
if (plot.getRunning() != 0) {
|
||||||
@ -64,7 +65,7 @@ public class PlotSetBiome extends Command {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
checkTrue(args.length == 1, TranslatableCaption.of("commandconfig.command_syntax"),
|
checkTrue(args.length == 1, TranslatableCaption.of("commandconfig.command_syntax"),
|
||||||
Templates.of("value", getUsage())
|
TagResolver.resolver("value", Tag.inserting(Component.text(getUsage())))
|
||||||
);
|
);
|
||||||
final Set<CuboidRegion> regions = plot.getRegions();
|
final Set<CuboidRegion> regions = plot.getRegions();
|
||||||
BiomeRegistry biomeRegistry =
|
BiomeRegistry biomeRegistry =
|
||||||
@ -80,7 +81,7 @@ public class PlotSetBiome extends Command {
|
|||||||
player.sendMessage(TranslatableCaption.of("biome.need_biome"));
|
player.sendMessage(TranslatableCaption.of("biome.need_biome"));
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
TranslatableCaption.of("commandconfig.subcommand_set_options_header"),
|
TranslatableCaption.of("commandconfig.subcommand_set_options_header"),
|
||||||
Templates.of("values", biomes)
|
TagResolver.resolver("value", Tag.inserting(Component.text(biomes)))
|
||||||
);
|
);
|
||||||
return CompletableFuture.completedFuture(false);
|
return CompletableFuture.completedFuture(false);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ public class PlotSquaredFeature extends FaweMaskManager {
|
|||||||
if (Settings.FAWE_Components.FAWE_HOOK) {
|
if (Settings.FAWE_Components.FAWE_HOOK) {
|
||||||
Settings.Enabled_Components.WORLDEDIT_RESTRICTIONS = false;
|
Settings.Enabled_Components.WORLDEDIT_RESTRICTIONS = false;
|
||||||
if (Settings.PLATFORM.toLowerCase(Locale.ROOT).startsWith("bukkit")) {
|
if (Settings.PLATFORM.toLowerCase(Locale.ROOT).startsWith("bukkit")) {
|
||||||
new FaweTrim();
|
// new FaweTrim();
|
||||||
}
|
}
|
||||||
// TODO: revisit this later on
|
// TODO: revisit this later on
|
||||||
/*
|
/*
|
||||||
|
@ -25,7 +25,6 @@ import com.sk89q.worldedit.util.YAMLConfiguration;
|
|||||||
import com.sk89q.worldedit.util.report.Unreported;
|
import com.sk89q.worldedit.util.report.Unreported;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,26 +52,6 @@ public class BukkitConfiguration extends YAMLConfiguration {
|
|||||||
WorldEdit.logger.warn("Editing without a Bukkit adapter has been enabled. You will not receive support "
|
WorldEdit.logger.warn("Editing without a Bukkit adapter has been enabled. You will not receive support "
|
||||||
+ "for any issues that arise as a result.");
|
+ "for any issues that arise as a result.");
|
||||||
}
|
}
|
||||||
migrateLegacyFolders();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void migrateLegacyFolders() {
|
|
||||||
migrate(scriptsDir, "craftscripts");
|
|
||||||
migrate(saveDir, "schematics");
|
|
||||||
migrate("drawings", "draw.js images");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void migrate(String file, String name) {
|
|
||||||
File fromDir = new File(".", file);
|
|
||||||
File toDir = new File(getWorkingDirectoryPath().toFile(), file);
|
|
||||||
if (fromDir.exists() & !toDir.exists()) {
|
|
||||||
if (fromDir.renameTo(toDir)) {
|
|
||||||
plugin.getLogger().info("Migrated " + name + " folder '" + file
|
|
||||||
+ "' from server root to plugin data folder.");
|
|
||||||
} else {
|
|
||||||
plugin.getLogger().warning("Error while migrating " + name + " folder!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -42,6 +42,7 @@ import com.sk89q.worldedit.util.SideEffect;
|
|||||||
import com.sk89q.worldedit.util.lifecycle.Lifecycled;
|
import com.sk89q.worldedit.util.lifecycle.Lifecycled;
|
||||||
import com.sk89q.worldedit.world.DataFixer;
|
import com.sk89q.worldedit.world.DataFixer;
|
||||||
import com.sk89q.worldedit.world.registry.Registries;
|
import com.sk89q.worldedit.world.registry.Registries;
|
||||||
|
import io.papermc.lib.PaperLib;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
@ -258,6 +259,14 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser
|
|||||||
return SUPPORTED_SIDE_EFFECTS;
|
return SUPPORTED_SIDE_EFFECTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTickCount() {
|
||||||
|
if (PaperLib.isPaper()) {
|
||||||
|
return Bukkit.getCurrentTick();
|
||||||
|
}
|
||||||
|
return super.getTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
public void unregisterCommands() {
|
public void unregisterCommands() {
|
||||||
dynamicCommands.unregisterCommands();
|
dynamicCommands.unregisterCommands();
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import com.sk89q.worldedit.WorldEdit;
|
|||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
import com.sk89q.worldedit.event.platform.SessionIdleEvent;
|
import com.sk89q.worldedit.event.platform.SessionIdleEvent;
|
||||||
import com.sk89q.worldedit.extension.platform.Actor;
|
import com.sk89q.worldedit.extension.platform.Actor;
|
||||||
|
import com.sk89q.worldedit.internal.event.InteractionDebouncer;
|
||||||
import com.sk89q.worldedit.util.Direction;
|
import com.sk89q.worldedit.util.Direction;
|
||||||
import com.sk89q.worldedit.util.Location;
|
import com.sk89q.worldedit.util.Location;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
@ -55,6 +56,7 @@ import java.util.Optional;
|
|||||||
public class WorldEditListener implements Listener {
|
public class WorldEditListener implements Listener {
|
||||||
|
|
||||||
private final WorldEditPlugin plugin;
|
private final WorldEditPlugin plugin;
|
||||||
|
private final InteractionDebouncer debouncer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct the object.
|
* Construct the object.
|
||||||
@ -63,6 +65,7 @@ public class WorldEditListener implements Listener {
|
|||||||
*/
|
*/
|
||||||
public WorldEditListener(WorldEditPlugin plugin) {
|
public WorldEditListener(WorldEditPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
debouncer = new InteractionDebouncer(plugin.getInternalPlatform());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
@ -128,62 +131,58 @@ public class WorldEditListener implements Listener {
|
|||||||
*/
|
*/
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||||
if (!plugin.getInternalPlatform().isHookingEvents()) {
|
if (!plugin.getInternalPlatform().isHookingEvents()
|
||||||
return;
|
|| event.useItemInHand() == Result.DENY
|
||||||
}
|
|| event.getHand() == EquipmentSlot.OFF_HAND
|
||||||
|
|| event.getAction() == Action.PHYSICAL) {
|
||||||
if (event.useItemInHand() == Result.DENY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.getHand() == EquipmentSlot.OFF_HAND) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player player = plugin.wrapPlayer(event.getPlayer());
|
final Player player = plugin.wrapPlayer(event.getPlayer());
|
||||||
|
|
||||||
|
if (event.getAction() != Action.LEFT_CLICK_BLOCK) {
|
||||||
|
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
|
||||||
|
if (previousResult.isPresent()) {
|
||||||
|
if (previousResult.get()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final World world = player.getWorld();
|
final World world = player.getWorld();
|
||||||
final WorldEdit we = plugin.getWorldEdit();
|
final WorldEdit we = plugin.getWorldEdit();
|
||||||
final Direction direction = BukkitAdapter.adapt(event.getBlockFace());
|
final Direction direction = BukkitAdapter.adapt(event.getBlockFace());
|
||||||
|
final Block clickedBlock = event.getClickedBlock();
|
||||||
|
final Location pos = clickedBlock == null ? null : new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ());
|
||||||
|
|
||||||
Action action = event.getAction();
|
boolean result = false;
|
||||||
if (action == Action.LEFT_CLICK_BLOCK) {
|
switch (event.getAction()) {
|
||||||
final Block clickedBlock = event.getClickedBlock();
|
case LEFT_CLICK_BLOCK:
|
||||||
final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ());
|
result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player);
|
||||||
|
break;
|
||||||
if (we.handleBlockLeftClick(player, pos, direction)) {
|
case LEFT_CLICK_AIR:
|
||||||
event.setCancelled(true);
|
result = we.handleArmSwing(player);
|
||||||
}
|
break;
|
||||||
|
case RIGHT_CLICK_BLOCK:
|
||||||
if (we.handleArmSwing(player)) {
|
result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player);
|
||||||
event.setCancelled(true);
|
break;
|
||||||
}
|
case RIGHT_CLICK_AIR:
|
||||||
|
result = we.handleRightClick(player);
|
||||||
} else if (action == Action.LEFT_CLICK_AIR) {
|
break;
|
||||||
|
default:
|
||||||
if (we.handleArmSwing(player)) {
|
break;
|
||||||
event.setCancelled(true);
|
}
|
||||||
}
|
debouncer.setLastInteraction(player, result);
|
||||||
|
if (result) {
|
||||||
} else if (action == Action.RIGHT_CLICK_BLOCK) {
|
event.setCancelled(true);
|
||||||
final Block clickedBlock = event.getClickedBlock();
|
|
||||||
final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ());
|
|
||||||
|
|
||||||
if (we.handleBlockRightClick(player, pos, direction)) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (we.handleRightClick(player)) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
}
|
|
||||||
} else if (action == Action.RIGHT_CLICK_AIR) {
|
|
||||||
if (we.handleRightClick(player)) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
debouncer.clearInteraction(plugin.wrapPlayer(event.getPlayer()));
|
||||||
|
|
||||||
plugin.getWorldEdit().getEventBus().post(new SessionIdleEvent(new BukkitPlayer.SessionKeyImpl(event.getPlayer())));
|
plugin.getWorldEdit().getEventBus().post(new SessionIdleEvent(new BukkitPlayer.SessionKeyImpl(event.getPlayer())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,6 +672,13 @@ public class WorldEditPlugin extends JavaPlugin {
|
|||||||
String label = buffer.substring(0, firstSpace);
|
String label = buffer.substring(0, firstSpace);
|
||||||
// Strip leading slash, if present.
|
// Strip leading slash, if present.
|
||||||
label = label.startsWith("/") ? label.substring(1) : label;
|
label = label.startsWith("/") ? label.substring(1) : label;
|
||||||
|
|
||||||
|
// If command not owned by FAWE, do not tab complete
|
||||||
|
Plugin owner = platform.getDynamicCommands().getCommandOwner(label);
|
||||||
|
if (owner != WorldEditPlugin.this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Optional<org.enginehub.piston.Command> command
|
final Optional<org.enginehub.piston.Command> command
|
||||||
= WorldEdit.getInstance().getPlatformManager().getPlatformCommandManager().getCommandManager().getCommand(
|
= WorldEdit.getInstance().getPlatformManager().getPlatformCommandManager().getCommandManager().getCommand(
|
||||||
label);
|
label);
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package com.sk89q.wepif;
|
package com.sk89q.wepif;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.profile.PlayerProfile;
|
||||||
|
import org.bukkit.BanEntry;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
@ -30,10 +32,13 @@ import org.bukkit.permissions.Permission;
|
|||||||
import org.bukkit.permissions.PermissionAttachment;
|
import org.bukkit.permissions.PermissionAttachment;
|
||||||
import org.bukkit.permissions.PermissionAttachmentInfo;
|
import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.profile.PlayerProfile;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -139,6 +144,11 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Tester";
|
return "Tester";
|
||||||
@ -158,6 +168,33 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible {
|
|||||||
throw new UnsupportedOperationException("Not supported yet.");
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends BanEntry<? super PlayerProfile>> @Nullable E ban(
|
||||||
|
@Nullable final String reason,
|
||||||
|
@Nullable final Instant expires,
|
||||||
|
@Nullable final String source
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends BanEntry<? super PlayerProfile>> @Nullable E ban(
|
||||||
|
@Nullable final String reason,
|
||||||
|
@Nullable final Duration duration,
|
||||||
|
@Nullable final String source
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable BanEntry<org.bukkit.profile.PlayerProfile> ban(
|
||||||
|
@Nullable final String reason,
|
||||||
|
@Nullable final Date expires,
|
||||||
|
@Nullable final String source
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWhitelisted() {
|
public boolean isWhitelisted() {
|
||||||
throw new UnsupportedOperationException("Not supported yet.");
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
@ -323,4 +360,9 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Location getLastDeathLocation() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,16 +27,16 @@ dependencies {
|
|||||||
|
|
||||||
// Minecraft expectations
|
// Minecraft expectations
|
||||||
annotationProcessor(libs.guava)
|
annotationProcessor(libs.guava)
|
||||||
implementation("com.google.guava:guava")
|
implementation(libs.guava)
|
||||||
implementation("com.google.code.gson:gson")
|
implementation(libs.gson)
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.log4jBom) {
|
implementation(libs.log4jBom) {
|
||||||
because("We control Log4J on this platform")
|
because("We control Log4J on this platform")
|
||||||
}
|
}
|
||||||
implementation("org.apache.logging.log4j:log4j-api")
|
implementation(libs.log4jApi)
|
||||||
implementation(libs.log4jCore)
|
implementation(libs.log4jCore)
|
||||||
implementation("commons-cli:commons-cli:1.5.0")
|
implementation(libs.commonsCli)
|
||||||
api(libs.parallelgzip) { isTransitive = false }
|
api(libs.parallelgzip) { isTransitive = false }
|
||||||
api(libs.lz4Java)
|
api(libs.lz4Java)
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ applyPlatformAndCoreConfiguration()
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
constraints {
|
constraints {
|
||||||
implementation("org.yaml:snakeyaml") {
|
implementation(libs.snakeyaml) {
|
||||||
version { strictly("2.0") }
|
version { strictly("2.2") }
|
||||||
because("Bukkit provides SnakeYaml")
|
because("Bukkit provides SnakeYaml")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,18 +24,17 @@ dependencies {
|
|||||||
|
|
||||||
// Minecraft expectations
|
// Minecraft expectations
|
||||||
implementation(libs.fastutil)
|
implementation(libs.fastutil)
|
||||||
implementation("com.google.guava:guava")
|
implementation(libs.guava)
|
||||||
implementation("com.google.code.gson:gson")
|
implementation(libs.gson)
|
||||||
|
|
||||||
// Platform expectations
|
// Platform expectations
|
||||||
// TODO update bom-newest
|
implementation(libs.snakeyaml)
|
||||||
implementation("org.yaml:snakeyaml:2.0")
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("org.apache.logging.log4j:log4j-api")
|
implementation(libs.log4jApi)
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
compileOnly("com.plotsquared:PlotSquared-Core") { isTransitive = false }
|
compileOnly(libs.plotSquaredCore) { isTransitive = false }
|
||||||
|
|
||||||
// ensure this is on the classpath for the AP
|
// ensure this is on the classpath for the AP
|
||||||
annotationProcessor(libs.guava)
|
annotationProcessor(libs.guava)
|
||||||
@ -46,11 +45,11 @@ dependencies {
|
|||||||
compileOnly(libs.truezip)
|
compileOnly(libs.truezip)
|
||||||
implementation(libs.findbugs)
|
implementation(libs.findbugs)
|
||||||
implementation(libs.rhino)
|
implementation(libs.rhino)
|
||||||
compileOnly("net.kyori:adventure-api")
|
compileOnly(libs.adventureApi)
|
||||||
compileOnlyApi(libs.adventureNbt)
|
compileOnlyApi(libs.adventureNbt)
|
||||||
compileOnlyApi("net.kyori:adventure-text-minimessage")
|
compileOnlyApi(libs.adventureMiniMessage)
|
||||||
implementation(libs.zstd) { isTransitive = false }
|
implementation(libs.zstd) { isTransitive = false }
|
||||||
compileOnly("com.intellectualsites.paster:Paster")
|
compileOnly(libs.paster)
|
||||||
compileOnly(libs.lz4Java) { isTransitive = false }
|
compileOnly(libs.lz4Java) { isTransitive = false }
|
||||||
compileOnly(libs.sparsebitset)
|
compileOnly(libs.sparsebitset)
|
||||||
compileOnly(libs.parallelgzip) { isTransitive = false }
|
compileOnly(libs.parallelgzip) { isTransitive = false }
|
||||||
|
@ -134,15 +134,29 @@ public class MobSpawnerBlock extends BaseBlock {
|
|||||||
values.put("MaxNearbyEntities", new ShortTag(maxNearbyEntities));
|
values.put("MaxNearbyEntities", new ShortTag(maxNearbyEntities));
|
||||||
values.put("RequiredPlayerRange", new ShortTag(requiredPlayerRange));
|
values.put("RequiredPlayerRange", new ShortTag(requiredPlayerRange));
|
||||||
if (spawnData == null) {
|
if (spawnData == null) {
|
||||||
values.put("SpawnData", new CompoundTag(ImmutableMap.of("id", new StringTag(mobType))));
|
values.put(
|
||||||
|
"SpawnData",
|
||||||
|
new CompoundTag(ImmutableMap.of("entity", new CompoundTag(ImmutableMap.of("id", new StringTag(mobType)))))
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
values.put("SpawnData", new CompoundTag(spawnData.getValue()));
|
values.put("SpawnData", new CompoundTag(spawnData.getValue()));
|
||||||
}
|
}
|
||||||
if (spawnPotentials == null) {
|
if (spawnPotentials == null) {
|
||||||
values.put("SpawnPotentials", new ListTag(CompoundTag.class, ImmutableList.of(
|
values.put(
|
||||||
new CompoundTag(ImmutableMap.of("Weight", new IntTag(1), "Entity",
|
"SpawnPotentials",
|
||||||
new CompoundTag(ImmutableMap.of("id", new StringTag(mobType)))
|
new ListTag(
|
||||||
)))));
|
CompoundTag.class,
|
||||||
|
ImmutableList.of(new CompoundTag(ImmutableMap.of(
|
||||||
|
"weight",
|
||||||
|
new IntTag(1),
|
||||||
|
"data",
|
||||||
|
new CompoundTag(ImmutableMap.of(
|
||||||
|
"entity",
|
||||||
|
new CompoundTag(ImmutableMap.of("id", new StringTag(mobType)))
|
||||||
|
))
|
||||||
|
)))
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
values.put("SpawnPotentials", new ListTag(CompoundTag.class, spawnPotentials.getValue()));
|
values.put("SpawnPotentials", new ListTag(CompoundTag.class, spawnPotentials.getValue()));
|
||||||
}
|
}
|
||||||
|
@ -449,7 +449,6 @@ public class Fawe {
|
|||||||
* @return Executor used for clipboard IO if clipboard on disk is enabled or null
|
* @return Executor used for clipboard IO if clipboard on disk is enabled or null
|
||||||
* @since 2.6.2
|
* @since 2.6.2
|
||||||
*/
|
*/
|
||||||
@Nullable
|
|
||||||
public KeyQueuedExecutorService<UUID> getClipboardExecutor() {
|
public KeyQueuedExecutorService<UUID> getClipboardExecutor() {
|
||||||
return this.clipboardExecutor;
|
return this.clipboardExecutor;
|
||||||
}
|
}
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
package com.fastasyncworldedit.core.concurrent;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.Condition;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.StampedLock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows for reentrant behaviour of a wrapped {@link StampedLock}. Will not count the number of times it is re-entered.
|
|
||||||
*
|
|
||||||
* @since 2.3.0
|
|
||||||
*/
|
|
||||||
public class ReentrantWrappedStampedLock implements Lock {
|
|
||||||
|
|
||||||
private final StampedLock parent = new StampedLock();
|
|
||||||
private volatile Thread owner;
|
|
||||||
private volatile long stamp = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void lock() {
|
|
||||||
if (Thread.currentThread() == owner) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stamp = parent.writeLock();
|
|
||||||
owner = Thread.currentThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void lockInterruptibly() throws InterruptedException {
|
|
||||||
if (Thread.currentThread() == owner) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stamp = parent.writeLockInterruptibly();
|
|
||||||
owner = Thread.currentThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean tryLock() {
|
|
||||||
if (Thread.currentThread() == owner) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (parent.isWriteLocked()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stamp = parent.writeLock();
|
|
||||||
owner = Thread.currentThread();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean tryLock(final long time, @NotNull final TimeUnit unit) throws InterruptedException {
|
|
||||||
if (Thread.currentThread() == owner) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!parent.isWriteLocked()) {
|
|
||||||
stamp = parent.writeLock();
|
|
||||||
owner = Thread.currentThread();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
stamp = parent.tryWriteLock(time, unit);
|
|
||||||
owner = Thread.currentThread();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unlock() {
|
|
||||||
if (owner != Thread.currentThread()) {
|
|
||||||
throw new IllegalCallerException("The lock should only be unlocked by the owning thread when a stamp is not supplied");
|
|
||||||
}
|
|
||||||
unlock(stamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Condition newCondition() {
|
|
||||||
throw new UnsupportedOperationException("Conditions are not supported by StampedLock");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the stamp associated with the current lock. 0 if the wrapped {@link StampedLock} is not write-locked. This method is
|
|
||||||
* thread-checking.
|
|
||||||
*
|
|
||||||
* @return lock stam[ or 0 if not locked.
|
|
||||||
* @throws IllegalCallerException if the {@link StampedLock} is write-locked and the calling thread is not the lock owner
|
|
||||||
* @since 2.3.0
|
|
||||||
*/
|
|
||||||
public long getStampChecked() {
|
|
||||||
if (stamp != 0 && owner != Thread.currentThread()) {
|
|
||||||
throw new IllegalCallerException("The stamp should be be acquired by a thread that does not own the lock");
|
|
||||||
}
|
|
||||||
return stamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unlock the wrapped {@link StampedLock} using the given stamp. This can be called by any thread.
|
|
||||||
*
|
|
||||||
* @param stamp Stamp to unlock with
|
|
||||||
* @throws IllegalMonitorStateException if the given stamp does not match the lock's stamp
|
|
||||||
* @since 2.3.0
|
|
||||||
*/
|
|
||||||
public void unlock(final long stamp) {
|
|
||||||
parent.unlockWrite(stamp);
|
|
||||||
this.stamp = 0;
|
|
||||||
owner = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the lock is currently held.
|
|
||||||
*
|
|
||||||
* @return true if the lock is currently held.
|
|
||||||
* @since 2.3.0
|
|
||||||
*/
|
|
||||||
public boolean isLocked() {
|
|
||||||
return owner == null && this.stamp == 0 && parent.isWriteLocked(); // Be verbose
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -400,6 +400,7 @@ public class Settings extends Config {
|
|||||||
"of a waterlogged fence). For blocking/remapping of all occurrences of a property like waterlogged, see",
|
"of a waterlogged fence). For blocking/remapping of all occurrences of a property like waterlogged, see",
|
||||||
"remap-properties below.",
|
"remap-properties below.",
|
||||||
"To generate a blank list, substitute the default content with a set of square brackets [] instead.",
|
"To generate a blank list, substitute the default content with a set of square brackets [] instead.",
|
||||||
|
"The 'worldedit.anyblock' permission is not considered here.",
|
||||||
"Example block property blocking:",
|
"Example block property blocking:",
|
||||||
" - \"minecraft:conduit[waterlogged=true]\"",
|
" - \"minecraft:conduit[waterlogged=true]\"",
|
||||||
" - \"minecraft:piston[extended=false,facing=west]\"",
|
" - \"minecraft:piston[extended=false,facing=west]\"",
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package com.fastasyncworldedit.core.extension.factory.parser.pattern;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.configuration.Caption;
|
||||||
|
import com.fastasyncworldedit.core.extension.factory.parser.RichParser;
|
||||||
|
import com.fastasyncworldedit.core.function.pattern.TypeSwapPattern;
|
||||||
|
import com.fastasyncworldedit.core.util.Permission;
|
||||||
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.extension.input.InputParseException;
|
||||||
|
import com.sk89q.worldedit.extension.input.ParserContext;
|
||||||
|
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class TypeSwapPatternParser extends RichParser<Pattern> {
|
||||||
|
|
||||||
|
private static final List<String> SUGGESTIONS = List.of("oak", "spruce", "stone", "sandstone");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}.
|
||||||
|
*
|
||||||
|
* @param worldEdit the worldedit instance.
|
||||||
|
*/
|
||||||
|
public TypeSwapPatternParser(WorldEdit worldEdit) {
|
||||||
|
super(worldEdit, "#typeswap", "#ts", "#swaptype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getSuggestions(String argumentInput, int index) {
|
||||||
|
if (index > 2) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
return SUGGESTIONS.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pattern parseFromInput(@Nonnull String[] input, ParserContext context) throws InputParseException {
|
||||||
|
if (input.length != 2) {
|
||||||
|
throw new InputParseException(Caption.of(
|
||||||
|
"fawe.error.command.syntax",
|
||||||
|
TextComponent.of(getPrefix() + "[input][output] (e.g. " + getPrefix() + "[spruce][oak])")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return new TypeSwapPattern(
|
||||||
|
context.requireExtent(),
|
||||||
|
input[0],
|
||||||
|
input[1],
|
||||||
|
Permission.hasPermission(context.requireActor(), "fawe.pattern.typeswap.regex")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,38 +6,46 @@ import com.fastasyncworldedit.core.queue.IChunkGet;
|
|||||||
import com.fastasyncworldedit.core.queue.IChunkSet;
|
import com.fastasyncworldedit.core.queue.IChunkSet;
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processor that removes existing entities that would not be in air after the edit
|
* Processor that removes existing entities that would not be in air after the edit
|
||||||
*
|
*
|
||||||
* @since TODO
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
public class EntityInBlockRemovingProcessor implements IBatchProcessor {
|
public class EntityInBlockRemovingProcessor implements IBatchProcessor {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
|
public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
|
||||||
for (CompoundTag tag : get.getEntities()) {
|
try {
|
||||||
// Empty tags for seemingly non-existent entities can exist?
|
for (CompoundTag tag : get.getEntities()) {
|
||||||
if (tag.getList("Pos").size() == 0) {
|
// Empty tags for seemingly non-existent entities can exist?
|
||||||
continue;
|
if (tag.getList("Pos").size() == 0) {
|
||||||
}
|
continue;
|
||||||
BlockVector3 pos = tag.getEntityPosition().toBlockPoint();
|
}
|
||||||
int x = pos.getX() & 15;
|
BlockVector3 pos = tag.getEntityPosition().toBlockPoint();
|
||||||
int y = pos.getY();
|
int x = pos.getX() & 15;
|
||||||
int z = pos.getZ() & 15;
|
int y = pos.getY();
|
||||||
if (!set.hasSection(y >> 4)) {
|
int z = pos.getZ() & 15;
|
||||||
continue;
|
if (!set.hasSection(y >> 4)) {
|
||||||
}
|
continue;
|
||||||
if (set.getBlock(x, y, z).getBlockType() != BlockTypes.__RESERVED__ && !set
|
}
|
||||||
.getBlock(x, y, z)
|
if (set.getBlock(x, y, z).getBlockType() != BlockTypes.__RESERVED__ && !set
|
||||||
.getBlockType()
|
.getBlock(x, y, z)
|
||||||
.getMaterial()
|
.getBlockType()
|
||||||
.isAir()) {
|
.getMaterial()
|
||||||
set.removeEntity(tag.getUUID());
|
.isAir()) {
|
||||||
|
set.removeEntity(tag.getUUID());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.warn("Could not remove entities in blocks in chunk {},{}", chunk.getX(), chunk.getZ(), e);
|
||||||
}
|
}
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import com.sk89q.worldedit.function.mask.AbstractExtentMask;
|
|||||||
import com.sk89q.worldedit.function.mask.Mask;
|
import com.sk89q.worldedit.function.mask.Mask;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class IdMask extends AbstractExtentMask implements ResettableMask {
|
public class IdMask extends AbstractExtentMask implements ResettableMask {
|
||||||
|
|
||||||
private transient int id = -1;
|
private final AtomicInteger id = new AtomicInteger(-1);
|
||||||
|
|
||||||
public IdMask(Extent extent) {
|
public IdMask(Extent extent) {
|
||||||
super(extent);
|
super(extent);
|
||||||
@ -15,12 +17,9 @@ public class IdMask extends AbstractExtentMask implements ResettableMask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(Extent extent, BlockVector3 vector) {
|
public boolean test(Extent extent, BlockVector3 vector) {
|
||||||
if (id != -1) {
|
int blockID = extent.getBlock(vector).getInternalBlockTypeId();
|
||||||
return extent.getBlock(vector).getInternalBlockTypeId() == id;
|
int testId = id.compareAndExchange(-1, blockID);
|
||||||
} else {
|
return blockID == testId || testId == -1;
|
||||||
id = extent.getBlock(vector).getInternalBlockTypeId();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -30,12 +29,12 @@ public class IdMask extends AbstractExtentMask implements ResettableMask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
this.id = -1;
|
this.id.set(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mask copy() {
|
public Mask copy() {
|
||||||
return new IdMask(getExtent());
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
package com.fastasyncworldedit.core.function.pattern;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.extent.filter.block.FilterBlock;
|
||||||
|
import com.fastasyncworldedit.core.util.StringMan;
|
||||||
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockType;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern that replaces blocks based on their ID, matching for an "input" and replacing with an "output" string. The "input"
|
||||||
|
* string may be regex. Keeps as much of the block state as possible, excluding NBT data.
|
||||||
|
*
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public class TypeSwapPattern extends AbstractExtentPattern {
|
||||||
|
|
||||||
|
private static final Pattern SPLITTER = Pattern.compile("[|,]");
|
||||||
|
|
||||||
|
private final String inputString;
|
||||||
|
private final String outputString;
|
||||||
|
private final String[] inputs;
|
||||||
|
private Pattern inputPattern = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance
|
||||||
|
*
|
||||||
|
* @param extent extent to use
|
||||||
|
* @param inputString string to replace. May be regex.
|
||||||
|
* @param outputString string to replace with
|
||||||
|
* @param allowRegex if regex should be allowed for input string matching
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public TypeSwapPattern(Extent extent, String inputString, String outputString, boolean allowRegex) {
|
||||||
|
super(extent);
|
||||||
|
this.inputString = inputString;
|
||||||
|
this.outputString = outputString;
|
||||||
|
if (!StringMan.isAlphanumericUnd(inputString)) {
|
||||||
|
if (allowRegex) {
|
||||||
|
this.inputPattern = Pattern.compile(inputString.replace(",", "|"));
|
||||||
|
inputs = null;
|
||||||
|
} else {
|
||||||
|
inputs = SPLITTER.split(inputString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputs = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
|
||||||
|
BlockState existing = get.getBlock(extent);
|
||||||
|
BlockState newBlock = getNewBlock(existing);
|
||||||
|
if (newBlock == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return set.setBlock(extent, newBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyBlock(final FilterBlock block) {
|
||||||
|
BlockState existing = block.getBlock();
|
||||||
|
BlockState newState = getNewBlock(existing);
|
||||||
|
if (newState != null) {
|
||||||
|
block.setBlock(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseBlock applyBlock(final BlockVector3 position) {
|
||||||
|
BaseBlock existing = position.getFullBlock(getExtent());
|
||||||
|
BlockState newState = getNewBlock(existing.toBlockState());
|
||||||
|
return newState == null ? existing : newState.toBaseBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockState getNewBlock(BlockState existing) {
|
||||||
|
String oldId = existing.getBlockType().getId();
|
||||||
|
String newId = oldId;
|
||||||
|
if (inputPattern != null) {
|
||||||
|
newId = inputPattern.matcher(oldId).replaceAll(outputString);
|
||||||
|
} else if (inputs != null && inputs.length > 0) {
|
||||||
|
for (String input : inputs) {
|
||||||
|
newId = newId.replace(input, outputString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newId = oldId.replace(inputString, outputString);
|
||||||
|
}
|
||||||
|
if (newId.equals(oldId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BlockType newType = BlockTypes.get(newId);
|
||||||
|
if (newType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return newType.getDefaultState().withProperties(existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -41,7 +41,7 @@ public class FaweException extends RuntimeException {
|
|||||||
* New instance of a given {@link FaweException.Type}
|
* New instance of a given {@link FaweException.Type}
|
||||||
*
|
*
|
||||||
* @param ignorable if an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent}
|
* @param ignorable if an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent}
|
||||||
* @since TODO
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
public FaweException(Component reason, Type type, boolean ignorable) {
|
public FaweException(Component reason, Type type, boolean ignorable) {
|
||||||
this.message = reason;
|
this.message = reason;
|
||||||
@ -70,7 +70,7 @@ public class FaweException extends RuntimeException {
|
|||||||
/**
|
/**
|
||||||
* If an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent}
|
* If an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent}
|
||||||
*
|
*
|
||||||
* @since TODO
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
public boolean ignorable() {
|
public boolean ignorable() {
|
||||||
return ignorable;
|
return ignorable;
|
||||||
|
@ -96,7 +96,7 @@ public interface IBatchProcessor {
|
|||||||
}
|
}
|
||||||
for (int layer = maxLayer; layer < set.getMaxSectionPosition(); layer++) {
|
for (int layer = maxLayer; layer < set.getMaxSectionPosition(); layer++) {
|
||||||
if (set.hasSection(layer)) {
|
if (set.hasSection(layer)) {
|
||||||
if (layer == minLayer) {
|
if (layer == maxLayer) {
|
||||||
char[] arr = set.loadIfPresent(layer);
|
char[] arr = set.loadIfPresent(layer);
|
||||||
if (arr != null) {
|
if (arr != null) {
|
||||||
int index = ((maxY + 1) & 15) << 8;
|
int index = ((maxY + 1) & 15) << 8;
|
||||||
|
@ -2,9 +2,7 @@ package com.fastasyncworldedit.core.queue;
|
|||||||
|
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
import com.sk89q.jnbt.DoubleTag;
|
import com.sk89q.jnbt.DoubleTag;
|
||||||
import com.sk89q.jnbt.IntArrayTag;
|
|
||||||
import com.sk89q.jnbt.ListTag;
|
import com.sk89q.jnbt.ListTag;
|
||||||
import com.sk89q.jnbt.LongTag;
|
|
||||||
import com.sk89q.jnbt.NBTUtils;
|
import com.sk89q.jnbt.NBTUtils;
|
||||||
import com.sk89q.jnbt.StringTag;
|
import com.sk89q.jnbt.StringTag;
|
||||||
import com.sk89q.jnbt.Tag;
|
import com.sk89q.jnbt.Tag;
|
||||||
|
@ -27,6 +27,7 @@ import java.util.Queue;
|
|||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
import java.util.concurrent.ForkJoinTask;
|
import java.util.concurrent.ForkJoinTask;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@ -52,6 +53,7 @@ public abstract class QueueHandler implements Trimable, Runnable {
|
|||||||
null,
|
null,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the
|
* Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the
|
||||||
* primary queue. They may be IO-bound tasks.
|
* primary queue. They may be IO-bound tasks.
|
||||||
@ -508,4 +510,28 @@ public abstract class QueueHandler implements Trimable, Runnable {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary queue should be used for tasks that are unlikely to wait on other tasks, IO, etc. (i.e. spend most of their
|
||||||
|
* time utilising CPU.
|
||||||
|
* <p>
|
||||||
|
* Internal API usage only.
|
||||||
|
*
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public ExecutorService getForkJoinPoolPrimary() {
|
||||||
|
return forkJoinPoolPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the
|
||||||
|
* primary queue. They may be IO-bound tasks.
|
||||||
|
* <p>
|
||||||
|
* Internal API usage only.
|
||||||
|
*
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public ExecutorService getForkJoinPoolSecondary() {
|
||||||
|
return forkJoinPoolSecondary;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -83,17 +83,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
this.maxY = maxY;
|
this.maxY = maxY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Safety check to ensure that the thread being used matches the one being initialized on. - Can
|
|
||||||
* be removed later
|
|
||||||
*/
|
|
||||||
private void checkThread() {
|
|
||||||
if (Thread.currentThread() != currentThread && currentThread != null) {
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"This class must be used from a single thread. Use multiple queues for concurrent operations");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableQueue() {
|
public void enableQueue() {
|
||||||
enabledQueue = true;
|
enabledQueue = true;
|
||||||
@ -154,10 +143,10 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.chunks.isEmpty()) {
|
if (!this.chunks.isEmpty()) {
|
||||||
|
getChunkLock.lock();
|
||||||
for (IChunk chunk : this.chunks.values()) {
|
for (IChunk chunk : this.chunks.values()) {
|
||||||
chunk.recycle();
|
chunk.recycle();
|
||||||
}
|
}
|
||||||
getChunkLock.lock();
|
|
||||||
this.chunks.clear();
|
this.chunks.clear();
|
||||||
getChunkLock.unlock();
|
getChunkLock.unlock();
|
||||||
}
|
}
|
||||||
@ -233,9 +222,21 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
*/
|
*/
|
||||||
private <V extends Future<V>> V submitUnchecked(IQueueChunk chunk) {
|
private <V extends Future<V>> V submitUnchecked(IQueueChunk chunk) {
|
||||||
if (chunk.isEmpty()) {
|
if (chunk.isEmpty()) {
|
||||||
chunk.recycle();
|
if (chunk instanceof ChunkHolder<?> holder) {
|
||||||
Future result = Futures.immediateFuture(null);
|
long age = holder.initAge();
|
||||||
return (V) result;
|
// Ensure we've given time for the chunk to be used - it was likely used for a reason!
|
||||||
|
if (age < 5) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(5 - age);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chunk.isEmpty()) {
|
||||||
|
chunk.recycle();
|
||||||
|
Future result = Futures.immediateFuture(null);
|
||||||
|
return (V) result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Fawe.isMainThread()) {
|
if (Fawe.isMainThread()) {
|
||||||
@ -451,6 +452,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void flush() {
|
public synchronized void flush() {
|
||||||
if (!chunks.isEmpty()) {
|
if (!chunks.isEmpty()) {
|
||||||
|
getChunkLock.lock();
|
||||||
if (MemUtil.isMemoryLimited()) {
|
if (MemUtil.isMemoryLimited()) {
|
||||||
for (IQueueChunk chunk : chunks.values()) {
|
for (IQueueChunk chunk : chunks.values()) {
|
||||||
final Future future = submitUnchecked(chunk);
|
final Future future = submitUnchecked(chunk);
|
||||||
@ -467,7 +469,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getChunkLock.lock();
|
|
||||||
chunks.clear();
|
chunks.clear();
|
||||||
getChunkLock.unlock();
|
getChunkLock.unlock();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.fastasyncworldedit.core.queue.implementation.chunk;
|
package com.fastasyncworldedit.core.queue.implementation.chunk;
|
||||||
|
|
||||||
import com.fastasyncworldedit.core.FaweCache;
|
import com.fastasyncworldedit.core.FaweCache;
|
||||||
import com.fastasyncworldedit.core.concurrent.ReentrantWrappedStampedLock;
|
|
||||||
import com.fastasyncworldedit.core.configuration.Settings;
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
|
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
|
||||||
import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor;
|
import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor;
|
||||||
@ -27,6 +26,8 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract {@link IChunk} class that implements basic get/set blocks.
|
* An abstract {@link IChunk} class that implements basic get/set blocks.
|
||||||
@ -44,7 +45,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
return POOL.poll();
|
return POOL.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ReentrantWrappedStampedLock calledLock = new ReentrantWrappedStampedLock();
|
private final Lock calledLock = new ReentrantLock();
|
||||||
|
|
||||||
private volatile IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes)
|
private volatile IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes)
|
||||||
private volatile IChunkSet chunkSet; // The blocks to be set to the chunkExisting
|
private volatile IChunkSet chunkSet; // The blocks to be set to the chunkExisting
|
||||||
@ -56,6 +57,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
private int bitMask = -1; // Allow forceful setting of bitmask (for lighting)
|
private int bitMask = -1; // Allow forceful setting of bitmask (for lighting)
|
||||||
private boolean isInit = false; // Lighting handles queue differently. It relies on the chunk cache and not doing init.
|
private boolean isInit = false; // Lighting handles queue differently. It relies on the chunk cache and not doing init.
|
||||||
private boolean createCopy = false;
|
private boolean createCopy = false;
|
||||||
|
private long initTime = -1L;
|
||||||
|
|
||||||
private ChunkHolder() {
|
private ChunkHolder() {
|
||||||
this.delegate = NULL;
|
this.delegate = NULL;
|
||||||
@ -67,6 +69,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void recycle() {
|
public synchronized void recycle() {
|
||||||
|
calledLock.lock();
|
||||||
delegate = NULL;
|
delegate = NULL;
|
||||||
if (chunkSet != null) {
|
if (chunkSet != null) {
|
||||||
chunkSet.recycle();
|
chunkSet.recycle();
|
||||||
@ -75,6 +78,11 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
chunkExisting = null;
|
chunkExisting = null;
|
||||||
extent = null;
|
extent = null;
|
||||||
POOL.offer(this);
|
POOL.offer(this);
|
||||||
|
calledLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long initAge() {
|
||||||
|
return System.currentTimeMillis() - initTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized IBlockDelegate getDelegate() {
|
public synchronized IBlockDelegate getDelegate() {
|
||||||
@ -85,10 +93,10 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
* If the chunk is currently being "called", this method will block until completed.
|
* If the chunk is currently being "called", this method will block until completed.
|
||||||
*/
|
*/
|
||||||
private void checkAndWaitOnCalledLock() {
|
private void checkAndWaitOnCalledLock() {
|
||||||
if (calledLock.isLocked()) {
|
if (!calledLock.tryLock()) {
|
||||||
calledLock.lock();
|
calledLock.lock();
|
||||||
calledLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
calledLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1031,6 +1039,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized <V extends IChunk> void init(IQueueExtent<V> extent, int chunkX, int chunkZ) {
|
public synchronized <V extends IChunk> void init(IQueueExtent<V> extent, int chunkX, int chunkZ) {
|
||||||
|
this.initTime = System.currentTimeMillis();
|
||||||
this.extent = extent;
|
this.extent = extent;
|
||||||
this.chunkX = chunkX;
|
this.chunkX = chunkX;
|
||||||
this.chunkZ = chunkZ;
|
this.chunkZ = chunkZ;
|
||||||
@ -1047,14 +1056,15 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized T call() {
|
public synchronized T call() {
|
||||||
calledLock.lock();
|
calledLock.lock();
|
||||||
final long stamp = calledLock.getStampChecked();
|
|
||||||
if (chunkSet != null && !chunkSet.isEmpty()) {
|
if (chunkSet != null && !chunkSet.isEmpty()) {
|
||||||
this.delegate = GET;
|
this.delegate = GET;
|
||||||
chunkSet.setBitMask(bitMask);
|
chunkSet.setBitMask(bitMask);
|
||||||
try {
|
try {
|
||||||
IChunkSet copy = chunkSet.createCopy();
|
IChunkSet copy = chunkSet.createCopy();
|
||||||
chunkSet = null;
|
chunkSet = null;
|
||||||
return this.call(copy, () -> calledLock.unlock(stamp));
|
return this.call(copy, () -> {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
calledLock.unlock();
|
calledLock.unlock();
|
||||||
throw t;
|
throw t;
|
||||||
@ -1079,6 +1089,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
} else {
|
} else {
|
||||||
finalizer = finalize;
|
finalizer = finalize;
|
||||||
}
|
}
|
||||||
|
calledLock.unlock();
|
||||||
return get.call(set, finalizer);
|
return get.call(set, finalizer);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -38,7 +38,7 @@ public class FaweMask implements IDelegateRegion {
|
|||||||
* @param type type of mask
|
* @param type type of mask
|
||||||
* @param notify if the player should be notified
|
* @param notify if the player should be notified
|
||||||
* @return if still valid
|
* @return if still valid
|
||||||
* @since TODO
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
public boolean isValid(Player player, FaweMaskManager.MaskType type, boolean notify) {
|
public boolean isValid(Player player, FaweMaskManager.MaskType type, boolean notify) {
|
||||||
return isValid(player, type);
|
return isValid(player, type);
|
||||||
|
@ -30,7 +30,7 @@ public abstract class FaweMaskManager {
|
|||||||
/**
|
/**
|
||||||
* Get a {@link FaweMask} for the given player and {@link MaskType}. If isWhitelist is false, will return a "blacklist" mask.
|
* Get a {@link FaweMask} for the given player and {@link MaskType}. If isWhitelist is false, will return a "blacklist" mask.
|
||||||
*
|
*
|
||||||
* @since TODO
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
public FaweMask getMask(final Player player, MaskType type, boolean isWhitelist, boolean notify) {
|
public FaweMask getMask(final Player player, MaskType type, boolean isWhitelist, boolean notify) {
|
||||||
return getMask(player, type, isWhitelist);
|
return getMask(player, type, isWhitelist);
|
||||||
|
@ -4,6 +4,7 @@ import com.sk89q.worldedit.extent.AbstractDelegateExtent;
|
|||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
public class ExtentTraverser<T extends Extent> {
|
public class ExtentTraverser<T extends Extent> {
|
||||||
@ -24,6 +25,7 @@ public class ExtentTraverser<T extends Extent> {
|
|||||||
return root != null;
|
return root != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public T get() {
|
public T get() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@ -49,9 +51,11 @@ public class ExtentTraverser<T extends Extent> {
|
|||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <U extends Extent> U findAndGet(Class<U> clazz) {
|
@SuppressWarnings("unchecked")
|
||||||
ExtentTraverser<U> traverser = find(clazz);
|
@Nullable
|
||||||
return (traverser != null) ? traverser.get() : null;
|
public <U> U findAndGet(Class<U> clazz) {
|
||||||
|
ExtentTraverser<Extent> traverser = find(clazz);
|
||||||
|
return (traverser != null) ? (U) traverser.get() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.fastasyncworldedit.core.util;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor;
|
||||||
|
import com.fastasyncworldedit.core.queue.IBatchProcessor;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
public class ProcessorTraverser<T extends IBatchProcessor> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private final T root;
|
||||||
|
|
||||||
|
public ProcessorTraverser(@Nonnull T root) {
|
||||||
|
this.root = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <U extends IBatchProcessor> @Nullable U find(Class<U> clazz) {
|
||||||
|
try {
|
||||||
|
Queue<IBatchProcessor> processors = new ArrayDeque<>();
|
||||||
|
IBatchProcessor processor = root;
|
||||||
|
do {
|
||||||
|
if (clazz.isAssignableFrom(processor.getClass())) {
|
||||||
|
return clazz.cast(processor);
|
||||||
|
} else if (processor instanceof MultiBatchProcessor multiProcessor) {
|
||||||
|
processors.addAll(multiProcessor.getBatchProcessors());
|
||||||
|
} else if (processor instanceof BatchProcessorHolder holder) {
|
||||||
|
processors.add(holder.getProcessor());
|
||||||
|
}
|
||||||
|
} while ((processor = processors.poll()) != null);
|
||||||
|
return null;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOGGER.error("Error traversing processors", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,19 +4,19 @@ import sun.misc.Unsafe;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
import java.lang.reflect.AccessibleObject;
|
import java.lang.reflect.AccessibleObject;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an internal class not meant to be used outside the FAWE internals.
|
* This is an internal class not meant to be used outside the FAWE internals.
|
||||||
*/
|
*/
|
||||||
public class ReflectionUtils {
|
public class ReflectionUtils {
|
||||||
|
|
||||||
|
private static final VarHandle REFERENCE_ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(Object[].class);
|
||||||
private static Unsafe UNSAFE;
|
private static Unsafe UNSAFE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -33,6 +33,21 @@ public class ReflectionUtils {
|
|||||||
return t.isInstance(o) ? t.cast(o) : null;
|
return t.isInstance(o) ? t.cast(o) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs CAS on the array element at the given index.
|
||||||
|
*
|
||||||
|
* @param array the array in which to compare and set the value
|
||||||
|
* @param expectedValue the value expected to be at the index
|
||||||
|
* @param newValue the new value to be set at the index if the expected value matches
|
||||||
|
* @param index the index at which to compare and set the value
|
||||||
|
* @param <T> the type of elements in the array
|
||||||
|
* @return true if the value at the index was successfully updated to the new value, false otherwise
|
||||||
|
* @see VarHandle#compareAndSet(Object...)
|
||||||
|
*/
|
||||||
|
public static <T> boolean compareAndSet(T[] array, T expectedValue, T newValue, int index) {
|
||||||
|
return REFERENCE_ARRAY_HANDLE.compareAndSet(array, index, expectedValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setAccessibleNonFinal(Field field) {
|
public static void setAccessibleNonFinal(Field field) {
|
||||||
// let's make the field accessible
|
// let's make the field accessible
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
|
@ -91,7 +91,7 @@ public abstract class TaskManager {
|
|||||||
*
|
*
|
||||||
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true, since = "TODO")
|
@Deprecated(forRemoval = true, since = "2.7.0")
|
||||||
public void parallel(Collection<Runnable> runables) {
|
public void parallel(Collection<Runnable> runables) {
|
||||||
for (Runnable run : runables) {
|
for (Runnable run : runables) {
|
||||||
pool.submit(run);
|
pool.submit(run);
|
||||||
@ -106,7 +106,7 @@ public abstract class TaskManager {
|
|||||||
* @param numThreads number of threads (null = config.yml parallel threads)
|
* @param numThreads number of threads (null = config.yml parallel threads)
|
||||||
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true, since = "TODO")
|
@Deprecated(forRemoval = true, since = "2.7.0")
|
||||||
public void parallel(Collection<Runnable> runnables, @Nullable Integer numThreads) {
|
public void parallel(Collection<Runnable> runnables, @Nullable Integer numThreads) {
|
||||||
if (runnables == null) {
|
if (runnables == null) {
|
||||||
return;
|
return;
|
||||||
@ -278,7 +278,7 @@ public abstract class TaskManager {
|
|||||||
/**
|
/**
|
||||||
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true, since = "TODO")
|
@Deprecated(forRemoval = true, since = "2.7.0")
|
||||||
public void wait(AtomicBoolean running, int timeout) {
|
public void wait(AtomicBoolean running, int timeout) {
|
||||||
try {
|
try {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
@ -299,7 +299,7 @@ public abstract class TaskManager {
|
|||||||
/**
|
/**
|
||||||
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true, since = "TODO")
|
@Deprecated(forRemoval = true, since = "2.7.0")
|
||||||
public void notify(AtomicBoolean running) {
|
public void notify(AtomicBoolean running) {
|
||||||
running.set(false);
|
running.set(false);
|
||||||
synchronized (running) {
|
synchronized (running) {
|
||||||
|
@ -59,7 +59,7 @@ public class UpdateNotification {
|
|||||||
Document doc = db.parse(body);
|
Document doc = db.parse(body);
|
||||||
faweVersion = doc.getElementsByTagName("lastSuccessfulBuild").item(0).getFirstChild().getTextContent();
|
faweVersion = doc.getElementsByTagName("lastSuccessfulBuild").item(0).getFirstChild().getTextContent();
|
||||||
FaweVersion faweVersion = Fawe.instance().getVersion();
|
FaweVersion faweVersion = Fawe.instance().getVersion();
|
||||||
if (faweVersion.build == 0) {
|
if (faweVersion.build == 0 && !faweVersion.snapshot) {
|
||||||
LOGGER.warn("You are using a snapshot or a custom version of FAWE. This is not an official build distributed " +
|
LOGGER.warn("You are using a snapshot or a custom version of FAWE. This is not an official build distributed " +
|
||||||
"via https://www.spigotmc.org/resources/13932/");
|
"via https://www.spigotmc.org/resources/13932/");
|
||||||
return;
|
return;
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.fastasyncworldedit.core.util.task;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* async queue that accepts a {@link Thread.UncaughtExceptionHandler} for exception handling per instance, delegating to a
|
||||||
|
* parent {@link KeyQueuedExecutorService}.
|
||||||
|
*
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public class AsyncNotifyKeyedQueue implements Closeable {
|
||||||
|
|
||||||
|
private static final KeyQueuedExecutorService<UUID> QUEUE_SUBMISSIONS = new KeyQueuedExecutorService<>(new ForkJoinPool(
|
||||||
|
Settings.settings().QUEUE.PARALLEL_THREADS,
|
||||||
|
new FaweForkJoinWorkerThreadFactory("AsyncNotifyKeyedQueue - %s"),
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
));
|
||||||
|
|
||||||
|
private final Thread.UncaughtExceptionHandler handler;
|
||||||
|
private final Supplier<UUID> key;
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New instance
|
||||||
|
*
|
||||||
|
* @param handler exception handler
|
||||||
|
* @param key supplier of UUID key
|
||||||
|
*/
|
||||||
|
public AsyncNotifyKeyedQueue(Thread.UncaughtExceptionHandler handler, Supplier<UUID> key) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread.UncaughtExceptionHandler getHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Future<T> run(Runnable task) {
|
||||||
|
return call(() -> {
|
||||||
|
task.run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Future<T> call(Callable<T> task) {
|
||||||
|
Future[] self = new Future[1];
|
||||||
|
Callable<T> wrapped = () -> {
|
||||||
|
if (!closed) {
|
||||||
|
try {
|
||||||
|
return task.call();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
handler.uncaughtException(Thread.currentThread(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self[0] != null) {
|
||||||
|
self[0].cancel(true);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
self[0] = QUEUE_SUBMISSIONS.submit(key.get(), wrapped);
|
||||||
|
return self[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
package com.fastasyncworldedit.core.util.task;
|
package com.fastasyncworldedit.core.util.task;
|
||||||
|
|
||||||
import com.fastasyncworldedit.core.Fawe;
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
import com.fastasyncworldedit.core.configuration.Settings;
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ForkJoinPool;
|
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@ -15,13 +11,6 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
public class AsyncNotifyQueue implements Closeable {
|
public class AsyncNotifyQueue implements Closeable {
|
||||||
|
|
||||||
private static final ForkJoinPool QUEUE_SUBMISSIONS = new ForkJoinPool(
|
|
||||||
Settings.settings().QUEUE.PARALLEL_THREADS,
|
|
||||||
new FaweForkJoinWorkerThreadFactory("AsyncNotifyQueue - %s"),
|
|
||||||
null,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
private final Lock lock = new ReentrantLock(true);
|
private final Lock lock = new ReentrantLock(true);
|
||||||
private final Thread.UncaughtExceptionHandler handler;
|
private final Thread.UncaughtExceptionHandler handler;
|
||||||
private boolean closed;
|
private boolean closed;
|
||||||
@ -56,9 +45,6 @@ public class AsyncNotifyQueue implements Closeable {
|
|||||||
return task.call();
|
return task.call();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
handler.uncaughtException(Thread.currentThread(), e);
|
handler.uncaughtException(Thread.currentThread(), e);
|
||||||
if (self[0] != null) {
|
|
||||||
self[0].cancel(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -70,7 +56,7 @@ public class AsyncNotifyQueue implements Closeable {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
self[0] = QUEUE_SUBMISSIONS.submit(wrapped);
|
self[0] = Fawe.instance().getQueueHandler().async(wrapped);
|
||||||
return self[0];
|
return self[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +98,8 @@ public class YAMLProcessor extends YAMLNode {
|
|||||||
|
|
||||||
LoaderOptions loaderOptions = new LoaderOptions();
|
LoaderOptions loaderOptions = new LoaderOptions();
|
||||||
try {
|
try {
|
||||||
|
int yamlAliasLimit = Integer.getInteger("worldedit.yaml.aliasLimit", 50);
|
||||||
|
loaderOptions.setMaxAliasesForCollections(yamlAliasLimit);
|
||||||
// 64 MB default
|
// 64 MB default
|
||||||
int yamlCodePointLimit = Integer.getInteger("worldedit.yaml.codePointLimit", 64 * 1024 * 1024);
|
int yamlCodePointLimit = Integer.getInteger("worldedit.yaml.codePointLimit", 64 * 1024 * 1024);
|
||||||
loaderOptions.setCodePointLimit(yamlCodePointLimit);
|
loaderOptions.setCodePointLimit(yamlCodePointLimit);
|
||||||
@ -105,7 +107,7 @@ public class YAMLProcessor extends YAMLNode {
|
|||||||
// pre-1.32 snakeyaml
|
// pre-1.32 snakeyaml
|
||||||
}
|
}
|
||||||
|
|
||||||
yaml = new Yaml(new SafeConstructor(new LoaderOptions()), representer, dumperOptions, loaderOptions);
|
yaml = new Yaml(new SafeConstructor(loaderOptions), representer, dumperOptions, loaderOptions);
|
||||||
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import com.fastasyncworldedit.core.extent.ResettableExtent;
|
|||||||
import com.fastasyncworldedit.core.extent.SingleRegionExtent;
|
import com.fastasyncworldedit.core.extent.SingleRegionExtent;
|
||||||
import com.fastasyncworldedit.core.extent.SourceMaskExtent;
|
import com.fastasyncworldedit.core.extent.SourceMaskExtent;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
|
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.ExtentBatchProcessorHolder;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
import com.fastasyncworldedit.core.function.SurfaceRegionFunction;
|
import com.fastasyncworldedit.core.function.SurfaceRegionFunction;
|
||||||
@ -55,6 +56,7 @@ import com.fastasyncworldedit.core.queue.implementation.preloader.Preloader;
|
|||||||
import com.fastasyncworldedit.core.util.ExtentTraverser;
|
import com.fastasyncworldedit.core.util.ExtentTraverser;
|
||||||
import com.fastasyncworldedit.core.util.MaskTraverser;
|
import com.fastasyncworldedit.core.util.MaskTraverser;
|
||||||
import com.fastasyncworldedit.core.util.MathMan;
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
|
import com.fastasyncworldedit.core.util.ProcessorTraverser;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
import com.fastasyncworldedit.core.util.collection.BlockVector3Set;
|
import com.fastasyncworldedit.core.util.collection.BlockVector3Set;
|
||||||
import com.fastasyncworldedit.core.util.task.RunnableVal;
|
import com.fastasyncworldedit.core.util.task.RunnableVal;
|
||||||
@ -524,9 +526,17 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
|||||||
* @return mask, may be null
|
* @return mask, may be null
|
||||||
*/
|
*/
|
||||||
public Mask getMask() {
|
public Mask getMask() {
|
||||||
//FAWE start - ExtendTraverser & MaskingExtents
|
//FAWE start - ExtentTraverser & MaskingExtents
|
||||||
ExtentTraverser<MaskingExtent> maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class);
|
MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class);
|
||||||
return maskingExtent != null ? maskingExtent.get().getMask() : null;
|
if (maskingExtent == null) {
|
||||||
|
ExtentBatchProcessorHolder processorExtent =
|
||||||
|
new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class);
|
||||||
|
if (processorExtent != null) {
|
||||||
|
maskingExtent =
|
||||||
|
new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maskingExtent != null ? maskingExtent.getMask() : null;
|
||||||
//FAWE end
|
//FAWE end
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,23 +619,31 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
|||||||
//FAWE start - use MaskingExtent & ExtentTraverser
|
//FAWE start - use MaskingExtent & ExtentTraverser
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a mask.
|
* Set a mask. Combines with any existing masks, set null to clear existing masks.
|
||||||
*
|
*
|
||||||
* @param mask mask or null
|
* @param mask mask or null
|
||||||
*/
|
*/
|
||||||
public void setMask(Mask mask) {
|
public void setMask(@Nullable Mask mask) {
|
||||||
if (mask == null) {
|
if (mask == null) {
|
||||||
mask = Masks.alwaysTrue();
|
mask = Masks.alwaysTrue();
|
||||||
} else {
|
} else {
|
||||||
new MaskTraverser(mask).reset(this);
|
new MaskTraverser(mask).reset(this);
|
||||||
}
|
}
|
||||||
ExtentTraverser<MaskingExtent> maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class);
|
MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class);
|
||||||
if (maskingExtent != null && maskingExtent.get() != null) {
|
if (maskingExtent == null && mask != Masks.alwaysTrue()) {
|
||||||
Mask oldMask = maskingExtent.get().getMask();
|
ExtentBatchProcessorHolder processorExtent =
|
||||||
|
new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class);
|
||||||
|
if (processorExtent != null) {
|
||||||
|
maskingExtent =
|
||||||
|
new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maskingExtent != null) {
|
||||||
|
Mask oldMask = maskingExtent.getMask();
|
||||||
if (oldMask instanceof ResettableMask) {
|
if (oldMask instanceof ResettableMask) {
|
||||||
((ResettableMask) oldMask).reset();
|
((ResettableMask) oldMask).reset();
|
||||||
}
|
}
|
||||||
maskingExtent.get().setMask(mask);
|
maskingExtent.setMask(mask);
|
||||||
} else if (mask != Masks.alwaysTrue()) {
|
} else if (mask != Masks.alwaysTrue()) {
|
||||||
addProcessor(new MaskingExtent(getExtent(), mask));
|
addProcessor(new MaskingExtent(getExtent(), mask));
|
||||||
}
|
}
|
||||||
@ -2270,6 +2288,90 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
|||||||
//FAWE end
|
//FAWE end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a cone.
|
||||||
|
*
|
||||||
|
* @param pos Center of the cone
|
||||||
|
* @param block The block pattern to use
|
||||||
|
* @param radiusX The cone's largest north/south extent
|
||||||
|
* @param radiusZ The cone's largest east/west extent
|
||||||
|
* @param height The cone's up/down extent. If negative, extend downward.
|
||||||
|
* @param filled If false, only a shell will be generated.
|
||||||
|
* @param thickness The cone's wall thickness, if it's hollow.
|
||||||
|
* @return number of blocks changed
|
||||||
|
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||||
|
*/
|
||||||
|
public int makeCone(
|
||||||
|
BlockVector3 pos,
|
||||||
|
Pattern block,
|
||||||
|
double radiusX,
|
||||||
|
double radiusZ,
|
||||||
|
int height,
|
||||||
|
boolean filled,
|
||||||
|
double thickness
|
||||||
|
) throws MaxChangedBlocksException {
|
||||||
|
int affected = 0;
|
||||||
|
|
||||||
|
final int ceilRadiusX = (int) Math.ceil(radiusX);
|
||||||
|
final int ceilRadiusZ = (int) Math.ceil(radiusZ);
|
||||||
|
|
||||||
|
double rx2 = Math.pow(radiusX, 2);
|
||||||
|
double ry2 = Math.pow(height, 2);
|
||||||
|
double rz2 = Math.pow(radiusZ, 2);
|
||||||
|
|
||||||
|
int cx = pos.getX();
|
||||||
|
int cy = pos.getY();
|
||||||
|
int cz = pos.getZ();
|
||||||
|
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
double ySquaredMinusHeightOverHeightSquared = Math.pow(y - height, 2) / ry2;
|
||||||
|
int yy = cy + y;
|
||||||
|
forX:
|
||||||
|
for (int x = 0; x <= ceilRadiusX; ++x) {
|
||||||
|
double xSquaredOverRadiusX = Math.pow(x, 2) / rx2;
|
||||||
|
int xx = cx + x;
|
||||||
|
forZ:
|
||||||
|
for (int z = 0; z <= ceilRadiusZ; ++z) {
|
||||||
|
int zz = cz + z;
|
||||||
|
double zSquaredOverRadiusZ = Math.pow(z, 2) / rz2;
|
||||||
|
double distanceFromOriginMinusHeightSquared = xSquaredOverRadiusX + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared;
|
||||||
|
|
||||||
|
if (distanceFromOriginMinusHeightSquared > 1) {
|
||||||
|
if (z == 0) {
|
||||||
|
break forX;
|
||||||
|
}
|
||||||
|
break forZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filled) {
|
||||||
|
double xNext = Math.pow(x + thickness, 2) / rx2 + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared;
|
||||||
|
double yNext = xSquaredOverRadiusX + zSquaredOverRadiusZ - Math.pow(y + thickness - height, 2) / ry2;
|
||||||
|
double zNext = xSquaredOverRadiusX + Math.pow(z + thickness, 2) / rz2 - ySquaredMinusHeightOverHeightSquared;
|
||||||
|
if (xNext <= 0 && zNext <= 0 && (yNext <= 0 && y + thickness != height)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distanceFromOriginMinusHeightSquared <= 0) {
|
||||||
|
if (setBlock(xx, yy, zz, block)) {
|
||||||
|
++affected;
|
||||||
|
}
|
||||||
|
if (setBlock(xx, yy, zz, block)) {
|
||||||
|
++affected;
|
||||||
|
}
|
||||||
|
if (setBlock(xx, yy, zz, block)) {
|
||||||
|
++affected;
|
||||||
|
}
|
||||||
|
if (setBlock(xx, yy, zz, block)) {
|
||||||
|
++affected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return affected;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the blocks in a region a certain direction.
|
* Move the blocks in a region a certain direction.
|
||||||
*
|
*
|
||||||
@ -2991,9 +3093,10 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
|||||||
} catch (ExpressionTimeoutException e) {
|
} catch (ExpressionTimeoutException e) {
|
||||||
timedOut[0] = timedOut[0] + 1;
|
timedOut[0] = timedOut[0] + 1;
|
||||||
return null;
|
return null;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.warn("Failed to create shape", e);
|
throw new RuntimeException(e);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -607,20 +607,17 @@ public final class EditSessionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FaweRegionExtent regionExtent = null;
|
FaweRegionExtent regionExtent = null;
|
||||||
if (disallowedRegions != null) { // Always use MultiRegionExtent if we have blacklist regions
|
// Always use MultiRegionExtent if we have blacklist regions
|
||||||
|
if (allowedRegions != null && allowedRegions.length == 0) {
|
||||||
|
regionExtent = new NullExtent(this.extent, FaweCache.NO_REGION);
|
||||||
|
} else if (disallowedRegions != null && disallowedRegions.length != 0) {
|
||||||
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, disallowedRegions);
|
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, disallowedRegions);
|
||||||
} else if (allowedRegions == null) {
|
} else if (allowedRegions == null) {
|
||||||
allowedRegions = new Region[]{RegionWrapper.GLOBAL()};
|
allowedRegions = new Region[]{RegionWrapper.GLOBAL()};
|
||||||
|
} else if (allowedRegions.length == 1) {
|
||||||
|
regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]);
|
||||||
} else {
|
} else {
|
||||||
if (allowedRegions.length == 0) {
|
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null);
|
||||||
regionExtent = new NullExtent(this.extent, FaweCache.NO_REGION);
|
|
||||||
} else {
|
|
||||||
if (allowedRegions.length == 1) {
|
|
||||||
regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]);
|
|
||||||
} else {
|
|
||||||
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (regionExtent != null) {
|
if (regionExtent != null) {
|
||||||
if (placeChunks) {
|
if (placeChunks) {
|
||||||
|
@ -23,8 +23,10 @@ import com.fastasyncworldedit.core.Fawe;
|
|||||||
import com.fastasyncworldedit.core.configuration.Caption;
|
import com.fastasyncworldedit.core.configuration.Caption;
|
||||||
import com.fastasyncworldedit.core.configuration.Settings;
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
import com.fastasyncworldedit.core.extent.ResettableExtent;
|
import com.fastasyncworldedit.core.extent.ResettableExtent;
|
||||||
|
import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
||||||
import com.fastasyncworldedit.core.history.DiskStorageHistory;
|
import com.fastasyncworldedit.core.history.DiskStorageHistory;
|
||||||
|
import com.fastasyncworldedit.core.internal.exception.FaweClipboardVersionMismatchException;
|
||||||
import com.fastasyncworldedit.core.internal.io.FaweInputStream;
|
import com.fastasyncworldedit.core.internal.io.FaweInputStream;
|
||||||
import com.fastasyncworldedit.core.internal.io.FaweOutputStream;
|
import com.fastasyncworldedit.core.internal.io.FaweOutputStream;
|
||||||
import com.fastasyncworldedit.core.limit.FaweLimit;
|
import com.fastasyncworldedit.core.limit.FaweLimit;
|
||||||
@ -50,6 +52,8 @@ import com.sk89q.worldedit.command.tool.Tool;
|
|||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
import com.sk89q.worldedit.extension.platform.Actor;
|
import com.sk89q.worldedit.extension.platform.Actor;
|
||||||
import com.sk89q.worldedit.extension.platform.Locatable;
|
import com.sk89q.worldedit.extension.platform.Locatable;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
import com.sk89q.worldedit.extent.inventory.BlockBag;
|
import com.sk89q.worldedit.extent.inventory.BlockBag;
|
||||||
import com.sk89q.worldedit.function.mask.Mask;
|
import com.sk89q.worldedit.function.mask.Mask;
|
||||||
import com.sk89q.worldedit.function.operation.ChangeSetExecutor;
|
import com.sk89q.worldedit.function.operation.ChangeSetExecutor;
|
||||||
@ -93,6 +97,7 @@ import java.util.ListIterator;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@ -877,6 +882,58 @@ public class LocalSession implements TextureHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a clipboard from disk and into this localsession. Synchronises with other clipboard setting/getting to and from
|
||||||
|
* this session
|
||||||
|
*
|
||||||
|
* @param file Clipboard file to load
|
||||||
|
* @throws FaweClipboardVersionMismatchException in clipboard version mismatch (between saved and internal, expected, version)
|
||||||
|
* @throws ExecutionException if the computation threw an exception
|
||||||
|
* @throws InterruptedException if the current thread was interrupted while waiting
|
||||||
|
*/
|
||||||
|
public void loadClipboardFromDisk(File file) throws FaweClipboardVersionMismatchException, ExecutionException,
|
||||||
|
InterruptedException {
|
||||||
|
synchronized (clipboardLock) {
|
||||||
|
if (file.exists() && file.length() > 5) {
|
||||||
|
try {
|
||||||
|
if (getClipboard() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (EmptyClipboardException ignored) {
|
||||||
|
}
|
||||||
|
DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit(
|
||||||
|
uuid,
|
||||||
|
() -> DiskOptimizedClipboard.loadFromFile(file)
|
||||||
|
).get();
|
||||||
|
Clipboard clip = doc.toClipboard();
|
||||||
|
ClipboardHolder holder = new ClipboardHolder(clip);
|
||||||
|
setClipboard(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteClipboardOnDisk() {
|
||||||
|
synchronized (clipboardLock) {
|
||||||
|
ClipboardHolder holder = getExistingClipboard();
|
||||||
|
if (holder != null) {
|
||||||
|
for (Clipboard clipboard : holder.getClipboards()) {
|
||||||
|
DiskOptimizedClipboard doc;
|
||||||
|
if (clipboard instanceof DiskOptimizedClipboard) {
|
||||||
|
doc = (DiskOptimizedClipboard) clipboard;
|
||||||
|
} else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) {
|
||||||
|
doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent();
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Fawe.instance().getClipboardExecutor().submit(uuid, () -> {
|
||||||
|
doc.close(); // Ensure closed before deletion
|
||||||
|
doc.getFile().delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
//FAWE end
|
//FAWE end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.command;
|
package com.sk89q.worldedit.command;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
import com.fastasyncworldedit.core.FaweAPI;
|
import com.fastasyncworldedit.core.FaweAPI;
|
||||||
import com.fastasyncworldedit.core.FaweCache;
|
import com.fastasyncworldedit.core.FaweCache;
|
||||||
import com.fastasyncworldedit.core.configuration.Caption;
|
import com.fastasyncworldedit.core.configuration.Caption;
|
||||||
@ -28,6 +29,7 @@ import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
|
|||||||
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
|
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
|
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
|
||||||
|
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
||||||
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
|
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
|
||||||
import com.fastasyncworldedit.core.limit.FaweLimit;
|
import com.fastasyncworldedit.core.limit.FaweLimit;
|
||||||
import com.fastasyncworldedit.core.util.ImgurUtility;
|
import com.fastasyncworldedit.core.util.ImgurUtility;
|
||||||
@ -160,7 +162,7 @@ public class ClipboardCommands {
|
|||||||
session.getPlacementPosition(actor));
|
session.getPlacementPosition(actor));
|
||||||
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
|
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
|
||||||
copy.setCopyingEntities(copyEntities);
|
copy.setCopyingEntities(copyEntities);
|
||||||
createCopy(session, editSession, copyBiomes, mask, clipboard, copy);
|
createCopy(actor, session, editSession, copyBiomes, mask, clipboard, copy);
|
||||||
|
|
||||||
copy.getStatusMessages().forEach(actor::print);
|
copy.getStatusMessages().forEach(actor::print);
|
||||||
//FAWE end
|
//FAWE end
|
||||||
@ -271,7 +273,7 @@ public class ClipboardCommands {
|
|||||||
copy.setSourceFunction(new BlockReplace(editSession, leavePattern));
|
copy.setSourceFunction(new BlockReplace(editSession, leavePattern));
|
||||||
copy.setCopyingEntities(copyEntities);
|
copy.setCopyingEntities(copyEntities);
|
||||||
copy.setRemovingEntities(true);
|
copy.setRemovingEntities(true);
|
||||||
createCopy(session, editSession, copyBiomes, mask, clipboard, copy);
|
createCopy(actor, session, editSession, copyBiomes, mask, clipboard, copy);
|
||||||
|
|
||||||
if (!actor.hasPermission("fawe.tips")) {
|
if (!actor.hasPermission("fawe.tips")) {
|
||||||
actor.print(Caption.of("fawe.tips.tip.lazycut"));
|
actor.print(Caption.of("fawe.tips.tip.lazycut"));
|
||||||
@ -281,6 +283,7 @@ public class ClipboardCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createCopy(
|
private void createCopy(
|
||||||
|
final Actor actor,
|
||||||
final LocalSession session,
|
final LocalSession session,
|
||||||
final EditSession editSession,
|
final EditSession editSession,
|
||||||
final boolean copyBiomes,
|
final boolean copyBiomes,
|
||||||
@ -311,9 +314,22 @@ public class ClipboardCommands {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Operations.completeLegacy(copy);
|
Operations.completeLegacy(copy);
|
||||||
} finally {
|
} catch (Exception e) {
|
||||||
clipboard.flush();
|
DiskOptimizedClipboard doc;
|
||||||
|
if (clipboard instanceof DiskOptimizedClipboard) {
|
||||||
|
doc = (DiskOptimizedClipboard) clipboard;
|
||||||
|
} else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) {
|
||||||
|
doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent();
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
Fawe.instance().getClipboardExecutor().submit(actor.getUniqueId(), () -> {
|
||||||
|
clipboard.close();
|
||||||
|
doc.getFile().delete();
|
||||||
|
});
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
clipboard.flush();
|
||||||
session.setClipboard(new ClipboardHolder(clipboard));
|
session.setClipboard(new ClipboardHolder(clipboard));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +188,49 @@ public class GenerationCommands {
|
|||||||
return affected;
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "/cone",
|
||||||
|
desc = "Generates a cone."
|
||||||
|
)
|
||||||
|
@CommandPermissions("worldedit.generation.cone")
|
||||||
|
@Logging(PLACEMENT)
|
||||||
|
public int cone(Actor actor, LocalSession session, EditSession editSession,
|
||||||
|
@Arg(desc = "The pattern of blocks to generate")
|
||||||
|
Pattern pattern,
|
||||||
|
@Arg(desc = "The radii of the cone. 1st is N/S, 2nd is E/W")
|
||||||
|
@Radii(2)
|
||||||
|
List<Double> radii,
|
||||||
|
@Arg(desc = "The height of the cone", def = "1")
|
||||||
|
int height,
|
||||||
|
@Switch(name = 'h', desc = "Make a hollow cone")
|
||||||
|
boolean hollow,
|
||||||
|
@Arg(desc = "Thickness of the hollow cone", def = "1")
|
||||||
|
double thickness
|
||||||
|
) throws WorldEditException {
|
||||||
|
double radiusX;
|
||||||
|
double radiusZ;
|
||||||
|
switch (radii.size()) {
|
||||||
|
case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0));
|
||||||
|
case 2 -> {
|
||||||
|
radiusX = Math.max(1, radii.get(0));
|
||||||
|
radiusZ = Math.max(1, radii.get(1));
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
actor.printError(Caption.of("worldedit.cone.invalid-radius"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
worldEdit.checkMaxRadius(radiusX);
|
||||||
|
worldEdit.checkMaxRadius(radiusZ);
|
||||||
|
worldEdit.checkMaxRadius(height);
|
||||||
|
|
||||||
|
BlockVector3 pos = session.getPlacementPosition(actor);
|
||||||
|
int affected = editSession.makeCone(pos, pattern, radiusX, radiusZ, height, !hollow, thickness);
|
||||||
|
actor.printInfo(Caption.of("worldedit.cone.created", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
name = "/hsphere",
|
name = "/hsphere",
|
||||||
desc = "Generates a hollow sphere."
|
desc = "Generates a hollow sphere."
|
||||||
|
@ -426,23 +426,7 @@ public interface Player extends Entity, Actor {
|
|||||||
cancel(true);
|
cancel(true);
|
||||||
LocalSession session = getSession();
|
LocalSession session = getSession();
|
||||||
if (Settings.settings().CLIPBOARD.USE_DISK && Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
|
if (Settings.settings().CLIPBOARD.USE_DISK && Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
|
||||||
ClipboardHolder holder = session.getExistingClipboard();
|
session.deleteClipboardOnDisk();
|
||||||
if (holder != null) {
|
|
||||||
for (Clipboard clipboard : holder.getClipboards()) {
|
|
||||||
DiskOptimizedClipboard doc;
|
|
||||||
if (clipboard instanceof DiskOptimizedClipboard) {
|
|
||||||
doc = (DiskOptimizedClipboard) clipboard;
|
|
||||||
} else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) {
|
|
||||||
doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent();
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> {
|
|
||||||
doc.close(); // Ensure closed before deletion
|
|
||||||
doc.getFile().delete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (Settings.settings().CLIPBOARD.USE_DISK) {
|
} else if (Settings.settings().CLIPBOARD.USE_DISK) {
|
||||||
Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null));
|
Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null));
|
||||||
} else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
|
} else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
|
||||||
@ -464,22 +448,7 @@ public interface Player extends Entity, Actor {
|
|||||||
Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd"
|
Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd"
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
if (file.exists() && file.length() > 5) {
|
getSession().loadClipboardFromDisk(file);
|
||||||
LocalSession session = getSession();
|
|
||||||
try {
|
|
||||||
if (session.getClipboard() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (EmptyClipboardException ignored) {
|
|
||||||
}
|
|
||||||
DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit(
|
|
||||||
getUniqueId(),
|
|
||||||
() -> DiskOptimizedClipboard.loadFromFile(file)
|
|
||||||
).get();
|
|
||||||
Clipboard clip = doc.toClipboard();
|
|
||||||
ClipboardHolder holder = new ClipboardHolder(clip);
|
|
||||||
session.setClipboard(holder);
|
|
||||||
}
|
|
||||||
} catch (FaweClipboardVersionMismatchException e) {
|
} catch (FaweClipboardVersionMismatchException e) {
|
||||||
print(e.getComponent());
|
print(e.getComponent());
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
@ -42,6 +42,7 @@ import com.fastasyncworldedit.core.extension.factory.parser.pattern.OffsetPatter
|
|||||||
import com.fastasyncworldedit.core.extension.factory.parser.pattern.PerlinPatternParser;
|
import com.fastasyncworldedit.core.extension.factory.parser.pattern.PerlinPatternParser;
|
||||||
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomFullClipboardPatternParser;
|
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomFullClipboardPatternParser;
|
||||||
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomOffsetPatternParser;
|
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomOffsetPatternParser;
|
||||||
|
import com.fastasyncworldedit.core.extension.factory.parser.pattern.TypeSwapPatternParser;
|
||||||
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
|
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
|
||||||
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RelativePatternParser;
|
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RelativePatternParser;
|
||||||
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RichPatternParser;
|
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RichPatternParser;
|
||||||
@ -133,6 +134,7 @@ public final class PatternFactory extends AbstractFactory<Pattern> {
|
|||||||
register(new SimplexPatternParser(worldEdit));
|
register(new SimplexPatternParser(worldEdit));
|
||||||
register(new SolidRandomOffsetPatternParser(worldEdit));
|
register(new SolidRandomOffsetPatternParser(worldEdit));
|
||||||
register(new SurfaceRandomOffsetPatternParser(worldEdit));
|
register(new SurfaceRandomOffsetPatternParser(worldEdit));
|
||||||
|
register(new TypeSwapPatternParser(worldEdit));
|
||||||
register(new VoronoiPatternParser(worldEdit));
|
register(new VoronoiPatternParser(worldEdit));
|
||||||
//FAWE end
|
//FAWE end
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,9 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.extension.platform;
|
package com.sk89q.worldedit.extension.platform;
|
||||||
|
|
||||||
import com.fastasyncworldedit.core.configuration.Caption;
|
|
||||||
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue;
|
import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue;
|
||||||
import com.sk89q.worldedit.WorldEditException;
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
import com.sk89q.worldedit.internal.cui.CUIEvent;
|
import com.sk89q.worldedit.internal.cui.CUIEvent;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
@ -68,7 +67,7 @@ public abstract class AbstractNonPlayerActor implements Actor {
|
|||||||
|
|
||||||
// Queue for async tasks
|
// Queue for async tasks
|
||||||
private final AtomicInteger runningCount = new AtomicInteger();
|
private final AtomicInteger runningCount = new AtomicInteger();
|
||||||
private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue((thread, throwable) -> {
|
private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue((thread, throwable) -> {
|
||||||
while (throwable.getCause() != null) {
|
while (throwable.getCause() != null) {
|
||||||
throwable = throwable.getCause();
|
throwable = throwable.getCause();
|
||||||
}
|
}
|
||||||
@ -82,7 +81,7 @@ public abstract class AbstractNonPlayerActor implements Actor {
|
|||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, this::getUniqueId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a task either async, or on the current thread.
|
* Run a task either async, or on the current thread.
|
||||||
|
@ -55,4 +55,9 @@ public abstract class AbstractPlatform implements Platform {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTickCount() {
|
||||||
|
return System.nanoTime() / 50_000_000;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import com.fastasyncworldedit.core.math.MutableBlockVector3;
|
|||||||
import com.fastasyncworldedit.core.regions.FaweMaskManager;
|
import com.fastasyncworldedit.core.regions.FaweMaskManager;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
import com.fastasyncworldedit.core.util.WEManager;
|
import com.fastasyncworldedit.core.util.WEManager;
|
||||||
import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue;
|
import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue;
|
||||||
import com.sk89q.worldedit.EditSession;
|
import com.sk89q.worldedit.EditSession;
|
||||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
@ -81,7 +81,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
|||||||
|
|
||||||
// Queue for async tasks
|
// Queue for async tasks
|
||||||
private final AtomicInteger runningCount = new AtomicInteger();
|
private final AtomicInteger runningCount = new AtomicInteger();
|
||||||
private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue(
|
private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue(
|
||||||
(thread, throwable) -> {
|
(thread, throwable) -> {
|
||||||
while (throwable.getCause() != null) {
|
while (throwable.getCause() != null) {
|
||||||
throwable = throwable.getCause();
|
throwable = throwable.getCause();
|
||||||
@ -96,7 +96,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
|||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, this::getUniqueId);
|
||||||
|
|
||||||
public AbstractPlayerActor(Map<String, Object> meta) {
|
public AbstractPlayerActor(Map<String, Object> meta) {
|
||||||
this.meta = meta;
|
this.meta = meta;
|
||||||
|
@ -216,6 +216,14 @@ public interface Platform extends Keyed {
|
|||||||
*/
|
*/
|
||||||
Set<SideEffect> getSupportedSideEffects();
|
Set<SideEffect> getSupportedSideEffects();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of ticks since the server started.
|
||||||
|
* On some platforms this value may be an approximation based on the JVM run time.
|
||||||
|
*
|
||||||
|
* @return The number of ticks since the server started.
|
||||||
|
*/
|
||||||
|
long getTickCount();
|
||||||
|
|
||||||
//FAWE start
|
//FAWE start
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.internal.event;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.extension.platform.Platform;
|
||||||
|
import com.sk89q.worldedit.util.Identifiable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class InteractionDebouncer {
|
||||||
|
private final Platform platform;
|
||||||
|
private final Map<UUID, Interaction> lastInteractions = new HashMap<>();
|
||||||
|
|
||||||
|
public InteractionDebouncer(Platform platform) {
|
||||||
|
this.platform = platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearInteraction(Identifiable player) {
|
||||||
|
lastInteractions.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastInteraction(Identifiable player, boolean result) {
|
||||||
|
lastInteractions.put(player.getUniqueId(), new Interaction(platform.getTickCount(), result));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Boolean> getDuplicateInteractionResult(Identifiable player) {
|
||||||
|
Interaction last = lastInteractions.get(player.getUniqueId());
|
||||||
|
if (last == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = platform.getTickCount();
|
||||||
|
if (now - last.tick <= 1) {
|
||||||
|
return Optional.of(last.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Interaction {
|
||||||
|
public final long tick;
|
||||||
|
public final boolean result;
|
||||||
|
|
||||||
|
public Interaction(long tick, boolean result) {
|
||||||
|
this.tick = tick;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -86,7 +86,7 @@ public class BlockType implements Keyed, Pattern {
|
|||||||
* a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use
|
* a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use
|
||||||
* {@link BlockTypes#get(String)} instead.
|
* {@link BlockTypes#get(String)} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated(since = "TODO")
|
@Deprecated(since = "2.7.0")
|
||||||
//FAWE end
|
//FAWE end
|
||||||
public BlockType(String id) {
|
public BlockType(String id) {
|
||||||
this(id, null);
|
this(id, null);
|
||||||
@ -98,7 +98,7 @@ public class BlockType implements Keyed, Pattern {
|
|||||||
* a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use
|
* a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use
|
||||||
* {@link BlockTypes#get(String)} instead.
|
* {@link BlockTypes#get(String)} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated(since = "TODO")
|
@Deprecated(since = "2.7.0")
|
||||||
//FAWE end
|
//FAWE end
|
||||||
public BlockType(String id, Function<BlockState, BlockState> values) {
|
public BlockType(String id, Function<BlockState, BlockState> values) {
|
||||||
// If it has no namespace, assume minecraft.
|
// If it has no namespace, assume minecraft.
|
||||||
|
@ -489,6 +489,8 @@
|
|||||||
"worldedit.jumpto.none": "No block in sight (or too far away)!",
|
"worldedit.jumpto.none": "No block in sight (or too far away)!",
|
||||||
"worldedit.up.obstructed": "You would hit something above you.",
|
"worldedit.up.obstructed": "You would hit something above you.",
|
||||||
"worldedit.up.moved": "Woosh!",
|
"worldedit.up.moved": "Woosh!",
|
||||||
|
"worldedit.cone.invalid-radius": "You must either specify 1 or 2 radius values.",
|
||||||
|
"worldedit.cone.created": "{0} blocks have been created.",
|
||||||
"worldedit.cyl.invalid-radius": "You must either specify 1 or 2 radius values.",
|
"worldedit.cyl.invalid-radius": "You must either specify 1 or 2 radius values.",
|
||||||
"worldedit.cyl.created": "{0} blocks have been created.",
|
"worldedit.cyl.created": "{0} blocks have been created.",
|
||||||
"worldedit.hcyl.thickness-too-large": "Thickness cannot be larger than x or z radii.",
|
"worldedit.hcyl.thickness-too-large": "Thickness cannot be larger than x or z radii.",
|
||||||
|
@ -28,7 +28,7 @@ dependencies {
|
|||||||
})
|
})
|
||||||
api("org.apache.logging.log4j:log4j-api")
|
api("org.apache.logging.log4j:log4j-api")
|
||||||
api("org.bstats:bstats-sponge:1.7")
|
api("org.bstats:bstats-sponge:1.7")
|
||||||
testImplementation("org.mockito:mockito-core:5.4.0")
|
testImplementation("org.mockito:mockito-core:5.5.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren