Telesphoreo 2023-06-07 13:15:02 -05:00
commit 80ffba7a63
117 changed files with 2004 additions and 653 deletions

View File

@ -13,7 +13,7 @@ jobs:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
DISCORD_USERNAME: FastAsyncWorldEdit Release DISCORD_USERNAME: FastAsyncWorldEdit Release
DISCORD_AVATAR: https://raw.githubusercontent.com/IntellectualSites/Assets/main/plugins/FastAsyncWorldEdit/FastAsyncWorldEdit.png DISCORD_AVATAR: https://raw.githubusercontent.com/IntellectualSites/Assets/main/plugins/FastAsyncWorldEdit/FastAsyncWorldEdit.png
uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9 # ratchet:Ilshidur/action-discord@0.3.2 uses: Ilshidur/action-discord@0.3.2
with: with:
args: | args: |
"<@&525015715300900875> <@&706463154804097105> <@&671372968462516240>" "<@&525015715300900875> <@&706463154804097105> <@&671372968462516240>"

View File

@ -16,6 +16,12 @@ jobs:
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: temurin distribution: temurin
cache: gradle
java-version: 17 java-version: 17
- name: Build on ${{ matrix.os }} - name: Build on ${{ matrix.os }}
run: ./gradlew clean build run: ./gradlew build -s
- name: Archive artifacts
uses: actions/upload-artifact@v3
with:
name: FastAsyncWorldEdit-SNAPSHOT
path: worldedit-bukkit/build/libs/FastAsyncWorldEdit-Bukkit-*.jar

View File

@ -1,5 +1,7 @@
name: "CodeQL" name: "CodeQL"
on: on:
push:
branches: [main]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [main] branches: [main]
@ -18,6 +20,12 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: temurin
cache: gradle
java-version: 17
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v2
with: with:

2
.gitignore vendored
View File

@ -33,3 +33,5 @@ worldedit-core/src/main/resources/lang/*
/worldedit-core/.factorypath /worldedit-core/.factorypath
.DS_Store .DS_Store
### Run server ignore
run-*

11
Jenkinsfile vendored
View File

@ -4,14 +4,13 @@ pipeline {
disableConcurrentBuilds() disableConcurrentBuilds()
} }
stages { stages {
stage('Set JDK 17') {
steps {
tool name: 'Temurin-17.0.6+10', type: 'jdk'
}
}
stage('Build') { stage('Build') {
steps { steps {
sh './gradlew clean build' withEnv([
"PATH+JAVA=${tool 'Temurin-17.0.7_7'}/bin"
]) {
sh './gradlew clean build'
}
} }
} }
stage('Archive artifacts') { stage('Archive artifacts') {

View File

@ -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.0.1" id("xyz.jpenilla.run-paper") version "2.1.0"
} }
if (!File("$rootDir/.git").exists()) { if (!File("$rootDir/.git").exists()) {
@ -34,7 +34,7 @@ logger.lifecycle("""
******************************************* *******************************************
""") """)
var rootVersion by extra("2.6.0") var rootVersion by extra("2.6.3")
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("")
@ -105,7 +105,7 @@ tasks {
} }
nexusPublishing { nexusPublishing {
repositories { this.repositories {
sonatype { sonatype {
nexusUrl.set(URI.create("https://s01.oss.sonatype.org/service/local/")) nexusUrl.set(URI.create("https://s01.oss.sonatype.org/service/local/"))
snapshotRepositoryUrl.set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/")) snapshotRepositoryUrl.set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/"))

View File

@ -22,9 +22,9 @@ val properties = Properties().also { props ->
dependencies { dependencies {
implementation(gradleApi()) implementation(gradleApi())
implementation("org.ajoberstar.grgit:grgit-gradle:5.0.0") implementation("org.ajoberstar.grgit:grgit-gradle:5.2.0")
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2") implementation("com.github.johnrengelman:shadow:8.1.1")
implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.3") implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.5")
} }
kotlin { kotlin {

View File

@ -6,12 +6,12 @@ log4j = "2.19.0"
# Plugins # Plugins
dummypermscompat = "1.10" dummypermscompat = "1.10"
worldguard-bukkit = "7.0.7" worldguard-bukkit = "7.0.8"
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.98.4.18" towny = "0.99.1.2"
# Third party # Third party
bstats = "3.0.2" bstats = "3.0.2"
@ -23,7 +23,7 @@ auto-value = "1.10.1"
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.12.0" antlr4 = "4.13.0"
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"
@ -36,7 +36,7 @@ text = "3.0.4"
piston = "0.5.7" piston = "0.5.7"
# Tests # Tests
mockito = "5.2.0" mockito = "5.3.1"
# Gradle plugins # Gradle plugins
pluginyml = "0.5.3" pluginyml = "0.5.3"
@ -54,7 +54,7 @@ mapmanager = { group = "com.github.InventivetalentDev", name = "MapManager", ver
griefprevention = { group = "com.github.TechFortress", name = "GriefPrevention", version.ref = "griefprevention" } griefprevention = { group = "com.github.TechFortress", name = "GriefPrevention", version.ref = "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.github.TownyAdvanced", name = "Towny", version.ref = "towny" } towny = { group = "com.palmergames.bukkit.towny", name = "towny", version.ref = "towny" }
# Third Party # Third Party
bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" } bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" }

Binary file not shown.

View File

@ -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-7.6.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
networkTimeout=10000 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

7
gradlew vendored
View File

@ -85,9 +85,6 @@ done
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# 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
@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in # shell script including quotes and variable substitutions, so put them in

View File

@ -1,3 +1,5 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
applyPaperweightAdapterConfiguration() applyPaperweightAdapterConfiguration()
plugins { plugins {
@ -19,6 +21,6 @@ configurations.all {
dependencies { dependencies {
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("io.papermc:paperlib")
} }

View File

@ -103,6 +103,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -658,10 +659,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.registryAccess() .registryAccess()
.ownedRegistryOrThrow( .ownedRegistryOrThrow(
Registry.BIOME_REGISTRY); Registry.BIOME_REGISTRY);
return biomeRegistry.stream() List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey) .map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
.map(CraftNamespacedKey::fromMinecraft) List<NamespacedKey> namespacedKeys = new ArrayList<>();
.collect(Collectors.toList()); 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 @Override

View File

@ -445,10 +445,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << layer; bitMask |= 1 << layer;
// Changes may still be written to chunk SET char[] setArr = set.load(layerNo);
char[] tmp = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size. // submitted to keep loaded internal chunks to queue target size.

View File

@ -29,7 +29,9 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.TicketType;
import net.minecraft.util.BitStorage; import net.minecraft.util.BitStorage;
import net.minecraft.util.Unit;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
@ -209,10 +211,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else { } else {
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
// Avoid "async" methods from the main thread. // Avoid "async" methods from the main thread.
@ -230,6 +234,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); 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
net.minecraft.server.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel
.getChunkSource()
.addRegionTicket(TicketType.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE));
}
public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
try { try {

View File

@ -1,3 +1,5 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
plugins { plugins {
java java
} }
@ -10,6 +12,6 @@ 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/
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("io.papermc:paperlib")
} }

View File

@ -656,10 +656,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.registryAccess() .registryAccess()
.ownedRegistryOrThrow( .ownedRegistryOrThrow(
Registry.BIOME_REGISTRY); Registry.BIOME_REGISTRY);
return biomeRegistry.stream() List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull) .map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
.map(CraftNamespacedKey::fromMinecraft) List<NamespacedKey> namespacedKeys = new ArrayList<>();
.collect(Collectors.toList()); 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 @Override

View File

@ -491,9 +491,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex; bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo); char[] setArr = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size. // submitted to keep loaded internal chunks to queue target size.

View File

@ -29,9 +29,11 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.TicketType;
import net.minecraft.util.BitStorage; import net.minecraft.util.BitStorage;
import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.SimpleBitStorage;
import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ThreadingDetector;
import net.minecraft.util.Unit;
import net.minecraft.util.ZeroBitStorage; import net.minecraft.util.ZeroBitStorage;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
@ -237,10 +239,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else { } else {
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
// Avoid "async" methods from the main thread. // Avoid "async" methods from the main thread.
@ -258,6 +262,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); 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
net.minecraft.server.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel
.getChunkSource()
.addRegionTicket(TicketType.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE));
}
public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
try { try {

View File

@ -1,3 +1,5 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
plugins { plugins {
java java
} }
@ -9,6 +11,6 @@ repositories {
} }
dependencies { dependencies {
paperDevBundle("1.19.2-R0.1-20221206.184705-189") the<PaperweightUserDependenciesExtension>().paperDevBundle("1.19.2-R0.1-20221206.184705-189")
compileOnly("io.papermc:paperlib") compileOnly("io.papermc:paperlib")
} }

View File

@ -645,10 +645,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.registryAccess() .registryAccess()
.ownedRegistryOrThrow( .ownedRegistryOrThrow(
Registry.BIOME_REGISTRY); Registry.BIOME_REGISTRY);
return biomeRegistry.stream() List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull) .map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
.map(CraftNamespacedKey::fromMinecraft) List<NamespacedKey> namespacedKeys = new ArrayList<>();
.collect(Collectors.toList()); 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 @Override

View File

@ -488,9 +488,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex; bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo); char[] setArr = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size. // submitted to keep loaded internal chunks to queue target size.

View File

@ -29,10 +29,12 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.TicketType;
import net.minecraft.util.BitStorage; import net.minecraft.util.BitStorage;
import net.minecraft.util.ExceptionCollector; import net.minecraft.util.ExceptionCollector;
import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.SimpleBitStorage;
import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ThreadingDetector;
import net.minecraft.util.Unit;
import net.minecraft.util.ZeroBitStorage; import net.minecraft.util.ZeroBitStorage;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
@ -270,10 +272,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else { } else {
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
// Avoid "async" methods from the main thread. // Avoid "async" methods from the main thread.
@ -291,6 +295,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); 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.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE));
}
public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
try { try {

View File

@ -1,3 +1,5 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
plugins { plugins {
java java
} }
@ -10,6 +12,6 @@ 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/
paperDevBundle("1.19.3-R0.1-20230312.180621-141") the<PaperweightUserDependenciesExtension>().paperDevBundle("1.19.3-R0.1-20230312.180621-141")
compileOnly("io.papermc:paperlib") compileOnly("io.papermc:paperlib")
} }

View File

@ -650,10 +650,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.getServer() .getServer()
.registryAccess() .registryAccess()
.registryOrThrow(BIOME); .registryOrThrow(BIOME);
return biomeRegistry.stream() List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull) .map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
.map(CraftNamespacedKey::fromMinecraft) List<NamespacedKey> namespacedKeys = new ArrayList<>();
.collect(Collectors.toList()); 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 @Override

View File

@ -490,9 +490,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex; bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo); char[] setArr = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size. // submitted to keep loaded internal chunks to queue target size.

View File

@ -29,10 +29,12 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.TicketType;
import net.minecraft.util.BitStorage; import net.minecraft.util.BitStorage;
import net.minecraft.util.ExceptionCollector; import net.minecraft.util.ExceptionCollector;
import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.SimpleBitStorage;
import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ThreadingDetector;
import net.minecraft.util.Unit;
import net.minecraft.util.ZeroBitStorage; import net.minecraft.util.ZeroBitStorage;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
@ -267,10 +269,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else { } else {
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
// Avoid "async" methods from the main thread. // Avoid "async" methods from the main thread.
@ -288,6 +292,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); 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.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE));
}
public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
try { try {

View File

@ -1,3 +1,5 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
plugins { plugins {
java java
} }
@ -9,6 +11,6 @@ repositories {
} }
dependencies { dependencies {
paperDevBundle("1.19.4-R0.1-20230412.010331-64") the<PaperweightUserDependenciesExtension>().paperDevBundle("1.19.4-R0.1-20230601.025018-99")
compileOnly("io.papermc:paperlib") compileOnly("io.papermc:paperlib")
} }

View File

@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap;
import com.sk89q.jnbt.Tag; import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.TileEntityBlock;
import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
@ -50,7 +49,6 @@ import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.block.BlockTypesCache;
import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.entity.EntityType;
import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemType;
@ -60,7 +58,6 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.core.WritableRegistry; import net.minecraft.core.WritableRegistry;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.IntTag;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
@ -71,14 +68,12 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.StringRepresentable; import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
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.entity.BlockEntity; 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.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty; import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
@ -86,7 +81,6 @@ import org.bukkit.Material;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.TreeType; import org.bukkit.TreeType;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
import org.bukkit.craftbukkit.v1_19_R3.CraftServer; import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockState; import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockState;
@ -602,10 +596,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.getServer() .getServer()
.registryAccess() .registryAccess()
.registryOrThrow(BIOME); .registryOrThrow(BIOME);
return biomeRegistry.stream() List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull) .map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
.map(CraftNamespacedKey::fromMinecraft) List<NamespacedKey> namespacedKeys = new ArrayList<>();
.collect(Collectors.toList()); 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 @Override

View File

@ -490,9 +490,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex; bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo); char[] setArr = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size. // submitted to keep loaded internal chunks to queue target size.

View File

@ -29,10 +29,12 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.TicketType;
import net.minecraft.util.BitStorage; import net.minecraft.util.BitStorage;
import net.minecraft.util.ExceptionCollector; import net.minecraft.util.ExceptionCollector;
import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.SimpleBitStorage;
import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ThreadingDetector;
import net.minecraft.util.Unit;
import net.minecraft.util.ZeroBitStorage; import net.minecraft.util.ZeroBitStorage;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
@ -182,7 +184,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
removeBlockEntityTicker.setAccessible(true); removeBlockEntityTicker.setAccessible(true);
methodremoveTickingBlockEntity = lookup.unreflect(removeBlockEntityTicker); methodremoveTickingBlockEntity = lookup.unreflect(removeBlockEntityTicker);
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p")); fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q"));
fieldRemove.setAccessible(true); fieldRemove.setAccessible(true);
CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class);
@ -292,10 +294,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else { } else {
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
if (nmsChunk != null) { if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk; return nmsChunk;
} }
// Avoid "async" methods from the main thread. // Avoid "async" methods from the main thread.
@ -305,6 +309,7 @@ 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 = (CraftChunk) future.get();
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) {
e.printStackTrace(); e.printStackTrace();
@ -313,6 +318,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); 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) { public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
try { try {

View File

@ -3,7 +3,7 @@ import io.papermc.paperweight.userdev.attribute.Obfuscation
plugins { plugins {
`java-library` `java-library`
id("com.modrinth.minotaur") version "2.+" id("com.modrinth.minotaur") version "2.7.5"
} }
project.description = "Bukkit" project.description = "Bukkit"
@ -32,6 +32,10 @@ repositories {
name = "OSS Sonatype Snapshots" name = "OSS Sonatype Snapshots"
url = uri("https://oss.sonatype.org/content/repositories/snapshots/") url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
} }
maven {
name = "Glaremasters"
url = uri("https://repo.glaremasters.me/repository/towny/")
}
flatDir { dir(File("src/main/resources")) } flatDir { dir(File("src/main/resources")) }
} }
@ -47,7 +51,13 @@ val adapters = configurations.create("adapters") {
isCanBeResolved = true isCanBeResolved = true
shouldResolveConsistentlyWith(configurations["runtimeClasspath"]) shouldResolveConsistentlyWith(configurations["runtimeClasspath"])
attributes { attributes {
attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, objects.named(Obfuscation.OBFUSCATED)) attribute(Obfuscation.OBFUSCATION_ATTRIBUTE,
if ((project.findProperty("enginehub.obf.none") as String?).toBoolean()) {
objects.named(Obfuscation.NONE)
} else {
objects.named(Obfuscation.OBFUSCATED)
}
)
} }
} }

View File

@ -3,8 +3,6 @@ package com.fastasyncworldedit.bukkit.adapter;
import co.aikar.timings.Timings; import co.aikar.timings.Timings;
import com.fastasyncworldedit.bukkit.listener.ChunkListener; import com.fastasyncworldedit.bukkit.listener.ChunkListener;
import com.fastasyncworldedit.core.queue.implementation.QueueHandler; import com.fastasyncworldedit.core.queue.implementation.QueueHandler;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -31,7 +29,7 @@ public class BukkitQueueHandler extends QueueHandler {
} }
@Override @Override
public void startSet(boolean parallel) { public void startUnsafe(boolean parallel) {
ChunkListener.physicsFreeze = true; ChunkListener.physicsFreeze = true;
if (parallel) { if (parallel) {
try { try {
@ -51,7 +49,7 @@ public class BukkitQueueHandler extends QueueHandler {
} }
@Override @Override
public void endSet(boolean parallel) { public void endUnsafe(boolean parallel) {
ChunkListener.physicsFreeze = false; ChunkListener.physicsFreeze = false;
if (parallel) { if (parallel) {
try { try {

View File

@ -165,7 +165,11 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
.setNameFormat("fawe-regen-%d") .setNameFormat("fawe-regen-%d")
.build() .build()
); );
} // else using sequential chunk generation, concurrent not supported } else { // else using sequential chunk generation, concurrent not supported
executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
.setNameFormat("fawe-regen-%d")
.build());
}
//TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)? //TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)?
//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
@ -253,10 +257,13 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
e.printStackTrace(); e.printStackTrace();
} }
} else { // Concurrency.NONE or generateConcurrent == false } else { // Concurrency.NONE or generateConcurrent == false
// run sequential // run sequential but submit to different thread
for (long xz : coords) { // running regen on the main thread otherwise triggers async-only events on the main thread
chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); executor.submit(() -> {
} for (long xz : coords) {
chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
}
}).get(); // wait until finished this step
} }
} }

View File

@ -371,48 +371,6 @@ public abstract class ChunkListener implements Listener {
} }
} }
/**
* Prevent firework from loading chunks.
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onChunkLoad(ChunkLoadEvent event) {
if (!Settings.settings().TICK_LIMITER.FIREWORKS_LOAD_CHUNKS) {
Chunk chunk = event.getChunk();
Entity[] entities = chunk.getEntities();
World world = chunk.getWorld();
Exception e = new Exception();
int start = 14;
int end = 22;
int depth = Math.min(end, getDepth(e));
for (int frame = start; frame < depth; frame++) {
StackTraceElement elem = getElement(e, frame);
if (elem == null) {
return;
}
String className = elem.getClassName();
int len = className.length();
if (len > 15 && className.charAt(len - 15) == 'E' && className
.endsWith("EntityFireworks")) {
for (Entity ent : world.getEntities()) {
if (ent.getType() == EntityType.FIREWORK) {
Vector velocity = ent.getVelocity();
double vertical = Math.abs(velocity.getY());
if (Math.abs(velocity.getX()) > vertical
|| Math.abs(velocity.getZ()) > vertical) {
LOGGER.warn(
"[FAWE `tick-limiter`] Detected and cancelled rogue FireWork at {}",
ent.getLocation());
ent.remove();
}
}
}
}
}
}
}
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public void onItemSpawn(ItemSpawnEvent event) { public void onItemSpawn(ItemSpawnEvent event) {
if (physicsFreeze) { if (physicsFreeze) {

View File

@ -112,11 +112,15 @@ public class WorldEditPlugin extends JavaPlugin {
private BukkitServerInterface platform; private BukkitServerInterface platform;
private BukkitConfiguration config; private BukkitConfiguration config;
private BukkitPermissionAttachmentManager permissionAttachmentManager; private BukkitPermissionAttachmentManager permissionAttachmentManager;
// Fawe start
private BukkitCommandSender bukkitConsoleCommandSender;
// Fawe end
@Override @Override
public void onLoad() { public void onLoad() {
//FAWE start //FAWE start
this.bukkitConsoleCommandSender = new BukkitCommandSender(this, Bukkit.getConsoleSender());
// This is already covered by Spigot, however, a more pesky warning with a proper explanation over "Ambiguous plugin name..." can't hurt. // This is already covered by Spigot, however, a more pesky warning with a proper explanation over "Ambiguous plugin name..." can't hurt.
Plugin[] plugins = Bukkit.getServer().getPluginManager().getPlugins(); Plugin[] plugins = Bukkit.getServer().getPluginManager().getPlugins();
for (Plugin p : plugins) { for (Plugin p : plugins) {
@ -594,7 +598,7 @@ public class WorldEditPlugin extends JavaPlugin {
return new BukkitBlockCommandSender(this, (BlockCommandSender) sender); return new BukkitBlockCommandSender(this, (BlockCommandSender) sender);
} }
return new BukkitCommandSender(this, sender); return bukkitConsoleCommandSender;
} }
public BukkitServerInterface getInternalPlatform() { public BukkitServerInterface getInternalPlatform() {

View File

@ -23,6 +23,7 @@ import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.regions.selector.ExtendingCuboidRegionSelector;
import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.task.Task; import com.sk89q.worldedit.util.task.Task;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
@ -39,9 +40,12 @@ public class CLIExtraCommands {
desc = "Select the entire world" desc = "Select the entire world"
) )
public void selectWorld(Actor actor, World world, LocalSession session) { public void selectWorld(Actor actor, World world, LocalSession session) {
session.setRegionSelector(world, new CuboidRegionSelector( final CuboidRegionSelector selector;
world, world.getMinimumPoint(), world.getMaximumPoint() if (session.getRegionSelector(world) instanceof ExtendingCuboidRegionSelector) {
)); selector = new ExtendingCuboidRegionSelector(world, world.getMinimumPoint(), world.getMaximumPoint());
} else {
selector = new CuboidRegionSelector(world, world.getMinimumPoint(), world.getMaximumPoint());
}
actor.printInfo(TextComponent.of("Selected the entire world.")); actor.printInfo(TextComponent.of("Selected the entire world."));
} }

View File

@ -1,14 +1,14 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "1.5.30" kotlin("jvm") version "1.8.20"
application application
} }
applyCommonConfiguration() applyCommonConfiguration()
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11" kotlinOptions.jvmTarget = "17"
} }
application.mainClass.set("com.sk89q.worldedit.internal.util.DocumentationPrinter") application.mainClass.set("com.sk89q.worldedit.internal.util.DocumentationPrinter")

View File

@ -12,7 +12,9 @@ import com.fastasyncworldedit.core.util.RandomTextureUtil;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.TextureUtil; import com.fastasyncworldedit.core.util.TextureUtil;
import com.fastasyncworldedit.core.util.WEManager; import com.fastasyncworldedit.core.util.WEManager;
import com.fastasyncworldedit.core.util.task.KeyQueuedExecutorService;
import com.github.luben.zstd.Zstd; import com.github.luben.zstd.Zstd;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.util.LogManagerCompat;
import net.jpountz.lz4.LZ4Factory; import net.jpountz.lz4.LZ4Factory;
@ -33,6 +35,9 @@ import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage; import java.lang.management.MemoryUsage;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -86,6 +91,7 @@ public class Fawe {
* The platform specific implementation. * The platform specific implementation.
*/ */
private final IFawe implementation; private final IFawe implementation;
private final KeyQueuedExecutorService<UUID> clipboardExecutor;
private FaweVersion version; private FaweVersion version;
private TextureUtil textures; private TextureUtil textures;
private QueueHandler queueHandler; private QueueHandler queueHandler;
@ -131,6 +137,15 @@ public class Fawe {
}, 0); }, 0);
TaskManager.taskManager().repeat(timer, 1); TaskManager.taskManager().repeat(timer, 1);
clipboardExecutor = new KeyQueuedExecutorService<>(new ThreadPoolExecutor(
1,
Settings.settings().QUEUE.PARALLEL_THREADS,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder().setNameFormat("fawe-clipboard-%d").build()
));
} }
/** /**
@ -339,9 +354,10 @@ public class Fawe {
Settings.settings().QUEUE.TARGET_SIZE, Settings.settings().QUEUE.TARGET_SIZE,
Settings.settings().QUEUE.PARALLEL_THREADS Settings.settings().QUEUE.PARALLEL_THREADS
); );
if (Settings.settings().QUEUE.TARGET_SIZE < 2 * Settings.settings().QUEUE.PARALLEL_THREADS) { if (Settings.settings().QUEUE.TARGET_SIZE < 4 * Settings.settings().QUEUE.PARALLEL_THREADS) {
LOGGER.error( LOGGER.error(
"queue.target_size is {}, and queue.parallel_threads is {}. It is HIGHLY recommended that queue" + ".target_size be at least twice queue.parallel_threads or higher.", "queue.target_size is {}, and queue.parallel_threads is {}. It is HIGHLY recommended that queue" +
".target_size be at least four times queue.parallel_threads or greater.",
Settings.settings().QUEUE.TARGET_SIZE, Settings.settings().QUEUE.TARGET_SIZE,
Settings.settings().QUEUE.PARALLEL_THREADS Settings.settings().QUEUE.PARALLEL_THREADS
); );
@ -427,4 +443,15 @@ public class Fawe {
return this.thread = Thread.currentThread(); return this.thread = Thread.currentThread();
} }
/**
* Gets the 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
*/
@Nullable
public KeyQueuedExecutorService<UUID> getClipboardExecutor() {
return this.clipboardExecutor;
}
} }

