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_USERNAME: FastAsyncWorldEdit Release
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:
args: |
"<@&525015715300900875> <@&706463154804097105> <@&671372968462516240>"

View File

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

2
.gitignore vendored
View File

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

11
Jenkinsfile vendored
View File

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

View File

@ -7,7 +7,7 @@ import xyz.jpenilla.runpaper.task.RunServer
plugins {
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()) {
@ -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 revision: String by extra("")
var buildNumber by extra("")
@ -105,7 +105,7 @@ tasks {
}
nexusPublishing {
repositories {
this.repositories {
sonatype {
nexusUrl.set(URI.create("https://s01.oss.sonatype.org/service/local/"))
snapshotRepositoryUrl.set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/"))

View File

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

View File

@ -6,12 +6,12 @@ log4j = "2.19.0"
# Plugins
dummypermscompat = "1.10"
worldguard-bukkit = "7.0.7"
worldguard-bukkit = "7.0.8"
mapmanager = "1.8.0-SNAPSHOT"
griefprevention = "16.18.1"
griefdefender = "2.1.0-SNAPSHOT"
residence = "4.5._13.1"
towny = "0.98.4.18"
towny = "0.99.1.2"
# Third party
bstats = "3.0.2"
@ -23,7 +23,7 @@ auto-value = "1.10.1"
findbugs = "3.0.2"
rhino-runtime = "1.7.14"
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"
jlibnoise = "1.0.0"
jchronic = "0.2.4a"
@ -36,7 +36,7 @@ text = "3.0.4"
piston = "0.5.7"
# Tests
mockito = "5.2.0"
mockito = "5.3.1"
# Gradle plugins
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" }
griefdefender = { group = "com.griefdefender", name = "api", version.ref = "griefdefender" }
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
bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
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
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

11
gradlew vendored
View File

@ -85,9 +85,6 @@ done
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -144,7 +141,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@ -152,7 +149,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
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;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in

View File

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

View File

@ -103,6 +103,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.Set;
import java.util.function.Supplier;
@ -658,10 +659,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.registryAccess()
.ownedRegistryOrThrow(
Registry.BIOME_REGISTRY);
return biomeRegistry.stream()
.map(biomeRegistry::getKey)
.map(CraftNamespacedKey::fromMinecraft)
.collect(Collectors.toList());
List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
List<NamespacedKey> namespacedKeys = new ArrayList<>();
for (ResourceLocation key : keys) {
try {
namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key));
} catch (IllegalArgumentException e) {
LOGGER.error("Error converting biome key {}", key.toString(), e);
}
}
return namespacedKeys;
}
@Override

View File

@ -445,10 +445,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << layer;
// Changes may still be written to chunk SET
char[] tmp = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
char[] setArr = set.load(layerNo);
// 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.

View File

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

View File

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

View File

@ -656,10 +656,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.registryAccess()
.ownedRegistryOrThrow(
Registry.BIOME_REGISTRY);
return biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull)
.map(CraftNamespacedKey::fromMinecraft)
.collect(Collectors.toList());
List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
List<NamespacedKey> namespacedKeys = new ArrayList<>();
for (ResourceLocation key : keys) {
try {
namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key));
} catch (IllegalArgumentException e) {
LOGGER.error("Error converting biome key {}", key.toString(), e);
}
}
return namespacedKeys;
}
@Override

View File

@ -491,9 +491,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
char[] setArr = set.load(layerNo);
// 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.

View File

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

View File

@ -1,3 +1,5 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
plugins {
java
}
@ -9,6 +11,6 @@ repositories {
}
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")
}

View File

@ -645,10 +645,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.registryAccess()
.ownedRegistryOrThrow(
Registry.BIOME_REGISTRY);
return biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull)
.map(CraftNamespacedKey::fromMinecraft)
.collect(Collectors.toList());
List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
List<NamespacedKey> namespacedKeys = new ArrayList<>();
for (ResourceLocation key : keys) {
try {
namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key));
} catch (IllegalArgumentException e) {
LOGGER.error("Error converting biome key {}", key.toString(), e);
}
}
return namespacedKeys;
}
@Override

View File

@ -488,9 +488,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
char[] setArr = set.load(layerNo);
// 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.

View File

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

View File

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

View File

@ -650,10 +650,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.getServer()
.registryAccess()
.registryOrThrow(BIOME);
return biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull)
.map(CraftNamespacedKey::fromMinecraft)
.collect(Collectors.toList());
List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
List<NamespacedKey> namespacedKeys = new ArrayList<>();
for (ResourceLocation key : keys) {
try {
namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key));
} catch (IllegalArgumentException e) {
LOGGER.error("Error converting biome key {}", key.toString(), e);
}
}
return namespacedKeys;
}
@Override

View File

@ -490,9 +490,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
char[] setArr = set.load(layerNo);
// 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.

View File

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

View File

@ -1,3 +1,5 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
plugins {
java
}
@ -9,6 +11,6 @@ repositories {
}
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")
}

View File

@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.TileEntityBlock;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.BukkitWorld;
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.BlockStateHolder;
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.entity.EntityType;
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.WritableRegistry;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.IntTag;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
@ -71,14 +68,12 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -86,7 +81,6 @@ import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.TreeType;
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.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockState;
@ -602,10 +596,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
.getServer()
.registryAccess()
.registryOrThrow(BIOME);
return biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull)
.map(CraftNamespacedKey::fromMinecraft)
.collect(Collectors.toList());
List<ResourceLocation> keys = biomeRegistry.stream()
.map(biomeRegistry::getKey).filter(Objects::nonNull).toList();
List<NamespacedKey> namespacedKeys = new ArrayList<>();
for (ResourceLocation key : keys) {
try {
namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key));
} catch (IllegalArgumentException e) {
LOGGER.error("Error converting biome key {}", key.toString(), e);
}
}
return namespacedKeys;
}
@Override

