3
0
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:
Jordan 2023-10-01 19:53:43 +01:00 committet von GitHub
Commit d987643378
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
97 geänderte Dateien mit 9755 neuen und 671 gelöschten Zeilen

Datei anzeigen

@ -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
Datei anzeigen

@ -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",

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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:

Datei anzeigen

@ -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

Datei anzeigen

@ -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())

Datei anzeigen

@ -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") {

Datei anzeigen

@ -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

Datei anzeigen

@ -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äre Datei nicht angezeigt.

Datei anzeigen

@ -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
Datei anzeigen

@ -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

Datei anzeigen

@ -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")
} }

Datei anzeigen

@ -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)
} }

Datei anzeigen

@ -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();

Datei anzeigen

@ -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,

Datei anzeigen

@ -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)
} }

Datei anzeigen

@ -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();

Datei anzeigen

@ -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,

Datei anzeigen

@ -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)
} }

Datei anzeigen

@ -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) {

Datei anzeigen

@ -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,

Datei anzeigen

@ -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)
} }

Datei anzeigen

@ -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;

Datei anzeigen

@ -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) {

Datei anzeigen

@ -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,

Datei anzeigen

@ -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 anzeigen

@ -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) {
}
}

Datei anzeigen

@ -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() {
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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 anzeigen

@ -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;
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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();
}
}
}

Datei anzeigen

@ -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)
);
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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);
}
}
}

Datei anzeigen

@ -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}")

Datei anzeigen

@ -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/");
} }
} }

Datei anzeigen

@ -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) {

Datei anzeigen

@ -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)

Datei anzeigen

@ -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;

Datei anzeigen

@ -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);
} }

Datei anzeigen

@ -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
/* /*

Datei anzeigen

@ -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

Datei anzeigen

@ -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();
} }

Datei anzeigen

@ -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());
Action action = event.getAction();
if (action == Action.LEFT_CLICK_BLOCK) {
final Block clickedBlock = event.getClickedBlock(); final Block clickedBlock = event.getClickedBlock();
final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ()); final Location pos = clickedBlock == null ? null : new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ());
if (we.handleBlockLeftClick(player, pos, direction)) { boolean result = false;
event.setCancelled(true); switch (event.getAction()) {
case LEFT_CLICK_BLOCK:
result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player);
break;
case LEFT_CLICK_AIR:
result = we.handleArmSwing(player);
break;
case RIGHT_CLICK_BLOCK:
result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player);
break;
case RIGHT_CLICK_AIR:
result = we.handleRightClick(player);
break;
default:
break;
} }
debouncer.setLastInteraction(player, result);
if (we.handleArmSwing(player)) { if (result) {
event.setCancelled(true); event.setCancelled(true);
} }
} else if (action == Action.LEFT_CLICK_AIR) {
if (we.handleArmSwing(player)) {
event.setCancelled(true);
}
} else if (action == Action.RIGHT_CLICK_BLOCK) {
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())));
} }

Datei anzeigen

@ -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);

Datei anzeigen

@ -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;
}
} }

Datei anzeigen

@ -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)
} }

Datei anzeigen

@ -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 }

Datei anzeigen

@ -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 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))) 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()));
} }

Datei anzeigen

@ -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;
} }

Datei anzeigen

@ -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
}
}

Datei anzeigen

@ -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]\"",

Datei anzeigen

@ -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")
);
}
}

Datei anzeigen

@ -6,19 +6,24 @@ 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) {
try {
for (CompoundTag tag : get.getEntities()) { for (CompoundTag tag : get.getEntities()) {
// Empty tags for seemingly non-existent entities can exist? // Empty tags for seemingly non-existent entities can exist?
if (tag.getList("Pos").size() == 0) { if (tag.getList("Pos").size() == 0) {
@ -39,6 +44,9 @@ public class EntityInBlockRemovingProcessor implements IBatchProcessor {
set.removeEntity(tag.getUUID()); 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;
} }

Datei anzeigen

@ -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

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -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;

Datei anzeigen

@ -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;

Datei anzeigen

@ -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;
}
} }

Datei anzeigen

@ -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();
} }
@ -232,11 +221,23 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
* Submit without first checking that it has been removed from the chunk map * Submit without first checking that it has been removed from the chunk map
*/ */
private <V extends Future<V>> V submitUnchecked(IQueueChunk chunk) { private <V extends Future<V>> V submitUnchecked(IQueueChunk chunk) {
if (chunk.isEmpty()) {
if (chunk instanceof ChunkHolder<?> holder) {
long age = holder.initAge();
// 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()) { if (chunk.isEmpty()) {
chunk.recycle(); chunk.recycle();
Future result = Futures.immediateFuture(null); Future result = Futures.immediateFuture(null);
return (V) result; return (V) result;
} }
}
if (Fawe.isMainThread()) { if (Fawe.isMainThread()) {
V result = (V) chunk.call(); V result = (V) chunk.call();
@ -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();
} }

Datei anzeigen

@ -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;

Datei anzeigen

@ -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);

Datei anzeigen

@ -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);

Datei anzeigen

@ -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")

Datei anzeigen

@ -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;
}
}
}

Datei anzeigen

@ -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);

Datei anzeigen

@ -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) {

Datei anzeigen

@ -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;

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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];
} }

Datei anzeigen

@ -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;
} }

Datei anzeigen

@ -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;
} }
} }
}; };

Datei anzeigen

@ -607,21 +607,18 @@ 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 { } else if (allowedRegions.length == 1) {
if (allowedRegions.length == 0) {
regionExtent = new NullExtent(this.extent, FaweCache.NO_REGION);
} else {
if (allowedRegions.length == 1) {
regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]); regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]);
} else { } else {
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null); regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null);
} }
}
}
if (regionExtent != null) { if (regionExtent != null) {
if (placeChunks) { if (placeChunks) {
queue.addProcessor(regionExtent); queue.addProcessor(regionExtent);

Datei anzeigen

@ -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
/** /**

Datei anzeigen

@ -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));
} }

Datei anzeigen

@ -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."

Datei anzeigen

@ -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) {

Datei anzeigen

@ -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
} }

Datei anzeigen

@ -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.

Datei anzeigen

@ -55,4 +55,9 @@ public abstract class AbstractPlatform implements Platform {
return null; return null;
} }
@Override
public long getTickCount() {
return System.nanoTime() / 50_000_000;
}
} }

Datei anzeigen

@ -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;

Datei anzeigen

@ -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
/** /**

Datei anzeigen

@ -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;
}
}
}

Datei anzeigen

@ -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.

Datei anzeigen

@ -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.",

Datei anzeigen

@ -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