View File

@ -1,6 +1,8 @@
package com.fastasyncworldedit.core.command; package com.fastasyncworldedit.core.command;
import com.fastasyncworldedit.core.configuration.Caption;
import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.util.formatting.text.Component;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
@ -13,33 +15,81 @@ public class SuggestInputParseException extends InputParseException {
private final InputParseException cause; private final InputParseException cause;
private final Supplier<List<String>> getSuggestions; private final Supplier<List<String>> getSuggestions;
private String prefix;
/**
* @deprecated Use {@link SuggestInputParseException#SuggestInputParseException(Component, Supplier)}
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public SuggestInputParseException(String msg, String prefix, Supplier<List<String>> getSuggestions) { public SuggestInputParseException(String msg, String prefix, Supplier<List<String>> getSuggestions) {
this(new InputParseException(msg), prefix, getSuggestions); this(new InputParseException(msg), getSuggestions);
} }
/**
* @deprecated Use {@link SuggestInputParseException#of(Throwable, Supplier)}
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public static SuggestInputParseException of(Throwable other, String prefix, Supplier<List<String>> getSuggestions) { public static SuggestInputParseException of(Throwable other, String prefix, Supplier<List<String>> getSuggestions) {
if (other instanceof InputParseException) { if (other instanceof InputParseException) {
return of((InputParseException) other, prefix, getSuggestions); return of((InputParseException) other, getSuggestions);
} }
return of(new InputParseException(other.getMessage()), prefix, getSuggestions); return of(new InputParseException(other.getMessage()), getSuggestions);
} }
/**
* @deprecated Use {@link SuggestInputParseException#of(InputParseException, Supplier)}
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public static SuggestInputParseException of(InputParseException other, String prefix, Supplier<List<String>> getSuggestions) { public static SuggestInputParseException of(InputParseException other, String prefix, Supplier<List<String>> getSuggestions) {
if (other instanceof SuggestInputParseException) { if (other instanceof SuggestInputParseException) {
return (SuggestInputParseException) other; return (SuggestInputParseException) other;
} }
return new SuggestInputParseException(other, prefix, getSuggestions); return new SuggestInputParseException(other, getSuggestions);
} }
/**
* @deprecated Use {@link SuggestInputParseException#SuggestInputParseException(InputParseException, Supplier)}
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public SuggestInputParseException(InputParseException other, String prefix, Supplier<List<String>> getSuggestions) { public SuggestInputParseException(InputParseException other, String prefix, Supplier<List<String>> getSuggestions) {
super(other.getMessage()); super(other.getRichMessage());
checkNotNull(getSuggestions);
checkNotNull(other);
this.cause = other;
this.getSuggestions = getSuggestions;
}
/**
* Create a new SuggestInputParseException instance
*
* @param message Message to send
* @param getSuggestions Supplier of list of suggestions to give to user
* @since 2.6.2
*/
public SuggestInputParseException(Component message, Supplier<List<String>> getSuggestions) {
this(new InputParseException(message), getSuggestions);
}
public static SuggestInputParseException of(Throwable other, Supplier<List<String>> getSuggestions) {
if (other instanceof InputParseException) {
return of((InputParseException) other, getSuggestions);
}
//noinspection deprecation
return of(new InputParseException(other.getMessage()), getSuggestions);
}
public static SuggestInputParseException of(InputParseException other, Supplier<List<String>> getSuggestions) {
if (other instanceof SuggestInputParseException) {
return (SuggestInputParseException) other;
}
return new SuggestInputParseException(other, getSuggestions);
}
public SuggestInputParseException(InputParseException other, Supplier<List<String>> getSuggestions) {
super(other.getRichMessage());
checkNotNull(getSuggestions); checkNotNull(getSuggestions);
checkNotNull(other); checkNotNull(other);
this.cause = other; this.cause = other;
this.getSuggestions = getSuggestions; this.getSuggestions = getSuggestions;
this.prefix = prefix;
} }
public static SuggestInputParseException get(InvocationTargetException e) { public static SuggestInputParseException get(InvocationTargetException e) {
@ -54,7 +104,7 @@ public class SuggestInputParseException extends InputParseException {
} }
public static SuggestInputParseException of(String input, List<Object> values) { public static SuggestInputParseException of(String input, List<Object> values) {
throw new SuggestInputParseException("No value: " + input, input, () -> throw new SuggestInputParseException(Caption.of("fawe.error.no-value-for-input", input), () ->
values.stream() values.stream()
.map(Object::toString) .map(Object::toString)
.filter(v -> v.startsWith(input)) .filter(v -> v.startsWith(input))
@ -76,8 +126,12 @@ public class SuggestInputParseException extends InputParseException {
return getSuggestions.get(); return getSuggestions.get();
} }
/**
* @deprecated Unused
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public SuggestInputParseException prepend(String input) { public SuggestInputParseException prepend(String input) {
this.prefix = input + prefix; // Do nothing
return this; return this;
} }

View File

@ -102,11 +102,10 @@ public class Settings extends Config {
public FaweLimit getLimit(Actor actor) { public FaweLimit getLimit(Actor actor) {
FaweLimit limit; FaweLimit limit;
if (actor.hasPermission("fawe.limit.*") || actor.hasPermission("fawe.bypass")) { if (actor.hasPermission("fawe.bypass") || actor.hasPermission("fawe.limit.unlimited")) {
limit = FaweLimit.MAX.copy(); return FaweLimit.MAX.copy();
} else {
limit = new FaweLimit();
} }
limit = new FaweLimit();
ArrayList<String> keys = new ArrayList<>(LIMITS.getSections()); ArrayList<String> keys = new ArrayList<>(LIMITS.getSections());
if (keys.remove("default")) { if (keys.remove("default")) {
keys.add("default"); keys.add("default");
@ -394,6 +393,7 @@ public class Settings extends Config {
"Where block properties are specified, any blockstate with the property will be disallowed (e.g. all directions", "Where block properties are specified, any blockstate with the property will be disallowed (e.g. all directions",
"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.",
"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]\"",
@ -520,10 +520,10 @@ public class Settings extends Config {
" - A smaller value will reduce memory usage", " - A smaller value will reduce memory usage",
" - A value too small may break some operations (deform?)", " - A value too small may break some operations (deform?)",
" - Values smaller than the configurated parallel-threads are not accepted", " - Values smaller than the configurated parallel-threads are not accepted",
" - It is recommended this option be at least 2x greater than parallel-threads" " - It is recommended this option be at least 4x greater than parallel-threads"
}) })
public int TARGET_SIZE = 64; public int TARGET_SIZE = 8 * Runtime.getRuntime().availableProcessors();
@Comment({ @Comment({
"Force FAWE to start placing chunks regardless of whether an edit is finished processing", "Force FAWE to start placing chunks regardless of whether an edit is finished processing",
" - A larger value will use slightly less CPU time", " - A larger value will use slightly less CPU time",
@ -671,6 +671,14 @@ public class Settings extends Config {
}) })
public int MAX_IMAGE_SIZE = 8294400; public int MAX_IMAGE_SIZE = 8294400;
@Comment({
"Whitelist of hostnames to allow images to be downloaded from",
" - Adding '*' to the list will allow any host, but this is NOT adviseable",
" - Crash exploits exist with malformed images",
" - See: https://medium.com/chargebee-engineering/perils-of-parsing-pixel-flood-attack-on-java-imageio-a97aeb06637d"
})
public List<String> ALLOWED_IMAGE_HOSTS = new ArrayList<>(Collections.singleton(("i.imgur.com")));
} }
public static class EXTENT { public static class EXTENT {
@ -694,7 +702,7 @@ public class Settings extends Config {
public static class TICK_LIMITER { public static class TICK_LIMITER {
@Comment("Enable the limiter") @Comment("Enable the limiter")
public boolean ENABLED = true; public boolean ENABLED = false;
@Comment("The interval in ticks") @Comment("The interval in ticks")
public int INTERVAL = 20; public int INTERVAL = 20;
@Comment("Max falling blocks per interval (per chunk)") @Comment("Max falling blocks per interval (per chunk)")
@ -703,12 +711,6 @@ public class Settings extends Config {
public int PHYSICS_MS = 10; public int PHYSICS_MS = 10;
@Comment("Max item spawns per interval (per chunk)") @Comment("Max item spawns per interval (per chunk)")
public int ITEMS = 256; public int ITEMS = 256;
@Comment({
"Whether fireworks can load chunks",
" - Fireworks usually travel vertically so do not load any chunks",
" - Horizontal fireworks can be hacked in to crash a server"
})
public boolean FIREWORKS_LOAD_CHUNKS = false;
} }

View File

@ -50,7 +50,7 @@ public class RichMaskParser extends FaweParser<Mask> {
@Override @Override
public Mask parseFromInput(String input, ParserContext context) throws InputParseException { public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
if (input.isEmpty()) { if (input.isEmpty()) {
throw new SuggestInputParseException("No input provided", "", () -> Stream throw new SuggestInputParseException(Caption.of("fawe.error.no-input-provided"), () -> Stream
.of("#", ",", "&") .of("#", ",", "&")
.map(n -> n + ":") .map(n -> n + ":")
.collect(Collectors.toList()) .collect(Collectors.toList())
@ -95,7 +95,6 @@ public class RichMaskParser extends FaweParser<Mask> {
"https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns" "https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns"
)) ))
)), )),
full,
() -> { () -> {
if (full.length() == 1) { if (full.length() == 1) {
return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions("")); return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions(""));
@ -148,6 +147,7 @@ public class RichMaskParser extends FaweParser<Mask> {
try { try {
builder.addRegex(full); builder.addRegex(full);
} catch (InputParseException ignored) { } catch (InputParseException ignored) {
builder.clear();
context.setPreferringWildcard(false); context.setPreferringWildcard(false);
context.setRestricted(false); context.setRestricted(false);
BaseBlock block = worldEdit.getBlockFactory().parseFromInput(full, context); BaseBlock block = worldEdit.getBlockFactory().parseFromInput(full, context);
@ -162,7 +162,6 @@ public class RichMaskParser extends FaweParser<Mask> {
"https://intellectualsites.github.io/fastasyncworldedit-documentation/masks/masks" "https://intellectualsites.github.io/fastasyncworldedit-documentation/masks/masks"
)) ))
)), )),
full,
() -> { () -> {
if (full.length() == 1) { if (full.length() == 1) {
return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions("")); return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions(""));

View File

@ -47,8 +47,7 @@ public class RichPatternParser extends FaweParser<Pattern> {
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException { public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
if (input.isEmpty()) { if (input.isEmpty()) {
throw new SuggestInputParseException( throw new SuggestInputParseException(
"No input provided", Caption.of("fawe.error.no-input-provided"),
"",
() -> Stream () -> Stream
.concat(Stream.of("#", ",", "&"), BlockTypes.getNameSpaces().stream().map(n -> n + ":")) .concat(Stream.of("#", ",", "&"), BlockTypes.getNameSpaces().stream().map(n -> n + ":"))
.collect(Collectors.toList()) .collect(Collectors.toList())
@ -88,7 +87,6 @@ public class RichPatternParser extends FaweParser<Pattern> {
"https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns" "https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns"
)) ))
)), )),
full,
() -> { () -> {
if (full.length() == 1) { if (full.length() == 1) {
return new ArrayList<>(worldEdit.getPatternFactory().getSuggestions("")); return new ArrayList<>(worldEdit.getPatternFactory().getSuggestions(""));

View File

@ -141,7 +141,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
BlockState state = BlockTypesCache.states[block]; BlockState state = BlockTypesCache.states[block];
if (blockedBlocks != null) { if (blockedBlocks != null) {
if (blockedBlocks.contains(state.getBlockType().getId())) { if (blockedBlocks.contains(state.getBlockType().getId())) {
blocks[i] = 0; blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
continue; continue;
} }
} }
@ -150,7 +150,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
} }
for (FuzzyBlockState fuzzy : blockedStates) { for (FuzzyBlockState fuzzy : blockedStates) {
if (fuzzy.equalsFuzzy(state)) { if (fuzzy.equalsFuzzy(state)) {
blocks[i] = 0; blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
continue it; continue it;
} }
} }
@ -178,7 +178,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
@Override @Override
public ProcessorScope getScope() { public ProcessorScope getScope() {
return ProcessorScope.CHANGING_BLOCKS; return ProcessorScope.REMOVING_BLOCKS;
} }
} }

View File

@ -168,7 +168,7 @@ public abstract class FaweRegionExtent extends ResettableExtent implements IBatc
@Override @Override
public ProcessorScope getScope() { public ProcessorScope getScope() {
return ProcessorScope.READING_SET_BLOCKS; return ProcessorScope.REMOVING_BLOCKS;
} }
} }

View File

@ -172,7 +172,7 @@ public class MultiRegionExtent extends FaweRegionExtent {
set = intersection.processSet(chunk, get, set); set = intersection.processSet(chunk, get, set);
} }
if (disallowedIntersection != null) { if (disallowedIntersection != null) {
intersection.processSet(chunk, get, set); set = disallowedIntersection.processSet(chunk, get, set, true);
} }
return set; return set;
} }

View File

@ -11,6 +11,7 @@ import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
@ -191,8 +192,8 @@ public class CPUOptimizedClipboard extends LinearClipboard {
@Override @Override
public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) { public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) {
char ordinal = block.getOrdinalChar(); char ordinal = block.getOrdinalChar();
if (ordinal == 0) { if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = 1; ordinal = BlockTypesCache.ReservedIDs.AIR;
} }
states[index] = ordinal; states[index] = ordinal;
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData(); boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();

View File

@ -26,6 +26,7 @@ import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -155,7 +156,9 @@ public class DiskOptimizedClipboard extends LinearClipboard {
/** /**
* Load an existing file as a DiskOptimizedClipboard. The file MUST exist and MUST be created as a DiskOptimizedClipboard * Load an existing file as a DiskOptimizedClipboard. The file MUST exist and MUST be created as a DiskOptimizedClipboard
* with data written to it. * with data written to it.
* @deprecated Will be made private, use {@link DiskOptimizedClipboard#loadFromFile(File)}
*/ */
@Deprecated(forRemoval = true, since = "2.6.2")
public DiskOptimizedClipboard(File file) { public DiskOptimizedClipboard(File file) {
this(file, VERSION); this(file, VERSION);
} }
@ -166,14 +169,15 @@ public class DiskOptimizedClipboard extends LinearClipboard {
* *
* @param file File to read from * @param file File to read from
* @param versionOverride An override version to allow loading of older clipboards if required * @param versionOverride An override version to allow loading of older clipboards if required
* @deprecated Will be made private, use {@link DiskOptimizedClipboard#loadFromFile(File)}
*/ */
@Deprecated(forRemoval = true, since = "2.6.2")
public DiskOptimizedClipboard(File file, int versionOverride) { public DiskOptimizedClipboard(File file, int versionOverride) {
super(readSize(file, versionOverride), BlockVector3.ZERO); super(readSize(file, versionOverride), BlockVector3.ZERO);
headerSize = getHeaderSizeOverrideFromVersion(versionOverride); headerSize = getHeaderSizeOverrideFromVersion(versionOverride);
nbtMap = new HashMap<>(); nbtMap = new HashMap<>();
try { try {
this.file = file; this.file = file;
checkFileLength(file);
this.braf = new RandomAccessFile(file, "rw"); this.braf = new RandomAccessFile(file, "rw");
braf.setLength(file.length()); braf.setLength(file.length());
this.nbtBytesRemaining = Integer.MAX_VALUE - (int) file.length(); this.nbtBytesRemaining = Integer.MAX_VALUE - (int) file.length();
@ -202,32 +206,6 @@ public class DiskOptimizedClipboard extends LinearClipboard {
} }
} }
private void checkFileLength(File file) throws IOException {
long expectedFileSize = headerSize + ((long) getVolume() << 1);
if (file.length() > Integer.MAX_VALUE) {
if (expectedFileSize >= Integer.MAX_VALUE) {
throw new IOException(String.format(
"Cannot load clipboard of file size: %d > 2147483647 bytes (2.147 GiB), " + "volume: %d blocks",
file.length(),
getVolume()
));
} else {
throw new IOException(String.format(
"Cannot load clipboard of file size > 2147483647 bytes (2.147 GiB). Possible corrupt file? Mismatch" +
" between volume `%d` and file length `%d`!",
file.length(),
getVolume()
));
}
} else if (expectedFileSize != file.length()) {
throw new IOException(String.format(
"Possible corrupt clipboard file? Mismatch between expected file size `%d` and actual file size `%d`!",
expectedFileSize,
file.length()
));
}
}
/** /**
* Attempt to load a file into a new {@link DiskOptimizedClipboard} instance. Will attempt to recover on version mismatch * Attempt to load a file into a new {@link DiskOptimizedClipboard} instance. Will attempt to recover on version mismatch
* failure. * failure.
@ -723,8 +701,8 @@ public class DiskOptimizedClipboard extends LinearClipboard {
try { try {
int index = headerSize + (getIndex(x, y, z) << 1); int index = headerSize + (getIndex(x, y, z) << 1);
char ordinal = block.getOrdinalChar(); char ordinal = block.getOrdinalChar();
if (ordinal == 0) { if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = 1; ordinal = BlockTypesCache.ReservedIDs.AIR;
} }
byteBuffer.putChar(index, ordinal); byteBuffer.putChar(index, ordinal);
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData(); boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();

View File

@ -16,6 +16,7 @@ import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
@ -269,8 +270,8 @@ public class MemoryOptimizedClipboard extends LinearClipboard {
@Override @Override
public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) { public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) {
int ordinal = block.getOrdinal(); int ordinal = block.getOrdinal();
if (ordinal == 0) { if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = 1; ordinal = BlockTypesCache.ReservedIDs.AIR;
} }
setOrdinal(index, ordinal); setOrdinal(index, ordinal);
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData(); boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();

View File

@ -172,8 +172,8 @@ public class FastSchematicWriter implements ClipboardWriter {
} }
int ordinal = block.getOrdinal(); int ordinal = block.getOrdinal();
if (ordinal == 0) { if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = 1; ordinal = BlockTypesCache.ReservedIDs.AIR;
} }
char value = palette[ordinal]; char value = palette[ordinal];
if (value == Character.MAX_VALUE) { if (value == Character.MAX_VALUE) {

View File

@ -41,8 +41,8 @@ public class DistrFilter extends ForkedFilter<DistrFilter> {
@Override @Override
public final void applyBlock(FilterBlock block) { public final void applyBlock(FilterBlock block) {
int ordinal = block.getOrdinal(); int ordinal = block.getOrdinal();
if (ordinal == 0) { if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = 1; ordinal = BlockTypesCache.ReservedIDs.AIR;
} }
counter[ordinal]++; counter[ordinal]++;
} }

View File

@ -257,7 +257,7 @@ public class MultiBatchProcessor implements IBatchProcessor {
for (IBatchProcessor processor : processors) { for (IBatchProcessor processor : processors) {
scope = Math.max(scope, processor.getScope().intValue()); scope = Math.max(scope, processor.getScope().intValue());
} }
return ProcessorScope.valueOf(0); return ProcessorScope.valueOf(scope);
} }
/** /**

View File

@ -3,9 +3,9 @@ package com.fastasyncworldedit.core.extent.processor;
/** /**
* The scope of a processor. * The scope of a processor.
* Order in which processors are executed: * Order in which processors are executed:
* - ADDING_BLOCKS (processors that strictly ADD blocks to an edit ONLY) * - ADDING_BLOCKS (processors that may ADD blocks to an edit ONLY)
* - CHANGING_BLOCKS (processors that strictly ADD or CHANGE blocks being set) * - CHANGING_BLOCKS (processors that may ADD or CHANGE blocks being set)
* - REMOVING_BLOCKS (processors that string ADD, CHANGE or REMOVE blocks being set) * - REMOVING_BLOCKS (processors that may ADD, CHANGE or REMOVE blocks being set)
* - CUSTOM (processors that do not specify a SCOPE) * - CUSTOM (processors that do not specify a SCOPE)
* - READING_SET_BLOCKS (processors that do not alter blocks at all, and read the blocks that are actually going to set, e.g. history processors) * - READING_SET_BLOCKS (processors that do not alter blocks at all, and read the blocks that are actually going to set, e.g. history processors)
*/ */

View File

@ -73,11 +73,11 @@ public class HeightmapProcessor implements IBatchProcessor {
for (int y = 15; y >= 0; y--) { for (int y = 15; y >= 0; y--) {
// We don't need to actually iterate over x and z as we're both reading and writing an index // We don't need to actually iterate over x and z as we're both reading and writing an index
for (int j = 0; j < BLOCKS_PER_Y; j++) { for (int j = 0; j < BLOCKS_PER_Y; j++) {
char ordinal = 0; char ordinal = BlockTypesCache.ReservedIDs.__RESERVED__;
if (hasSectionSet) { if (hasSectionSet) {
ordinal = setSection[index(y, j)]; ordinal = setSection[index(y, j)];
} }
if (ordinal == 0) { if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
if (!hasSectionGet) { if (!hasSectionGet) {
if (!hasSectionSet) { if (!hasSectionSet) {
continue layer; continue layer;

View File

@ -178,8 +178,7 @@ public class BlockMaskBuilder {
} }
if (operator == null) { if (operator == null) {
throw new SuggestInputParseException( throw new SuggestInputParseException(
"No operator for " + input, Caption.of("fawe.error.no-operator-for-input", input),
"",
() -> Arrays.asList("=", "~", "!", "<", ">", "<=", ">=") () -> Arrays.asList("=", "~", "!", "<", ">", "<=", ">=")
); );
} }
@ -195,7 +194,7 @@ public class BlockMaskBuilder {
String value = charSequence.toString(); String value = charSequence.toString();
final PropertyKey fKey = key; final PropertyKey fKey = key;
Collection<BlockType> types = type != null ? Collections.singleton(type) : blockTypeList; Collection<BlockType> types = type != null ? Collections.singleton(type) : blockTypeList;
throw new SuggestInputParseException("No value for " + input, input, () -> { throw new SuggestInputParseException(Caption.of("fawe.error.no-value-for-input", input), () -> {
HashSet<String> values = new HashSet<>(); HashSet<String> values = new HashSet<>();
types.stream().filter(t -> t.hasProperty(fKey)).forEach(t -> { types.stream().filter(t -> t.hasProperty(fKey)).forEach(t -> {
Property<Object> p = t.getProperty(fKey); Property<Object> p = t.getProperty(fKey);
@ -287,7 +286,7 @@ public class BlockMaskBuilder {
} }
private void suggest(String input, String property, Collection<BlockType> finalTypes) throws InputParseException { private void suggest(String input, String property, Collection<BlockType> finalTypes) throws InputParseException {
throw new SuggestInputParseException(input + " does not have: " + property, input, () -> { throw new SuggestInputParseException(Caption.of("worldedit.error.parser.unknown-property", property, input), () -> {
Set<PropertyKey> keys = PropertyKeySet.empty(); Set<PropertyKey> keys = PropertyKeySet.empty();
finalTypes.forEach(t -> t.getProperties().forEach(p -> keys.add(p.getKey()))); finalTypes.forEach(t -> t.getProperties().forEach(p -> keys.add(p.getKey())));
return keys.stream().map(PropertyKey::getName) return keys.stream().map(PropertyKey::getName)

View File

@ -5,6 +5,7 @@ import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.history.UndoContext; import com.sk89q.worldedit.history.UndoContext;
import com.sk89q.worldedit.history.change.Change; import com.sk89q.worldedit.history.change.Change;
import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
public class MutableBiomeChange implements Change { public class MutableBiomeChange implements Change {
@ -13,8 +14,8 @@ public class MutableBiomeChange implements Change {
private int to; private int to;
public MutableBiomeChange() { public MutableBiomeChange() {
this.from = 0; this.from = BlockTypesCache.ReservedIDs.__RESERVED__;
this.to = 0; this.to = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
public void setBiome(int x, int y, int z, int from, int to) { public void setBiome(int x, int y, int z, int from, int to) {

View File

@ -6,6 +6,7 @@ import com.sk89q.worldedit.extent.inventory.BlockBagException;
import com.sk89q.worldedit.history.UndoContext; import com.sk89q.worldedit.history.UndoContext;
import com.sk89q.worldedit.history.change.Change; import com.sk89q.worldedit.history.change.Change;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache;
public class MutableFullBlockChange implements Change { public class MutableFullBlockChange implements Change {
@ -39,14 +40,14 @@ public class MutableFullBlockChange implements Change {
if (blockBag != null) { if (blockBag != null) {
BlockState toState = BlockState.getFromOrdinal(to); BlockState toState = BlockState.getFromOrdinal(to);
if (fromState != toState) { if (fromState != toState) {
if (allowFetch && from != 0) { if (allowFetch && from != BlockTypesCache.ReservedIDs.__RESERVED__) {
try { try {
blockBag.fetchPlacedBlock(fromState); blockBag.fetchPlacedBlock(fromState);
} catch (BlockBagException e) { } catch (BlockBagException e) {
return; return;
} }
} }
if (allowStore && to != 0) { if (allowStore && to != BlockTypesCache.ReservedIDs.__RESERVED__) {
try { try {
blockBag.storeDroppedBlock(toState); blockBag.storeDroppedBlock(toState);
} catch (BlockBagException ignored) { } catch (BlockBagException ignored) {

View File

@ -39,19 +39,27 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
/**
* This batch processor writes changes to a concrete implementation.
* {@link #processSet(IChunk, IChunkGet, IChunkSet)} is synchronized to guarantee consistency.
* To avoid many blocking threads on this method, changes are enqueued in {@link #queue}.
* This allows to keep other threads free for other work.
*/
public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final Logger LOGGER = LogManagerCompat.getLogger();
private final World world; private final World world;
private final AtomicInteger lastException = new AtomicInteger(); private final AtomicInteger lastException = new AtomicInteger();
protected AtomicInteger waitingCombined = new AtomicInteger(0); private final Semaphore workerSemaphore = new Semaphore(1, false);
protected AtomicInteger waitingAsync = new AtomicInteger(0); private final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();
protected volatile boolean closed;
protected boolean closed;
public AbstractChangeSet(World world) { public AbstractChangeSet(World world) {
this.world = world; this.world = world;
@ -65,16 +73,11 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
if (closed) { if (closed) {
return; return;
} }
waitingAsync.incrementAndGet();
TaskManager.taskManager().async(() -> { TaskManager.taskManager().async(() -> {
waitingAsync.decrementAndGet();
synchronized (waitingAsync) {
waitingAsync.notifyAll();
}
try { try {
close(); close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.catching(e);
} }
}); });
} }
@ -82,20 +85,10 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
@Override @Override
public void flush() { public void flush() {
try { try {
if (!Fawe.isMainThread()) { // drain with this thread too
while (waitingAsync.get() > 0) { drainQueue(true);
synchronized (waitingAsync) { } catch (Exception e) {
waitingAsync.wait(1000); LOGGER.catching(e);
}
}
}
while (waitingCombined.get() > 0) {
synchronized (waitingCombined) {
waitingCombined.wait(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} }
} }
@ -125,7 +118,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
} }
@Override @Override
public synchronized IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) { public final synchronized IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
int bx = chunk.getX() << 4; int bx = chunk.getX() << 4;
int bz = chunk.getZ() << 4; int bz = chunk.getZ() << 4;
@ -193,7 +186,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
} }
final int combinedFrom = from; final int combinedFrom = from;
final int combinedTo = blocksSet[index]; final int combinedTo = blocksSet[index];
if (combinedTo != 0) { if (combinedTo != BlockTypesCache.ReservedIDs.__RESERVED__) {
add(xx, yy, zz, combinedFrom, combinedTo); add(xx, yy, zz, combinedFrom, combinedTo);
} }
} }
@ -306,12 +299,12 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
BaseBlock to = change.getCurrent(); BaseBlock to = change.getCurrent();
add(loc, from, to); add(loc, from, to);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); LOGGER.catching(e);
} }
} }
public boolean isEmpty() { public boolean isEmpty() {
return waitingCombined.get() == 0 && waitingAsync.get() == 0 && size() == 0; return queue.isEmpty() && workerSemaphore.availablePermits() == 1 && size() == 0;
} }
public void add(BlockVector3 loc, BaseBlock from, BaseBlock to) { public void add(BlockVector3 loc, BaseBlock from, BaseBlock to) {
@ -353,7 +346,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
add(x, y, z, combinedFrom, combinedTo); add(x, y, z, combinedFrom, combinedTo);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); LOGGER.catching(e);
} }
} }
@ -362,7 +355,6 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
} }
public Future<?> addWriteTask(final Runnable writeTask, final boolean completeNow) { public Future<?> addWriteTask(final Runnable writeTask, final boolean completeNow) {
AbstractChangeSet.this.waitingCombined.incrementAndGet();
Runnable wrappedTask = () -> { Runnable wrappedTask = () -> {
try { try {
writeTask.run(); writeTask.run();
@ -372,25 +364,55 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
} else { } else {
int hash = t.getMessage().hashCode(); int hash = t.getMessage().hashCode();
if (lastException.getAndSet(hash) != hash) { if (lastException.getAndSet(hash) != hash) {
t.printStackTrace(); LOGGER.catching(t);
}
}
} finally {
if (AbstractChangeSet.this.waitingCombined.decrementAndGet() <= 0) {
synchronized (AbstractChangeSet.this.waitingAsync) {
AbstractChangeSet.this.waitingAsync.notifyAll();
}
synchronized (AbstractChangeSet.this.waitingCombined) {
AbstractChangeSet.this.waitingCombined.notifyAll();
} }
} }
} }
}; };
if (completeNow) { if (completeNow) {
wrappedTask.run(); wrappedTask.run();
return Futures.immediateCancelledFuture(); return Futures.immediateVoidFuture();
} else { } else {
return Fawe.instance().getQueueHandler().submit(wrappedTask); CompletableFuture<?> task = new CompletableFuture<>();
queue.add(() -> {
wrappedTask.run();
task.complete(null);
});
// make sure changes are processed
triggerWorker();
return task;
}
}
private void triggerWorker() {
if (workerSemaphore.availablePermits() == 0) {
return; // fast path to avoid additional tasks: a worker is already draining the queue
}
// create a new worker to drain the current queue
Fawe.instance().getQueueHandler().async(() -> drainQueue(false));
}
private void drainQueue(boolean ignoreRunningState) {
if (!workerSemaphore.tryAcquire()) {
if (ignoreRunningState) {
// ignoreRunningState means we want to block
// even if another thread is already draining
try {
workerSemaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} else {
return; // another thread is draining the queue already, ignore
}
}
try {
Runnable next;
while ((next = queue.poll()) != null) { // process all tasks in the queue
next.run();
}
} finally {
workerSemaphore.release();
} }
} }

View File

@ -25,8 +25,6 @@ public class AbstractDelegateChangeSet extends AbstractChangeSet {
public AbstractDelegateChangeSet(AbstractChangeSet parent) { public AbstractDelegateChangeSet(AbstractChangeSet parent) {
super(parent.getWorld()); super(parent.getWorld());
this.parent = parent; this.parent = parent;
this.waitingCombined = parent.waitingCombined;
this.waitingAsync = parent.waitingAsync;
} }
public final AbstractChangeSet getParent() { public final AbstractChangeSet getParent() {

View File

@ -258,7 +258,7 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
if (blockSize > 0) { if (blockSize > 0) {
return false; return false;
} }
if (waitingCombined.get() != 0 || waitingAsync.get() != 0) { if (!super.isEmpty()) {
return false; return false;
} }
flush(); flush();

View File

@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.limit;
import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.FaweCache;
import java.util.Collections;
import java.util.Set; import java.util.Set;
public class FaweLimit { public class FaweLimit {
@ -114,10 +115,10 @@ public class FaweLimit {
MAX.FAST_PLACEMENT = true; MAX.FAST_PLACEMENT = true;
MAX.CONFIRM_LARGE = true; MAX.CONFIRM_LARGE = true;
MAX.RESTRICT_HISTORY_TO_REGIONS = false; MAX.RESTRICT_HISTORY_TO_REGIONS = false;
MAX.STRIP_NBT = null; MAX.STRIP_NBT = Collections.emptySet();
MAX.UNIVERSAL_DISALLOWED_BLOCKS = false; MAX.UNIVERSAL_DISALLOWED_BLOCKS = false;
MAX.DISALLOWED_BLOCKS = null; MAX.DISALLOWED_BLOCKS = Collections.emptySet();
MAX.REMAP_PROPERTIES = null; MAX.REMAP_PROPERTIES = Collections.emptySet();
} }
public boolean MAX_CHANGES() { public boolean MAX_CHANGES() {
@ -241,7 +242,7 @@ public class FaweLimit {
&& FAST_PLACEMENT && FAST_PLACEMENT
&& !RESTRICT_HISTORY_TO_REGIONS && !RESTRICT_HISTORY_TO_REGIONS
&& (STRIP_NBT == null || STRIP_NBT.isEmpty()) && (STRIP_NBT == null || STRIP_NBT.isEmpty())
&& !UNIVERSAL_DISALLOWED_BLOCKS // && !UNIVERSAL_DISALLOWED_BLOCKS --> do not include this, it effectively has no relevance
&& (DISALLOWED_BLOCKS == null || DISALLOWED_BLOCKS.isEmpty()) && (DISALLOWED_BLOCKS == null || DISALLOWED_BLOCKS.isEmpty())
&& (REMAP_PROPERTIES == null || REMAP_PROPERTIES.isEmpty()); && (REMAP_PROPERTIES == null || REMAP_PROPERTIES.isEmpty());
} }

View File

@ -9,7 +9,20 @@ import java.util.Map;
public class BlockVector3ChunkMap<T> implements IAdaptedMap<BlockVector3, T, Integer, T> { public class BlockVector3ChunkMap<T> implements IAdaptedMap<BlockVector3, T, Integer, T> {
private final Int2ObjectArrayMap<T> map = new Int2ObjectArrayMap<>(); private final Int2ObjectArrayMap<T> map;
public BlockVector3ChunkMap() {
map = new Int2ObjectArrayMap<>();
}
/**
* Create a new instance that is a copy of an existing map
*
* @param map existing map to copy
*/
public BlockVector3ChunkMap(BlockVector3ChunkMap<T> map) {
this.map = new Int2ObjectArrayMap<>(map.getParent());
}
@Override @Override
public Map<Integer, T> getParent() { public Map<Integer, T> getParent() {

View File

@ -7,6 +7,7 @@ import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Map; import java.util.Map;
@ -71,7 +72,7 @@ public interface IBatchProcessor {
if (arr != null) { if (arr != null) {
int index = (minY & 15) << 8; int index = (minY & 15) << 8;
for (int i = 0; i < index; i++) { for (int i = 0; i < index; i++) {
arr[i] = 0; arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} else { } else {
arr = new char[4096]; arr = new char[4096];
@ -89,7 +90,7 @@ public interface IBatchProcessor {
if (arr != null) { if (arr != null) {
int index = ((maxY + 1) & 15) << 8; int index = ((maxY + 1) & 15) << 8;
for (int i = index; i < arr.length; i++) { for (int i = index; i < arr.length; i++) {
arr[i] = 0; arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} else { } else {
arr = new char[4096]; arr = new char[4096];
@ -130,7 +131,7 @@ public interface IBatchProcessor {
if (arr != null) { if (arr != null) {
int index = (minY & 15) << 8; int index = (minY & 15) << 8;
for (int i = index; i < 4096; i++) { for (int i = index; i < 4096; i++) {
arr[i] = 0; arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} }
set.setBlocks(layer, arr); set.setBlocks(layer, arr);
@ -139,7 +140,7 @@ public interface IBatchProcessor {
if (arr != null) { if (arr != null) {
int index = ((maxY + 1) & 15) << 8; int index = ((maxY + 1) & 15) << 8;
for (int i = 0; i < index; i++) { for (int i = 0; i < index; i++) {
arr[i] = 0; arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} }
set.setBlocks(layer, arr); set.setBlocks(layer, arr);

View File

@ -8,8 +8,9 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.HashMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -95,7 +96,7 @@ public interface IChunkSet extends IBlocks, OutputExtent {
} }
default Map<HeightMapType, int[]> getHeightMaps() { default Map<HeightMapType, int[]> getHeightMaps() {
return new HashMap<>(); return new EnumMap<>(HeightMapType.class);
} }
@Override @Override
@ -115,4 +116,15 @@ public interface IChunkSet extends IBlocks, OutputExtent {
*/ */
boolean hasBiomes(int layer); boolean hasBiomes(int layer);
/**
* Create an entirely distinct copy of this SET instance. All mutable data must be copied to sufficiently prevent leakage
* between the copy and the original.
*
* @return distinct new {@link IChunkSet instance}
*/
@Nonnull
default IChunkSet createCopy() {
return this;
}
} }

View File

@ -14,6 +14,7 @@ import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
import com.fastasyncworldedit.core.util.MemUtil; import com.fastasyncworldedit.core.util.MemUtil;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal; import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal;
import com.fastasyncworldedit.core.util.task.FaweForkJoinWorkerThreadFactory;
import com.fastasyncworldedit.core.wrappers.WorldWrapper; import com.fastasyncworldedit.core.wrappers.WorldWrapper;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
@ -39,10 +40,41 @@ import java.util.function.Supplier;
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
public abstract class QueueHandler implements Trimable, Runnable { public abstract class QueueHandler implements Trimable, Runnable {
private final ForkJoinPool forkJoinPoolPrimary = new ForkJoinPool(); private static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool();
/**
* 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.
*/
private final ForkJoinPool forkJoinPoolPrimary = new ForkJoinPool(
PROCESSORS,
new FaweForkJoinWorkerThreadFactory("FAWE Fork Join Pool Primary - %s"),
null,
false
);
/**
* 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.
*/
private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool(
PROCESSORS,
new FaweForkJoinWorkerThreadFactory("FAWE Fork Join Pool Secondary - %s"),
null,
false
);
/**
* Main "work-horse" queue for FAWE. Handles chunk submission (and chunk submission alone). Blocking in order to forcibly
* prevent overworking/over-submission of chunk process tasks.
*/
private final ThreadPoolExecutor blockingExecutor = FaweCache.INSTANCE.newBlockingExecutor(); private final ThreadPoolExecutor blockingExecutor = FaweCache.INSTANCE.newBlockingExecutor();
/**
* Queue for tasks to be completed on the main thread. These take priority of tasks submitted to syncWhenFree queue
*/
private final ConcurrentLinkedQueue<FutureTask> syncTasks = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<FutureTask> syncTasks = new ConcurrentLinkedQueue<>();
/**
* Queue for tasks to be completed on the main thread. These are completed only if and when there is time left in a tick
* after completing all tasks in the syncTasks queue
*/
private final ConcurrentLinkedQueue<FutureTask> syncWhenFree = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<FutureTask> syncWhenFree = new ConcurrentLinkedQueue<>();
private final Map<World, WeakReference<IChunkCache<IChunkGet>>> chunkGetCache = new HashMap<>(); private final Map<World, WeakReference<IChunkCache<IChunkGet>>> chunkGetCache = new HashMap<>();
@ -53,9 +85,8 @@ public abstract class QueueHandler implements Trimable, Runnable {
*/ */
private long last; private long last;
private long allocate = 50; private long allocate = 50;
private double targetTPS = 18;
public QueueHandler() { protected QueueHandler() {
TaskManager.taskManager().repeat(this, 1); TaskManager.taskManager().repeat(this, 1);
} }
@ -81,13 +112,19 @@ public abstract class QueueHandler implements Trimable, Runnable {
} }
} }
/**
* Get if the {@code blockingExecutor} is saturated with tasks or not. Under-utilisation implies the queue has space for
* more submissions.
*
* @return true if {@code blockingExecutor} is not saturated with tasks
*/
public boolean isUnderutilized() { public boolean isUnderutilized() {
return blockingExecutor.getActiveCount() < blockingExecutor.getMaximumPoolSize(); return blockingExecutor.getActiveCount() < blockingExecutor.getMaximumPoolSize();
} }
private long getAllocate() { private long getAllocate() {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
targetTPS = 18 - Math.max(Settings.settings().QUEUE.EXTRA_TIME_MS * 0.05, 0); double targetTPS = 18 - Math.max(Settings.settings().QUEUE.EXTRA_TIME_MS * 0.05, 0);
long diff = 50 + this.last - (this.last = now); long diff = 50 + this.last - (this.last = now);
long absDiff = Math.abs(diff); long absDiff = Math.abs(diff);
if (diff == 0) { if (diff == 0) {
@ -126,6 +163,10 @@ public abstract class QueueHandler implements Trimable, Runnable {
} while (System.currentTimeMillis() - start < currentAllocate); } while (System.currentTimeMillis() - start < currentAllocate);
} }
/**
* @deprecated For removal without replacement.
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public <T extends Future<T>> void complete(Future<T> task) { public <T extends Future<T>> void complete(Future<T> task) {
try { try {
while (task != null) { while (task != null) {
@ -136,49 +177,140 @@ public abstract class QueueHandler implements Trimable, Runnable {
} }
} }
/**
* Complete a task in the {@code forkJoinPoolSecondary} queue. 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.
*
* @param run Runnable to run
* @param value Value to return when done
* @param <T> Value type
* @return Future for submitted task
*/
public <T> Future<T> async(Runnable run, T value) { public <T> Future<T> async(Runnable run, T value) {
return forkJoinPoolSecondary.submit(run, value); return forkJoinPoolSecondary.submit(run, value);
} }
/**
* Complete a task in the {@code forkJoinPoolSecondary} queue. 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.
*
* @param run Runnable to run
* @return Future for submitted task
*/
public Future<?> async(Runnable run) { public Future<?> async(Runnable run) {
return forkJoinPoolSecondary.submit(run); return forkJoinPoolSecondary.submit(run);
} }
/**
* Complete a task in the {@code forkJoinPoolSecondary} queue. 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.
*
* @param call Callable to run
* @param <T> Return value type
* @return Future for submitted task
*/
public <T> Future<T> async(Callable<T> call) { public <T> Future<T> async(Callable<T> call) {
return forkJoinPoolSecondary.submit(call); return forkJoinPoolSecondary.submit(call);
} }
public ForkJoinTask submit(Runnable call) { /**
return forkJoinPoolPrimary.submit(call); * Complete a task in the {@code forkJoinPoolPrimary} queue. 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.
*
* @param run Task to run
* @return {@link ForkJoinTask} representing task being run
*/
public ForkJoinTask submit(Runnable run) {
return forkJoinPoolPrimary.submit(run);
} }
/**
* Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to
* maintain approx. 18 tps.
*
* @param run Task to run
* @param <T> Value type
* @return Future representing task
*/
public <T> Future<T> sync(Runnable run) { public <T> Future<T> sync(Runnable run) {
return sync(run, syncTasks); return sync(run, syncTasks);
} }
/**
* Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to
* maintain approx. 18 tps.
*
* @param call Task to run
* @param <T> Value type
* @return Future representing task
*/
public <T> Future<T> sync(Callable<T> call) throws Exception { public <T> Future<T> sync(Callable<T> call) throws Exception {
return sync(call, syncTasks); return sync(call, syncTasks);
} }
public <T> Future<T> sync(Supplier<T> call) { /**
return sync(call, syncTasks); * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to
* maintain approx. 18 tps.
*
* @param supplier Task to run
* @param <T> Value type
* @return Future representing task
*/
public <T> Future<T> sync(Supplier<T> supplier) {
return sync(supplier, syncTasks);
} }
// Lower priority sync task (runs only when there are no other tasks) /**
* Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to
* maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed
* only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods.
*
* @param run Task to run
* @param value Value to return when done
* @param <T> Value type
* @return Future representing task
*/
public <T> Future<T> syncWhenFree(Runnable run, T value) { public <T> Future<T> syncWhenFree(Runnable run, T value) {
return sync(run, value, syncWhenFree); return sync(run, value, syncWhenFree);
} }
/**
* Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to
* maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed
* only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods.
*
* @param run Task to run
* @param <T> Value type
* @return Future representing task
*/
public <T> Future<T> syncWhenFree(Runnable run) { public <T> Future<T> syncWhenFree(Runnable run) {
return sync(run, syncWhenFree); return sync(run, syncWhenFree);
} }
/**
* Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to
* maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed
* only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods.
*
* @param call Task to run
* @param <T> Value type
* @return Future representing task
*/
public <T> Future<T> syncWhenFree(Callable<T> call) throws Exception { public <T> Future<T> syncWhenFree(Callable<T> call) throws Exception {
return sync(call, syncWhenFree); return sync(call, syncWhenFree);
} }
public <T> Future<T> syncWhenFree(Supplier<T> call) { /**
return sync(call, syncWhenFree); * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to
* maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed
* only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods.
*
* @param supplier Task to run
* @param <T> Value type
* @return Future representing task
*/
public <T> Future<T> syncWhenFree(Supplier<T> supplier) {
return sync(supplier, syncWhenFree);
} }
private <T> Future<T> sync(Runnable run, T value, Queue<FutureTask> queue) { private <T> Future<T> sync(Runnable run, T value, Queue<FutureTask> queue) {
@ -229,6 +361,15 @@ public abstract class QueueHandler implements Trimable, Runnable {
} }
} }
/**
* Internal use only. Specifically for submitting {@link IQueueChunk} for "processing" an edit. Submits to the blocking
* executor, the main "work-horse" queue for FAWE. Handles chunk submission (and chunk submission alone). Blocking in order
* to forcibly prevent overworking/over-submission of chunk process tasks.
*
* @param chunk chunk
* @param <T>
* @return Future representing task
*/
public <T extends Future<T>> T submit(IQueueChunk<T> chunk) { public <T extends Future<T>> T submit(IQueueChunk<T> chunk) {
// if (MemUtil.isMemoryFree()) { TODO NOT IMPLEMENTED - optimize this // if (MemUtil.isMemoryFree()) { TODO NOT IMPLEMENTED - optimize this
// return (T) forkJoinPoolSecondary.submit(chunk); // return (T) forkJoinPoolSecondary.submit(chunk);
@ -260,6 +401,9 @@ public abstract class QueueHandler implements Trimable, Runnable {
return new SingleThreadQueueExtent(); return new SingleThreadQueueExtent();
} }
/**
* Sets the current thread's {@link IQueueExtent} instance in the queue pool to null.
*/
public void unCache() { public void unCache() {
queuePool.set(null); queuePool.set(null);
} }
@ -272,14 +416,58 @@ public abstract class QueueHandler implements Trimable, Runnable {
return queue; return queue;
} }
public abstract void startSet(boolean parallel); /**
* Indicate a "set" task is being started.
*
* @param parallel if the "set" being started is parallel/async
* @deprecated To be replaced by better-named {@link QueueHandler#startUnsafe(boolean)} )}
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public void startSet(boolean parallel) {
startUnsafe(parallel);
}
public abstract void endSet(boolean parallel);
/**
* Indicate a "set" task is ending.
*
* @param parallel if the "set" being started is parallel/async
* @deprecated To be replaced by better-named {@link QueueHandler#endUnsafe(boolean)} )}
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public void endSet(boolean parallel) {
startUnsafe(parallel);
}
/**
* Indicate an unsafe task is starting. Physics are frozen, async catchers disabled, etc. for the duration of the task
*
* @param parallel If the task is being run async and/or in parallel
*/
public abstract void startUnsafe(boolean parallel);
/**
* Indicate a/the unsafe task submitted after a {@link QueueHandler#startUnsafe(boolean)} call has ended.
*
* @param parallel If the task was being run async and/or in parallel
*/
public abstract void endUnsafe(boolean parallel);
/**
* Create a new queue for a given world.
*/
public IQueueExtent<IQueueChunk> getQueue(World world) { public IQueueExtent<IQueueChunk> getQueue(World world) {
return getQueue(world, null, null); return getQueue(world, null, null);
} }
/**
* Create a new queue for a given world.
*
* @param world World to create queue for
* @param processor existing processor to set to queue or null
* @param postProcessor existing post-processor to set to queue or null
* @return New queue for given world
*/
public IQueueExtent<IQueueChunk> getQueue(World world, IBatchProcessor processor, IBatchProcessor postProcessor) { public IQueueExtent<IQueueChunk> getQueue(World world, IBatchProcessor processor, IBatchProcessor postProcessor) {
final IQueueExtent<IQueueChunk> queue = pool(); final IQueueExtent<IQueueChunk> queue = pool();
IChunkCache<IChunkGet> cacheGet = getOrCreateWorldCache(world); IChunkCache<IChunkGet> cacheGet = getOrCreateWorldCache(world);
@ -294,6 +482,12 @@ public abstract class QueueHandler implements Trimable, Runnable {
return queue; return queue;
} }
/**
* Trims each chunk GET cache
*
* @param aggressive if each chunk GET cache should be trimmed aggressively
* @return true if all chunk GET caches could be trimmed
*/
@Override @Override
public boolean trim(boolean aggressive) { public boolean trim(boolean aggressive) {
boolean result = true; boolean result = true;

View File

@ -275,8 +275,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
* Get a new IChunk from either the pool, or create a new one<br> + Initialize it at the * Get a new IChunk from either the pool, or create a new one<br> + Initialize it at the
* coordinates * coordinates
* *
* @param chunkX * @param chunkX X chunk coordinate
* @param chunkZ * @param chunkZ Z chunk coordinate
* @return IChunk * @return IChunk
*/ */
private ChunkHolder poolOrCreate(int chunkX, int chunkZ) { private ChunkHolder poolOrCreate(int chunkX, int chunkZ) {
@ -309,19 +309,11 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
// If queueing is enabled AND either of the following // If queueing is enabled AND either of the following
// - memory is low & queue size > num threads + 8 // - memory is low & queue size > num threads + 8
// - queue size > target size and primary queue has less than num threads submissions // - queue size > target size and primary queue has less than num threads submissions
if (enabledQueue && ((lowMem && size > Settings.settings().QUEUE.PARALLEL_THREADS + 8) || (size > Settings.settings().QUEUE.TARGET_SIZE && Fawe int targetSize = lowMem ? Settings.settings().QUEUE.PARALLEL_THREADS + 8 : Settings.settings().QUEUE.TARGET_SIZE;
.instance() if (enabledQueue && size > targetSize && (lowMem || Fawe.instance().getQueueHandler().isUnderutilized())) {
.getQueueHandler()
.isUnderutilized()))) {
chunk = chunks.removeFirst(); chunk = chunks.removeFirst();
final Future future = submitUnchecked(chunk); final Future future = submitUnchecked(chunk);
if (future != null && !future.isDone()) { if (future != null && !future.isDone()) {
final int targetSize;
if (lowMem) {
targetSize = Settings.settings().QUEUE.PARALLEL_THREADS + 8;
} else {
targetSize = Settings.settings().QUEUE.TARGET_SIZE;
}
pollSubmissions(targetSize, lowMem); pollSubmissions(targetSize, lowMem);
submissions.add(future); submissions.add(future);
} }

View File

@ -15,12 +15,11 @@ import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.EnumMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.IntStream;
public class CharSetBlocks extends CharBlocks implements IChunkSet { public class CharSetBlocks extends CharBlocks implements IChunkSet {
@ -40,7 +39,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
public BlockVector3ChunkMap<CompoundTag> tiles; public BlockVector3ChunkMap<CompoundTag> tiles;
public HashSet<CompoundTag> entities; public HashSet<CompoundTag> entities;
public HashSet<UUID> entityRemoves; public HashSet<UUID> entityRemoves;
public Map<HeightMapType, int[]> heightMaps; public EnumMap<HeightMapType, int[]> heightMaps;
private boolean fastMode = false; private boolean fastMode = false;
private int bitMask = -1; private int bitMask = -1;
@ -93,7 +92,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public Map<HeightMapType, int[]> getHeightMaps() { public Map<HeightMapType, int[]> getHeightMaps() {
return heightMaps == null ? new HashMap<>() : heightMaps; return heightMaps == null ? new EnumMap<>(HeightMapType.class) : heightMaps;
} }
@Override @Override
@ -177,7 +176,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public void setHeightMap(HeightMapType type, int[] heightMap) { public void setHeightMap(HeightMapType type, int[] heightMap) {
if (heightMaps == null) { if (heightMaps == null) {
heightMaps = new HashMap<>(); heightMaps = new EnumMap<>(HeightMapType.class);
} }
heightMaps.put(type, heightMap); heightMaps.put(type, heightMap);
} }
@ -306,8 +305,12 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|| (heightMaps != null && !heightMaps.isEmpty())) { || (heightMaps != null && !heightMaps.isEmpty())) {
return false; return false;
} }
//noinspection SimplifyStreamApiCallChains - this is faster than using #noneMatch for (int i = minSectionPosition; i <= maxSectionPosition; i++) {
return !IntStream.range(minSectionPosition, maxSectionPosition + 1).anyMatch(this::hasSection); if (hasSection(i)) {
return false;
}
}
return true;
} }
@Override @Override
@ -316,6 +319,9 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
tiles = null; tiles = null;
entities = null; entities = null;
entityRemoves = null; entityRemoves = null;
light = null;
skyLight = null;
heightMaps = null;
super.reset(); super.reset();
return null; return null;
} }
@ -329,6 +335,62 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
return biomes != null && biomes[layer] != null; return biomes != null && biomes[layer] != null;
} }
@Override
public ThreadUnsafeCharBlocks createCopy() {
char[][] blocksCopy = new char[sectionCount][];
for (int i = 0; i < sectionCount; i++) {
if (blocks[i] != null) {
blocksCopy[i] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER];
System.arraycopy(blocks[i], 0, blocksCopy[i], 0, FaweCache.INSTANCE.BLOCKS_PER_LAYER);
}
}
BiomeType[][] biomesCopy;
if (biomes == null) {
biomesCopy = null;
} else {
biomesCopy = new BiomeType[sectionCount][];
for (int i = 0; i < sectionCount; i++) {
if (biomes[i] != null) {
biomesCopy[i] = new BiomeType[biomes[i].length];
System.arraycopy(biomes[i], 0, biomesCopy[i], 0, biomes[i].length);
}
}
}
char[][] lightCopy = createLightCopy(light, sectionCount);
char[][] skyLightCopy = createLightCopy(skyLight, sectionCount);
return new ThreadUnsafeCharBlocks(
blocksCopy,
minSectionPosition,
maxSectionPosition,
biomesCopy,
sectionCount,
lightCopy,
skyLightCopy,
tiles != null ? new BlockVector3ChunkMap<>(tiles) : null,
entities != null ? new HashSet<>(entities) : null,
entityRemoves != null ? new HashSet<>(entityRemoves) : null,
heightMaps != null ? new EnumMap<>(heightMaps) : null,
defaultOrdinal(),
fastMode,
bitMask
);
}
static char[][] createLightCopy(char[][] lightArr, int sectionCount) {
if (lightArr == null) {
return null;
} else {
char[][] lightCopy = new char[sectionCount][];
for (int i = 0; i < sectionCount; i++) {
if (lightArr[i] != null) {
lightCopy[i] = new char[lightArr[i].length];
System.arraycopy(lightArr[i], 0, lightCopy[i], 0, lightArr[i].length);
}
}
return lightCopy;
}
}
@Override @Override
public char[] load(final int layer) { public char[] load(final int layer) {
updateSectionIndexRange(layer); updateSectionIndexRange(layer);
@ -348,67 +410,47 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
if (layer < minSectionPosition) { if (layer < minSectionPosition) {
int diff = minSectionPosition - layer; int diff = minSectionPosition - layer;
sectionCount += diff; sectionCount += diff;
char[][] tmpBlocks = new char[sectionCount][];
Section[] tmpSections = new Section[sectionCount];
Object[] tmpSectionLocks = new Object[sectionCount];
System.arraycopy(blocks, 0, tmpBlocks, diff, blocks.length);
System.arraycopy(sections, 0, tmpSections, diff, sections.length);
System.arraycopy(sectionLocks, 0, tmpSectionLocks, diff, sections.length);
for (int i = 0; i < diff; i++) {
tmpSections[i] = EMPTY;
tmpSectionLocks[i] = new Object();
}
blocks = tmpBlocks;
sections = tmpSections;
sectionLocks = tmpSectionLocks;
minSectionPosition = layer; minSectionPosition = layer;
if (biomes != null) { resizeSectionsArrays(diff, false); // prepend new layer(s)
BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64];
System.arraycopy(biomes, 0, tmpBiomes, diff, biomes.length);
biomes = tmpBiomes;
}
if (light != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(light, 0, tmplight, diff, light.length);
light = tmplight;
}
if (skyLight != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(skyLight, 0, tmplight, diff, skyLight.length);
skyLight = tmplight;
}
} else { } else {
int diff = layer - maxSectionPosition; int diff = layer - maxSectionPosition;
sectionCount += diff; sectionCount += diff;
char[][] tmpBlocks = new char[sectionCount][];
Section[] tmpSections = new Section[sectionCount];
Object[] tmpSectionLocks = new Object[sectionCount];
System.arraycopy(blocks, 0, tmpBlocks, 0, blocks.length);
System.arraycopy(sections, 0, tmpSections, 0, sections.length);
System.arraycopy(sectionLocks, 0, tmpSectionLocks, 0, sections.length);
for (int i = sectionCount - diff; i < sectionCount; i++) {
tmpSections[i] = EMPTY;
tmpSectionLocks[i] = new Object();
}
blocks = tmpBlocks;
sections = tmpSections;
sectionLocks = tmpSectionLocks;
maxSectionPosition = layer; maxSectionPosition = layer;
if (biomes != null) { resizeSectionsArrays(diff, true); // append new layer(s)
BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64]; }
System.arraycopy(biomes, 0, tmpBiomes, 0, biomes.length); }
biomes = tmpBiomes;
} private void resizeSectionsArrays(int diff, boolean appendNew) {
if (light != null) { char[][] tmpBlocks = new char[sectionCount][];
char[][] tmplight = new char[sectionCount][]; Section[] tmpSections = new Section[sectionCount];
System.arraycopy(light, 0, tmplight, 0, light.length); Object[] tmpSectionLocks = new Object[sectionCount];
light = tmplight; int destPos = appendNew ? 0 : diff;
} System.arraycopy(blocks, 0, tmpBlocks, destPos, blocks.length);
if (skyLight != null) { System.arraycopy(sections, 0, tmpSections, destPos, sections.length);
char[][] tmplight = new char[sectionCount][]; System.arraycopy(sectionLocks, 0, tmpSectionLocks, destPos, sections.length);
System.arraycopy(skyLight, 0, tmplight, 0, skyLight.length); int toFillFrom = appendNew ? sectionCount - diff : 0;
skyLight = tmplight; int toFillTo = appendNew ? sectionCount : diff;
} for (int i = toFillFrom; i < toFillTo; i++) {
tmpSections[i] = EMPTY;
tmpSectionLocks[i] = new Object();
}
blocks = tmpBlocks;
sections = tmpSections;
sectionLocks = tmpSectionLocks;
if (biomes != null) {
BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64];
System.arraycopy(biomes, 0, tmpBiomes, destPos, biomes.length);
biomes = tmpBiomes;
}
if (light != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(light, 0, tmplight, destPos, light.length);
light = tmplight;
}
if (skyLight != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(skyLight, 0, tmplight, destPos, skyLight.length);
skyLight = tmplight;
} }
} }

View File

@ -0,0 +1,531 @@
package com.fastasyncworldedit.core.queue.implementation.blocks;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType;
import com.fastasyncworldedit.core.math.BlockVector3ChunkMap;
import com.fastasyncworldedit.core.queue.IBlocks;
import com.fastasyncworldedit.core.queue.IChunkSet;
import com.sk89q.jnbt.CompoundTag;
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.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Equivalent to {@link CharSetBlocks} without any attempt to make thread-safe for improved performance.
* This is currently only used as a "copy" of {@link CharSetBlocks} to provide to
* {@link com.fastasyncworldedit.core.queue.IBatchProcessor} instances for processing without overlapping the continuing edit.
*
* @since 2.6.2
*/
public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private final char defaultOrdinal;
private char[][] blocks;
private int minSectionPosition;
private int maxSectionPosition;
private int sectionCount;
private BiomeType[][] biomes;
private char[][] light;
private char[][] skyLight;
private BlockVector3ChunkMap<CompoundTag> tiles;
private HashSet<CompoundTag> entities;
private HashSet<UUID> entityRemoves;
private Map<HeightMapType, int[]> heightMaps;
private boolean fastMode;
private int bitMask;
/**
* New instance given the data stored in a {@link CharSetBlocks} instance.
*
* @since 2.6.2
*/
ThreadUnsafeCharBlocks(
char[][] blocks,
int minSectionPosition,
int maxSectionPosition,
BiomeType[][] biomes,
int sectionCount,
char[][] light,
char[][] skyLight,
BlockVector3ChunkMap<CompoundTag> tiles,
HashSet<CompoundTag> entities,
HashSet<UUID> entityRemoves,
Map<HeightMapType, int[]> heightMaps,
char defaultOrdinal,
boolean fastMode,
int bitMask
) {
this.blocks = blocks;
this.minSectionPosition = minSectionPosition;
this.maxSectionPosition = maxSectionPosition;
this.biomes = biomes;
this.sectionCount = sectionCount;
this.light = light;
this.skyLight = skyLight;
this.tiles = tiles;
this.entities = entities;
this.entityRemoves = entityRemoves;
this.heightMaps = heightMaps;
this.defaultOrdinal = defaultOrdinal;
this.fastMode = fastMode;
this.bitMask = bitMask;
}
@Override
public boolean hasSection(int layer) {
layer -= minSectionPosition;
return layer >= 0 && layer < blocks.length && blocks[layer] != null && blocks[layer].length == FaweCache.INSTANCE.BLOCKS_PER_LAYER;
}
@Override
public char[] load(int layer) {
updateSectionIndexRange(layer);
layer -= minSectionPosition;
char[] arr = blocks[layer];
if (arr == null) {
arr = blocks[layer] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER];
}
return arr;
}
@Nullable
@Override
public char[] loadIfPresent(int layer) {
if (layer < minSectionPosition || layer > maxSectionPosition) {
return null;
}
layer -= minSectionPosition;
return blocks[layer];
}
@Override
public Map<BlockVector3, CompoundTag> getTiles() {
return tiles == null ? Collections.emptyMap() : tiles;
}
@Override
public CompoundTag getTile(int x, int y, int z) {
return tiles.get(x, y, z);
}
@Override
public Set<CompoundTag> getEntities() {
return entities == null ? Collections.emptySet() : entities;
}
@Override
public Map<HeightMapType, int[]> getHeightMaps() {
return heightMaps == null ? new HashMap<>() : heightMaps;
}
@Override
public void removeSectionLighting(int layer, boolean sky) {
updateSectionIndexRange(layer);
layer -= minSectionPosition;
if (light == null) {
light = new char[sectionCount][];
}
if (light[layer] == null) {
light[layer] = new char[4096];
}
Arrays.fill(light[layer], (char) 0);
if (sky) {
if (skyLight == null) {
skyLight = new char[sectionCount][];
}
if (skyLight[layer] == null) {
skyLight[layer] = new char[4096];
}
Arrays.fill(skyLight[layer], (char) 0);
}
}
@Override
public boolean trim(boolean aggressive, int layer) {
return false;
}
@Override
public int getSectionCount() {
return sectionCount;
}
@Override
public int getMaxSectionPosition() {
return maxSectionPosition;
}
@Override
public int getMinSectionPosition() {
return minSectionPosition;
}
public char get(int x, int y, int z) {
int layer = (y >> 4);
if (!hasSection(layer)) {
return defaultOrdinal;
}
final int index = (y & 15) << 8 | z << 4 | x;
return blocks[layer - minSectionPosition][index];
}
@Override
public BiomeType getBiomeType(int x, int y, int z) {
int layer;
if (biomes == null || (y >> 4) < minSectionPosition || (y >> 4) > maxSectionPosition) {
return null;
} else if (biomes[(layer = (y >> 4) - minSectionPosition)] == null) {
return null;
}
return biomes[layer][(y & 15) >> 2 | (z >> 2) << 2 | x >> 2];
}
@Override
public BlockState getBlock(int x, int y, int z) {
return BlockTypesCache.states[get(x, y, z)];
}
@Override
public boolean setBiome(int x, int y, int z, BiomeType biome) {
updateSectionIndexRange(y >> 4);
int layer = (y >> 4) - minSectionPosition;
if (biomes == null) {
biomes = new BiomeType[sectionCount][];
biomes[layer] = new BiomeType[64];
} else if (biomes[layer] == null) {
biomes[layer] = new BiomeType[64];
}
biomes[layer][(y & 12) << 2 | (z & 12) | (x & 12) >> 2] = biome;
return true;
}
@Override
public boolean setBiome(BlockVector3 position, BiomeType biome) {
return setBiome(position.getX(), position.getY(), position.getZ(), biome);
}
public void set(int x, int y, int z, char value) {
final int layer = y >> 4;
final int index = (y & 15) << 8 | z << 4 | x;
try {
blocks[layer][index] = value;
} catch (ArrayIndexOutOfBoundsException exception) {
LOGGER.error("Tried setting block at coordinates (" + x + "," + y + "," + z + ")");
assert Fawe.platform() != null;
LOGGER.error("Layer variable was = {}", layer, exception);
}
}
@Override
public <T extends BlockStateHolder<T>> boolean setBlock(int x, int y, int z, T holder) {
updateSectionIndexRange(y >> 4);
set(x, y, z, holder.getOrdinalChar());
holder.applyTileEntity(this, x, y, z);
return true;
}
@Override
public void setBlocks(int layer, char[] data) {
updateSectionIndexRange(layer);
layer -= minSectionPosition;
this.blocks[layer] = data;
}
@Override
public boolean isEmpty() {
if (biomes != null
|| light != null
|| skyLight != null
|| (entities != null && !entities.isEmpty())
|| (tiles != null && !tiles.isEmpty())
|| (entityRemoves != null && !entityRemoves.isEmpty())
|| (heightMaps != null && !heightMaps.isEmpty())) {
return false;
}
for (int i = minSectionPosition; i <= maxSectionPosition; i++) {
if (hasSection(i)) {
return false;
}
}
return true;
}
@Override
public boolean setTile(int x, int y, int z, CompoundTag tile) {
updateSectionIndexRange(y >> 4);
if (tiles == null) {
tiles = new BlockVector3ChunkMap<>();
}
tiles.put(x, y, z, tile);
return true;
}
@Override
public void setBlockLight(int x, int y, int z, int value) {
updateSectionIndexRange(y >> 4);
if (light == null) {
light = new char[sectionCount][];
}
final int layer = (y >> 4) - minSectionPosition;
if (light[layer] == null) {
char[] c = new char[4096];
Arrays.fill(c, (char) 16);
light[layer] = c;
}
final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15);
light[layer][index] = (char) value;
}
@Override
public void setSkyLight(int x, int y, int z, int value) {
updateSectionIndexRange(y >> 4);
if (skyLight == null) {
skyLight = new char[sectionCount][];
}
final int layer = (y >> 4) - minSectionPosition;
if (skyLight[layer] == null) {
char[] c = new char[4096];
Arrays.fill(c, (char) 16);
skyLight[layer] = c;
}
final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15);
skyLight[layer][index] = (char) value;
}
@Override
public void setHeightMap(HeightMapType type, int[] heightMap) {
if (heightMaps == null) {
heightMaps = new EnumMap<>(HeightMapType.class);
}
heightMaps.put(type, heightMap);
}
@Override
public void setLightLayer(int layer, char[] toSet) {
updateSectionIndexRange(layer);
if (light == null) {
light = new char[sectionCount][];
}
layer -= minSectionPosition;
light[layer] = toSet;
}
@Override
public void setSkyLightLayer(int layer, char[] toSet) {
updateSectionIndexRange(layer);
if (skyLight == null) {
skyLight = new char[sectionCount][];
}
layer -= minSectionPosition;
skyLight[layer] = toSet;
}
@Override
public void setFullBright(int layer) {
updateSectionIndexRange(layer);
layer -= minSectionPosition;
if (light == null) {
light = new char[sectionCount][];
}
if (light[layer] == null) {
light[layer] = new char[4096];
}
if (skyLight == null) {
skyLight = new char[sectionCount][];
}
if (skyLight[layer] == null) {
skyLight[layer] = new char[4096];
}
Arrays.fill(light[layer], (char) 15);
Arrays.fill(skyLight[layer], (char) 15);
}
@Override
public void setEntity(CompoundTag tag) {
if (entities == null) {
entities = new HashSet<>();
}
entities.add(tag);
}
@Override
public void removeEntity(UUID uuid) {
if (entityRemoves == null) {
entityRemoves = new HashSet<>();
}
entityRemoves.add(uuid);
}
@Override
public void setFastMode(boolean fastMode) {
this.fastMode = fastMode;
}
@Override
public boolean isFastMode() {
return fastMode;
}
@Override
public void setBitMask(int bitMask) {
this.bitMask = bitMask;
}
@Override
public int getBitMask() {
return bitMask;
}
@Override
public Set<UUID> getEntityRemoves() {
return entityRemoves == null ? Collections.emptySet() : entityRemoves;
}
@Override
public BiomeType[][] getBiomes() {
return biomes;
}
@Override
public boolean hasBiomes() {
return IChunkSet.super.hasBiomes();
}
@Override
public char[][] getLight() {
return light;
}
@Override
public char[][] getSkyLight() {
return skyLight;
}
@Override
public boolean hasLight() {
return IChunkSet.super.hasLight();
}
@Override
public IChunkSet reset() {
blocks = new char[sectionCount][];
biomes = new BiomeType[sectionCount][];
light = new char[sectionCount][];
skyLight = new char[sectionCount][];
tiles.clear();
entities.clear();
entityRemoves.clear();
heightMaps.clear();
return this;
}
@Override
public boolean hasBiomes(int layer) {
layer -= minSectionPosition;
return layer >= 0 && layer < biomes.length && biomes[layer] != null && biomes[layer].length > 0;
}
@Override
public IChunkSet createCopy() {
char[][] blocksCopy = new char[sectionCount][];
for (int i = 0; i < sectionCount; i++) {
blocksCopy[i] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER];
if (blocks[i] != null) {
System.arraycopy(blocks[i], 0, blocksCopy[i], 0, FaweCache.INSTANCE.BLOCKS_PER_LAYER);
}
}
BiomeType[][] biomesCopy;
if (biomes == null) {
biomesCopy = null;
} else {
biomesCopy = new BiomeType[sectionCount][];
for (int i = 0; i < sectionCount; i++) {
if (biomes[i] != null) {
biomesCopy[i] = new BiomeType[biomes[i].length];
System.arraycopy(biomes[i], 0, biomesCopy[i], 0, biomes[i].length);
}
}
}
char[][] lightCopy = CharSetBlocks.createLightCopy(light, sectionCount);
char[][] skyLightCopy = CharSetBlocks.createLightCopy(skyLight, sectionCount);
return new ThreadUnsafeCharBlocks(
blocksCopy,
minSectionPosition,
maxSectionPosition,
biomesCopy,
sectionCount,
lightCopy,
skyLightCopy,
tiles != null ? new BlockVector3ChunkMap<>(tiles) : null,
entities != null ? new HashSet<>(entities) : null,
entityRemoves != null ? new HashSet<>(entityRemoves) : null,
heightMaps != null ? new HashMap<>(heightMaps) : null,
defaultOrdinal,
fastMode,
bitMask
);
}
@Override
public boolean trim(boolean aggressive) {
return false;
}
// Checks and updates the various section arrays against the new layer index
private void updateSectionIndexRange(int layer) {
if (layer >= minSectionPosition && layer <= maxSectionPosition) {
return;
}
if (layer < minSectionPosition) {
int diff = minSectionPosition - layer;
sectionCount += diff;
minSectionPosition = layer;
resizeSectionsArrays(layer, diff, false); // prepend new layer(s)
} else {
int diff = layer - maxSectionPosition;
sectionCount += diff;
maxSectionPosition = layer;
resizeSectionsArrays(layer, diff, true); // append new layer(s)
}
}
private void resizeSectionsArrays(int layer, int diff, boolean appendNew) {
char[][] tmpBlocks = new char[sectionCount][];
int destPos = appendNew ? 0 : diff;
System.arraycopy(blocks, 0, tmpBlocks, destPos, blocks.length);
blocks = tmpBlocks;
if (biomes != null) {
BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64];
System.arraycopy(biomes, 0, tmpBiomes, destPos, biomes.length);
biomes = tmpBiomes;
}
if (light != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(light, 0, tmplight, destPos, light.length);
light = tmplight;
}
if (skyLight != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(skyLight, 0, tmplight, destPos, skyLight.length);
skyLight = tmplight;
}
}
}

View File

@ -1044,7 +1044,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
if (chunkSet != null && !chunkSet.isEmpty()) { if (chunkSet != null && !chunkSet.isEmpty()) {
chunkSet.setBitMask(bitMask); chunkSet.setBitMask(bitMask);
try { try {
return this.call(chunkSet, () -> { return this.call(chunkSet.createCopy(), () -> {
this.delegate = NULL; this.delegate = NULL;
chunkSet = null; chunkSet = null;
calledLock.unlock(stamp); calledLock.unlock(stamp);

View File

@ -52,6 +52,7 @@ import java.io.PrintWriter;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
@ -68,7 +69,6 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -81,10 +81,8 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -533,6 +531,21 @@ public class MainUtil {
return readImage(new FileInputStream(file)); return readImage(new FileInputStream(file));
} }
public static void checkImageHost(URI uri) throws IOException {
if (Settings.settings().WEB.ALLOWED_IMAGE_HOSTS.contains("*")) {
return;
}
String host = uri.getHost();
if (Settings.settings().WEB.ALLOWED_IMAGE_HOSTS.stream().anyMatch(host::equalsIgnoreCase)) {
return;
}
throw new IOException(String.format(
"Host `%s` not allowed! Whitelisted image hosts are: %s",
host,
StringMan.join(Settings.settings().WEB.ALLOWED_IMAGE_HOSTS, ", ")
));
}
public static BufferedImage toRGB(BufferedImage src) { public static BufferedImage toRGB(BufferedImage src) {
if (src == null) { if (src == null) {
return src; return src;

View File

@ -157,13 +157,13 @@ public abstract class TaskManager {
*/ */
public void runUnsafe(Runnable run) { public void runUnsafe(Runnable run) {
QueueHandler queue = Fawe.instance().getQueueHandler(); QueueHandler queue = Fawe.instance().getQueueHandler();
queue.startSet(true); queue.startUnsafe(Fawe.isMainThread());
try { try {
run.run(); run.run();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
} }
queue.endSet(true); queue.endUnsafe(Fawe.isMainThread());
} }
/** /**

View File

@ -130,7 +130,9 @@ public class WEManager {
backupRegions.add(region); backupRegions.add(region);
} }
} else { } else {
player.print(Caption.of("fawe.error.region-mask-invalid", mask.getClass().getSimpleName())); if (Settings.settings().ENABLED_COMPONENTS.DEBUG) {
player.printDebug(Caption.of("fawe.error.region-mask-invalid", mask.getClass().getSimpleName()));
}
removed = true; removed = true;
iterator.remove(); iterator.remove();
} }

View File

@ -36,7 +36,7 @@ public class SimpleRandomCollection<E> extends RandomCollection<E> {
@Override @Override
public E next(int x, int y, int z) { public E next(int x, int y, int z) {
return map.ceilingEntry(getRandom().nextDouble(x, y, z)).getValue(); return map.ceilingEntry(getRandom().nextDouble(x, y, z) * this.total).getValue();
} }
} }

View File

@ -203,6 +203,7 @@ public class ImageUtil {
arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png";
} }
URL url = new URL(arg); URL url = new URL(arg);
MainUtil.checkImageHost(url.toURI());
BufferedImage img = MainUtil.readImage(url); BufferedImage img = MainUtil.readImage(url);
if (img == null) { if (img == null) {
throw new IOException("Failed to read " + url + ", please try again later"); throw new IOException("Failed to read " + url + ", please try again later");
@ -218,7 +219,7 @@ public class ImageUtil {
return MainUtil.readImage(file); return MainUtil.readImage(file);
} }
throw new InputParseException(Caption.of("fawe.error.invalid-image", TextComponent.of(arg))); throw new InputParseException(Caption.of("fawe.error.invalid-image", TextComponent.of(arg)));
} catch (IOException e) { } catch (IOException | URISyntaxException e) {
throw new InputParseException(TextComponent.of(e.getMessage())); throw new InputParseException(TextComponent.of(e.getMessage()));
} }
} }
@ -229,7 +230,9 @@ public class ImageUtil {
if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) { if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) {
arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png";
} }
return new URL(arg).toURI(); URI uri = new URI(arg);
MainUtil.checkImageHost(uri);
return uri;
} }
if (arg.startsWith("file:/")) { if (arg.startsWith("file:/")) {
arg = arg.replaceFirst("file:/+", ""); arg = arg.replaceFirst("file:/+", "");

View File

@ -1,9 +1,13 @@
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;
@ -11,6 +15,13 @@ 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;
@ -59,7 +70,7 @@ public class AsyncNotifyQueue implements Closeable {
} }
return null; return null;
}; };
self[0] = Fawe.instance().getQueueHandler().async(wrapped); self[0] = QUEUE_SUBMISSIONS.submit(wrapped);
return self[0]; return self[0];
} }

View File

@ -0,0 +1,21 @@
package com.fastasyncworldedit.core.util.task;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
public class FaweForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
private final String nameFormat;
public FaweForkJoinWorkerThreadFactory(String nameFormat) {
this.nameFormat = nameFormat;
}
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName(String.format(nameFormat, worker.getPoolIndex()));
return worker;
}
}

View File

@ -0,0 +1,172 @@
package com.fastasyncworldedit.core.util.task;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Executor service that queues tasks based on keys, executing tasks on a configurable {@link ThreadPoolExecutor}
*
* @param <K> Key type
* @since 2.6.2
*/
public class KeyQueuedExecutorService<K> {
private final ExecutorService parent;
private final Map<K, KeyRunner> keyQueue = new HashMap<>();
/**
* Create a new {@link KeyQueuedExecutorService} instance
*
* @param parent Parent {@link ExecutorService} to use for actual task completion
*/
public KeyQueuedExecutorService(ExecutorService parent) {
this.parent = parent;
}
/**
* Delegates to {@link ThreadPoolExecutor#shutdown()}
*/
public void shutdown() {
parent.shutdown();
}
/**
* Delegates to {@link ThreadPoolExecutor#shutdownNow()}
*/
@Nonnull
public List<Runnable> shutdownNow() {
return parent.shutdownNow();
}
/**
* Delegates to {@link ThreadPoolExecutor#isShutdown()}
*/
public boolean isShutdown() {
return parent.isShutdown();
}
/**
* Delegates to {@link ThreadPoolExecutor#isTerminated()}
*/
public boolean isTerminated() {
return parent.isTerminated();
}
/**
* Delegates to {@link ThreadPoolExecutor#awaitTermination(long, TimeUnit)}
*/
public boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) throws InterruptedException {
return parent.awaitTermination(timeout, unit);
}
protected <T> FutureTask<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<>(runnable, value);
}
protected <T> FutureTask<T> newTaskFor(Callable<T> callable) {
return new FutureTask<>(callable);
}
@Nonnull
public <T> Future<T> submit(@Nonnull K key, @Nonnull Callable<T> task) {
FutureTask<T> ftask = newTaskFor(task);
execute(key, ftask);
return ftask;
}
@Nonnull
public <T> Future<T> submit(@Nonnull K key, @Nonnull Runnable task, T result) {
FutureTask<T> ftask = newTaskFor(task, result);
execute(key, ftask);
return ftask;
}
@Nonnull
public Future<?> submit(@Nonnull K key, @Nonnull Runnable task) {
FutureTask<Void> ftask = newTaskFor(task, null);
execute(key, ftask);
return ftask;
}
public void execute(@Nonnull K key, @Nonnull FutureTask<?> command) {
synchronized (keyQueue) {
boolean triggerRun = false;
KeyRunner runner = keyQueue.get(key);
if (runner == null) {
runner = new KeyRunner(key);
keyQueue.put(key, runner);
triggerRun = true;
}
runner.add(command);
if (triggerRun) {
runner.triggerRun();
}
}
}
private final class KeyRunner {
private final Queue<FutureTask<?>> tasks = new ConcurrentLinkedQueue<>();
private final K key;
private KeyRunner(K key) {
this.key = key;
}
void add(FutureTask<?> task) {
if (!tasks.add(task)) {
throw new RejectedExecutionException(rejection());
}
}
void triggerRun() {
Runnable task = tasks.poll();
if (task == null) {
throw new RejectedExecutionException(rejection());
}
try {
run(task);
} catch (RejectedExecutionException e) {
synchronized (keyQueue) {
keyQueue.remove(key);
}
throw new RejectedExecutionException(rejection(), e);
}
}
private void run(Runnable task) {
parent.execute(() -> {
task.run();
Runnable next = tasks.poll();
if (next == null) {
synchronized (keyQueue) {
next = tasks.poll();
if (next == null) {
keyQueue.remove(key);
}
}
}
if (next != null) {
run(next);
}
});
}
private String rejection() {
return "Task for the key '" + key + "' rejected";
}
}
}

View File

@ -531,16 +531,14 @@ public final class EditSessionBuilder {
} }
if (allowedRegions == null && Settings.settings().REGION_RESTRICTIONS) { if (allowedRegions == null && Settings.settings().REGION_RESTRICTIONS) {
if (actor != null && !actor.hasPermission("fawe.bypass.regions")) { if (actor != null && !actor.hasPermission("fawe.bypass.regions")) {
if (actor instanceof Player) { if (actor instanceof Player player) {
Player player = (Player) actor;
allowedRegions = player.getAllowedRegions(); allowedRegions = player.getAllowedRegions();
} }
} }
} }
if (disallowedRegions == null && Settings.settings().REGION_RESTRICTIONS && Settings.settings().REGION_RESTRICTIONS_OPTIONS.ALLOW_BLACKLISTS) { if (disallowedRegions == null && Settings.settings().REGION_RESTRICTIONS && Settings.settings().REGION_RESTRICTIONS_OPTIONS.ALLOW_BLACKLISTS) {
if (actor != null && !actor.hasPermission("fawe.bypass.regions")) { if (actor != null && !actor.hasPermission("fawe.bypass.regions")) {
if (actor instanceof Player) { if (actor instanceof Player player) {
Player player = (Player) actor;
disallowedRegions = player.getDisallowedRegions(); disallowedRegions = player.getDisallowedRegions();
} }
} }
@ -561,6 +559,9 @@ public final class EditSessionBuilder {
} }
} }
} }
if (placeChunks && regionExtent != null) {
queue.addProcessor(regionExtent);
}
// There's no need to do the below (and it'll also just be a pain to implement) if we're not placing chunks // There's no need to do the below (and it'll also just be a pain to implement) if we're not placing chunks
if (placeChunks) { if (placeChunks) {
if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) { if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) {
@ -597,15 +598,14 @@ public final class EditSessionBuilder {
this.extent = regionExtent; this.extent = regionExtent;
} }
if (this.limit != null && this.limit.STRIP_NBT != null && !this.limit.STRIP_NBT.isEmpty()) { if (this.limit != null && this.limit.STRIP_NBT != null && !this.limit.STRIP_NBT.isEmpty()) {
this.extent = new StripNBTExtent(this.extent, this.limit.STRIP_NBT);
if (placeChunks) { if (placeChunks) {
queue.addProcessor(new StripNBTExtent(this.extent, this.limit.STRIP_NBT)); queue.addProcessor((IBatchProcessor) this.extent);
} else {
this.extent = new StripNBTExtent(this.extent, this.limit.STRIP_NBT);
} }
} }
if (this.limit != null && !this.limit.isUnlimited()) { if (this.limit != null && !this.limit.isUnlimited()) {
Set<String> limitBlocks = new HashSet<>(); Set<String> limitBlocks = new HashSet<>();
if ((getActor() == null || getActor().hasPermission("worldedit.anyblock")) && this.limit.UNIVERSAL_DISALLOWED_BLOCKS) { if (getActor() != null && !getActor().hasPermission("worldedit.anyblock") && this.limit.UNIVERSAL_DISALLOWED_BLOCKS) {
limitBlocks.addAll(WorldEdit.getInstance().getConfiguration().disallowedBlocks); limitBlocks.addAll(WorldEdit.getInstance().getConfiguration().disallowedBlocks);
} }
if (this.limit.DISALLOWED_BLOCKS != null && !this.limit.DISALLOWED_BLOCKS.isEmpty()) { if (this.limit.DISALLOWED_BLOCKS != null && !this.limit.DISALLOWED_BLOCKS.isEmpty()) {
@ -613,10 +613,9 @@ public final class EditSessionBuilder {
} }
Set<PropertyRemap<?>> remaps = this.limit.REMAP_PROPERTIES; Set<PropertyRemap<?>> remaps = this.limit.REMAP_PROPERTIES;
if (!limitBlocks.isEmpty() || (remaps != null && !remaps.isEmpty())) { if (!limitBlocks.isEmpty() || (remaps != null && !remaps.isEmpty())) {
this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps);
if (placeChunks) { if (placeChunks) {
queue.addProcessor(new DisallowedBlocksExtent(this.extent, limitBlocks, remaps)); queue.addProcessor((IBatchProcessor) this.extent);
} else {
this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps);
} }
} }
} }

View File

@ -82,7 +82,10 @@ public class BiomeCommands {
aliases = {"biomels", "/biomelist", "/listbiomes"}, aliases = {"biomels", "/biomelist", "/listbiomes"},
desc = "Gets all biomes available." desc = "Gets all biomes available."
) )
@CommandPermissions("worldedit.biome.list") @CommandPermissions(
value = "worldedit.biome.list",
queued = false
)
public void biomeList( public void biomeList(
Actor actor, Actor actor,
@ArgFlag(name = 'p', desc = "Page number.", def = "1") @ArgFlag(name = 'p', desc = "Page number.", def = "1")

View File

@ -134,6 +134,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.util.List; import java.util.List;
@ -310,11 +311,11 @@ public class BrushCommands {
}, },
desc = "Join multiple objects together in a curve", desc = "Join multiple objects together in a curve",
descFooter = descFooter =
"Click to select some objects,click the same block twice to connect the objects.\n" """
+ "Insufficient brush radius, or clicking the the wrong spot will result in undesired shapes. The shapes must be simple lines or loops.\n" Click to select some objects,click the same block twice to connect the objects.
+ "Pic1: http://i.imgur.com/CeRYAoV.jpg -> http://i.imgur.com/jtM0jA4.png\n" Insufficient brush radius, or clicking the the wrong spot will result in undesired shapes. The shapes must be simple lines or loops.
+ "Pic2: http://i.imgur.com/bUeyc72.png -> http://i.imgur.com/tg6MkcF.png" Pic1: http://i.imgur.com/CeRYAoV.jpg -> http://i.imgur.com/jtM0jA4.png
+ "Tutorial: https://www.planetminecraft.com/blog/fawe-tutorial/" Pic2: http://i.imgur.com/bUeyc72.png -> http://i.imgur.com/tg6MkcF.pngTutorial: https://www.planetminecraft.com/blog/fawe-tutorial/"""
) )
@CommandPermissions("worldedit.brush.spline") @CommandPermissions("worldedit.brush.spline")
public void splineBrush( public void splineBrush(
@ -337,9 +338,10 @@ public class BrushCommands {
"vaesweep" "vaesweep"
}, },
desc = "Sweep your clipboard content along a curve", desc = "Sweep your clipboard content along a curve",
descFooter = "Sweeps your clipboard content along a curve.\n" descFooter = """
+ "Define a curve by selecting the individual points with a brush\n" Sweeps your clipboard content along a curve.
+ "Set [copies] to a value > 0 if you want to have your selection pasted a limited amount of times equally spaced on the curve" Define a curve by selecting the individual points with a brush
Set [copies] to a value > 0 if you want to have your selection pasted a limited amount of times equally spaced on the curve"""
) )
@CommandPermissions("worldedit.brush.sweep") @CommandPermissions("worldedit.brush.sweep")
public void sweepBrush( public void sweepBrush(
@ -520,11 +522,9 @@ public class BrushCommands {
@Switch(name = 'a', desc = "Use image Alpha") boolean alpha, @Switch(name = 'a', desc = "Use image Alpha") boolean alpha,
@Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut @Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut
) )
throws WorldEditException, IOException { throws WorldEditException, IOException, URISyntaxException {
URL url = new URL(imageURL); URL url = new URL(imageURL);
if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { MainUtil.checkImageHost(url.toURI());
throw new IOException("Only i.imgur.com links are allowed!");
}
BufferedImage image = MainUtil.readImage(url); BufferedImage image = MainUtil.readImage(url);
worldEdit.checkMaxBrushRadius(radius); worldEdit.checkMaxBrushRadius(radius);
if (yscale != 1) { if (yscale != 1) {
@ -636,10 +636,10 @@ public class BrushCommands {
@Command( @Command(
name = "layer", name = "layer",
desc = "Replaces terrain with a layer.", desc = "Replaces terrain with a layer.",
descFooter = "Replaces terrain with a layer.\n" descFooter = """
+ "Example: /br layer 5 oak_planks,orange_stained_glass,magenta_stained_glass,black_wool - Places several " + Replaces terrain with a layer.
"layers on a surface\n" Example: /br layer 5 oak_planks,orange_stained_glass,magenta_stained_glass,black_wool - Places several layers on a surface
+ "Pic: https://i.imgur.com/XV0vYoX.png" Pic: https://i.imgur.com/XV0vYoX.png"""
) )
@CommandPermissions("worldedit.brush.layer") @CommandPermissions("worldedit.brush.layer")
public void surfaceLayer( public void surfaceLayer(
@ -658,11 +658,11 @@ public class BrushCommands {
@Command( @Command(
name = "splatter", name = "splatter",
desc = "Splatter a pattern on a surface", desc = "Splatter a pattern on a surface",
descFooter = "Sets a bunch of blocks randomly on a surface.\n" descFooter = """
+ "Pic: https://i.imgur.com/hMD29oO.png\n" Sets a bunch of blocks randomly on a surface.
+ "Example: /br splatter stone,dirt 30 15\n" Pic: https://i.imgur.com/hMD29oO.png
+ "Note: The seeds define how many splotches there are, recursion defines how large, " Example: /br splatter stone,dirt 30 15
+ "solid defines whether the pattern is applied per seed, else per block." Note: The seeds define how many splotches there are, recursion defines how large, solid defines whether the pattern is applied per seed, else per block."""
) )
@CommandPermissions("worldedit.brush.splatter") @CommandPermissions("worldedit.brush.splatter")
public void splatterBrush( public void splatterBrush(
@ -691,9 +691,10 @@ public class BrushCommands {
"scommand" "scommand"
}, },
desc = "Run commands at random points on a surface", desc = "Run commands at random points on a surface",
descFooter = "Run commands at random points on a surface\n" descFooter = """
+ " - Your selection will be expanded to the specified size around each point\n" Run commands at random points on a surface
+ " - Placeholders: {x}, {y}, {z}, {world}, {size}" - Your selection will be expanded to the specified size around each point
- Placeholders: {x}, {y}, {z}, {world}, {size}"""
) )
@CommandPermissions("worldedit.brush.scattercommand") @CommandPermissions("worldedit.brush.scattercommand")
public void scatterCommandBrush( public void scatterCommandBrush(
@ -723,9 +724,10 @@ public class BrushCommands {
name = "height", name = "height",
aliases = {"heightmap"}, aliases = {"heightmap"},
desc = "Raise or lower terrain using a heightmap", desc = "Raise or lower terrain using a heightmap",
descFooter = "This brush raises and lowers land.\n" descFooter = """
+ "Note: Use a negative yscale to reduce height\n" This brush raises and lowers land.
+ "Snow Pic: https://i.imgur.com/Hrzn0I4.png" Note: Use a negative yscale to reduce height
Snow Pic: https://i.imgur.com/Hrzn0I4.png"""
) )
@CommandPermissions("worldedit.brush.height") @CommandPermissions("worldedit.brush.height")
public void heightBrush( public void heightBrush(
@ -890,9 +892,11 @@ public class BrushCommands {
"copypasta" "copypasta"
}, },
desc = "Copy Paste brush", desc = "Copy Paste brush",
descFooter = "Left click the base of an object to copy.\n" + "Right click to paste\n" descFooter = """
+ "Note: Works well with the clipboard scroll action\n" Left click the base of an object to copy.
+ "Video: https://www.youtube.com/watch?v=RPZIaTbqoZw" Right click to paste
Note: Works well with the clipboard scroll action
Video: https://www.youtube.com/watch?v=RPZIaTbqoZw"""
) )
@CommandPermissions("worldedit.brush.copy") @CommandPermissions("worldedit.brush.copy")
public void copy( public void copy(
@ -914,9 +918,10 @@ public class BrushCommands {
name = "command", name = "command",
aliases = {"cmd"}, aliases = {"cmd"},
desc = "Command brush", desc = "Command brush",
descFooter = "Run the commands at the clicked position.\n" descFooter = """
+ " - Your selection will be expanded to the specified size around each point\n" Run the commands at the clicked position.
+ " - Placeholders: {x}, {y}, {z}, {world}, {size}" - Your selection will be expanded to the specified size around each point
- Placeholders: {x}, {y}, {z}, {world}, {size}"""
) )
@CommandPermissions("worldedit.brush.command") @CommandPermissions("worldedit.brush.command")
public void command( public void command(
@ -1036,7 +1041,7 @@ public class BrushCommands {
) )
throws WorldEditException { throws WorldEditException {
WorldEdit.getInstance().checkMaxBrushRadius(radius); WorldEdit.getInstance().checkMaxBrushRadius(radius);
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()); BrushTool tool = session.getBrushTool(player);
tool.setSize(radius); tool.setSize(radius);
tool.setFill(null); tool.setFill(null);
tool.setBrush(new OperationFactoryBrush(factory, shape, session), permission); tool.setBrush(new OperationFactoryBrush(factory, shape, session), permission);
@ -1196,17 +1201,12 @@ public class BrushCommands {
brush = new HollowSphereBrush(); brush = new HollowSphereBrush();
} else { } else {
//FAWE start - Suggest different brush material if sand or gravel is used //FAWE start - Suggest different brush material if sand or gravel is used
if (pattern instanceof BlockStateHolder) { if (pattern instanceof BlockStateHolder<?> holder) {
BlockType type = ((BlockStateHolder) pattern).getBlockType(); BlockType type = holder.getBlockType();
switch (type.getId()) { if (type == BlockTypes.SAND || type == BlockTypes.GRAVEL) {
case "minecraft:sand": player.print(
case "minecraft:gravel": Caption.of("fawe.worldedit.brush.brush.try.other"));
player.print( falling = true;
Caption.of("fawe.worldedit.brush.brush.try.other"));
falling = true;
break;
default:
break;
} }
} }
if (falling) { if (falling) {
@ -1258,13 +1258,12 @@ public class BrushCommands {
@Command( @Command(
name = "clipboard", name = "clipboard",
desc = "@Deprecated use instead: `/br copypaste`)", desc = "Paste your clipboard at the brush location. Includes any transforms.",
descFooter = "Choose the clipboard brush.\n" descFooter = "Choose the clipboard brush.\n"
+ "Without the -o flag, the paste will appear centered at the target location. " + "Without the -o flag, the paste will appear centered at the target location. "
+ "With the flag, then the paste will appear relative to where you had " + "With the flag, then the paste will appear relative to where you had "
+ "stood relative to the copied area when you copied it." + "stood relative to the copied area when you copied it."
) )
@Deprecated
@CommandPermissions("worldedit.brush.clipboard") @CommandPermissions("worldedit.brush.clipboard")
public void clipboardBrush( public void clipboardBrush(
Player player, LocalSession session, Player player, LocalSession session,
@ -1278,7 +1277,11 @@ public class BrushCommands {
boolean pasteBiomes, boolean pasteBiomes,
@ArgFlag(name = 'm', desc = "Skip blocks matching this mask in the clipboard") @ArgFlag(name = 'm', desc = "Skip blocks matching this mask in the clipboard")
@ClipboardMask @ClipboardMask
Mask sourceMask, InjectedValueAccess context Mask sourceMask, InjectedValueAccess context,
//FAWE start - random rotation
@Switch(name = 'r', desc = "Apply random rotation on paste, combines with existing clipboard transforms")
boolean randomRotate
//FAWE end
) throws WorldEditException { ) throws WorldEditException {
ClipboardHolder holder = session.getClipboard(); ClipboardHolder holder = session.getClipboard();
@ -1294,9 +1297,9 @@ public class BrushCommands {
set( set(
context, context,
new ClipboardBrush(newHolder, ignoreAir, usingOrigin, pasteEntities, pasteBiomes, //FAWE start - random rotation
sourceMask new ClipboardBrush(newHolder, ignoreAir, usingOrigin, pasteEntities, pasteBiomes, sourceMask, randomRotate),
), //FAWE end
"worldedit.brush.clipboard" "worldedit.brush.clipboard"
); );
} }
@ -1361,7 +1364,7 @@ public class BrushCommands {
iterations = Math.min(limit.MAX_ITERATIONS, iterations); iterations = Math.min(limit.MAX_ITERATIONS, iterations);
//FAWE end //FAWE end
set(context, new SnowSmoothBrush(iterations, mask), "worldedit.brush.snowsmooth").setSize(radius); set(context, new SnowSmoothBrush(iterations, snowBlockCount, mask), "worldedit.brush.snowsmooth").setSize(radius);
player.print(Caption.of( player.print(Caption.of(
"worldedit.brush.smooth.equip", "worldedit.brush.smooth.equip",
radius, radius,

View File

@ -79,7 +79,10 @@ public class ChunkCommands {
aliases = {"/chunkinfo"}, aliases = {"/chunkinfo"},
desc = "Get information about the chunk you're inside" desc = "Get information about the chunk you're inside"
) )
@CommandPermissions("worldedit.chunkinfo") @CommandPermissions(
value = "worldedit.chunkinfo",
queued = false
)
public void chunkInfo(Player player) { public void chunkInfo(Player player) {
Location pos = player.getBlockLocation(); Location pos = player.getBlockLocation();
int chunkX = (int) Math.floor(pos.getBlockX() / 16.0); int chunkX = (int) Math.floor(pos.getBlockX() / 16.0);
@ -99,7 +102,10 @@ public class ChunkCommands {
aliases = {"/listchunks"}, aliases = {"/listchunks"},
desc = "List chunks that your selection includes" desc = "List chunks that your selection includes"
) )
@CommandPermissions("worldedit.listchunks") @CommandPermissions(
value = "worldedit.listchunks",
queued = false
)
public void listChunks( public void listChunks(
Actor actor, World world, LocalSession session, Actor actor, World world, LocalSession session,
@ArgFlag(name = 'p', desc = "Page number.", def = "1") int page @ArgFlag(name = 'p', desc = "Page number.", def = "1") int page

View File

@ -74,6 +74,7 @@ import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionIntersection; import com.sk89q.worldedit.regions.RegionIntersection;
import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.regions.selector.ExtendingCuboidRegionSelector;
import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TextComponent;
@ -159,35 +160,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);
copy.setCopyingBiomes(copyBiomes); createCopy(session, editSession, copyBiomes, mask, clipboard, copy);
Mask sourceMask = editSession.getSourceMask();
Region[] regions = editSession.getAllowedRegions();
Region allowedRegion;
if (regions == null || regions.length == 0) {
allowedRegion = new NullRegion();
} else {
allowedRegion = new RegionIntersection(regions);
}
final Mask firstSourceMask = mask != null ? mask : sourceMask;
final Mask finalMask = MaskIntersection.of(firstSourceMask, new RegionMask(allowedRegion)).optimize();
if (finalMask != Masks.alwaysTrue()) {
copy.setSourceMask(finalMask);
}
if (sourceMask != null) {
editSession.setSourceMask(null);
new MaskTraverser(sourceMask).reset(editSession);
editSession.setSourceMask(null);
}
try {
Operations.completeLegacy(copy);
} catch (Throwable e) {
throw e;
} finally {
clipboard.flush();
}
session.setClipboard(new ClipboardHolder(clipboard));
copy.getStatusMessages().forEach(actor::print); copy.getStatusMessages().forEach(actor::print);
//FAWE end //FAWE end
@ -298,7 +271,25 @@ 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);
if (!actor.hasPermission("fawe.tips")) {
actor.print(Caption.of("fawe.tips.tip.lazycut"));
}
copy.getStatusMessages().forEach(actor::print);
//FAWE end
}
private void createCopy(
final LocalSession session,
final EditSession editSession,
final boolean copyBiomes,
final Mask mask,
final Clipboard clipboard,
final ForwardExtentCopy copy
) {
copy.setCopyingBiomes(copyBiomes); copy.setCopyingBiomes(copyBiomes);
Mask sourceMask = editSession.getSourceMask(); Mask sourceMask = editSession.getSourceMask();
Region[] regions = editSession.getAllowedRegions(); Region[] regions = editSession.getAllowedRegions();
Region allowedRegion; Region allowedRegion;
@ -317,20 +308,13 @@ public class ClipboardCommands {
new MaskTraverser(sourceMask).reset(editSession); new MaskTraverser(sourceMask).reset(editSession);
editSession.setSourceMask(null); editSession.setSourceMask(null);
} }
try { try {
Operations.completeLegacy(copy); Operations.completeLegacy(copy);
} catch (Throwable e) {
throw e;
} finally { } finally {
clipboard.flush(); clipboard.flush();
} }
session.setClipboard(new ClipboardHolder(clipboard)); session.setClipboard(new ClipboardHolder(clipboard));
if (!actor.hasPermission("fawe.tips")) {
actor.print(Caption.of("fawe.tips.tip.lazycut"));
}
copy.getStatusMessages().forEach(actor::print);
//FAWE end
} }
@Command( @Command(
@ -444,7 +428,12 @@ public class ClipboardCommands {
Vector3 max = realTo.add(holder Vector3 max = realTo.add(holder
.getTransform() .getTransform()
.apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3())); .apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3()));
RegionSelector selector = new CuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint()); final CuboidRegionSelector selector;
if (session.getRegionSelector(world) instanceof ExtendingCuboidRegionSelector) {
selector = new ExtendingCuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint());
} else {
selector = new CuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint());
}
session.setRegionSelector(world, selector); session.setRegionSelector(world, selector);
selector.learnChanges(); selector.learnChanges();
selector.explainRegionAdjust(actor, session); selector.explainRegionAdjust(actor, session);
@ -475,9 +464,10 @@ public class ClipboardCommands {
@Command( @Command(
name = "/rotate", name = "/rotate",
desc = "Rotate the contents of the clipboard", desc = "Rotate the contents of the clipboard",
descFooter = "Non-destructively rotate the contents of the clipboard.\n" descFooter = """
+ "Angles are provided in degrees and a positive angle will result in a clockwise rotation. " Non-destructively rotate the contents of the clipboard.
+ "Multiple rotations can be stacked. Interpolation is not performed so angles should be a multiple of 90 degrees.\n" Angles are provided in degrees and a positive angle will result in a clockwise rotation. Multiple rotations can be stacked. Interpolation is not performed so angles should be a multiple of 90 degrees.
"""
) )
@CommandPermissions("worldedit.clipboard.rotate") @CommandPermissions("worldedit.clipboard.rotate")
public void rotate( public void rotate(

View File

@ -149,14 +149,11 @@ public class GeneralCommands {
String arg0 = args.get(0).toLowerCase(Locale.ENGLISH); String arg0 = args.get(0).toLowerCase(Locale.ENGLISH);
String flipped; String flipped;
switch (arg0) { switch (arg0) {
case "on": case "on" -> flipped = "off";
flipped = "off"; case "off" -> flipped = "on";
break; default -> {
case "off":
flipped = "on";
break;
default:
return TextComponent.of("There is no replacement for //fast " + arg0); return TextComponent.of("There is no replacement for //fast " + arg0);
}
} }
return CommandUtil.createNewCommandReplacementText("//perf " + flipped); return CommandUtil.createNewCommandReplacementText("//perf " + flipped);
} }
@ -362,7 +359,10 @@ public class GeneralCommands {
descFooter = "This is dependent on platform implementation. " + descFooter = "This is dependent on platform implementation. " +
"Not all platforms support watchdog hooks, or contain a watchdog." "Not all platforms support watchdog hooks, or contain a watchdog."
) )
@CommandPermissions("worldedit.watchdog") @CommandPermissions(
value = "worldedit.watchdog",
queued = false
)
public void watchdog( public void watchdog(
Actor actor, LocalSession session, Actor actor, LocalSession session,
@Arg(desc = "The mode to set the watchdog hook to", def = "") @Arg(desc = "The mode to set the watchdog hook to", def = "")
@ -424,7 +424,10 @@ public class GeneralCommands {
aliases = {"/searchitem", "/l", "/search"}, aliases = {"/searchitem", "/l", "/search"},
desc = "Search for an item" desc = "Search for an item"
) )
@CommandPermissions("worldedit.searchitem") @CommandPermissions(
value = "worldedit.searchitem",
queued = false
)
public void searchItem( public void searchItem(
Actor actor, Actor actor,
@Switch(name = 'b', desc = "Only search for blocks") @Switch(name = 'b', desc = "Only search for blocks")
@ -573,7 +576,10 @@ public class GeneralCommands {
aliases = {"tips"}, aliases = {"tips"},
desc = "Toggle FAWE tips" desc = "Toggle FAWE tips"
) )
@CommandPermissions("fawe.tips") @CommandPermissions(
value = "fawe.tips",
queued = false
)
public void tips(Actor actor, LocalSession session) throws WorldEditException { public void tips(Actor actor, LocalSession session) throws WorldEditException {
if (actor.togglePermission("fawe.tips")) { if (actor.togglePermission("fawe.tips")) {
actor.print(Caption.of("fawe.info.worldedit.toggle.tips.on")); actor.print(Caption.of("fawe.info.worldedit.toggle.tips.on"));
@ -627,7 +633,6 @@ public class GeneralCommands {
String command = "/searchitem " + (blocksOnly ? "-b " : "") + (itemsOnly ? "-i " : "") + "-p %page% " + search; String command = "/searchitem " + (blocksOnly ? "-b " : "") + (itemsOnly ? "-i " : "") + "-p %page% " + search;
Map<String, Component> results = new TreeMap<>(); Map<String, Component> results = new TreeMap<>();
String idMatch = search.replace(' ', '_'); String idMatch = search.replace(' ', '_');
String nameMatch = search.toLowerCase(Locale.ROOT);
for (ItemType searchType : ItemType.REGISTRY) { for (ItemType searchType : ItemType.REGISTRY) {
if (blocksOnly && !searchType.hasBlockType()) { if (blocksOnly && !searchType.hasBlockType()) {
continue; continue;

View File

@ -65,6 +65,7 @@ import org.jetbrains.annotations.Range;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -119,18 +120,15 @@ public class GenerationCommands {
final double radiusX; final double radiusX;
final double radiusZ; final double radiusZ;
switch (radii.size()) { switch (radii.size()) {
case 1: case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0));
radiusX = radiusZ = Math.max(1, radii.get(0)); case 2 -> {
break;
case 2:
radiusX = Math.max(1, radii.get(0)); radiusX = Math.max(1, radii.get(0));
radiusZ = Math.max(1, radii.get(1)); radiusZ = Math.max(1, radii.get(1));
break; }
default -> {
default:
actor.print(Caption.of("worldedit.cyl.invalid-radius")); actor.print(Caption.of("worldedit.cyl.invalid-radius"));
return 0; return 0;
}
} }
worldEdit.checkMaxRadius(radiusX); worldEdit.checkMaxRadius(radiusX);
worldEdit.checkMaxRadius(radiusZ); worldEdit.checkMaxRadius(radiusZ);
@ -169,18 +167,15 @@ public class GenerationCommands {
final double radiusX; final double radiusX;
final double radiusZ; final double radiusZ;
switch (radii.size()) { switch (radii.size()) {
case 1: case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0));
radiusX = radiusZ = Math.max(1, radii.get(0)); case 2 -> {
break;
case 2:
radiusX = Math.max(1, radii.get(0)); radiusX = Math.max(1, radii.get(0));
radiusZ = Math.max(1, radii.get(1)); radiusZ = Math.max(1, radii.get(1));
break; }
default -> {
default:
actor.print(Caption.of("worldedit.cyl.invalid-radius")); actor.print(Caption.of("worldedit.cyl.invalid-radius"));
return 0; return 0;
}
} }
worldEdit.checkMaxRadius(radiusX); worldEdit.checkMaxRadius(radiusX);
@ -234,19 +229,16 @@ public class GenerationCommands {
final double radiusY; final double radiusY;
final double radiusZ; final double radiusZ;
switch (radii.size()) { switch (radii.size()) {
case 1: case 1 -> radiusX = radiusY = radiusZ = Math.max(0, radii.get(0));
radiusX = radiusY = radiusZ = Math.max(0, radii.get(0)); case 3 -> {
break;
case 3:
radiusX = Math.max(0, radii.get(0)); radiusX = Math.max(0, radii.get(0));
radiusY = Math.max(0, radii.get(1)); radiusY = Math.max(0, radii.get(1));
radiusZ = Math.max(0, radii.get(2)); radiusZ = Math.max(0, radii.get(2));
break; }
default -> {
default:
actor.print(Caption.of("worldedit.sphere.invalid-radius")); actor.print(Caption.of("worldedit.sphere.invalid-radius"));
return 0; return 0;
}
} }
worldEdit.checkMaxRadius(radiusX); worldEdit.checkMaxRadius(radiusX);
@ -437,9 +429,10 @@ public class GenerationCommands {
name = "/generatebiome", name = "/generatebiome",
aliases = {"/genbiome", "/gb"}, aliases = {"/genbiome", "/gb"},
desc = "Sets biome according to a formula.", desc = "Sets biome according to a formula.",
descFooter = "Formula must return positive numbers (true) if the point is inside the shape\n" descFooter = """
+ "Sets the biome of blocks in that shape.\n" Formula must return positive numbers (true) if the point is inside the shape
+ "For details, see https://ehub.to/we/expr" Sets the biome of blocks in that shape.
For details, see https://ehub.to/we/expr"""
) )
@CommandPermissions("worldedit.generation.shape.biome") @CommandPermissions("worldedit.generation.shape.biome")
@Logging(ALL) @Logging(ALL)
@ -588,12 +581,10 @@ public class GenerationCommands {
@Arg(desc = "boolean", def = "true") boolean randomize, @Arg(desc = "boolean", def = "true") boolean randomize,
@Arg(desc = "TODO", def = "100") int threshold, @Arg(desc = "TODO", def = "100") int threshold,
@Arg(desc = "BlockVector2", def = "") BlockVector2 dimensions @Arg(desc = "BlockVector2", def = "") BlockVector2 dimensions
) throws WorldEditException, IOException { ) throws WorldEditException, IOException, URISyntaxException {
TextureUtil tu = Fawe.instance().getCachedTextureUtil(randomize, 0, threshold); TextureUtil tu = Fawe.instance().getCachedTextureUtil(randomize, 0, threshold);
URL url = new URL(imageURL); URL url = new URL(imageURL);
if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { MainUtil.checkImageHost(url.toURI());
throw new IOException("Only i.imgur.com links are allowed!");
}
if (dimensions != null) { if (dimensions != null) {
checkCommandArgument( checkCommandArgument(
(long) dimensions.getX() * dimensions.getZ() <= Settings.settings().WEB.MAX_IMAGE_SIZE, (long) dimensions.getX() * dimensions.getZ() <= Settings.settings().WEB.MAX_IMAGE_SIZE,
@ -626,14 +617,12 @@ public class GenerationCommands {
BlockVector3 pos1 = session.getPlacementPosition(actor); BlockVector3 pos1 = session.getPlacementPosition(actor);
BlockVector3 pos2 = pos1.add(image.getWidth() - 1, 0, image.getHeight() - 1); BlockVector3 pos2 = pos1.add(image.getWidth() - 1, 0, image.getHeight() - 1);
CuboidRegion region = new CuboidRegion(pos1, pos2); CuboidRegion region = new CuboidRegion(pos1, pos2);
int[] count = new int[1];
final BufferedImage finalImage = image; final BufferedImage finalImage = image;
RegionVisitor visitor = new RegionVisitor(region, pos -> { RegionVisitor visitor = new RegionVisitor(region, pos -> {
int x = pos.getBlockX() - pos1.getBlockX(); int x = pos.getBlockX() - pos1.getBlockX();
int z = pos.getBlockZ() - pos1.getBlockZ(); int z = pos.getBlockZ() - pos1.getBlockZ();
int color = finalImage.getRGB(x, z); int color = finalImage.getRGB(x, z);
BlockType block = tu.getNearestBlock(color); BlockType block = tu.getNearestBlock(color);
count[0]++;
if (block != null) { if (block != null) {
return editSession.setBlock(pos, block.getDefaultState()); return editSession.setBlock(pos, block.getDefaultState());
} }

View File

@ -223,7 +223,10 @@ public class HistorySubCommands {
aliases = {"summary", "summarize"}, aliases = {"summary", "summarize"},
desc = "Summarize an edit" desc = "Summarize an edit"
) )
@CommandPermissions("worldedit.history.info") @CommandPermissions(
value = "worldedit.history.info",
queued = false
)
public synchronized void summary( public synchronized void summary(
Player player, RollbackDatabase database, Arguments arguments, Player player, RollbackDatabase database, Arguments arguments,
@Arg(desc = "Player uuid/name") @Arg(desc = "Player uuid/name")
@ -314,8 +317,7 @@ public class HistorySubCommands {
public Component apply(@Nullable Supplier<? extends ChangeSet> input) { public Component apply(@Nullable Supplier<? extends ChangeSet> input) {
ChangeSet edit = input.get(); ChangeSet edit = input.get();
if (edit instanceof RollbackOptimizedHistory) { if (edit instanceof RollbackOptimizedHistory rollback) {
RollbackOptimizedHistory rollback = (RollbackOptimizedHistory) edit;
UUID uuid = rollback.getUUID(); UUID uuid = rollback.getUUID();
int index = rollback.getIndex(); int index = rollback.getIndex();
@ -368,7 +370,10 @@ public class HistorySubCommands {
aliases = {"inspect", "search", "near"}, aliases = {"inspect", "search", "near"},
desc = "Find nearby edits" desc = "Find nearby edits"
) )
@CommandPermissions("worldedit.history.find") @CommandPermissions(
value = "worldedit.history.find",
queued = false
)
public synchronized void find( public synchronized void find(
Player player, World world, RollbackDatabase database, Arguments arguments, Player player, World world, RollbackDatabase database, Arguments arguments,
@ArgFlag(name = 'u', def = "", desc = "String user") @ArgFlag(name = 'u', def = "", desc = "String user")
@ -429,7 +434,10 @@ public class HistorySubCommands {
aliases = {"distribution"}, aliases = {"distribution"},
desc = "View block distribution for an edit" desc = "View block distribution for an edit"
) )
@CommandPermissions("worldedit.history.distr") @CommandPermissions(
value = "worldedit.history.distr",
queued = false
)
public void distr( public void distr(
Player player, LocalSession session, RollbackDatabase database, Arguments arguments, Player player, LocalSession session, RollbackDatabase database, Arguments arguments,
@Arg(desc = "Player uuid/name") @Arg(desc = "Player uuid/name")
@ -468,7 +476,10 @@ public class HistorySubCommands {
name = "list", name = "list",
desc = "List your history" desc = "List your history"
) )
@CommandPermissions("worldedit.history.list") @CommandPermissions(
value = "worldedit.history.list",
queued = false
)
public void list( public void list(
Player player, LocalSession session, RollbackDatabase database, Arguments arguments, Player player, LocalSession session, RollbackDatabase database, Arguments arguments,
@Arg(desc = "Player uuid/name") @Arg(desc = "Player uuid/name")
@ -476,7 +487,6 @@ public class HistorySubCommands {
@ArgFlag(name = 'p', desc = "Page to view.", def = "") @ArgFlag(name = 'p', desc = "Page to view.", def = "")
Integer page Integer page
) { ) {
int index = session.getHistoryIndex();
List<Supplier<? extends ChangeSet>> history = Lists.transform( List<Supplier<? extends ChangeSet>> history = Lists.transform(
session.getHistory(), session.getHistory(),
(Function<ChangeSet, Supplier<ChangeSet>>) input -> () -> input (Function<ChangeSet, Supplier<ChangeSet>>) input -> () -> input

View File

@ -60,7 +60,10 @@ public class NavigationCommands {
aliases = {"!", "/unstuck"}, aliases = {"!", "/unstuck"},
desc = "Escape from being stuck inside a block" desc = "Escape from being stuck inside a block"
) )
@CommandPermissions("worldedit.navigation.unstuck") @CommandPermissions(
value = "worldedit.navigation.unstuck",
queued = false
)
public void unstuck(Player player) throws WorldEditException { public void unstuck(Player player) throws WorldEditException {
player.findFreePosition(); player.findFreePosition();
player.print(Caption.of("worldedit.unstuck.moved")); player.print(Caption.of("worldedit.unstuck.moved"));
@ -71,7 +74,10 @@ public class NavigationCommands {
aliases = {"asc", "/asc", "/ascend"}, aliases = {"asc", "/asc", "/ascend"},
desc = "Go up a floor" desc = "Go up a floor"
) )
@CommandPermissions("worldedit.navigation.ascend") @CommandPermissions(
value = "worldedit.navigation.ascend",
queued = false
)
public void ascend( public void ascend(
Player player, Player player,
@Arg(desc = "# of levels to ascend", def = "1") @Arg(desc = "# of levels to ascend", def = "1")
@ -102,7 +108,10 @@ public class NavigationCommands {
aliases = {"desc", "/desc", "/descend"}, aliases = {"desc", "/desc", "/descend"},
desc = "Go down a floor" desc = "Go down a floor"
) )
@CommandPermissions("worldedit.navigation.descend") @CommandPermissions(
value = "worldedit.navigation.descend",
queued = false
)
public void descend( public void descend(
Player player, Player player,
@Arg(desc = "# of levels to descend", def = "1") @Arg(desc = "# of levels to descend", def = "1")
@ -159,7 +168,10 @@ public class NavigationCommands {
aliases = {"/thru"}, aliases = {"/thru"},
desc = "Pass through walls" desc = "Pass through walls"
) )
@CommandPermissions("worldedit.navigation.thru.command") @CommandPermissions(
value = "worldedit.navigation.thru.command",
queued = false
)
public void thru(Player player) throws WorldEditException { public void thru(Player player) throws WorldEditException {
if (player.passThroughForwardWall(6)) { if (player.passThroughForwardWall(6)) {
player.print(Caption.of("worldedit.thru.moved")); player.print(Caption.of("worldedit.thru.moved"));
@ -173,7 +185,10 @@ public class NavigationCommands {
aliases = {"j", "/jumpto", "/j"}, aliases = {"j", "/jumpto", "/j"},
desc = "Teleport to a location" desc = "Teleport to a location"
) )
@CommandPermissions("worldedit.navigation.jumpto.command") @CommandPermissions(
value = "worldedit.navigation.jumpto.command",
queued = false
)
public void jumpTo( public void jumpTo(
Player player, Player player,
@Arg(desc = "Location to jump to", def = "") @Arg(desc = "Location to jump to", def = "")

View File

@ -140,7 +140,10 @@ public class RegionCommands {
name = "/test", name = "/test",
desc = "test region" desc = "test region"
) )
@CommandPermissions("worldedit.region.test") @CommandPermissions(
value = "worldedit.region.test",
queued = false
)
@Logging(REGION) @Logging(REGION)
public void test( public void test(
Actor actor, EditSession editSession, Actor actor, EditSession editSession,
@ -175,7 +178,10 @@ public class RegionCommands {
aliases = "/nbt", aliases = "/nbt",
desc = "View nbt info for a block" desc = "View nbt info for a block"
) )
@CommandPermissions("worldedit.nbtinfo") @CommandPermissions(
value = "worldedit.nbtinfo",
queued = false
)
public void nbtinfo(Player player, EditSession editSession) { public void nbtinfo(Player player, EditSession editSession) {
Location pos = player.getBlockTrace(128); Location pos = player.getBlockTrace(128);
if (pos == null) { if (pos == null) {
@ -228,13 +234,12 @@ public class RegionCommands {
@Switch(name = 'h', desc = "Generate only a shell") @Switch(name = 'h', desc = "Generate only a shell")
boolean shell boolean shell
) throws WorldEditException { ) throws WorldEditException {
if (!(region instanceof CuboidRegion)) { if (!(region instanceof CuboidRegion cuboidregion)) {
actor.print(Caption.of("worldedit.line.cuboid-only")); actor.print(Caption.of("worldedit.line.cuboid-only"));
return 0; return 0;
} }
checkCommandArgument(thickness >= 0, "Thickness must be >= 0"); checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
CuboidRegion cuboidregion = (CuboidRegion) region;
BlockVector3 pos1 = cuboidregion.getPos1(); BlockVector3 pos1 = cuboidregion.getPos1();
BlockVector3 pos2 = cuboidregion.getPos2(); BlockVector3 pos2 = cuboidregion.getPos2();
int blocksChanged = editSession.drawLine(pattern, pos1, pos2, thickness, !shell); int blocksChanged = editSession.drawLine(pattern, pos1, pos2, thickness, !shell);
@ -261,13 +266,12 @@ public class RegionCommands {
@Switch(name = 'h', desc = "Generate only a shell") @Switch(name = 'h', desc = "Generate only a shell")
boolean shell boolean shell
) throws WorldEditException { ) throws WorldEditException {
if (!(region instanceof ConvexPolyhedralRegion)) { if (!(region instanceof ConvexPolyhedralRegion cpregion)) {
actor.print(Caption.of("worldedit.curve.invalid-type")); actor.print(Caption.of("worldedit.curve.invalid-type"));
return 0; return 0;
} }
checkCommandArgument(thickness >= 0, "Thickness must be >= 0"); checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
ConvexPolyhedralRegion cpregion = (ConvexPolyhedralRegion) region;
List<BlockVector3> vectors = new ArrayList<>(cpregion.getVertices()); List<BlockVector3> vectors = new ArrayList<>(cpregion.getVertices());
int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell); int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell);
@ -468,7 +472,10 @@ public class RegionCommands {
desc = "Bypass region restrictions", desc = "Bypass region restrictions",
descFooter = "Bypass region restrictions" descFooter = "Bypass region restrictions"
) )
@CommandPermissions("fawe.admin") @CommandPermissions(
value = "fawe.admin",
queued = false
)
public void wea(Actor actor) throws WorldEditException { public void wea(Actor actor) throws WorldEditException {
if (actor.togglePermission("fawe.bypass")) { if (actor.togglePermission("fawe.bypass")) {
actor.print(Caption.of("fawe.info.worldedit.bypassed")); actor.print(Caption.of("fawe.info.worldedit.bypassed"));
@ -697,7 +704,7 @@ public class RegionCommands {
actor.print(Caption.of("fawe.regen.time")); actor.print(Caption.of("fawe.regen.time"));
//FAWE end //FAWE end
RegenOptions options = RegenOptions.builder() RegenOptions options = RegenOptions.builder()
.seed(!randomSeed ? seed : new Long(ThreadLocalRandom.current().nextLong())) .seed(!randomSeed ? seed : Long.valueOf(ThreadLocalRandom.current().nextLong()))
.regenBiomes(regenBiomes) .regenBiomes(regenBiomes)
.biomeType(biomeType) .biomeType(biomeType)
.build(); .build();
@ -718,9 +725,10 @@ public class RegionCommands {
@Command( @Command(
name = "/deform", name = "/deform",
desc = "Deforms a selected region with an expression", desc = "Deforms a selected region with an expression",
descFooter = "The expression is executed for each block and is expected\n" descFooter = """
+ "to modify the variables x, y and z to point to a new block\n" The expression is executed for each block and is expected
+ "to fetch. For details, see https://ehub.to/we/expr" to modify the variables x, y and z to point to a new block
to fetch. For details, see https://ehub.to/we/expr"""
) )
@CommandPermissions("worldedit.region.deform") @CommandPermissions("worldedit.region.deform")
@Logging(ALL) @Logging(ALL)
@ -794,9 +802,10 @@ public class RegionCommands {
@Command( @Command(
name = "/hollow", name = "/hollow",
desc = "Hollows out the object contained in this selection", desc = "Hollows out the object contained in this selection",
descFooter = "Hollows out the object contained in this selection.\n" descFooter = """
+ "Optionally fills the hollowed out part with the given block.\n" Hollows out the object contained in this selection.
+ "Thickness is measured in manhattan distance." Optionally fills the hollowed out part with the given block.
Thickness is measured in manhattan distance."""
) )
@CommandPermissions("worldedit.region.hollow") @CommandPermissions("worldedit.region.hollow")
@Logging(REGION) @Logging(REGION)

View File

@ -26,7 +26,6 @@ import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.io.schematic.MinecraftStructure; import com.fastasyncworldedit.core.extent.clipboard.io.schematic.MinecraftStructure;
import com.fastasyncworldedit.core.util.MainUtil; import com.fastasyncworldedit.core.util.MainUtil;
import com.google.common.base.Function;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
@ -46,6 +45,7 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.formatting.component.ErrorFormat; import com.sk89q.worldedit.util.formatting.component.ErrorFormat;
@ -90,6 +90,8 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.fastasyncworldedit.core.util.ReflectionUtils.as; import static com.fastasyncworldedit.core.util.ReflectionUtils.as;
@ -211,11 +213,9 @@ public class SchematicCommands {
} }
ClipboardHolder clipboard = session.getClipboard(); ClipboardHolder clipboard = session.getClipboard();
if (clipboard instanceof URIClipboardHolder) { if (clipboard instanceof URIClipboardHolder identifiable) {
URIClipboardHolder identifiable = (URIClipboardHolder) clipboard;
if (identifiable.contains(uri)) { if (identifiable.contains(uri)) {
if (identifiable instanceof MultiClipboardHolder) { if (identifiable instanceof MultiClipboardHolder multi) {
MultiClipboardHolder multi = (MultiClipboardHolder) identifiable;
multi.remove(uri); multi.remove(uri);
if (multi.getHolders().isEmpty()) { if (multi.getHolders().isEmpty()) {
session.setClipboard(null); session.setClipboard(null);
@ -317,12 +317,16 @@ public class SchematicCommands {
@Arg(desc = "File name.") @Arg(desc = "File name.")
String filename, String filename,
@Arg(desc = "Format name.", def = "fast") @Arg(desc = "Format name.", def = "fast")
String formatName String formatName,
//FAWE start - random rotation
@Switch(name = 'r', desc = "Apply random rotation to the clipboard")
boolean randomRotate
//FAWE end
) throws FilenameException { ) throws FilenameException {
LocalConfiguration config = worldEdit.getConfiguration(); LocalConfiguration config = worldEdit.getConfiguration();
//FAWE start //FAWE start
ClipboardFormat format = null; ClipboardFormat format;
InputStream in = null; InputStream in = null;
try { try {
URI uri; URI uri;
@ -385,6 +389,12 @@ public class SchematicCommands {
uri = file.toURI(); uri = file.toURI();
format.hold(actor, uri, in); format.hold(actor, uri, in);
if (randomRotate) {
AffineTransform transform = new AffineTransform();
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
transform = transform.rotateY(rotate);
session.getClipboard().setTransform(transform);
}
actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename)); actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
actor.print(Caption.of("worldedit.schematic.unknown-filename", TextComponent.of(filename))); actor.print(Caption.of("worldedit.schematic.unknown-filename", TextComponent.of(filename)));
@ -515,7 +525,10 @@ public class SchematicCommands {
aliases = {"listformats", "f"}, aliases = {"listformats", "f"},
desc = "List available formats" desc = "List available formats"
) )
@CommandPermissions("worldedit.schematic.formats") @CommandPermissions(
value = "worldedit.schematic.formats",
queued = false
)
public void formats(Actor actor) { public void formats(Actor actor) {
actor.print(Caption.of("worldedit.schematic.formats.title")); actor.print(Caption.of("worldedit.schematic.formats.title"));
StringBuilder builder; StringBuilder builder;
@ -541,7 +554,10 @@ public class SchematicCommands {
desc = "List saved schematics", desc = "List saved schematics",
descFooter = "Note: Format is not fully verified until loading." descFooter = "Note: Format is not fully verified until loading."
) )
@CommandPermissions("worldedit.schematic.list") @CommandPermissions(
value = "worldedit.schematic.list",
queued = false
)
public void list( public void list(
Actor actor, LocalSession session, Actor actor, LocalSession session,
@ArgFlag(name = 'p', desc = "Page to view.", def = "1") @ArgFlag(name = 'p', desc = "Page to view.", def = "1")
@ -812,7 +828,6 @@ public class SchematicCommands {
final String SCHEMATIC_NAME = file.getName(); final String SCHEMATIC_NAME = file.getName();
double oldKbOverwritten = 0; double oldKbOverwritten = 0;
String overwrittenPath = curFilepath;
int numFiles = -1; int numFiles = -1;
if (checkFilesize) { if (checkFilesize) {
@ -828,10 +843,10 @@ public class SchematicCommands {
if (overwrite) { if (overwrite) {
oldKbOverwritten = Files.size(Paths.get(file.getAbsolutePath())) / 1000.0; oldKbOverwritten = Files.size(Paths.get(file.getAbsolutePath())) / 1000.0;
int iter = 1; int iter = 1;
while (new File(overwrittenPath + "." + iter + "." + format.getPrimaryFileExtension()).exists()) { while (new File(curFilepath + "." + iter + "." + format.getPrimaryFileExtension()).exists()) {
iter++; iter++;
} }
file = new File(overwrittenPath + "." + iter + "." + format.getPrimaryFileExtension()); file = new File(curFilepath + "." + iter + "." + format.getPrimaryFileExtension());
} }
} }

View File

@ -314,7 +314,10 @@ public class SelectionCommands {
name = "/wand", name = "/wand",
desc = "Get the wand object" desc = "Get the wand object"
) )
@CommandPermissions("worldedit.wand") @CommandPermissions(
value = "worldedit.wand",
queued = false
)
public void wand( public void wand(
Player player, LocalSession session, Player player, LocalSession session,
@Switch(name = 'n', desc = "Get a navigation wand") boolean navWand @Switch(name = 'n', desc = "Get a navigation wand") boolean navWand
@ -348,7 +351,10 @@ public class SelectionCommands {
aliases = {"/toggleeditwand"}, aliases = {"/toggleeditwand"},
desc = "Remind the user that the wand is now a tool and can be unbound with /tool none." desc = "Remind the user that the wand is now a tool and can be unbound with /tool none."
) )
@CommandPermissions("worldedit.wand.toggle") @CommandPermissions(
value = "worldedit.wand.toggle",
queued = false
)
public void toggleWand(Player player) { public void toggleWand(Player player) {
player.print( player.print(
Caption.of( Caption.of(
@ -499,7 +505,10 @@ public class SelectionCommands {
name = "/size", name = "/size",
desc = "Get information about the selection" desc = "Get information about the selection"
) )
@CommandPermissions("worldedit.selection.size") @CommandPermissions(
value = "worldedit.selection.size",
queued = false
)
public void size( public void size(
Actor actor, World world, LocalSession session, Actor actor, World world, LocalSession session,
@Switch(name = 'c', desc = "Get clipboard info instead") @Switch(name = 'c', desc = "Get clipboard info instead")
@ -734,6 +743,7 @@ public class SelectionCommands {
box.appendCommand("sphere", Caption.of("worldedit.select.sphere.description"), "//sel sphere"); box.appendCommand("sphere", Caption.of("worldedit.select.sphere.description"), "//sel sphere");
box.appendCommand("cyl", Caption.of("worldedit.select.cyl.description"), "//sel cyl"); box.appendCommand("cyl", Caption.of("worldedit.select.cyl.description"), "//sel cyl");
box.appendCommand("convex", Caption.of("worldedit.select.convex.description"), "//sel convex"); box.appendCommand("convex", Caption.of("worldedit.select.convex.description"), "//sel convex");
//FAWE start //FAWE start
box.appendCommand("polyhedral", Caption.of("fawe.selection.sel.polyhedral"), "//sel polyhedral"); box.appendCommand("polyhedral", Caption.of("fawe.selection.sel.polyhedral"), "//sel polyhedral");
box.appendCommand("fuzzy[=<mask>]", Caption.of("fawe.selection.sel.fuzzy-instruction"), "//sel fuzzy[=<mask>]"); box.appendCommand("fuzzy[=<mask>]", Caption.of("fawe.selection.sel.fuzzy-instruction"), "//sel fuzzy[=<mask>]");

View File

@ -98,7 +98,10 @@ public class SnapshotCommands {
name = "list", name = "list",
desc = "List snapshots" desc = "List snapshots"
) )
@CommandPermissions("worldedit.snapshots.list") @CommandPermissions(
value = "worldedit.snapshots.list",
queued = false
)
void list( void list(
Actor actor, World world, Actor actor, World world,
@ArgFlag(name = 'p', desc = "Page of results to return", def = "1") @ArgFlag(name = 'p', desc = "Page of results to return", def = "1")
@ -127,8 +130,7 @@ public class SnapshotCommands {
TextComponent.of(world.getName()) TextComponent.of(world.getName())
)); ));
if (config.snapshotDatabase instanceof FileSystemSnapshotDatabase) { if (config.snapshotDatabase instanceof FileSystemSnapshotDatabase db) {
FileSystemSnapshotDatabase db = (FileSystemSnapshotDatabase) config.snapshotDatabase;
Path root = db.getRoot(); Path root = db.getRoot();
if (Files.isDirectory(root)) { if (Files.isDirectory(root)) {
WorldEdit.logger.info("No snapshots were found for world '" WorldEdit.logger.info("No snapshots were found for world '"

View File

@ -95,7 +95,7 @@ public class SnapshotUtilCommands {
if (snapshotName != null) { if (snapshotName != null) {
URI uri = resolveSnapshotName(config, snapshotName); URI uri = resolveSnapshotName(config, snapshotName);
Optional<Snapshot> snapOpt = config.snapshotDatabase.getSnapshot(uri); Optional<Snapshot> snapOpt = config.snapshotDatabase.getSnapshot(uri);
if (!snapOpt.isPresent()) { if (snapOpt.isEmpty()) {
actor.print(Caption.of("worldedit.restore.not-available")); actor.print(Caption.of("worldedit.restore.not-available"));
return; return;
} }

View File

@ -140,7 +140,7 @@ public class ToolUtilCommands {
@Arg(desc = "The range of the brush") @Arg(desc = "The range of the brush")
int range int range
) throws WorldEditException { ) throws WorldEditException {
session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()).setRange(range); session.getBrushTool(player).setRange(range);
player.print(Caption.of("worldedit.tool.range.set")); player.print(Caption.of("worldedit.tool.range.set"));
} }
@ -156,7 +156,7 @@ public class ToolUtilCommands {
) throws WorldEditException { ) throws WorldEditException {
we.checkMaxBrushRadius(size); we.checkMaxBrushRadius(size);
session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()).setSize(size); session.getBrushTool(player).setSize(size);
player.print(Caption.of("worldedit.tool.size.set")); player.print(Caption.of("worldedit.tool.size.set"));
} }

View File

@ -29,7 +29,6 @@ import com.fastasyncworldedit.core.util.StringMan;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.image.ImageUtil; import com.fastasyncworldedit.core.util.image.ImageUtil;
import com.fastasyncworldedit.core.util.task.DelegateConsumer; import com.fastasyncworldedit.core.util.task.DelegateConsumer;
import com.google.common.base.Function;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
@ -98,6 +97,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -130,7 +130,10 @@ public class UtilityCommands {
aliases = {"/hmi", "hmi"}, aliases = {"/hmi", "hmi"},
desc = "Generate the heightmap interface: https://github.com/IntellectualSites/HeightMap" desc = "Generate the heightmap interface: https://github.com/IntellectualSites/HeightMap"
) )
@CommandPermissions("fawe.admin") @CommandPermissions(
value = "fawe.admin",
queued = false
)
public void heightmapInterface( public void heightmapInterface(
Actor actor, Actor actor,
@Arg(name = "min", desc = "int", def = "100") int min, @Arg(name = "min", desc = "int", def = "100") int min,
@ -145,12 +148,9 @@ public class UtilityCommands {
final int sub = srcFolder.getAbsolutePath().length(); final int sub = srcFolder.getAbsolutePath().length();
List<String> images = new ArrayList<>(); List<String> images = new ArrayList<>();
MainUtil.iterateFiles(srcFolder, file -> { MainUtil.iterateFiles(srcFolder, file -> {
switch (file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(Locale.ROOT)) { String s = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(Locale.ROOT);
case ".png": if (!s.equals(".png") && !s.equals(".jpeg")) {
case ".jpeg": return;
break;
default:
return;
} }
try { try {
String name = file.getAbsolutePath().substring(sub); String name = file.getAbsolutePath().substring(sub);
@ -187,7 +187,7 @@ public class UtilityCommands {
StringBuilder config = new StringBuilder(); StringBuilder config = new StringBuilder();
config.append("var images = [\n"); config.append("var images = [\n");
for (String image : images) { for (String image : images) {
config.append('"' + image.replace(File.separator, "/") + "\",\n"); config.append('"').append(image.replace(File.separator, "/")).append("\",\n");
} }
config.append("];\n"); config.append("];\n");
config.append("// The low res images (they should all be the same size)\n"); config.append("// The low res images (they should all be the same size)\n");
@ -805,7 +805,10 @@ public class UtilityCommands {
name = "/help", name = "/help",
desc = "Displays help for WorldEdit commands" desc = "Displays help for WorldEdit commands"
) )
@CommandPermissions("worldedit.help") @CommandPermissions(
value = "worldedit.help",
queued = false
)
public void help( public void help(
Actor actor, Actor actor,
@Switch(name = 's', desc = "List sub-commands of the given command, if applicable") @Switch(name = 's', desc = "List sub-commands of the given command, if applicable")
@ -859,7 +862,6 @@ public class UtilityCommands {
URI uri = input.getKey(); URI uri = input.getKey();
String path = input.getValue(); String path = input.getValue();
boolean url = false;
boolean loaded = isLoaded.apply(uri); boolean loaded = isLoaded.apply(uri);
URIType type = URIType.FILE; URIType type = URIType.FILE;
@ -959,21 +961,13 @@ public class UtilityCommands {
if (len > 0) { if (len > 0) {
for (String arg : args) { for (String arg : args) {
switch (arg.toLowerCase(Locale.ROOT)) { switch (arg.toLowerCase(Locale.ROOT)) {
case "me": case "me", "mine", "local", "private" -> listMine = true;
case "mine": case "public", "global" -> listGlobal = true;
case "local": case "all" -> {
case "private":
listMine = true;
break;
case "public":
case "global":
listGlobal = true;
break;
case "all":
listMine = true; listMine = true;
listGlobal = true; listGlobal = true;
break; }
default: default -> {
if (arg.endsWith("/") || arg.endsWith(File.separator)) { if (arg.endsWith("/") || arg.endsWith(File.separator)) {
arg = arg.replace("/", File.separator); arg = arg.replace("/", File.separator);
String newDirFilter = dirFilter + arg; String newDirFilter = dirFilter + arg;
@ -995,7 +989,7 @@ public class UtilityCommands {
} else { } else {
filters.add(arg); filters.add(arg);
} }
break; }
} }
} }
} }
@ -1005,7 +999,7 @@ public class UtilityCommands {
List<File> toFilter = new ArrayList<>(); List<File> toFilter = new ArrayList<>();
if (!filters.isEmpty()) { if (!filters.isEmpty()) {
forEachFile = new DelegateConsumer<File>(forEachFile) { forEachFile = new DelegateConsumer<>(forEachFile) {
@Override @Override
public void accept(File file) { public void accept(File file) {
toFilter.add(file); toFilter.add(file);
@ -1015,7 +1009,7 @@ public class UtilityCommands {
if (formatName != null) { if (formatName != null) {
final ClipboardFormat cf = ClipboardFormats.findByAlias(formatName); final ClipboardFormat cf = ClipboardFormats.findByAlias(formatName);
forEachFile = new DelegateConsumer<File>(forEachFile) { forEachFile = new DelegateConsumer<>(forEachFile) {
@Override @Override
public void accept(File file) { public void accept(File file) {
if (cf.isFormat(file)) { if (cf.isFormat(file)) {
@ -1024,7 +1018,7 @@ public class UtilityCommands {
} }
}; };
} else { } else {
forEachFile = new DelegateConsumer<File>(forEachFile) { forEachFile = new DelegateConsumer<>(forEachFile) {
@Override @Override
public void accept(File file) { public void accept(File file) {
if (!file.toString().endsWith(".cached")) { if (!file.toString().endsWith(".cached")) {
@ -1062,7 +1056,7 @@ public class UtilityCommands {
} }
if (listGlobal) { if (listGlobal) {
File rel = MainUtil.resolveRelative(new File(dir, dirFilter)); File rel = MainUtil.resolveRelative(new File(dir, dirFilter));
forEachFile = new DelegateConsumer<File>(forEachFile) { forEachFile = new DelegateConsumer<>(forEachFile) {
@Override @Override
public void accept(File f) { public void accept(File f) {
try { try {
@ -1172,7 +1166,7 @@ public class UtilityCommands {
StringBuilder name = new StringBuilder(); StringBuilder name = new StringBuilder();
if (relative.isAbsolute()) { if (relative.isAbsolute()) {
relative = root.toURI().relativize(file.toURI()); relative = root.toURI().relativize(file.toURI());
name.append(".." + File.separator); name.append("..").append(File.separator);
} }
name.append(relative.getPath()); name.append(relative.getPath());
return name.toString(); return name.toString();

View File

@ -27,9 +27,13 @@ import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.ClipboardHolder;
import java.util.concurrent.ThreadLocalRandom;
public class ClipboardBrush implements Brush { public class ClipboardBrush implements Brush {
private final ClipboardHolder holder; private final ClipboardHolder holder;
@ -38,6 +42,9 @@ public class ClipboardBrush implements Brush {
private final boolean pasteEntities; private final boolean pasteEntities;
private final boolean pasteBiomes; private final boolean pasteBiomes;
private final Mask sourceMask; private final Mask sourceMask;
//FAWE start - random rotation
private final boolean randomRotate;
//FAWE end
public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin) { public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin) {
this.holder = holder; this.holder = holder;
@ -46,23 +53,48 @@ public class ClipboardBrush implements Brush {
this.pasteBiomes = false; this.pasteBiomes = false;
this.pasteEntities = false; this.pasteEntities = false;
this.sourceMask = null; this.sourceMask = null;
//FAWE start - random rotation
this.randomRotate = false;
//FAWE end
} }
public ClipboardBrush( public ClipboardBrush(
ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities, ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities,
boolean pasteBiomes, Mask sourceMask boolean pasteBiomes, Mask sourceMask
) { ) {
//FAWE start - random rotation
this(holder, ignoreAirBlocks, usingOrigin, pasteEntities, pasteBiomes, sourceMask, false);
}
public ClipboardBrush(
ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities,
boolean pasteBiomes, Mask sourceMask, boolean randomRotate
) {
//FAWE end
this.holder = holder; this.holder = holder;
this.ignoreAirBlocks = ignoreAirBlocks; this.ignoreAirBlocks = ignoreAirBlocks;
this.usingOrigin = usingOrigin; this.usingOrigin = usingOrigin;
this.pasteEntities = pasteEntities; this.pasteEntities = pasteEntities;
this.pasteBiomes = pasteBiomes; this.pasteBiomes = pasteBiomes;
this.sourceMask = sourceMask; this.sourceMask = sourceMask;
this.randomRotate = randomRotate;
} }
@Override @Override
public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws
MaxChangedBlocksException { MaxChangedBlocksException {
//FAWE start - random rotation
Transform originalTransform = holder.getTransform();
Transform transform = new AffineTransform();
if (this.randomRotate) {
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
transform = ((AffineTransform) transform).rotateY(rotate);
if (originalTransform != null) {
transform = originalTransform.combine(transform);
}
}
holder.setTransform(transform);
//FAWE end
Clipboard clipboard = holder.getClipboard(); Clipboard clipboard = holder.getClipboard();
Region region = clipboard.getRegion(); Region region = clipboard.getRegion();
BlockVector3 centerOffset = region.getCenter().toBlockPoint().subtract(clipboard.getOrigin()); BlockVector3 centerOffset = region.getCenter().toBlockPoint().subtract(clipboard.getOrigin());
@ -77,6 +109,10 @@ public class ClipboardBrush implements Brush {
.build(); .build();
Operations.completeLegacy(operation); Operations.completeLegacy(operation);
//FAWE start - random rotation
// reset transform
holder.setTransform(originalTransform);
//FAWE end
} }
} }

View File

@ -24,7 +24,7 @@ public class ConfirmHandler implements CommandCallListener {
} }
Optional<Actor> actorOpt = parameters.injectedValue(Key.of(Actor.class)); Optional<Actor> actorOpt = parameters.injectedValue(Key.of(Actor.class));
if (!actorOpt.isPresent()) { if (actorOpt.isEmpty()) {
return; return;
} }
Actor actor = actorOpt.get(); Actor actor = actorOpt.get();

View File

@ -437,14 +437,16 @@ public interface Player extends Entity, Actor {
} else { } else {
continue; continue;
} }
WorldEdit.getInstance().getExecutorService().submit(() -> { Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> {
doc.close(); // Ensure closed before deletion doc.close(); // Ensure closed before deletion
doc.getFile().delete(); doc.getFile().delete();
}); });
} }
} }
} else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT || Settings.settings().CLIPBOARD.USE_DISK) { } else if (Settings.settings().CLIPBOARD.USE_DISK) {
WorldEdit.getInstance().getExecutorService().submit(() -> session.setClipboard(null)); Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null));
} else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
session.setClipboard(null);
} }
if (Settings.settings().HISTORY.DELETE_ON_LOGOUT) { if (Settings.settings().HISTORY.DELETE_ON_LOGOUT) {
session.clearHistory(); session.clearHistory();
@ -470,7 +472,10 @@ public interface Player extends Entity, Actor {
} }
} catch (EmptyClipboardException ignored) { } catch (EmptyClipboardException ignored) {
} }
DiskOptimizedClipboard doc = DiskOptimizedClipboard.loadFromFile(file); DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit(
getUniqueId(),
() -> DiskOptimizedClipboard.loadFromFile(file)
).get();
Clipboard clip = doc.toClipboard(); Clipboard clip = doc.toClipboard();
ClipboardHolder holder = new ClipboardHolder(clip); ClipboardHolder holder = new ClipboardHolder(clip);
session.setClipboard(holder); session.setClipboard(holder);

View File

@ -321,10 +321,24 @@ public class PlatformManager {
return queryCapability(Capability.CONFIGURATION).getConfiguration(); return queryCapability(Capability.CONFIGURATION).getConfiguration();
} }
/**
* Get the current supported {@link SideEffect}s.
*
* @return the supported side effects
* @throws NoCapablePlatformException thrown if no platform is capable
*/
public Collection<SideEffect> getSupportedSideEffects() { public Collection<SideEffect> getSupportedSideEffects() {
return queryCapability(Capability.WORLD_EDITING).getSupportedSideEffects(); return queryCapability(Capability.WORLD_EDITING).getSupportedSideEffects();
} }
/**
* Get the initialized state of the Platform.
* {@return if the platform manager is initialized}
*/
public boolean isInitialized() {
return initialized.get();
}
/** /**
* You shouldn't have been calling this anyways, but this is now deprecated. Either don't * You shouldn't have been calling this anyways, but this is now deprecated. Either don't
* fire this event at all, or fire the new event via the event bus if you're a platform. * fire this event at all, or fire the new event via the event bus if you're a platform.

View File

@ -266,8 +266,8 @@ public class SpongeSchematicReader extends NBTSchematicReader {
if (id == null) { if (id == null) {
continue; continue;
} }
values.put("id", id);
//FAWE end //FAWE end
values.put("id", values.get("Id"));
values.remove("Id"); values.remove("Id");
values.remove("Pos"); values.remove("Pos");
if (fixer != null) { if (fixer != null) {

View File

@ -75,6 +75,7 @@ public final class Functions {
); );
handle = handle.asType(handle.type().changeReturnType(Number.class)); handle = handle.asType(handle.type().changeReturnType(Number.class));
handle = filterReturnValue(handle, DOUBLE_VALUE); handle = filterReturnValue(handle, DOUBLE_VALUE);
handle = handle.asType(handle.type().wrap());
} }
// return vararg-ity // return vararg-ity
if (wasVarargs) { if (wasVarargs) {

View File

@ -31,6 +31,7 @@ import com.fastasyncworldedit.core.queue.IChunkSet;
import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import com.sk89q.worldedit.world.storage.ChunkStore; import com.sk89q.worldedit.world.storage.ChunkStore;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -823,14 +824,15 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
boolean trimX = lowerX != 0 || upperX != 15; boolean trimX = lowerX != 0 || upperX != 15;
boolean trimZ = lowerZ != 0 || upperZ != 15; boolean trimZ = lowerZ != 0 || upperZ != 15;
if (!(trimX || trimZ)) {
return set;
}
for (int layer = get.getMinSectionPosition(); layer < get.getMaxSectionPosition(); layer++) { for (int layer = get.getMinSectionPosition(); layer < get.getMaxSectionPosition(); layer++) {
if (!set.hasSection(layer)) { if (!set.hasSection(layer)) {
continue; continue;
} }
char[] arr = Objects.requireNonNull(set.loadIfPresent(layer)); // This shouldn't be null if above is true char[] arr = Objects.requireNonNull(set.loadIfPresent(layer)); // This shouldn't be null if above is true
if (!(trimX || trimZ)) {
continue;
}
int indexY = 0; int indexY = 0;
for (int y = 0; y < 16; y++, indexY += 256) { // For each y layer within a chunk section for (int y = 0; y < 16; y++, indexY += 256) { // For each y layer within a chunk section
int index; int index;
@ -839,14 +841,14 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
for (int z = 0; z < lowerZ; z++) { for (int z = 0; z < lowerZ; z++) {
// null the z values // null the z values
for (int x = 0; x < 16; x++, index++) { for (int x = 0; x < 16; x++, index++) {
arr[index] = 0; arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} }
index = indexY + upperZi; index = indexY + upperZi;
for (int z = upperZ + 1; z < 16; z++) { for (int z = upperZ + 1; z < 16; z++) {
// null the z values // null the z values
for (int x = 0; x < 16; x++, index++) { for (int x = 0; x < 16; x++, index++) {
arr[index] = 0; arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} }
} }
@ -855,11 +857,11 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
for (int z = lowerZ; z <= upperZ; z++, index += 16) { for (int z = lowerZ; z <= upperZ; z++, index += 16) {
for (int x = 0; x < lowerX; x++) { for (int x = 0; x < lowerX; x++) {
// null the x values // null the x values
arr[index + x] = 0; arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
for (int x = upperX + 1; x < 16; x++) { for (int x = upperX + 1; x < 16; x++) {
// null the x values // null the x values
arr[index + x] = 0; arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} }
} }
@ -925,7 +927,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
for (int z = lowerZ; z <= upperZ; z++) { for (int z = lowerZ; z <= upperZ; z++) {
// null the z values // null the z values
for (int x = 0; x < 16; x++, index++) { for (int x = 0; x < 16; x++, index++) {
arr[index] = 0; arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} }
} }
@ -934,7 +936,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
for (int z = lowerZ; z <= upperZ; z++, index += 16) { for (int z = lowerZ; z <= upperZ; z++, index += 16) {
for (int x = lowerX; x <= upperX; x++) { for (int x = lowerX; x <= upperX; x++) {
// null the x values // null the x values
arr[index + x] = 0; arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__;
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More