View File

@ -490,9 +490,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
char[] tmp = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
char[] setArr = set.load(layerNo);
// 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.

View File

@ -29,10 +29,12 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.TicketType;
import net.minecraft.util.BitStorage;
import net.minecraft.util.ExceptionCollector;
import net.minecraft.util.SimpleBitStorage;
import net.minecraft.util.ThreadingDetector;
import net.minecraft.util.Unit;
import net.minecraft.util.ZeroBitStorage;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos;
@ -182,7 +184,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
removeBlockEntityTicker.setAccessible(true);
methodremoveTickingBlockEntity = lookup.unreflect(removeBlockEntityTicker);
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p"));
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q"));
fieldRemove.setAccessible(true);
CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class);
@ -292,10 +294,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else {
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk;
}
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
if (nmsChunk != null) {
addTicket(serverLevel, chunkX, chunkZ);
return nmsChunk;
}
// Avoid "async" methods from the main thread.
@ -305,6 +309,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk = (CraftChunk) future.get();
addTicket(serverLevel, chunkX, chunkZ);
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
} catch (Throwable e) {
e.printStackTrace();
@ -313,6 +318,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ));
}
private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) {
// Ensure chunk is definitely loaded before applying a ticket
io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel
.getChunkSource()
.addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE));
}
public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
try {

View File

@ -3,7 +3,7 @@ import io.papermc.paperweight.userdev.attribute.Obfuscation
plugins {
`java-library`
id("com.modrinth.minotaur") version "2.+"
id("com.modrinth.minotaur") version "2.7.5"
}
project.description = "Bukkit"
@ -32,6 +32,10 @@ repositories {
name = "OSS Sonatype 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")) }
}
@ -47,7 +51,13 @@ val adapters = configurations.create("adapters") {
isCanBeResolved = true
shouldResolveConsistentlyWith(configurations["runtimeClasspath"])
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 com.fastasyncworldedit.bukkit.listener.ChunkListener;
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.Method;
@ -31,7 +29,7 @@ public class BukkitQueueHandler extends QueueHandler {
}
@Override
public void startSet(boolean parallel) {
public void startUnsafe(boolean parallel) {
ChunkListener.physicsFreeze = true;
if (parallel) {
try {
@ -51,7 +49,7 @@ public class BukkitQueueHandler extends QueueHandler {
}
@Override
public void endSet(boolean parallel) {
public void endUnsafe(boolean parallel) {
ChunkListener.physicsFreeze = false;
if (parallel) {
try {

View File

@ -165,7 +165,11 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
.setNameFormat("fawe-regen-%d")
.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, ...)?
//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();
}
} else { // Concurrency.NONE or generateConcurrent == false
// run sequential
for (long xz : coords) {
chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
}
// run sequential but submit to different thread
// running regen on the main thread otherwise triggers async-only events on the main thread
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)
public void onItemSpawn(ItemSpawnEvent event) {
if (physicsFreeze) {

View File

@ -112,11 +112,15 @@ public class WorldEditPlugin extends JavaPlugin {
private BukkitServerInterface platform;
private BukkitConfiguration config;
private BukkitPermissionAttachmentManager permissionAttachmentManager;
// Fawe start
private BukkitCommandSender bukkitConsoleCommandSender;
// Fawe end
@Override
public void onLoad() {
//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.
Plugin[] plugins = Bukkit.getServer().getPluginManager().getPlugins();
for (Plugin p : plugins) {
@ -594,7 +598,7 @@ public class WorldEditPlugin extends JavaPlugin {
return new BukkitBlockCommandSender(this, (BlockCommandSender) sender);
}
return new BukkitCommandSender(this, sender);
return bukkitConsoleCommandSender;
}
public BukkitServerInterface getInternalPlatform() {

View File

@ -23,6 +23,7 @@ import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.platform.Actor;
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.task.Task;
import com.sk89q.worldedit.world.World;
@ -39,9 +40,12 @@ public class CLIExtraCommands {
desc = "Select the entire world"
)
public void selectWorld(Actor actor, World world, LocalSession session) {
session.setRegionSelector(world, new CuboidRegionSelector(
world, world.getMinimumPoint(), world.getMaximumPoint()
));
final CuboidRegionSelector selector;
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."));
}

View File

@ -1,14 +1,14 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.30"
kotlin("jvm") version "1.8.20"
application
}
applyCommonConfiguration()
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
kotlinOptions.jvmTarget = "17"
}
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.TextureUtil;
import com.fastasyncworldedit.core.util.WEManager;
import com.fastasyncworldedit.core.util.task.KeyQueuedExecutorService;
import com.github.luben.zstd.Zstd;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import net.jpountz.lz4.LZ4Factory;
@ -33,6 +35,9 @@ import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
@ -86,6 +91,7 @@ public class Fawe {
* The platform specific implementation.
*/
private final IFawe implementation;
private final KeyQueuedExecutorService<UUID> clipboardExecutor;
private FaweVersion version;
private TextureUtil textures;
private QueueHandler queueHandler;
@ -131,6 +137,15 @@ public class Fawe {
}, 0);
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.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(
"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.PARALLEL_THREADS
);
@ -427,4 +443,15 @@ public class Fawe {
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;
import com.fastasyncworldedit.core.configuration.Caption;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.util.formatting.text.Component;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
@ -13,33 +15,81 @@ public class SuggestInputParseException extends InputParseException {
private final InputParseException cause;
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) {
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) {
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) {
if (other instanceof SuggestInputParseException) {
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) {
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(other);
this.cause = other;
this.getSuggestions = getSuggestions;
this.prefix = prefix;
}
public static SuggestInputParseException get(InvocationTargetException e) {
@ -54,7 +104,7 @@ public class SuggestInputParseException extends InputParseException {
}
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()
.map(Object::toString)
.filter(v -> v.startsWith(input))
@ -76,8 +126,12 @@ public class SuggestInputParseException extends InputParseException {
return getSuggestions.get();
}
/**
* @deprecated Unused
*/
@Deprecated(forRemoval = true, since = "2.6.2")
public SuggestInputParseException prepend(String input) {
this.prefix = input + prefix;
// Do nothing
return this;
}

View File

@ -102,11 +102,10 @@ public class Settings extends Config {
public FaweLimit getLimit(Actor actor) {
FaweLimit limit;
if (actor.hasPermission("fawe.limit.*") || actor.hasPermission("fawe.bypass")) {
limit = FaweLimit.MAX.copy();
} else {
limit = new FaweLimit();
if (actor.hasPermission("fawe.bypass") || actor.hasPermission("fawe.limit.unlimited")) {
return FaweLimit.MAX.copy();
}
limit = new FaweLimit();
ArrayList<String> keys = new ArrayList<>(LIMITS.getSections());
if (keys.remove("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",
"of a waterlogged fence). For blocking/remapping of all occurrences of a property like waterlogged, see",
"remap-properties below.",
"To generate a blank list, substitute the default content with a set of square brackets [] instead.",
"Example block property blocking:",
" - \"minecraft:conduit[waterlogged=true]\"",
" - \"minecraft:piston[extended=false,facing=west]\"",
@ -520,10 +520,10 @@ public class Settings extends Config {
" - A smaller value will reduce memory usage",
" - A value too small may break some operations (deform?)",
" - 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({
"Force FAWE to start placing chunks regardless of whether an edit is finished processing",
" - A larger value will use slightly less CPU time",
@ -671,6 +671,14 @@ public class Settings extends Config {
})
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 {
@ -694,7 +702,7 @@ public class Settings extends Config {
public static class TICK_LIMITER {
@Comment("Enable the limiter")
public boolean ENABLED = true;
public boolean ENABLED = false;
@Comment("The interval in ticks")
public int INTERVAL = 20;
@Comment("Max falling blocks per interval (per chunk)")
@ -703,12 +711,6 @@ public class Settings extends Config {
public int PHYSICS_MS = 10;
@Comment("Max item spawns per interval (per chunk)")
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
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
if (input.isEmpty()) {
throw new SuggestInputParseException("No input provided", "", () -> Stream
throw new SuggestInputParseException(Caption.of("fawe.error.no-input-provided"), () -> Stream
.of("#", ",", "&")
.map(n -> n + ":")
.collect(Collectors.toList())
@ -95,7 +95,6 @@ public class RichMaskParser extends FaweParser<Mask> {
"https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns"
))
)),
full,
() -> {
if (full.length() == 1) {
return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions(""));
@ -148,6 +147,7 @@ public class RichMaskParser extends FaweParser<Mask> {
try {
builder.addRegex(full);
} catch (InputParseException ignored) {
builder.clear();
context.setPreferringWildcard(false);
context.setRestricted(false);
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"
))
)),
full,
() -> {
if (full.length() == 1) {
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 {
if (input.isEmpty()) {
throw new SuggestInputParseException(
"No input provided",
"",
Caption.of("fawe.error.no-input-provided"),
() -> Stream
.concat(Stream.of("#", ",", "&"), BlockTypes.getNameSpaces().stream().map(n -> n + ":"))
.collect(Collectors.toList())
@ -88,7 +87,6 @@ public class RichPatternParser extends FaweParser<Pattern> {
"https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns"
))
)),
full,
() -> {
if (full.length() == 1) {
return new ArrayList<>(worldEdit.getPatternFactory().getSuggestions(""));

View File

@ -141,7 +141,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
BlockState state = BlockTypesCache.states[block];
if (blockedBlocks != null) {
if (blockedBlocks.contains(state.getBlockType().getId())) {
blocks[i] = 0;
blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
continue;
}
}
@ -150,7 +150,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
}
for (FuzzyBlockState fuzzy : blockedStates) {
if (fuzzy.equalsFuzzy(state)) {
blocks[i] = 0;
blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
continue it;
}
}
@ -178,7 +178,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
@Override
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
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);
}
if (disallowedIntersection != null) {
intersection.processSet(chunk, get, set);
set = disallowedIntersection.processSet(chunk, get, set, true);
}
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.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.io.IOException;
import java.util.Collection;
@ -191,8 +192,8 @@ public class CPUOptimizedClipboard extends LinearClipboard {
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) {
char ordinal = block.getOrdinalChar();
if (ordinal == 0) {
ordinal = 1;
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = BlockTypesCache.ReservedIDs.AIR;
}
states[index] = ordinal;
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.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import org.apache.logging.log4j.Logger;
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
* 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) {
this(file, VERSION);
}
@ -166,14 +169,15 @@ public class DiskOptimizedClipboard extends LinearClipboard {
*
* @param file File to read from
* @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) {
super(readSize(file, versionOverride), BlockVector3.ZERO);
headerSize = getHeaderSizeOverrideFromVersion(versionOverride);
nbtMap = new HashMap<>();
try {
this.file = file;
checkFileLength(file);
this.braf = new RandomAccessFile(file, "rw");
braf.setLength(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
* failure.
@ -723,8 +701,8 @@ public class DiskOptimizedClipboard extends LinearClipboard {
try {
int index = headerSize + (getIndex(x, y, z) << 1);
char ordinal = block.getOrdinalChar();
if (ordinal == 0) {
ordinal = 1;
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = BlockTypesCache.ReservedIDs.AIR;
}
byteBuffer.putChar(index, ordinal);
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.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.io.IOException;
import java.util.Collection;
@ -269,8 +270,8 @@ public class MemoryOptimizedClipboard extends LinearClipboard {
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) {
int ordinal = block.getOrdinal();
if (ordinal == 0) {
ordinal = 1;
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = BlockTypesCache.ReservedIDs.AIR;
}
setOrdinal(index, ordinal);
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();

View File

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

View File

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

View File

@ -257,7 +257,7 @@ public class MultiBatchProcessor implements IBatchProcessor {
for (IBatchProcessor processor : processors) {
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.
* Order in which processors are executed:
* - ADDING_BLOCKS (processors that strictly ADD blocks to an edit ONLY)
* - CHANGING_BLOCKS (processors that strictly ADD or CHANGE blocks being set)
* - REMOVING_BLOCKS (processors that string ADD, CHANGE or REMOVE blocks being set)
* - ADDING_BLOCKS (processors that may ADD blocks to an edit ONLY)
* - CHANGING_BLOCKS (processors that may ADD or CHANGE blocks being set)
* - REMOVING_BLOCKS (processors that may ADD, CHANGE or REMOVE blocks being set)
* - 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)
*/

View File

@ -73,11 +73,11 @@ public class HeightmapProcessor implements IBatchProcessor {
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
for (int j = 0; j < BLOCKS_PER_Y; j++) {
char ordinal = 0;
char ordinal = BlockTypesCache.ReservedIDs.__RESERVED__;
if (hasSectionSet) {
ordinal = setSection[index(y, j)];
}
if (ordinal == 0) {
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
if (!hasSectionGet) {
if (!hasSectionSet) {
continue layer;

View File

@ -178,8 +178,7 @@ public class BlockMaskBuilder {
}
if (operator == null) {
throw new SuggestInputParseException(
"No operator for " + input,
"",
Caption.of("fawe.error.no-operator-for-input", input),
() -> Arrays.asList("=", "~", "!", "<", ">", "<=", ">=")
);
}
@ -195,7 +194,7 @@ public class BlockMaskBuilder {
String value = charSequence.toString();
final PropertyKey fKey = key;
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<>();
types.stream().filter(t -> t.hasProperty(fKey)).forEach(t -> {
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 {
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();
finalTypes.forEach(t -> t.getProperties().forEach(p -> keys.add(p.getKey())));
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.change.Change;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
public class MutableBiomeChange implements Change {
@ -13,8 +14,8 @@ public class MutableBiomeChange implements Change {
private int to;
public MutableBiomeChange() {
this.from = 0;
this.to = 0;
this.from = BlockTypesCache.ReservedIDs.__RESERVED__;
this.to = BlockTypesCache.ReservedIDs.__RESERVED__;
}
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.change.Change;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache;
public class MutableFullBlockChange implements Change {
@ -39,14 +40,14 @@ public class MutableFullBlockChange implements Change {
if (blockBag != null) {
BlockState toState = BlockState.getFromOrdinal(to);
if (fromState != toState) {
if (allowFetch && from != 0) {
if (allowFetch && from != BlockTypesCache.ReservedIDs.__RESERVED__) {
try {
blockBag.fetchPlacedBlock(fromState);
} catch (BlockBagException e) {
return;
}
}
if (allowStore && to != 0) {
if (allowStore && to != BlockTypesCache.ReservedIDs.__RESERVED__) {
try {
blockBag.storeDroppedBlock(toState);
} catch (BlockBagException ignored) {

View File

@ -39,19 +39,27 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
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 {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private final World world;
private final AtomicInteger lastException = new AtomicInteger();
protected AtomicInteger waitingCombined = new AtomicInteger(0);
protected AtomicInteger waitingAsync = new AtomicInteger(0);
protected boolean closed;
private final Semaphore workerSemaphore = new Semaphore(1, false);
private final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();
protected volatile boolean closed;
public AbstractChangeSet(World world) {
this.world = world;
@ -65,16 +73,11 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
if (closed) {
return;
}
waitingAsync.incrementAndGet();
TaskManager.taskManager().async(() -> {
waitingAsync.decrementAndGet();
synchronized (waitingAsync) {
waitingAsync.notifyAll();
}
try {
close();
} catch (IOException e) {
e.printStackTrace();
LOGGER.catching(e);
}
});
}
@ -82,20 +85,10 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
@Override
public void flush() {
try {
if (!Fawe.isMainThread()) {
while (waitingAsync.get() > 0) {
synchronized (waitingAsync) {
waitingAsync.wait(1000);
}
}
}
while (waitingCombined.get() > 0) {
synchronized (waitingCombined) {
waitingCombined.wait(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
// drain with this thread too
drainQueue(true);
} catch (Exception e) {
LOGGER.catching(e);
}
}
@ -125,7 +118,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
}
@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 bz = chunk.getZ() << 4;
@ -193,7 +186,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
}
final int combinedFrom = from;
final int combinedTo = blocksSet[index];
if (combinedTo != 0) {
if (combinedTo != BlockTypesCache.ReservedIDs.__RESERVED__) {
add(xx, yy, zz, combinedFrom, combinedTo);
}
}
@ -306,12 +299,12 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
BaseBlock to = change.getCurrent();
add(loc, from, to);
} catch (Exception e) {
e.printStackTrace();
LOGGER.catching(e);
}
}
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) {
@ -353,7 +346,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
add(x, y, z, combinedFrom, combinedTo);
} 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) {
AbstractChangeSet.this.waitingCombined.incrementAndGet();
Runnable wrappedTask = () -> {
try {
writeTask.run();
@ -372,25 +364,55 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
} else {
int hash = t.getMessage().hashCode();
if (lastException.getAndSet(hash) != hash) {
t.printStackTrace();
}
}
} finally {
if (AbstractChangeSet.this.waitingCombined.decrementAndGet() <= 0) {
synchronized (AbstractChangeSet.this.waitingAsync) {
AbstractChangeSet.this.waitingAsync.notifyAll();
}
synchronized (AbstractChangeSet.this.waitingCombined) {
AbstractChangeSet.this.waitingCombined.notifyAll();
LOGGER.catching(t);
}
}
}
};
if (completeNow) {
wrappedTask.run();
return Futures.immediateCancelledFuture();
return Futures.immediateVoidFuture();
} 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) {
super(parent.getWorld());
this.parent = parent;
this.waitingCombined = parent.waitingCombined;
this.waitingAsync = parent.waitingAsync;
}
public final AbstractChangeSet getParent() {

View File

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

View File

@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.limit;
import com.fastasyncworldedit.core.FaweCache;
import java.util.Collections;
import java.util.Set;
public class FaweLimit {
@ -114,10 +115,10 @@ public class FaweLimit {
MAX.FAST_PLACEMENT = true;
MAX.CONFIRM_LARGE = true;
MAX.RESTRICT_HISTORY_TO_REGIONS = false;
MAX.STRIP_NBT = null;
MAX.STRIP_NBT = Collections.emptySet();
MAX.UNIVERSAL_DISALLOWED_BLOCKS = false;
MAX.DISALLOWED_BLOCKS = null;
MAX.REMAP_PROPERTIES = null;
MAX.DISALLOWED_BLOCKS = Collections.emptySet();
MAX.REMAP_PROPERTIES = Collections.emptySet();
}
public boolean MAX_CHANGES() {
@ -241,7 +242,7 @@ public class FaweLimit {
&& FAST_PLACEMENT
&& !RESTRICT_HISTORY_TO_REGIONS
&& (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())
&& (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> {
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
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.extent.Extent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import javax.annotation.Nullable;
import java.util.Map;
@ -71,7 +72,7 @@ public interface IBatchProcessor {
if (arr != null) {
int index = (minY & 15) << 8;
for (int i = 0; i < index; i++) {
arr[i] = 0;
arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
}
} else {
arr = new char[4096];
@ -89,7 +90,7 @@ public interface IBatchProcessor {
if (arr != null) {
int index = ((maxY + 1) & 15) << 8;
for (int i = index; i < arr.length; i++) {
arr[i] = 0;
arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
}
} else {
arr = new char[4096];
@ -130,7 +131,7 @@ public interface IBatchProcessor {
if (arr != null) {
int index = (minY & 15) << 8;
for (int i = index; i < 4096; i++) {
arr[i] = 0;
arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
}
}
set.setBlocks(layer, arr);
@ -139,7 +140,7 @@ public interface IBatchProcessor {
if (arr != null) {
int index = ((maxY + 1) & 15) << 8;
for (int i = 0; i < index; i++) {
arr[i] = 0;
arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
}
}
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.block.BlockStateHolder;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -95,7 +96,7 @@ public interface IChunkSet extends IBlocks, OutputExtent {
}
default Map<HeightMapType, int[]> getHeightMaps() {
return new HashMap<>();
return new EnumMap<>(HeightMapType.class);
}
@Override
@ -115,4 +116,15 @@ public interface IChunkSet extends IBlocks, OutputExtent {
*/
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.TaskManager;
import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal;
import com.fastasyncworldedit.core.util.task.FaweForkJoinWorkerThreadFactory;
import com.fastasyncworldedit.core.wrappers.WorldWrapper;
import com.google.common.util.concurrent.Futures;
import com.sk89q.worldedit.world.World;
@ -39,10 +40,41 @@ import java.util.function.Supplier;
@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class QueueHandler implements Trimable, Runnable {
private final ForkJoinPool forkJoinPoolPrimary = new ForkJoinPool();
private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool();
private static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
/**
* 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();
/**
* 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<>();
/**
* 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 Map<World, WeakReference<IChunkCache<IChunkGet>>> chunkGetCache = new HashMap<>();
@ -53,9 +85,8 @@ public abstract class QueueHandler implements Trimable, Runnable {
*/
private long last;
private long allocate = 50;
private double targetTPS = 18;
public QueueHandler() {
protected QueueHandler() {
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() {
return blockingExecutor.getActiveCount() < blockingExecutor.getMaximumPoolSize();
}
private long getAllocate() {
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 absDiff = Math.abs(diff);
if (diff == 0) {
@ -126,6 +163,10 @@ public abstract class QueueHandler implements Trimable, Runnable {
} 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) {
try {
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) {
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) {
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) {
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) {
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 {
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) {
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) {
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 {
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) {
@ -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) {
// if (MemUtil.isMemoryFree()) { TODO NOT IMPLEMENTED - optimize this
// return (T) forkJoinPoolSecondary.submit(chunk);
@ -260,6 +401,9 @@ public abstract class QueueHandler implements Trimable, Runnable {
return new SingleThreadQueueExtent();
}
/**
* Sets the current thread's {@link IQueueExtent} instance in the queue pool to null.
*/
public void unCache() {
queuePool.set(null);
}
@ -272,14 +416,58 @@ public abstract class QueueHandler implements Trimable, Runnable {
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) {
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) {
final IQueueExtent<IQueueChunk> queue = pool();
IChunkCache<IChunkGet> cacheGet = getOrCreateWorldCache(world);
@ -294,6 +482,12 @@ public abstract class QueueHandler implements Trimable, Runnable {
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
public boolean trim(boolean aggressive) {
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
* coordinates
*
* @param chunkX
* @param chunkZ
* @param chunkX X chunk coordinate
* @param chunkZ Z chunk coordinate
* @return IChunk
*/
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
// - memory is low & queue size > num threads + 8
// - 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
.instance()
.getQueueHandler()
.isUnderutilized()))) {
int targetSize = lowMem ? Settings.settings().QUEUE.PARALLEL_THREADS + 8 : Settings.settings().QUEUE.TARGET_SIZE;
if (enabledQueue && size > targetSize && (lowMem || Fawe.instance().getQueueHandler().isUnderutilized())) {
chunk = chunks.removeFirst();
final Future future = submitUnchecked(chunk);
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);
submissions.add(future);
}

View File

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

View File

@ -52,6 +52,7 @@ import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
@ -68,7 +69,6 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -81,10 +81,8 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@ -533,6 +531,21 @@ public class MainUtil {
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) {
if (src == null) {
return src;

View File

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

View File

@ -130,7 +130,9 @@ public class WEManager {
backupRegions.add(region);
}
} 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;
iterator.remove();
}

View File

@ -36,7 +36,7 @@ public class SimpleRandomCollection<E> extends RandomCollection<E> {
@Override
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";
}
URL url = new URL(arg);
MainUtil.checkImageHost(url.toURI());
BufferedImage img = MainUtil.readImage(url);
if (img == null) {
throw new IOException("Failed to read " + url + ", please try again later");
@ -218,7 +219,7 @@ public class ImageUtil {
return MainUtil.readImage(file);
}
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()));
}
}
@ -229,7 +230,9 @@ public class ImageUtil {
if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) {
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:/")) {
arg = arg.replaceFirst("file:/+", "");

View File

@ -1,9 +1,13 @@
package com.fastasyncworldedit.core.util.task;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.configuration.Settings;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.Closeable;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -11,6 +15,13 @@ import java.util.function.Supplier;
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 Thread.UncaughtExceptionHandler handler;
private boolean closed;
@ -59,7 +70,7 @@ public class AsyncNotifyQueue implements Closeable {
}
return null;
};
self[0] = Fawe.instance().getQueueHandler().async(wrapped);
self[0] = QUEUE_SUBMISSIONS.submit(wrapped);
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 (actor != null && !actor.hasPermission("fawe.bypass.regions")) {
if (actor instanceof Player) {
Player player = (Player) actor;
if (actor instanceof Player player) {
allowedRegions = player.getAllowedRegions();
}
}
}
if (disallowedRegions == null && Settings.settings().REGION_RESTRICTIONS && Settings.settings().REGION_RESTRICTIONS_OPTIONS.ALLOW_BLACKLISTS) {
if (actor != null && !actor.hasPermission("fawe.bypass.regions")) {
if (actor instanceof Player) {
Player player = (Player) actor;
if (actor instanceof Player player) {
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
if (placeChunks) {
if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) {
@ -597,15 +598,14 @@ public final class EditSessionBuilder {
this.extent = regionExtent;
}
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) {
queue.addProcessor(new StripNBTExtent(this.extent, this.limit.STRIP_NBT));
} else {
this.extent = new StripNBTExtent(this.extent, this.limit.STRIP_NBT);
queue.addProcessor((IBatchProcessor) this.extent);
}
}
if (this.limit != null && !this.limit.isUnlimited()) {
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);
}
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;
if (!limitBlocks.isEmpty() || (remaps != null && !remaps.isEmpty())) {
this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps);
if (placeChunks) {
queue.addProcessor(new DisallowedBlocksExtent(this.extent, limitBlocks, remaps));
} else {
this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps);
queue.addProcessor((IBatchProcessor) this.extent);
}
}
}

View File

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

View File

@ -79,7 +79,10 @@ public class ChunkCommands {
aliases = {"/chunkinfo"},
desc = "Get information about the chunk you're inside"
)
@CommandPermissions("worldedit.chunkinfo")
@CommandPermissions(
value = "worldedit.chunkinfo",
queued = false
)
public void chunkInfo(Player player) {
Location pos = player.getBlockLocation();
int chunkX = (int) Math.floor(pos.getBlockX() / 16.0);
@ -99,7 +102,10 @@ public class ChunkCommands {
aliases = {"/listchunks"},
desc = "List chunks that your selection includes"
)
@CommandPermissions("worldedit.listchunks")
@CommandPermissions(
value = "worldedit.listchunks",
queued = false
)
public void listChunks(
Actor actor, World world, LocalSession session,
@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.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.regions.selector.ExtendingCuboidRegionSelector;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
@ -159,35 +160,7 @@ public class ClipboardCommands {
session.getPlacementPosition(actor));
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
copy.setCopyingEntities(copyEntities);
copy.setCopyingBiomes(copyBiomes);
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));
createCopy(session, editSession, copyBiomes, mask, clipboard, copy);
copy.getStatusMessages().forEach(actor::print);
//FAWE end
@ -298,7 +271,25 @@ public class ClipboardCommands {
copy.setSourceFunction(new BlockReplace(editSession, leavePattern));
copy.setCopyingEntities(copyEntities);
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);
Mask sourceMask = editSession.getSourceMask();
Region[] regions = editSession.getAllowedRegions();
Region allowedRegion;
@ -317,20 +308,13 @@ public class ClipboardCommands {
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));
if (!actor.hasPermission("fawe.tips")) {
actor.print(Caption.of("fawe.tips.tip.lazycut"));
}
copy.getStatusMessages().forEach(actor::print);
//FAWE end
}
@Command(
@ -444,7 +428,12 @@ public class ClipboardCommands {
Vector3 max = realTo.add(holder
.getTransform()
.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);
selector.learnChanges();
selector.explainRegionAdjust(actor, session);
@ -475,9 +464,10 @@ public class ClipboardCommands {
@Command(
name = "/rotate",
desc = "Rotate the contents of the clipboard",
descFooter = "Non-destructively rotate the contents of the clipboard.\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.\n"
descFooter = """
Non-destructively rotate the contents of the clipboard.
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")
public void rotate(

View File

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

View File

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

View File

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

View File

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

View File

@ -140,7 +140,10 @@ public class RegionCommands {
name = "/test",
desc = "test region"
)
@CommandPermissions("worldedit.region.test")
@CommandPermissions(
value = "worldedit.region.test",
queued = false
)
@Logging(REGION)
public void test(
Actor actor, EditSession editSession,
@ -175,7 +178,10 @@ public class RegionCommands {
aliases = "/nbt",
desc = "View nbt info for a block"
)
@CommandPermissions("worldedit.nbtinfo")
@CommandPermissions(
value = "worldedit.nbtinfo",
queued = false
)
public void nbtinfo(Player player, EditSession editSession) {
Location pos = player.getBlockTrace(128);
if (pos == null) {
@ -228,13 +234,12 @@ public class RegionCommands {
@Switch(name = 'h', desc = "Generate only a shell")
boolean shell
) throws WorldEditException {
if (!(region instanceof CuboidRegion)) {
if (!(region instanceof CuboidRegion cuboidregion)) {
actor.print(Caption.of("worldedit.line.cuboid-only"));
return 0;
}
checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
CuboidRegion cuboidregion = (CuboidRegion) region;
BlockVector3 pos1 = cuboidregion.getPos1();
BlockVector3 pos2 = cuboidregion.getPos2();
int blocksChanged = editSession.drawLine(pattern, pos1, pos2, thickness, !shell);
@ -261,13 +266,12 @@ public class RegionCommands {
@Switch(name = 'h', desc = "Generate only a shell")
boolean shell
) throws WorldEditException {
if (!(region instanceof ConvexPolyhedralRegion)) {
if (!(region instanceof ConvexPolyhedralRegion cpregion)) {
actor.print(Caption.of("worldedit.curve.invalid-type"));
return 0;
}
checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
ConvexPolyhedralRegion cpregion = (ConvexPolyhedralRegion) region;
List<BlockVector3> vectors = new ArrayList<>(cpregion.getVertices());
int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell);
@ -468,7 +472,10 @@ public class RegionCommands {
desc = "Bypass region restrictions",
descFooter = "Bypass region restrictions"
)
@CommandPermissions("fawe.admin")
@CommandPermissions(
value = "fawe.admin",
queued = false
)
public void wea(Actor actor) throws WorldEditException {
if (actor.togglePermission("fawe.bypass")) {
actor.print(Caption.of("fawe.info.worldedit.bypassed"));
@ -697,7 +704,7 @@ public class RegionCommands {
actor.print(Caption.of("fawe.regen.time"));
//FAWE end
RegenOptions options = RegenOptions.builder()
.seed(!randomSeed ? seed : new Long(ThreadLocalRandom.current().nextLong()))
.seed(!randomSeed ? seed : Long.valueOf(ThreadLocalRandom.current().nextLong()))
.regenBiomes(regenBiomes)
.biomeType(biomeType)
.build();
@ -718,9 +725,10 @@ public class RegionCommands {
@Command(
name = "/deform",
desc = "Deforms a selected region with an expression",
descFooter = "The expression is executed for each block and is expected\n"
+ "to modify the variables x, y and z to point to a new block\n"
+ "to fetch. For details, see https://ehub.to/we/expr"
descFooter = """
The expression is executed for each block and is expected
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")
@Logging(ALL)
@ -794,9 +802,10 @@ public class RegionCommands {
@Command(
name = "/hollow",
desc = "Hollows out the object contained in this selection",
descFooter = "Hollows out the object contained in this selection.\n"
+ "Optionally fills the hollowed out part with the given block.\n"
+ "Thickness is measured in manhattan distance."
descFooter = """
Hollows out the object contained in this selection.
Optionally fills the hollowed out part with the given block.
Thickness is measured in manhattan distance."""
)
@CommandPermissions("worldedit.region.hollow")
@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.io.schematic.MinecraftStructure;
import com.fastasyncworldedit.core.util.MainUtil;
import com.google.common.base.Function;
import com.google.common.collect.Multimap;
import com.sk89q.worldedit.LocalConfiguration;
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.function.operation.Operations;
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.session.ClipboardHolder;
import com.sk89q.worldedit.util.formatting.component.ErrorFormat;
@ -90,6 +90,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
import java.util.regex.Pattern;
import static com.fastasyncworldedit.core.util.ReflectionUtils.as;
@ -211,11 +213,9 @@ public class SchematicCommands {
}
ClipboardHolder clipboard = session.getClipboard();
if (clipboard instanceof URIClipboardHolder) {
URIClipboardHolder identifiable = (URIClipboardHolder) clipboard;
if (clipboard instanceof URIClipboardHolder identifiable) {
if (identifiable.contains(uri)) {
if (identifiable instanceof MultiClipboardHolder) {
MultiClipboardHolder multi = (MultiClipboardHolder) identifiable;
if (identifiable instanceof MultiClipboardHolder multi) {
multi.remove(uri);
if (multi.getHolders().isEmpty()) {
session.setClipboard(null);
@ -317,12 +317,16 @@ public class SchematicCommands {
@Arg(desc = "File name.")
String filename,
@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 {
LocalConfiguration config = worldEdit.getConfiguration();
//FAWE start
ClipboardFormat format = null;
ClipboardFormat format;
InputStream in = null;
try {
URI uri;
@ -385,6 +389,12 @@ public class SchematicCommands {
uri = file.toURI();
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));
} catch (IllegalArgumentException e) {
actor.print(Caption.of("worldedit.schematic.unknown-filename", TextComponent.of(filename)));
@ -515,7 +525,10 @@ public class SchematicCommands {
aliases = {"listformats", "f"},
desc = "List available formats"
)
@CommandPermissions("worldedit.schematic.formats")
@CommandPermissions(
value = "worldedit.schematic.formats",
queued = false
)
public void formats(Actor actor) {
actor.print(Caption.of("worldedit.schematic.formats.title"));
StringBuilder builder;
@ -541,7 +554,10 @@ public class SchematicCommands {
desc = "List saved schematics",
descFooter = "Note: Format is not fully verified until loading."
)
@CommandPermissions("worldedit.schematic.list")
@CommandPermissions(
value = "worldedit.schematic.list",
queued = false
)
public void list(
Actor actor, LocalSession session,
@ArgFlag(name = 'p', desc = "Page to view.", def = "1")
@ -812,7 +828,6 @@ public class SchematicCommands {
final String SCHEMATIC_NAME = file.getName();
double oldKbOverwritten = 0;
String overwrittenPath = curFilepath;
int numFiles = -1;
if (checkFilesize) {
@ -828,10 +843,10 @@ public class SchematicCommands {
if (overwrite) {
oldKbOverwritten = Files.size(Paths.get(file.getAbsolutePath())) / 1000.0;
int iter = 1;
while (new File(overwrittenPath + "." + iter + "." + format.getPrimaryFileExtension()).exists()) {
while (new File(curFilepath + "." + iter + "." + format.getPrimaryFileExtension()).exists()) {
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",
desc = "Get the wand object"
)
@CommandPermissions("worldedit.wand")
@CommandPermissions(
value = "worldedit.wand",
queued = false
)
public void wand(
Player player, LocalSession session,
@Switch(name = 'n', desc = "Get a navigation wand") boolean navWand
@ -348,7 +351,10 @@ public class SelectionCommands {
aliases = {"/toggleeditwand"},
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) {
player.print(
Caption.of(
@ -499,7 +505,10 @@ public class SelectionCommands {
name = "/size",
desc = "Get information about the selection"
)
@CommandPermissions("worldedit.selection.size")
@CommandPermissions(
value = "worldedit.selection.size",
queued = false
)
public void size(
Actor actor, World world, LocalSession session,
@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("cyl", Caption.of("worldedit.select.cyl.description"), "//sel cyl");
box.appendCommand("convex", Caption.of("worldedit.select.convex.description"), "//sel convex");
//FAWE start
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>]");

View File

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

View File

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

View File

@ -140,7 +140,7 @@ public class ToolUtilCommands {
@Arg(desc = "The range of the brush")
int range
) 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"));
}
@ -156,7 +156,7 @@ public class ToolUtilCommands {
) throws WorldEditException {
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"));
}

View File

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

View File

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

View File

@ -321,10 +321,24 @@ public class PlatformManager {
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() {
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
* 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) {
continue;
}
values.put("id", id);
//FAWE end
values.put("id", values.get("Id"));
values.remove("Id");
values.remove("Pos");
if (fixer != null) {

View File

@ -75,6 +75,7 @@ public final class Functions {
);
handle = handle.asType(handle.type().changeReturnType(Number.class));
handle = filterReturnValue(handle, DOUBLE_VALUE);
handle = handle.asType(handle.type().wrap());
}
// return vararg-ity
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.BlockVector3;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import com.sk89q.worldedit.world.storage.ChunkStore;
import javax.annotation.Nonnull;
@ -823,14 +824,15 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
boolean trimX = lowerX != 0 || upperX != 15;
boolean trimZ = lowerZ != 0 || upperZ != 15;
if (!(trimX || trimZ)) {
return set;
}
for (int layer = get.getMinSectionPosition(); layer < get.getMaxSectionPosition(); layer++) {
if (!set.hasSection(layer)) {
continue;
}
char[] arr = Objects.requireNonNull(set.loadIfPresent(layer)); // This shouldn't be null if above is true
if (!(trimX || trimZ)) {
continue;
}
int indexY = 0;
for (int y = 0; y < 16; y++, indexY += 256) { // For each y layer within a chunk section
int index;
@ -839,14 +841,14 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
for (int z = 0; z < lowerZ; z++) {
// null the z values
for (int x = 0; x < 16; x++, index++) {
arr[index] = 0;
arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__;
}
}
index = indexY + upperZi;
for (int z = upperZ + 1; z < 16; z++) {
// null the z values
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 x = 0; x < lowerX; x++) {
// null the x values
arr[index + x] = 0;
arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__;
}
for (int x = upperX + 1; x < 16; x++) {
// 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++) {
// null the z values
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 x = lowerX; x <= upperX; x++) {
// 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