mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 17:27:38 +00:00
This commit is contained in:
commit
80ffba7a63
@ -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>"
|
||||
|
8
.github/workflows/build-pr.yml
vendored
8
.github/workflows/build-pr.yml
vendored
@ -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
|
||||
|
8
.github/workflows/codeql.yml
vendored
8
.github/workflows/codeql.yml
vendored
@ -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
2
.gitignore
vendored
@ -33,3 +33,5 @@ worldedit-core/src/main/resources/lang/*
|
||||
/worldedit-core/.factorypath
|
||||
|
||||
.DS_Store
|
||||
### Run server ignore
|
||||
run-*
|
||||
|
11
Jenkinsfile
vendored
11
Jenkinsfile
vendored
@ -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') {
|
||||
|
@ -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/"))
|
||||
|
@ -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 {
|
||||
|
@ -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" }
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
7
gradlew
vendored
7
gradlew
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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."));
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
@ -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(""));
|
||||
|
@ -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(""));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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]++;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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:/+", "");
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 = "")
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>]");
|
||||
|
@ -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 '"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user