Telesphoreo 2023-07-22 13:27:54 -05:00
commit 9d489791ee
No known key found for this signature in database
GPG Key ID: 68B745F7F8C2FADA
69 changed files with 1126 additions and 493 deletions

View File

@ -32,7 +32,6 @@ body:
- '1.19.4'
- '1.18.2'
- '1.17.1'
- '1.16.5'
validations:
required: true

View File

@ -19,8 +19,6 @@
"net.fabricmc:fabric-loader",
"net.fabricmc.fabric-api:fabric-api",
"com.github.luben:zstd-jni",
"net.kyori",
"net.kyori:adventure-nbt",
"org.jetbrains.kotlin.jvm",
"log4j"
],

View File

@ -34,7 +34,7 @@ logger.lifecycle("""
*******************************************
""")
var rootVersion by extra("2.6.5")
var rootVersion by extra("2.7.1")
var snapshot by extra("SNAPSHOT")
var revision: String by extra("")
var buildNumber by extra("")
@ -83,7 +83,7 @@ allprojects {
}
applyCommonConfiguration()
val supportedVersions = listOf("1.16.5", "1.17.1", "1.18.2", "1.19.4", "1.20", "1.20.1")
val supportedVersions = listOf("1.17.1", "1.18.2", "1.19.4", "1.20", "1.20.1")
tasks {
supportedVersions.forEach {

View File

@ -15,7 +15,7 @@ fun Project.applyPaperweightAdapterConfiguration() {
dependencies {
"implementation"(project(":worldedit-bukkit"))
"implementation"(platform("com.intellectualsites.bom:bom-1.18.x:1.9"))
"implementation"(platform("com.intellectualsites.bom:bom-newest:1.33"))
}
tasks.named("assemble") {

View File

@ -45,7 +45,7 @@ fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, banSlf4j: Boolean
"testImplementation"("org.mockito:mockito-core:5.1.1")
"testImplementation"("org.mockito:mockito-junit-jupiter:5.1.1")
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.9.2")
"implementation"(platform("com.intellectualsites.bom:bom-1.18.x:1.9"))
"implementation"(platform("com.intellectualsites.bom:bom-newest:1.33"))
}
// Java 8 turns on doclint which we fail

View File

@ -11,13 +11,13 @@ mapmanager = "1.8.0-SNAPSHOT"
griefprevention = "16.18.1"
griefdefender = "2.1.0-SNAPSHOT"
residence = "4.5._13.1"
towny = "0.99.2.5"
towny = "0.99.5.4"
# Third party
bstats = "3.0.2"
sparsebitset = "1.2"
parallelgzip = "1.0.5"
adventure = "4.9.3"
adventure = "4.14.0"
truezip = "6.8.4"
auto-value = "1.10.2"
findbugs = "3.0.2"
@ -30,7 +30,6 @@ jchronic = "0.2.4a"
lz4-java = "1.8.0"
lz4-stream = "1.0.0"
## Internal
adventure-text-minimessage = "4.2.0-SNAPSHOT"
text-adapter = "3.0.6"
text = "3.0.4"
piston = "0.5.7"
@ -39,7 +38,7 @@ piston = "0.5.7"
mockito = "5.4.0"
# Gradle plugins
pluginyml = "0.5.3"
pluginyml = "0.6.0"
[libraries]
# Minecraft expectations

Binary file not shown.

View File

@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

5
gradlew vendored
View File

@ -130,10 +130,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.

View File

@ -445,7 +445,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << layer;
char[] setArr = set.load(layerNo);
// setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to
// this chunk GET when #updateGet is called. Future dords, please listen this time.
char[] tmp = set.load(layerNo);
char[] setArr = new char[tmp.length];
System.arraycopy(tmp, 0, setArr, 0, tmp.length);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size.

View File

@ -12,6 +12,7 @@ import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.adapter.Refraction;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
@ -48,6 +49,8 @@ import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.gameevent.GameEventDispatcher;
import net.minecraft.world.level.gameevent.GameEventListener;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_17_R1.CraftChunk;
import sun.misc.Unsafe;
@ -61,6 +64,8 @@ import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Stream;
@ -91,6 +96,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
private static final Field fieldRemove;
private static final Logger LOGGER = LogManagerCompat.getLogger();
static {
try {
fieldBits = PalettedContainer.class.getDeclaredField(Refraction.pickName("bits", "l"));
@ -225,7 +232,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk = (CraftChunk) future.get();
CraftChunk chunk;
try {
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
String world = serverLevel.getWorld().getName();
// We've already taken 10 seconds we can afford to wait a little here.
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
if (loaded) {
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
// Retry chunk load
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
} else {
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
}
}
return chunk.getHandle();
} catch (Throwable e) {
e.printStackTrace();

View File

@ -491,7 +491,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
char[] setArr = set.load(layerNo);
// setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to
// this chunk GET when #updateGet is called. Future dords, please listen this time.
char[] tmp = set.load(layerNo);
char[] setArr = new char[tmp.length];
System.arraycopy(tmp, 0, setArr, 0, tmp.length);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size.

View File

@ -13,17 +13,16 @@ import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.Refraction;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import io.papermc.lib.PaperLib;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.IdMap;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
@ -41,7 +40,6 @@ import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.GlobalPalette;
import net.minecraft.world.level.chunk.HashMapPalette;
@ -51,8 +49,8 @@ import net.minecraft.world.level.chunk.LinearPalette;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.SingleValuePalette;
import net.minecraft.world.level.gameevent.GameEventDispatcher;
import net.minecraft.world.level.gameevent.GameEventListener;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_18_R2.CraftChunk;
import sun.misc.Unsafe;
@ -75,6 +73,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
public final class PaperweightPlatformAdapter extends NMSAdapter {
@ -106,6 +106,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
private static final Field fieldRemove;
private static final Logger LOGGER = LogManagerCompat.getLogger();
static {
try {
fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "d"));
@ -253,7 +255,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk = (CraftChunk) future.get();
CraftChunk chunk;
try {
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
String world = serverLevel.getWorld().getName();
// We've already taken 10 seconds we can afford to wait a little here.
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
if (loaded) {
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
// Retry chunk load
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
} else {
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
}
}
return chunk.getHandle();
} catch (Throwable e) {
e.printStackTrace();

View File

@ -332,6 +332,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override
public Set<CompoundTag> getEntities() {
ensureLoaded(serverLevel, chunkX, chunkZ);
List<Entity> entities = PaperweightPlatformAdapter.getEntities(getChunk());
if (entities.isEmpty()) {
return Collections.emptySet();
@ -490,7 +491,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
char[] setArr = set.load(layerNo);
// setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to
// this chunk GET when #updateGet is called. Future dords, please listen this time.
char[] tmp = set.load(layerNo);
char[] setArr = new char[tmp.length];
System.arraycopy(tmp, 0, setArr, 0, tmp.length);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size.

View File

@ -14,6 +14,7 @@ import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.Refraction;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
@ -43,7 +44,6 @@ import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.GlobalPalette;
import net.minecraft.world.level.chunk.HashMapPalette;
@ -54,6 +54,8 @@ import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.SingleValuePalette;
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
import sun.misc.Unsafe;
@ -61,7 +63,6 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@ -77,9 +78,10 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import static java.lang.invoke.MethodType.methodType;
import static net.minecraft.core.registries.Registries.BIOME;
public final class PaperweightPlatformAdapter extends NMSAdapter {
@ -111,6 +113,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
private static final Field fieldRemove;
private static final Logger LOGGER = LogManagerCompat.getLogger();
static final boolean POST_CHUNK_REWRITE;
private static Method PAPER_CHUNK_GEN_ALL_ENTITIES;
private static Field LEVEL_CHUNK_ENTITIES;
@ -287,7 +291,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk = (CraftChunk) future.get();
CraftChunk chunk;
try {
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
String world = serverLevel.getWorld().getName();
// We've already taken 10 seconds we can afford to wait a little here.
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
if (loaded) {
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
// Retry chunk load
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
} else {
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
}
}
addTicket(serverLevel, chunkX, chunkZ);
return (LevelChunk) chunk.getHandle(ChunkStatus.FULL);
} catch (Throwable e) {

View File

@ -310,6 +310,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override
public Set<CompoundTag> getEntities() {
ensureLoaded(serverLevel, chunkX, chunkZ);
List<Entity> entities = PaperweightPlatformAdapter.getEntities(getChunk());
if (entities.isEmpty()) {
return Collections.emptySet();
@ -468,7 +469,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
char[] setArr = set.load(layerNo);
// setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to
// this chunk GET when #updateGet is called. Future dords, please listen this time.
char[] tmp = set.load(layerNo);
char[] setArr = new char[tmp.length];
System.arraycopy(tmp, 0, setArr, 0, tmp.length);
// synchronise on internal section to avoid circular locking with a continuing edit if the chunk was
// submitted to keep loaded internal chunks to queue target size.

View File

@ -14,6 +14,7 @@ import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.Refraction;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
@ -54,6 +55,8 @@ import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.SingleValuePalette;
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_20_R1.CraftChunk;
import sun.misc.Unsafe;
@ -77,6 +80,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import static java.lang.invoke.MethodType.methodType;
@ -117,6 +122,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
private static final Field fieldRemove;
private static final Logger LOGGER = LogManagerCompat.getLogger();
static final boolean POST_CHUNK_REWRITE;
private static Method PAPER_CHUNK_GEN_ALL_ENTITIES;
private static Field LEVEL_CHUNK_ENTITIES;
@ -307,7 +314,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk = (CraftChunk) future.get();
CraftChunk chunk;
try {
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
String world = serverLevel.getWorld().getName();
// We've already taken 10 seconds we can afford to wait a little here.
boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null);
if (loaded) {
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
// Retry chunk load
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
} else {
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
}
}
addTicket(serverLevel, chunkX, chunkZ);
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
} catch (Throwable e) {

View File

@ -3,7 +3,7 @@ import io.papermc.paperweight.userdev.attribute.Obfuscation
plugins {
`java-library`
id("com.modrinth.minotaur") version "2.8.1"
id("com.modrinth.minotaur") version "2.8.2"
}
project.description = "Bukkit"
@ -101,8 +101,8 @@ dependencies {
compileOnly(libs.griefdefender) { isTransitive = false }
compileOnly(libs.residence) { isTransitive = false }
compileOnly(libs.towny) { isTransitive = false }
compileOnly("com.plotsquared:PlotSquared-Bukkit") { isTransitive = false }
compileOnly("com.plotsquared:PlotSquared-Core") { isTransitive = false }
compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit") { isTransitive = false }
compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false }
// Third party
implementation("io.papermc:paperlib")
@ -183,7 +183,7 @@ tasks.named<ShadowJar>("shadowJar") {
include(dependency("org.lz4:lz4-java:1.8.0"))
}
relocate("net.kyori", "com.fastasyncworldedit.core.adventure") {
include(dependency("net.kyori:adventure-nbt:4.9.3"))
include(dependency("net.kyori:adventure-nbt:4.14.0"))
}
relocate("com.zaxxer", "com.fastasyncworldedit.core.math") {
include(dependency("com.zaxxer:SparseBitSet:1.2"))

View File

@ -108,6 +108,14 @@ public class FaweBukkit implements IFawe, Listener {
if (version.isEqualOrHigherThan(MinecraftVersion.CAVES_18) && Settings.settings().HISTORY.SMALL_EDITS) {
LOGGER.warn("Small-edits enabled (maximum y range of 0 -> 256) with 1.18 world heights. Are you sure?");
}
if (version.isEqualOrLowerThan(MinecraftVersion.ONE_DOT_SIXTEEN_EOL)) {
LOGGER.warn("You are running Minecraft 1.16.5. This version has been released over two years ago (January 2021).");
LOGGER.warn("FastAsyncWorldEdit will stop operating on this version in the near future.");
LOGGER.warn("Neither Mojang, nor Spigot or other software vendors support this version anymore." +
"Please update your server to a newer version of Minecraft (1.20+) to continue receiving updates and " +
"support.");
}
}
@Override
@ -317,18 +325,9 @@ public class FaweBukkit implements IFawe, Listener {
if (plotSquared == null) {
return;
}
if (PlotSquared.get().getVersion().version[0] == 6) {
if (PlotSquared.get().getVersion().version[0] == 7) {
WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature());
LOGGER.info("Plugin 'PlotSquared' v6 found. Using it now.");
} else if (PlotSquared.get().getVersion().version[0] == 7) {
WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature());
LOGGER.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
LOGGER.error("!! !!");
LOGGER.error("!! ERROR: PlotSquared v7 found. This FAWE version does not support PlotSquared V7 !!");
LOGGER.error("!! Follow the instructions when notified of v7 release candidates and use FAWE from !!");
LOGGER.error("!! https://ci.athion.net/job/FastAsyncWorldEdit-Pull-Requests/view/change-requests/job/PR-2075/ !!");
LOGGER.error("!! !!");
LOGGER.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
LOGGER.info("Plugin 'PlotSquared' v7 found. Using it now.");
} else {
LOGGER.error("Incompatible version of PlotSquared found. Please use PlotSquared v6.");
LOGGER.info("https://www.spigotmc.org/resources/77506/");

View File

@ -30,8 +30,7 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl {
ordinal = BlockTypesCache.ReservedIDs.AIR;
nonAir--;
}
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR ->
nonAir--;
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> nonAir--;
}
int palette = blockToPalette[ordinal];
if (palette == Integer.MAX_VALUE) {
@ -74,8 +73,6 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl {
CachedBukkitAdapter adapter,
short[] nonEmptyBlockCount
) {
// Write to new array to avoid editing SET array
char[] copy = new char[set.length];
short nonAir = 4096;
int num_palette = 0;
char[] getArr = null;
@ -86,19 +83,23 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl {
if (getArr == null) {
getArr = get.apply(layer);
}
switch (ordinal = getArr[i]) {
// write to set array as this should be a copied array, and will be important when the changes are written
// to the GET chunk cached by FAWE. Future dords, actually read this comment please.
set[i] = switch (ordinal = getArr[i]) {
case BlockTypesCache.ReservedIDs.__RESERVED__ -> {
nonAir--;
ordinal = BlockTypesCache.ReservedIDs.AIR;
yield (ordinal = BlockTypesCache.ReservedIDs.AIR);
}
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR ->
nonAir--;
}
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR,
BlockTypesCache.ReservedIDs.VOID_AIR -> {
nonAir--;
yield ordinal;
}
default -> ordinal;
};
}
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR ->
nonAir--;
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> nonAir--;
}
copy[i] = ordinal;
int palette = blockToPalette[ordinal];
if (palette == Integer.MAX_VALUE) {
blockToPalette[ordinal] = num_palette;
@ -116,7 +117,7 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl {
System.arraycopy(adapter.getOrdinalToIbdID(), 0, blockToPalette, 0, adapter.getOrdinalToIbdID().length);
}
for (int i = 0; i < 4096; i++) {
char ordinal = copy[i];
char ordinal = set[i];
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
LOGGER.error("Empty (__RESERVED__) ordinal given where not expected, default to air.");
ordinal = BlockTypesCache.ReservedIDs.AIR;

View File

@ -171,18 +171,17 @@ public class FaweDelegateRegionManager {
.limitUnlimited()
.changeSetNull()
.build();
File schematicFile = new File(hybridPlotWorld.getRoot(), "plot.schem");
File schematicFile = new File(hybridPlotWorld.getSchematicRoot(), "plot.schem");
if (!schematicFile.exists()) {
schematicFile = new File(hybridPlotWorld.getRoot(), "plot.schematic");
schematicFile = new File(hybridPlotWorld.getSchematicRoot(), "plot.schematic");
}
BlockVector3 to = plot.getBottomAbs().getBlockVector3().withY(Settings.Schematics.PASTE_ON_TOP
? hybridPlotWorld.SCHEM_Y
: hybridPlotWorld.getMinBuildHeight());
BlockVector3 to = plot.getBottomAbs().getBlockVector3().withY(hybridPlotWorld.getPlotYStart());
try {
Clipboard clip = ClipboardFormats
.findByFile(schematicFile)
.getReader(new FileInputStream(schematicFile))
.read();
clip.setOrigin(clip.getRegion().getMinimumPoint());
clip.paste(scheditsession, to, true, true, true);
} catch (IOException e) {
e.printStackTrace();
@ -214,7 +213,7 @@ public class FaweDelegateRegionManager {
) {
TaskManager.taskManager().async(() -> {
synchronized (FaweDelegateRegionManager.class) {
//todo because of the following code this should proably be in the Bukkit module
//todo because of the following code this should probably be in the Bukkit module
World pos1World = BukkitAdapter.adapt(getWorld(pos1.getWorldName()));
World pos3World = BukkitAdapter.adapt(getWorld(swapPos.getWorldName()));
EditSession sessionA = WorldEdit.getInstance().newEditSessionBuilder().world(pos1World)

View File

@ -152,6 +152,7 @@ public class FaweDelegateSchematicHandler {
final BlockVector3 to = BlockVector3
.at(region.getMinimumPoint().getX() + xOffset, y_offset_actual, region.getMinimumPoint().getZ() + zOffset);
final Clipboard clipboard = schematic.getClipboard();
clipboard.setOrigin(clipboard.getRegion().getMinimumPoint());
clipboard.paste(editSession, to, true, false, true);
if (whenDone != null) {
whenDone.value = true;

View File

@ -1,13 +1,15 @@
package com.fastasyncworldedit.bukkit.regions.plotsquared;
import com.fastasyncworldedit.core.util.TaskManager;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.command.CommandCategory;
import com.plotsquared.core.command.CommandDeclaration;
import com.plotsquared.core.command.RequiredType;
import com.plotsquared.core.command.SubCommand;
import com.plotsquared.core.configuration.caption.StaticCaption;
import com.plotsquared.core.configuration.caption.Templates;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
@ -33,7 +35,7 @@ public class FaweTrim extends SubCommand {
return false;
}
if (!PlotSquared.platform().worldUtil().isWorld(strings[0])) {
plotPlayer.sendMessage(TranslatableCaption.of("errors.not_valid_plot_world"), Templates.of("value", strings[0]));
plotPlayer.sendMessage(TranslatableCaption.of("errors.not_valid_plot_world"), TagResolver.resolver("value", Tag.inserting(Component.text(strings[0]))));
return false;
}
ran = true;

View File

@ -6,11 +6,9 @@ import com.plotsquared.core.command.CommandCategory;
import com.plotsquared.core.command.CommandDeclaration;
import com.plotsquared.core.command.MainCommand;
import com.plotsquared.core.command.RequiredType;
import com.plotsquared.core.configuration.caption.Templates;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.util.Permissions;
import com.plotsquared.core.util.StringMan;
import com.plotsquared.core.util.task.RunnableVal2;
import com.plotsquared.core.util.task.RunnableVal3;
@ -24,6 +22,9 @@ import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.biome.Biomes;
import com.sk89q.worldedit.world.registry.BiomeRegistry;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.bukkit.Bukkit;
import java.util.Collection;
@ -56,7 +57,7 @@ public class PlotSetBiome extends Command {
) throws CommandException {
final Plot plot = check(player.getCurrentPlot(), TranslatableCaption.of("errors.not_in_plot"));
checkTrue(
plot.isOwner(player.getUUID()) || Permissions.hasPermission(player, "plots.admin.command.generatebiome"),
plot.isOwner(player.getUUID()) || player.hasPermission("plots.admin.command.generatebiome"),
TranslatableCaption.of("permission.no_plot_perms")
);
if (plot.getRunning() != 0) {
@ -64,7 +65,7 @@ public class PlotSetBiome extends Command {
return null;
}
checkTrue(args.length == 1, TranslatableCaption.of("commandconfig.command_syntax"),
Templates.of("value", getUsage())
TagResolver.resolver("value", Tag.inserting(Component.text(getUsage())))
);
final Set<CuboidRegion> regions = plot.getRegions();
BiomeRegistry biomeRegistry =
@ -80,7 +81,7 @@ public class PlotSetBiome extends Command {
player.sendMessage(TranslatableCaption.of("biome.need_biome"));
player.sendMessage(
TranslatableCaption.of("commandconfig.subcommand_set_options_header"),
Templates.of("values", biomes)
TagResolver.resolver("value", Tag.inserting(Component.text(biomes)))
);
return CompletableFuture.completedFuture(false);
}

View File

@ -23,9 +23,11 @@ import com.sk89q.worldedit.world.World;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import javax.annotation.Nonnull;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@ -40,7 +42,7 @@ public class PlotSquaredFeature extends FaweMaskManager {
if (Settings.FAWE_Components.FAWE_HOOK) {
Settings.Enabled_Components.WORLDEDIT_RESTRICTIONS = false;
if (Settings.PLATFORM.toLowerCase(Locale.ROOT).startsWith("bukkit")) {
new FaweTrim();
// new FaweTrim();
}
// TODO: revisit this later on
/*
@ -192,6 +194,10 @@ public class PlotSquaredFeature extends FaweMaskManager {
maskedRegion = new RegionIntersection(world, weRegions);
}
if (plot == null) {
return new FaweMask(maskedRegion);
}
return new PlotSquaredMask(maskedRegion, finalPlot);
}
@ -201,9 +207,9 @@ public class PlotSquaredFeature extends FaweMaskManager {
private final WeakReference<Set<Plot>> connectedPlots;
private final boolean singlePlot;
private PlotSquaredMask(Region region, Plot plot) {
private PlotSquaredMask(@Nonnull Region region, @Nonnull Plot plot) {
super(region);
this.plot = plot;
this.plot = Objects.requireNonNull(plot);
Set<Plot> connected = plot.getConnectedPlots();
connectedPlots = new WeakReference<>(connected);
singlePlot = connected.size() == 1;
@ -211,8 +217,9 @@ public class PlotSquaredFeature extends FaweMaskManager {
@Override
public boolean isValid(Player player, MaskType type, boolean notify) {
if ((!connectedPlots.refersTo(plot.getConnectedPlots()) && !singlePlot) || (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(
plot))) {
if ((!connectedPlots.refersTo(plot.getConnectedPlots()) && (!singlePlot || plot
.getConnectedPlots()
.size() > 1)) || (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot))) {
return false;
}
return isAllowed(player, plot, type, notify);

View File

@ -13,6 +13,7 @@ import java.util.regex.Pattern;
public class MinecraftVersion implements Comparable<MinecraftVersion> {
public static final MinecraftVersion NETHER = new MinecraftVersion(1, 16);
public static final MinecraftVersion ONE_DOT_SIXTEEN_EOL = new MinecraftVersion(1, 16, 5);
public static final MinecraftVersion CAVES_17 = new MinecraftVersion(1, 17);
public static final MinecraftVersion CAVES_18 = new MinecraftVersion(1, 18);
private static MinecraftVersion current = null;

View File

@ -19,6 +19,8 @@
package com.sk89q.wepif;
import com.destroystokyo.paper.profile.PlayerProfile;
import org.bukkit.BanEntry;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
@ -30,10 +32,11 @@ import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import org.bukkit.profile.PlayerProfile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
@ -158,6 +161,15 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public @Nullable BanEntry<org.bukkit.profile.PlayerProfile> ban(
@Nullable final String reason,
@Nullable final Date expires,
@Nullable final String source
) {
return null;
}
@Override
public boolean isWhitelisted() {
throw new UnsupportedOperationException("Not supported yet.");
@ -323,4 +335,9 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible {
}
@Override
public @Nullable Location getLastDeathLocation() {
return null;
}
}

View File

@ -28,14 +28,13 @@ dependencies {
implementation("com.google.code.gson:gson")
// Platform expectations
// TODO update bom-newest
implementation("org.yaml:snakeyaml:2.0")
implementation("org.yaml:snakeyaml")
// Logging
implementation("org.apache.logging.log4j:log4j-api")
// Plugins
compileOnly("com.plotsquared:PlotSquared-Core") { isTransitive = false }
compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false }
// ensure this is on the classpath for the AP
annotationProcessor(libs.guava)

View File

@ -134,15 +134,29 @@ public class MobSpawnerBlock extends BaseBlock {
values.put("MaxNearbyEntities", new ShortTag(maxNearbyEntities));
values.put("RequiredPlayerRange", new ShortTag(requiredPlayerRange));
if (spawnData == null) {
values.put("SpawnData", new CompoundTag(ImmutableMap.of("id", new StringTag(mobType))));
values.put(
"SpawnData",
new CompoundTag(ImmutableMap.of("entity", new CompoundTag(ImmutableMap.of("id", new StringTag(mobType)))))
);
} else {
values.put("SpawnData", new CompoundTag(spawnData.getValue()));
}
if (spawnPotentials == null) {
values.put("SpawnPotentials", new ListTag(CompoundTag.class, ImmutableList.of(
new CompoundTag(ImmutableMap.of("Weight", new IntTag(1), "Entity",
new CompoundTag(ImmutableMap.of("id", new StringTag(mobType)))
)))));
values.put(
"SpawnPotentials",
new ListTag(
CompoundTag.class,
ImmutableList.of(new CompoundTag(ImmutableMap.of(
"weight",
new IntTag(1),
"data",
new CompoundTag(ImmutableMap.of(
"entity",
new CompoundTag(ImmutableMap.of("id", new StringTag(mobType)))
))
)))
)
);
} else {
values.put("SpawnPotentials", new ListTag(CompoundTag.class, spawnPotentials.getValue()));
}

View File

@ -449,7 +449,6 @@ public class Fawe {
* @return Executor used for clipboard IO if clipboard on disk is enabled or null
* @since 2.6.2
*/
@Nullable
public KeyQueuedExecutorService<UUID> getClipboardExecutor() {
return this.clipboardExecutor;
}

View File

@ -22,9 +22,27 @@ import java.util.List;
public class CommandBrush implements Brush {
private final String command;
private final boolean print;
/**
* New instance
*
* @deprecated Use {@link CommandBrush#CommandBrush(String, boolean)}
*/
@Deprecated(forRemoval = true)
public CommandBrush(String command) {
this.command = command.charAt(0) == '/' ? "/" + command : command;
this(command, false);
}
/**
* New instance
*
* @param command command to run, or commands split by ';'
* @param print if output should be printed to the actor for the run commands
*/
public CommandBrush(String command, boolean print) {
this.command = command;
this.print = print;
}
@Override
@ -36,7 +54,7 @@ public class CommandBrush implements Brush {
position.subtract(radius, radius, radius),
position.add(radius, radius, radius)
);
String replaced = command.replace("{x}", position.getBlockX() + "")
String replaced = command.replace("{x}", Integer.toString(position.getBlockX()))
.replace("{y}", Integer.toString(position.getBlockY()))
.replace("{z}", Integer.toString(position.getBlockZ()))
.replace("{world}", editSession.getWorld().getName())
@ -46,21 +64,22 @@ public class CommandBrush implements Brush {
if (!(actor instanceof Player player)) {
throw FaweCache.PLAYER_ONLY;
}
//Use max world height to allow full coverage of the world height
Location face = player.getBlockTraceFace(editSession.getWorld().getMaxY(), true);
if (face == null) {
position = position.add(0, 1, 1);
} else {
position = position.add(face.getDirection().toBlockPoint());
}
player.setSelection(selector);
AsyncPlayer wePlayer = new SilentPlayerWrapper(new LocationMaskedPlayerWrapper(
AsyncPlayer wePlayer = new LocationMaskedPlayerWrapper(
player,
new Location(player.getExtent(), position.toVector3())
));
);
if (!print) {
wePlayer = new SilentPlayerWrapper(wePlayer);
}
List<String> cmds = StringMan.split(replaced, ';');
for (String cmd : cmds) {
CommandEvent event = new CommandEvent(wePlayer, cmd);
if (cmd.isBlank()) {
continue;
}
cmd = cmd.charAt(0) != '/' ? "/" + cmd : cmd;
cmd = cmd.length() >1 && cmd.charAt(1) == '/' ? cmd.substring(1) : cmd;
CommandEvent event = new CommandEvent(wePlayer, cmd, editSession);
PlatformCommandManager.getInstance().handleCommandOnCurrentThread(event);
}
}

View File

@ -3,7 +3,9 @@ package com.fastasyncworldedit.core.command.tool.brush;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.LocalBlockVectorSet;
import com.fastasyncworldedit.core.util.StringMan;
import com.fastasyncworldedit.core.wrappers.AsyncPlayer;
import com.fastasyncworldedit.core.wrappers.LocationMaskedPlayerWrapper;
import com.fastasyncworldedit.core.wrappers.SilentPlayerWrapper;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.entity.Player;
@ -13,7 +15,7 @@ import com.sk89q.worldedit.extension.platform.PlatformCommandManager;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.Location;
import java.util.List;
@ -43,7 +45,7 @@ public class ScatterCommand extends ScatterBrush {
position.subtract(radius, radius, radius),
position.add(radius, radius, radius)
);
String replaced = command.replace("{x}", position.getBlockX() + "")
String replaced = command.replace("{x}", Integer.toString(position.getBlockX()))
.replace("{y}", Integer.toString(position.getBlockY()))
.replace("{z}", Integer.toString(position.getBlockZ()))
.replace("{world}", editSession.getWorld().getName())
@ -55,41 +57,22 @@ public class ScatterCommand extends ScatterBrush {
}
player.setSelection(selector);
List<String> cmds = StringMan.split(replaced, ';');
AsyncPlayer wePlayer = new LocationMaskedPlayerWrapper(
player,
new Location(player.getExtent(), position.toVector3())
);
if (!print) {
wePlayer = new SilentPlayerWrapper(wePlayer);
}
for (String cmd : cmds) {
Player p = print ?
new LocationMaskedPlayerWrapper(player, player.getLocation().setPosition(position.toVector3()), false) :
new ScatterCommandPlayerWrapper(player, position);
CommandEvent event = new CommandEvent(p, cmd, editSession);
if (cmd.isBlank()) {
continue;
}
cmd = cmd.charAt(0) != '/' ? "/" + cmd : cmd;
cmd = cmd.length() >1 && cmd.charAt(1) == '/' ? cmd.substring(1) : cmd;
CommandEvent event = new CommandEvent(wePlayer, cmd, editSession);
PlatformCommandManager.getInstance().handleCommandOnCurrentThread(event);
}
}
private static final class ScatterCommandPlayerWrapper extends LocationMaskedPlayerWrapper {
ScatterCommandPlayerWrapper(Player player, BlockVector3 position) {
super(player, player.getLocation().setPosition(position.toVector3()), false);
}
@Override
public void print(String msg) {
}
@Override
public void print(Component component) {
}
@Override
public void printDebug(String msg) {
}
@Override
public void printError(String msg) {
}
@Override
public void printRaw(String msg) {
}
}
}

View File

@ -1,119 +0,0 @@
package com.fastasyncworldedit.core.concurrent;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.StampedLock;
/**
* Allows for reentrant behaviour of a wrapped {@link StampedLock}. Will not count the number of times it is re-entered.
*
* @since 2.3.0
*/
public class ReentrantWrappedStampedLock implements Lock {
private final StampedLock parent = new StampedLock();
private volatile Thread owner;
private volatile long stamp = 0;
@Override
public void lock() {
if (Thread.currentThread() == owner) {
return;
}
stamp = parent.writeLock();
owner = Thread.currentThread();
}
@Override
public void lockInterruptibly() throws InterruptedException {
if (Thread.currentThread() == owner) {
return;
}
stamp = parent.writeLockInterruptibly();
owner = Thread.currentThread();
}
@Override
public boolean tryLock() {
if (Thread.currentThread() == owner) {
return true;
}
if (parent.isWriteLocked()) {
return false;
}
stamp = parent.writeLock();
owner = Thread.currentThread();
return true;
}
@Override
public boolean tryLock(final long time, @NotNull final TimeUnit unit) throws InterruptedException {
if (Thread.currentThread() == owner) {
return true;
}
if (!parent.isWriteLocked()) {
stamp = parent.writeLock();
owner = Thread.currentThread();
return true;
}
stamp = parent.tryWriteLock(time, unit);
owner = Thread.currentThread();
return false;
}
@Override
public void unlock() {
if (owner != Thread.currentThread()) {
throw new IllegalCallerException("The lock should only be unlocked by the owning thread when a stamp is not supplied");
}
unlock(stamp);
}
@NotNull
@Override
public Condition newCondition() {
throw new UnsupportedOperationException("Conditions are not supported by StampedLock");
}
/**
* Retrieves the stamp associated with the current lock. 0 if the wrapped {@link StampedLock} is not write-locked. This method is
* thread-checking.
*
* @return lock stam[ or 0 if not locked.
* @throws IllegalCallerException if the {@link StampedLock} is write-locked and the calling thread is not the lock owner
* @since 2.3.0
*/
public long getStampChecked() {
if (stamp != 0 && owner != Thread.currentThread()) {
throw new IllegalCallerException("The stamp should be be acquired by a thread that does not own the lock");
}
return stamp;
}
/**
* Unlock the wrapped {@link StampedLock} using the given stamp. This can be called by any thread.
*
* @param stamp Stamp to unlock with
* @throws IllegalMonitorStateException if the given stamp does not match the lock's stamp
* @since 2.3.0
*/
public void unlock(final long stamp) {
parent.unlockWrite(stamp);
this.stamp = 0;
owner = null;
}
/**
* Returns true if the lock is currently held.
*
* @return true if the lock is currently held.
* @since 2.3.0
*/
public boolean isLocked() {
return owner == null && this.stamp == 0 && parent.isWriteLocked(); // Be verbose
}
}

View File

@ -468,24 +468,6 @@ public class Settings extends Config {
})
public int BUFFER_SIZE = 531441;
@Comment({
"The maximum time in milliseconds to wait for a chunk to load for an edit.",
" (50ms = 1 server tick, 0 = Fastest).",
" The default value of 100 should be safe for most cases.",
"",
"Actions which require loaded chunks (e.g. copy) which do not load in time",
" will use the last chunk as filler, which may appear as bands of duplicated blocks.",
"Actions usually wait about 25-50ms for the chunk to load, more if the server is lagging.",
"A value of 100ms does not force it to wait 100ms if the chunk loads in 10ms.",
"",
"This value is a timeout in case a chunk is never going to load (for whatever odd reason).",
"If the action times out, the operation continues by using the previous chunk as filler,",
" and displaying an error message. In this case, either copy a smaller section,",
" or increase chunk-wait-ms.",
"A value of 0 is faster simply because it doesn't bother loading the chunks or waiting.",
})
public int CHUNK_WAIT_MS = 1000;
@Comment("Delete history on disk after a number of days")
public int DELETE_AFTER_DAYS = 7;
@Comment("Delete history in memory on logout (does not effect disk)")
@ -493,6 +475,7 @@ public class Settings extends Config {
@Comment({
"If history should be enabled by default for plugins using WorldEdit:",
" - It is faster to have disabled",
" - It is faster to have disabled",
" - Use of the FAWE API will not be effected"
})
public boolean ENABLE_FOR_CONSOLE = true;
@ -515,10 +498,12 @@ public class Settings extends Config {
@Create
public static PROGRESS PROGRESS;
@Comment({
"This should equal the number of processors you have",
})
public int PARALLEL_THREADS = Math.max(1, Runtime.getRuntime().availableProcessors());
@Comment({
"When doing edits that effect more than this many chunks:",
" - FAWE will start placing before all calculations are finished",
@ -530,14 +515,6 @@ public class Settings extends Config {
})
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",
" - A smaller value will reduce memory usage",
" - A value too small may break some operations (deform?)"
})
//TODO Find out where this was used and why the usage was removed
public int MAX_WAIT_MS = 1000;
@Comment({
"Increase or decrease queue intensity (ms) [-50,50]:",
@ -566,13 +543,6 @@ public class Settings extends Config {
})
public boolean POOL = true;
@Comment({
"Discard edits which have been idle for a certain amount of time (ms)",
" - E.g. A plugin creates an EditSession but never does anything with it",
" - This only applies to plugins improperly using WorldEdit's legacy API"
})
public int DISCARD_AFTER_MS = 60000;
@Comment({
"When using fastmode do not bother to tick existing/placed blocks/fluids",
"Only works in versions up to 1.17.1"
@ -639,7 +609,7 @@ public class Settings extends Config {
public boolean REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL = true;
@Comment({
"Other experimental features"
"Increased debug logging for brush actions and processor setup"
})
public boolean OTHER = false;

View File

@ -117,7 +117,7 @@ public class RichPatternParser extends FaweParser<Pattern> {
if (addBrackets) {
value += "[";
}
value += StringMan.join(entry.getValue(), " ");
value += StringMan.join(entry.getValue(), "][");
if (addBrackets) {
value += "]";
}

View File

@ -0,0 +1,54 @@
package com.fastasyncworldedit.core.extension.factory.parser.pattern;
import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.extension.factory.parser.RichParser;
import com.fastasyncworldedit.core.function.pattern.TypeSwapPattern;
import com.fastasyncworldedit.core.util.Permission;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.stream.Stream;
public class TypeSwapPatternParser extends RichParser<Pattern> {
private static final List<String> SUGGESTIONS = List.of("oak", "spruce", "stone", "sandstone");
/**
* Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}.
*
* @param worldEdit the worldedit instance.
*/
public TypeSwapPatternParser(WorldEdit worldEdit) {
super(worldEdit, "#typeswap", "#ts", "#swaptype");
}
@Override
public Stream<String> getSuggestions(String argumentInput, int index) {
if (index > 2) {
return Stream.empty();
}
return SUGGESTIONS.stream();
}
@Override
public Pattern parseFromInput(@Nonnull String[] input, ParserContext context) throws InputParseException {
if (input.length != 2) {
throw new InputParseException(Caption.of(
"fawe.error.command.syntax",
TextComponent.of(getPrefix() + "[input][output] (e.g. " + getPrefix() + "[spruce][oak])")
));
}
return new TypeSwapPattern(
context.requireExtent(),
input[0],
input[1],
Permission.hasPermission(context.requireActor(), "fawe.pattern.typeswap.regex")
);
}
}

View File

@ -39,6 +39,8 @@ import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -46,6 +48,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* A clipboard with disk backed storage. (lower memory + loads on crash)
@ -59,6 +62,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
private static final int HEADER_SIZE = 27; // Current header size
private static final int VERSION_1_HEADER_SIZE = 22; // Header size of "version 1"
private static final int VERSION_2_HEADER_SIZE = 27; // Header size of "version 2" i.e. when NBT/entities could be saved
private static final Map<String, LockHolder> LOCK_HOLDER_CACHE = new ConcurrentHashMap<>();
private final HashMap<IntTriple, CompoundTag> nbtMap;
private final File file;
@ -301,7 +305,23 @@ public class DiskOptimizedClipboard extends LinearClipboard {
private void init() throws IOException {
if (this.fileChannel == null) {
this.fileChannel = braf.getChannel();
this.fileChannel.lock();
try {
FileLock lock = this.fileChannel.lock();
LOCK_HOLDER_CACHE.put(file.getName(), new LockHolder(lock));
} catch (OverlappingFileLockException e) {
LockHolder existing = LOCK_HOLDER_CACHE.get(file.getName());
if (existing != null) {
long ms = System.currentTimeMillis() - existing.lockHeldSince;
LOGGER.error(
"Cannot lock clipboard file {} acquired by thread {}, {}ms ago",
file.getName(),
existing.thread,
ms
);
}
// Rethrow to prevent clipboard access
throw e;
}
this.byteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, braf.length());
}
}
@ -737,4 +757,18 @@ public class DiskOptimizedClipboard extends LinearClipboard {
return false;
}
private static class LockHolder {
final FileLock lock;
final long lockHeldSince;
final String thread;
LockHolder(FileLock lock) {
this.lock = lock;
lockHeldSince = System.currentTimeMillis();
this.thread = Thread.currentThread().getName();
}
}
}

View File

@ -6,38 +6,46 @@ import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BlockTypes;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/**
* Processor that removes existing entities that would not be in air after the edit
*
* @since TODO
* @since 2.7.0
*/
public class EntityInBlockRemovingProcessor implements IBatchProcessor {
private static final Logger LOGGER = LogManagerCompat.getLogger();
@Override
public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
for (CompoundTag tag : get.getEntities()) {
// Empty tags for seemingly non-existent entities can exist?
if (tag.getList("Pos").size() == 0) {
continue;
}
BlockVector3 pos = tag.getEntityPosition().toBlockPoint();
int x = pos.getX() & 15;
int y = pos.getY();
int z = pos.getZ() & 15;
if (!set.hasSection(y >> 4)) {
continue;
}
if (set.getBlock(x, y, z).getBlockType() != BlockTypes.__RESERVED__ && !set
.getBlock(x, y, z)
.getBlockType()
.getMaterial()
.isAir()) {
set.removeEntity(tag.getUUID());
try {
for (CompoundTag tag : get.getEntities()) {
// Empty tags for seemingly non-existent entities can exist?
if (tag.getList("Pos").size() == 0) {
continue;
}
BlockVector3 pos = tag.getEntityPosition().toBlockPoint();
int x = pos.getX() & 15;
int y = pos.getY();
int z = pos.getZ() & 15;
if (!set.hasSection(y >> 4)) {
continue;
}
if (set.getBlock(x, y, z).getBlockType() != BlockTypes.__RESERVED__ && !set
.getBlock(x, y, z)
.getBlockType()
.getMaterial()
.isAir()) {
set.removeEntity(tag.getUUID());
}
}
} catch (Exception e) {
LOGGER.warn("Could not remove entities in blocks in chunk {},{}", chunk.getX(), chunk.getZ(), e);
}
return set;
}

View File

@ -5,9 +5,11 @@ import com.sk89q.worldedit.function.mask.AbstractExtentMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.math.BlockVector3;
import java.util.concurrent.atomic.AtomicInteger;
public class IdMask extends AbstractExtentMask implements ResettableMask {
private transient int id = -1;
private final AtomicInteger id = new AtomicInteger(-1);
public IdMask(Extent extent) {
super(extent);
@ -15,12 +17,9 @@ public class IdMask extends AbstractExtentMask implements ResettableMask {
@Override
public boolean test(Extent extent, BlockVector3 vector) {
if (id != -1) {
return extent.getBlock(vector).getInternalBlockTypeId() == id;
} else {
id = extent.getBlock(vector).getInternalBlockTypeId();
return true;
}
int blockID = extent.getBlock(vector).getInternalBlockTypeId();
int testId = id.compareAndExchange(-1, blockID);
return blockID == testId || testId == -1;
}
@Override
@ -30,12 +29,12 @@ public class IdMask extends AbstractExtentMask implements ResettableMask {
@Override
public void reset() {
this.id = -1;
this.id.set(-1);
}
@Override
public Mask copy() {
return new IdMask(getExtent());
return this;
}
@Override

View File

@ -0,0 +1,103 @@
package com.fastasyncworldedit.core.function.pattern;
import com.fastasyncworldedit.core.extent.filter.block.FilterBlock;
import com.fastasyncworldedit.core.util.StringMan;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.util.regex.Pattern;
/**
* Pattern that replaces blocks based on their ID, matching for an "input" and replacing with an "output" string. The "input"
* string may be regex. Keeps as much of the block state as possible, excluding NBT data.
*
* @since 2.7.0
*/
public class TypeSwapPattern extends AbstractExtentPattern {
private static final Pattern SPLITTER = Pattern.compile("[|,]");
private final String inputString;
private final String outputString;
private final String[] inputs;
private Pattern inputPattern = null;
/**
* Create a new instance
*
* @param extent extent to use
* @param inputString string to replace. May be regex.
* @param outputString string to replace with
* @param allowRegex if regex should be allowed for input string matching
* @since 2.7.0
*/
public TypeSwapPattern(Extent extent, String inputString, String outputString, boolean allowRegex) {
super(extent);
this.inputString = inputString;
this.outputString = outputString;
if (!StringMan.isAlphanumericUnd(inputString)) {
if (allowRegex) {
this.inputPattern = Pattern.compile(inputString.replace(",", "|"));
inputs = null;
} else {
inputs = SPLITTER.split(inputString);
}
} else {
inputs = null;
}
}
@Override
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
BlockState existing = get.getBlock(extent);
BlockState newBlock = getNewBlock(existing);
if (newBlock == null) {
return false;
}
return set.setBlock(extent, newBlock);
}
@Override
public void applyBlock(final FilterBlock block) {
BlockState existing = block.getBlock();
BlockState newState = getNewBlock(existing);
if (newState != null) {
block.setBlock(newState);
}
}
@Override
public BaseBlock applyBlock(final BlockVector3 position) {
BaseBlock existing = position.getFullBlock(getExtent());
BlockState newState = getNewBlock(existing.toBlockState());
return newState == null ? existing : newState.toBaseBlock();
}
private BlockState getNewBlock(BlockState existing) {
String oldId = existing.getBlockType().getId();
String newId = oldId;
if (inputPattern != null) {
newId = inputPattern.matcher(oldId).replaceAll(outputString);
} else if (inputs != null && inputs.length > 0) {
for (String input : inputs) {
newId = newId.replace(input, outputString);
}
} else {
newId = oldId.replace(inputString, outputString);
}
if (newId.equals(oldId)) {
return null;
}
BlockType newType = BlockTypes.get(newId);
if (newType == null) {
return null;
}
return newType.getDefaultState().withProperties(existing);
}
}

View File

@ -41,7 +41,7 @@ public class FaweException extends RuntimeException {
* New instance of a given {@link FaweException.Type}
*
* @param ignorable if an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent}
* @since TODO
* @since 2.7.0
*/
public FaweException(Component reason, Type type, boolean ignorable) {
this.message = reason;
@ -70,7 +70,7 @@ public class FaweException extends RuntimeException {
/**
* If an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent}
*
* @since TODO
* @since 2.7.0
*/
public boolean ignorable() {
return ignorable;

View File

@ -85,7 +85,7 @@ public interface IBatchProcessor {
}
for (int layer = maxLayer; layer < set.getMaxSectionPosition(); layer++) {
if (set.hasSection(layer)) {
if (layer == minLayer) {
if (layer == maxLayer) {
char[] arr = set.loadIfPresent(layer);
if (arr != null) {
int index = ((maxY + 1) & 15) << 8;

View File

@ -2,9 +2,7 @@ package com.fastasyncworldedit.core.queue;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.IntArrayTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongTag;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;

View File

@ -27,6 +27,7 @@ import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.Future;
@ -52,6 +53,7 @@ public abstract class QueueHandler implements Trimable, Runnable {
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.
@ -508,4 +510,28 @@ public abstract class QueueHandler implements Trimable, Runnable {
return result;
}
/**
* Primary queue should be used for tasks that are unlikely to wait on other tasks, IO, etc. (i.e. spend most of their
* time utilising CPU.
* <p>
* Internal API usage only.
*
* @since 2.7.0
*/
public ExecutorService getForkJoinPoolPrimary() {
return forkJoinPoolPrimary;
}
/**
* Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the
* primary queue. They may be IO-bound tasks.
* <p>
* Internal API usage only.
*
* @since 2.7.0
*/
public ExecutorService getForkJoinPoolSecondary() {
return forkJoinPoolSecondary;
}
}

View File

@ -83,17 +83,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
this.maxY = maxY;
}
/**
* Safety check to ensure that the thread being used matches the one being initialized on. - Can
* be removed later
*/
private void checkThread() {
if (Thread.currentThread() != currentThread && currentThread != null) {
throw new UnsupportedOperationException(
"This class must be used from a single thread. Use multiple queues for concurrent operations");
}
}
@Override
public void enableQueue() {
enabledQueue = true;
@ -154,10 +143,10 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
return;
}
if (!this.chunks.isEmpty()) {
getChunkLock.lock();
for (IChunk chunk : this.chunks.values()) {
chunk.recycle();
}
getChunkLock.lock();
this.chunks.clear();
getChunkLock.unlock();
}
@ -233,9 +222,21 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
*/
private <V extends Future<V>> V submitUnchecked(IQueueChunk chunk) {
if (chunk.isEmpty()) {
chunk.recycle();
Future result = Futures.immediateFuture(null);
return (V) result;
if (chunk instanceof ChunkHolder<?> holder) {
long age = holder.initAge();
// Ensure we've given time for the chunk to be used - it was likely used for a reason!
if (age < 5) {
try {
Thread.sleep(5 - age);
} catch (InterruptedException ignored) {
}
}
}
if (chunk.isEmpty()) {
chunk.recycle();
Future result = Futures.immediateFuture(null);
return (V) result;
}
}
if (Fawe.isMainThread()) {
@ -451,6 +452,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
@Override
public synchronized void flush() {
if (!chunks.isEmpty()) {
getChunkLock.lock();
if (MemUtil.isMemoryLimited()) {
for (IQueueChunk chunk : chunks.values()) {
final Future future = submitUnchecked(chunk);
@ -467,7 +469,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
}
}
}
getChunkLock.lock();
chunks.clear();
getChunkLock.unlock();
}

View File

@ -1,7 +1,6 @@
package com.fastasyncworldedit.core.queue.implementation.chunk;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.concurrent.ReentrantWrappedStampedLock;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor;
@ -26,6 +25,8 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* An abstract {@link IChunk} class that implements basic get/set blocks.
@ -43,7 +44,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return POOL.poll();
}
private final ReentrantWrappedStampedLock calledLock = new ReentrantWrappedStampedLock();
private final Lock calledLock = new ReentrantLock();
private volatile IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes)
private volatile IChunkSet chunkSet; // The blocks to be set to the chunkExisting
@ -55,6 +56,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
private int bitMask = -1; // Allow forceful setting of bitmask (for lighting)
private boolean isInit = false; // Lighting handles queue differently. It relies on the chunk cache and not doing init.
private boolean createCopy = false;
private long initTime = -1L;
private ChunkHolder() {
this.delegate = NULL;
@ -66,6 +68,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public synchronized void recycle() {
calledLock.lock();
delegate = NULL;
if (chunkSet != null) {
chunkSet.recycle();
@ -74,6 +77,11 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
chunkExisting = null;
extent = null;
POOL.offer(this);
calledLock.unlock();
}
public long initAge() {
return System.currentTimeMillis() - initTime;
}
public synchronized IBlockDelegate getDelegate() {
@ -84,10 +92,10 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
* If the chunk is currently being "called", this method will block until completed.
*/
private void checkAndWaitOnCalledLock() {
if (calledLock.isLocked()) {
if (!calledLock.tryLock()) {
calledLock.lock();
calledLock.unlock();
}
calledLock.unlock();
}
@Override
@ -1024,6 +1032,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public synchronized <V extends IChunk> void init(IQueueExtent<V> extent, int chunkX, int chunkZ) {
this.initTime = System.currentTimeMillis();
this.extent = extent;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
@ -1040,14 +1049,15 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public synchronized T call() {
calledLock.lock();
final long stamp = calledLock.getStampChecked();
if (chunkSet != null && !chunkSet.isEmpty()) {
this.delegate = GET;
chunkSet.setBitMask(bitMask);
try {
IChunkSet copy = chunkSet.createCopy();
chunkSet = null;
return this.call(copy, () -> calledLock.unlock(stamp));
return this.call(copy, () -> {
// Do nothing
});
} catch (Throwable t) {
calledLock.unlock();
throw t;
@ -1072,6 +1082,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
} else {
finalizer = finalize;
}
calledLock.unlock();
return get.call(set, finalizer);
}
return null;

View File

@ -4,12 +4,15 @@ import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.regions.Region;
import javax.annotation.Nonnull;
import java.util.Objects;
public class FaweMask implements IDelegateRegion {
private final Region region;
public FaweMask(Region region) {
this.region = region;
public FaweMask(@Nonnull Region region) {
this.region = Objects.requireNonNull(region);
}
@Override
@ -35,7 +38,7 @@ public class FaweMask implements IDelegateRegion {
* @param type type of mask
* @param notify if the player should be notified
* @return if still valid
* @since TODO
* @since 2.7.0
*/
public boolean isValid(Player player, FaweMaskManager.MaskType type, boolean notify) {
return isValid(player, type);

View File

@ -30,7 +30,7 @@ public abstract class FaweMaskManager {
/**
* Get a {@link FaweMask} for the given player and {@link MaskType}. If isWhitelist is false, will return a "blacklist" mask.
*
* @since TODO
* @since 2.7.0
*/
public FaweMask getMask(final Player player, MaskType type, boolean isWhitelist, boolean notify) {
return getMask(player, type, isWhitelist);

View File

@ -4,6 +4,7 @@ import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.extent.Extent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
public class ExtentTraverser<T extends Extent> {
@ -24,6 +25,7 @@ public class ExtentTraverser<T extends Extent> {
return root != null;
}
@Nullable
public T get() {
return root;
}
@ -50,6 +52,7 @@ public class ExtentTraverser<T extends Extent> {
}
@SuppressWarnings("unchecked")
@Nullable
public <U> U findAndGet(Class<U> clazz) {
ExtentTraverser<Extent> traverser = find(clazz);
return (traverser != null) ? (U) traverser.get() : null;

View File

@ -0,0 +1,44 @@
package com.fastasyncworldedit.core.util;
import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder;
import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Queue;
public class ProcessorTraverser<T extends IBatchProcessor> {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private final T root;
public ProcessorTraverser(@Nonnull T root) {
this.root = root;
}
public <U extends IBatchProcessor> @Nullable U find(Class<U> clazz) {
try {
Queue<IBatchProcessor> processors = new ArrayDeque<>();
IBatchProcessor processor = root;
do {
if (clazz.isAssignableFrom(processor.getClass())) {
return clazz.cast(processor);
} else if (processor instanceof MultiBatchProcessor multiProcessor) {
processors.addAll(multiProcessor.getBatchProcessors());
} else if (processor instanceof BatchProcessorHolder holder) {
processors.add(holder.getProcessor());
}
} while ((processor = processors.poll()) != null);
return null;
} catch (Throwable e) {
LOGGER.error("Error traversing processors", e);
return null;
}
}
}

View File

@ -88,7 +88,10 @@ public abstract class TaskManager {
/**
* Run a bunch of tasks in parallel using the shared thread pool.
*
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
*/
@Deprecated(forRemoval = true, since = "2.7.0")
public void parallel(Collection<Runnable> runables) {
for (Runnable run : runables) {
pool.submit(run);
@ -101,8 +104,9 @@ public abstract class TaskManager {
*
* @param runnables the tasks to run
* @param numThreads number of threads (null = config.yml parallel threads)
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
*/
@Deprecated
@Deprecated(forRemoval = true, since = "2.7.0")
public void parallel(Collection<Runnable> runnables, @Nullable Integer numThreads) {
if (runnables == null) {
return;
@ -271,13 +275,17 @@ public abstract class TaskManager {
});
}
/**
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
*/
@Deprecated(forRemoval = true, since = "2.7.0")
public void wait(AtomicBoolean running, int timeout) {
try {
long start = System.currentTimeMillis();
synchronized (running) {
while (running.get()) {
running.wait(timeout);
if (running.get() && System.currentTimeMillis() - start > Settings.settings().QUEUE.DISCARD_AFTER_MS) {
if (running.get() && System.currentTimeMillis() - start > 60000) {
new RuntimeException("FAWE is taking a long time to execute a task (might just be a symptom): ").printStackTrace();
LOGGER.info("For full debug information use: /fawe threads");
}
@ -288,6 +296,10 @@ public abstract class TaskManager {
}
}
/**
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
*/
@Deprecated(forRemoval = true, since = "2.7.0")
public void notify(AtomicBoolean running) {
running.set(false);
synchronized (running) {

View File

@ -14,7 +14,13 @@ import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.net.URL;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class UpdateNotification {
@ -28,38 +34,54 @@ public class UpdateNotification {
*/
public static void doUpdateCheck() {
if (Settings.settings().ENABLED_COMPONENTS.UPDATE_NOTIFICATIONS) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new URL("https://ci.plex.us.org/job/Plex-FAWE/api/xml/").openStream());
faweVersion = doc.getElementsByTagName("lastSuccessfulBuild").item(0).getFirstChild().getTextContent();
FaweVersion faweVersion = Fawe.instance().getVersion();
if (faweVersion.build == 0) {
LOGGER.warn("You are using a snapshot or a custom version of FAWE. This is not an official build distributed " +
"via https://www.spigotmc.org/resources/13932/");
return;
}
if (faweVersion.build < Integer.parseInt(UpdateNotification.faweVersion)) {
hasUpdate = true;
int versionDifference = Integer.parseInt(UpdateNotification.faweVersion) - faweVersion.build;
LOGGER.warn(
"""
An update for FastAsyncWorldEdit is available. You are {} build(s) out of date.
You are running build {}, the latest version is build {}.
Update at https://ci.plex.us.org/job/Plex-FAWE/""",
versionDifference,
faweVersion.build,
UpdateNotification.faweVersion
);
}
} catch (Exception e) {
LOGGER.error("Unable to check for updates. Skipping.");
}
final HttpRequest request = HttpRequest
.newBuilder()
.uri(URI.create("https://ci.athion.net/job/FastAsyncWorldEdit/api/xml/"))
.timeout(Duration.of(10L, ChronoUnit.SECONDS))
.build();
HttpClient.newHttpClient()
.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream())
.whenComplete((response, thrown) -> {
if (thrown != null) {
LOGGER.error("Update check failed: {} ", thrown.getMessage());
}
processResponseBody(response.body());
});
}
}
private static void processResponseBody(InputStream body) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(body);
faweVersion = doc.getElementsByTagName("lastSuccessfulBuild").item(0).getFirstChild().getTextContent();
FaweVersion faweVersion = Fawe.instance().getVersion();
if (faweVersion.build == 0) {
LOGGER.warn("You are using a snapshot or a custom version of FAWE. This is not an official build distributed " +
"via https://www.spigotmc.org/resources/13932/");
return;
}
if (faweVersion.build < Integer.parseInt(UpdateNotification.faweVersion)) {
hasUpdate = true;
int versionDifference = Integer.parseInt(UpdateNotification.faweVersion) - faweVersion.build;
LOGGER.warn(
"""
An update for FastAsyncWorldEdit is available. You are {} build(s) out of date.
You are running build {}, the latest version is build {}.
Update at https://www.spigotmc.org/resources/13932/""",
versionDifference,
faweVersion.build,
UpdateNotification.faweVersion
);
}
} catch (Exception ignored) {
LOGGER.error("Unable to check for updates. Skipping.");
}
}
/**
* Trigger an update notification based on captions. Useful to notify server administrators ingame.
*
@ -76,8 +98,8 @@ public class UpdateNotification {
faweVersion.build,
UpdateNotification.faweVersion,
TextComponent
.of("https://ci.plex.us.org/job/Plex-FAWE/")
.clickEvent(ClickEvent.openUrl("https://ci.plex.us.org/job/Plex-FAWE/"))
.of("https://www.spigotmc.org/resources/13932/")
.clickEvent(ClickEvent.openUrl("https://www.spigotmc.org/resources/13932/"))
));
}
}

View File

@ -0,0 +1,81 @@
package com.fastasyncworldedit.core.util.task;
import com.fastasyncworldedit.core.configuration.Settings;
import java.io.Closeable;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.function.Supplier;
/**
* async queue that accepts a {@link Thread.UncaughtExceptionHandler} for exception handling per instance, delegating to a
* parent {@link KeyQueuedExecutorService}.
*
* @since 2.7.0
*/
public class AsyncNotifyKeyedQueue implements Closeable {
private static final KeyQueuedExecutorService<UUID> QUEUE_SUBMISSIONS = new KeyQueuedExecutorService<>(new ForkJoinPool(
Settings.settings().QUEUE.PARALLEL_THREADS,
new FaweForkJoinWorkerThreadFactory("AsyncNotifyKeyedQueue - %s"),
null,
false
));
private final Thread.UncaughtExceptionHandler handler;
private final Supplier<UUID> key;
private volatile boolean closed;
/**
* New instance
*
* @param handler exception handler
* @param key supplier of UUID key
*/
public AsyncNotifyKeyedQueue(Thread.UncaughtExceptionHandler handler, Supplier<UUID> key) {
this.handler = handler;
this.key = key;
}
public Thread.UncaughtExceptionHandler getHandler() {
return handler;
}
public <T> Future<T> run(Runnable task) {
return call(() -> {
task.run();
return null;
});
}
public <T> Future<T> call(Callable<T> task) {
Future[] self = new Future[1];
Callable<T> wrapped = () -> {
if (!closed) {
try {
return task.call();
} catch (Throwable e) {
handler.uncaughtException(Thread.currentThread(), e);
}
}
if (self[0] != null) {
self[0].cancel(true);
}
return null;
};
self[0] = QUEUE_SUBMISSIONS.submit(key.get(), wrapped);
return self[0];
}
@Override
public void close() {
closed = true;
}
public boolean isClosed() {
return closed;
}
}

View File

@ -1,13 +1,9 @@
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;
@ -15,13 +11,6 @@ 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;
@ -56,9 +45,6 @@ public class AsyncNotifyQueue implements Closeable {
return task.call();
} catch (Throwable e) {
handler.uncaughtException(Thread.currentThread(), e);
if (self[0] != null) {
self[0].cancel(true);
}
}
}
} finally {
@ -70,7 +56,7 @@ public class AsyncNotifyQueue implements Closeable {
}
return null;
};
self[0] = QUEUE_SUBMISSIONS.submit(wrapped);
self[0] = Fawe.instance().getQueueHandler().async(wrapped);
return self[0];
}

View File

@ -30,6 +30,7 @@ import com.fastasyncworldedit.core.extent.ResettableExtent;
import com.fastasyncworldedit.core.extent.SingleRegionExtent;
import com.fastasyncworldedit.core.extent.SourceMaskExtent;
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
import com.fastasyncworldedit.core.extent.processor.ExtentBatchProcessorHolder;
import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.function.SurfaceRegionFunction;
@ -55,6 +56,7 @@ import com.fastasyncworldedit.core.queue.implementation.preloader.Preloader;
import com.fastasyncworldedit.core.util.ExtentTraverser;
import com.fastasyncworldedit.core.util.MaskTraverser;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ProcessorTraverser;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.collection.BlockVector3Set;
import com.fastasyncworldedit.core.util.task.RunnableVal;
@ -524,9 +526,17 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
* @return mask, may be null
*/
public Mask getMask() {
//FAWE start - ExtendTraverser & MaskingExtents
ExtentTraverser<MaskingExtent> maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class);
return maskingExtent != null ? maskingExtent.get().getMask() : null;
//FAWE start - ExtentTraverser & MaskingExtents
MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class);
if (maskingExtent == null) {
ExtentBatchProcessorHolder processorExtent =
new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class);
if (processorExtent != null) {
maskingExtent =
new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class);
}
}
return maskingExtent != null ? maskingExtent.getMask() : null;
//FAWE end
}
@ -609,23 +619,31 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
//FAWE start - use MaskingExtent & ExtentTraverser
/**
* Set a mask.
* Set a mask. Combines with any existing masks, set null to clear existing masks.
*
* @param mask mask or null
*/
public void setMask(Mask mask) {
public void setMask(@Nullable Mask mask) {
if (mask == null) {
mask = Masks.alwaysTrue();
} else {
new MaskTraverser(mask).reset(this);
}
ExtentTraverser<MaskingExtent> maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class);
if (maskingExtent != null && maskingExtent.get() != null) {
Mask oldMask = maskingExtent.get().getMask();
MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class);
if (maskingExtent == null && mask != Masks.alwaysTrue()) {
ExtentBatchProcessorHolder processorExtent =
new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class);
if (processorExtent != null) {
maskingExtent =
new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class);
}
}
if (maskingExtent != null) {
Mask oldMask = maskingExtent.getMask();
if (oldMask instanceof ResettableMask) {
((ResettableMask) oldMask).reset();
}
maskingExtent.get().setMask(mask);
maskingExtent.setMask(mask);
} else if (mask != Masks.alwaysTrue()) {
addProcessor(new MaskingExtent(getExtent(), mask));
}
@ -2270,6 +2288,90 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
//FAWE end
}
/**
* Makes a cone.
*
* @param pos Center of the cone
* @param block The block pattern to use
* @param radiusX The cone's largest north/south extent
* @param radiusZ The cone's largest east/west extent
* @param height The cone's up/down extent. If negative, extend downward.
* @param filled If false, only a shell will be generated.
* @param thickness The cone's wall thickness, if it's hollow.
* @return number of blocks changed
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
public int makeCone(
BlockVector3 pos,
Pattern block,
double radiusX,
double radiusZ,
int height,
boolean filled,
double thickness
) throws MaxChangedBlocksException {
int affected = 0;
final int ceilRadiusX = (int) Math.ceil(radiusX);
final int ceilRadiusZ = (int) Math.ceil(radiusZ);
double rx2 = Math.pow(radiusX, 2);
double ry2 = Math.pow(height, 2);
double rz2 = Math.pow(radiusZ, 2);
int cx = pos.getX();
int cy = pos.getY();
int cz = pos.getZ();
for (int y = 0; y < height; ++y) {
double ySquaredMinusHeightOverHeightSquared = Math.pow(y - height, 2) / ry2;
int yy = cy + y;
forX:
for (int x = 0; x <= ceilRadiusX; ++x) {
double xSquaredOverRadiusX = Math.pow(x, 2) / rx2;
int xx = cx + x;
forZ:
for (int z = 0; z <= ceilRadiusZ; ++z) {
int zz = cz + z;
double zSquaredOverRadiusZ = Math.pow(z, 2) / rz2;
double distanceFromOriginMinusHeightSquared = xSquaredOverRadiusX + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared;
if (distanceFromOriginMinusHeightSquared > 1) {
if (z == 0) {
break forX;
}
break forZ;
}
if (!filled) {
double xNext = Math.pow(x + thickness, 2) / rx2 + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared;
double yNext = xSquaredOverRadiusX + zSquaredOverRadiusZ - Math.pow(y + thickness - height, 2) / ry2;
double zNext = xSquaredOverRadiusX + Math.pow(z + thickness, 2) / rz2 - ySquaredMinusHeightOverHeightSquared;
if (xNext <= 0 && zNext <= 0 && (yNext <= 0 && y + thickness != height)) {
continue;
}
}
if (distanceFromOriginMinusHeightSquared <= 0) {
if (setBlock(xx, yy, zz, block)) {
++affected;
}
if (setBlock(xx, yy, zz, block)) {
++affected;
}
if (setBlock(xx, yy, zz, block)) {
++affected;
}
if (setBlock(xx, yy, zz, block)) {
++affected;
}
}
}
}
}
return affected;
}
/**
* Move the blocks in a region a certain direction.
*
@ -2991,9 +3093,10 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
} catch (ExpressionTimeoutException e) {
timedOut[0] = timedOut[0] + 1;
return null;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
LOGGER.warn("Failed to create shape", e);
return null;
throw new RuntimeException(e);
}
}
};

View File

@ -23,8 +23,10 @@ import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.ResettableExtent;
import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.history.DiskStorageHistory;
import com.fastasyncworldedit.core.internal.exception.FaweClipboardVersionMismatchException;
import com.fastasyncworldedit.core.internal.io.FaweInputStream;
import com.fastasyncworldedit.core.internal.io.FaweOutputStream;
import com.fastasyncworldedit.core.limit.FaweLimit;
@ -50,6 +52,8 @@ import com.sk89q.worldedit.command.tool.Tool;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.Locatable;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.ChangeSetExecutor;
@ -93,6 +97,7 @@ import java.util.ListIterator;
import java.util.Objects;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -105,8 +110,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
public class LocalSession implements TextureHolder {
private static final transient int CUI_VERSION_UNINITIALIZED = -1;
public static transient int MAX_HISTORY_SIZE = 15;
public static int MAX_HISTORY_SIZE = 15;
private static final int CUI_VERSION_UNINITIALIZED = -1;
// Non-session related fields
private transient LocalConfiguration config;
@ -877,6 +882,58 @@ public class LocalSession implements TextureHolder {
}
}
}
/**
* Load a clipboard from disk and into this localsession. Synchronises with other clipboard setting/getting to and from
* this session
*
* @param file Clipboard file to load
* @throws FaweClipboardVersionMismatchException in clipboard version mismatch (between saved and internal, expected, version)
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
*/
public void loadClipboardFromDisk(File file) throws FaweClipboardVersionMismatchException, ExecutionException,
InterruptedException {
synchronized (clipboardLock) {
if (file.exists() && file.length() > 5) {
try {
if (getClipboard() != null) {
return;
}
} catch (EmptyClipboardException ignored) {
}
DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit(
uuid,
() -> DiskOptimizedClipboard.loadFromFile(file)
).get();
Clipboard clip = doc.toClipboard();
ClipboardHolder holder = new ClipboardHolder(clip);
setClipboard(holder);
}
}
}
public void deleteClipboardOnDisk() {
synchronized (clipboardLock) {
ClipboardHolder holder = getExistingClipboard();
if (holder != null) {
for (Clipboard clipboard : holder.getClipboards()) {
DiskOptimizedClipboard doc;
if (clipboard instanceof DiskOptimizedClipboard) {
doc = (DiskOptimizedClipboard) clipboard;
} else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) {
doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent();
} else {
continue;
}
Fawe.instance().getClipboardExecutor().submit(uuid, () -> {
doc.close(); // Ensure closed before deletion
doc.getFile().delete();
});
}
}
}
}
//FAWE end
/**

View File

@ -929,11 +929,13 @@ public class BrushCommands {
@Arg(desc = "Expression")
Expression radius,
@Arg(desc = "Command to run")
List<String> input
List<String> input,
@Switch(name = 'p', desc = "Show any printed output")
boolean print
) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius);
String cmd = StringMan.join(input, " ");
set(context, new CommandBrush(cmd), "worldedit.brush.command").setSize(radius);
set(context, new CommandBrush(cmd, print), "worldedit.brush.command").setSize(radius);
}
@Command(

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit.command;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweAPI;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.configuration.Caption;
@ -28,6 +29,7 @@ import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.ImgurUtility;
@ -160,7 +162,7 @@ public class ClipboardCommands {
session.getPlacementPosition(actor));
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
copy.setCopyingEntities(copyEntities);
createCopy(session, editSession, copyBiomes, mask, clipboard, copy);
createCopy(actor, session, editSession, copyBiomes, mask, clipboard, copy);
copy.getStatusMessages().forEach(actor::print);
//FAWE end
@ -271,7 +273,7 @@ public class ClipboardCommands {
copy.setSourceFunction(new BlockReplace(editSession, leavePattern));
copy.setCopyingEntities(copyEntities);
copy.setRemovingEntities(true);
createCopy(session, editSession, copyBiomes, mask, clipboard, copy);
createCopy(actor, session, editSession, copyBiomes, mask, clipboard, copy);
if (!actor.hasPermission("fawe.tips")) {
actor.print(Caption.of("fawe.tips.tip.lazycut"));
@ -281,6 +283,7 @@ public class ClipboardCommands {
}
private void createCopy(
final Actor actor,
final LocalSession session,
final EditSession editSession,
final boolean copyBiomes,
@ -311,9 +314,22 @@ public class ClipboardCommands {
try {
Operations.completeLegacy(copy);
} finally {
clipboard.flush();
} catch (Exception e) {
DiskOptimizedClipboard doc;
if (clipboard instanceof DiskOptimizedClipboard) {
doc = (DiskOptimizedClipboard) clipboard;
} else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) {
doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent();
} else {
throw e;
}
Fawe.instance().getClipboardExecutor().submit(actor.getUniqueId(), () -> {
clipboard.close();
doc.getFile().delete();
});
throw e;
}
clipboard.flush();
session.setClipboard(new ClipboardHolder(clipboard));
}

View File

@ -188,6 +188,49 @@ public class GenerationCommands {
return affected;
}
@Command(
name = "/cone",
desc = "Generates a cone."
)
@CommandPermissions("worldedit.generation.cone")
@Logging(PLACEMENT)
public int cone(Actor actor, LocalSession session, EditSession editSession,
@Arg(desc = "The pattern of blocks to generate")
Pattern pattern,
@Arg(desc = "The radii of the cone. 1st is N/S, 2nd is E/W")
@Radii(2)
List<Double> radii,
@Arg(desc = "The height of the cone", def = "1")
int height,
@Switch(name = 'h', desc = "Make a hollow cone")
boolean hollow,
@Arg(desc = "Thickness of the hollow cone", def = "1")
double thickness
) throws WorldEditException {
double radiusX;
double radiusZ;
switch (radii.size()) {
case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0));
case 2 -> {
radiusX = Math.max(1, radii.get(0));
radiusZ = Math.max(1, radii.get(1));
}
default -> {
actor.printError(Caption.of("worldedit.cone.invalid-radius"));
return 0;
}
}
worldEdit.checkMaxRadius(radiusX);
worldEdit.checkMaxRadius(radiusZ);
worldEdit.checkMaxRadius(height);
BlockVector3 pos = session.getPlacementPosition(actor);
int affected = editSession.makeCone(pos, pattern, radiusX, radiusZ, height, !hollow, thickness);
actor.printInfo(Caption.of("worldedit.cone.created", TextComponent.of(affected)));
return affected;
}
@Command(
name = "/hsphere",
desc = "Generates a hollow sphere."

View File

@ -50,12 +50,19 @@ import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
import com.sk89q.worldedit.function.EntityFunction;
import com.sk89q.worldedit.function.block.BlockReplace;
import com.sk89q.worldedit.function.mask.BlockTypeMask;
import com.sk89q.worldedit.function.mask.BoundedHeightMask;
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.MaskIntersection;
import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.function.mask.RegionMask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.DownwardVisitor;
import com.sk89q.worldedit.function.visitor.EntityVisitor;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import com.sk89q.worldedit.internal.annotation.Direction;
import com.sk89q.worldedit.internal.annotation.VertHeight;
import com.sk89q.worldedit.internal.expression.EvaluationException;
@ -63,8 +70,10 @@ import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector2;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.CylinderRegion;
import com.sk89q.worldedit.regions.EllipsoidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.formatting.component.SubtleFormat;
import com.sk89q.worldedit.util.formatting.text.Component;
@ -836,6 +845,55 @@ public class UtilityCommands {
}
}
// @Command(
// name = "/hollowr",
// desc = "Hollow out a space recursively with a pattern"
// )
// @CommandPermissions("worldedit.hollowr")
// @Logging(PLACEMENT)
// public int hollowr(
// Actor actor,
// LocalSession session,
// EditSession editSession,
// @Arg(desc = "The radius to hollow out") Expression radiusExp,
// @ArgFlag(name = 'p', desc = "The blocks to fill with") Pattern pattern,
// @ArgFlag(name = 'm', desc = "The blocks remove", def = "") Mask mask
// ) throws WorldEditException {
// //FAWE start
// double radius = radiusExp.evaluate();
// //FAWE end
// radius = Math.max(1, radius);
// we.checkMaxRadius(radius);
// if (mask == null) {
// Mask mask = new MaskIntersection(
// new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))),
// new BoundedHeightMask(
// Math.max(lowerBound, minY),
// Math.min(maxY, origin.getBlockY())
// ),
// Masks.negate(new ExistingBlockMask(this))
// );
// }
//
// // Want to replace blocks
// BlockReplace replace = new BlockReplace(this, pattern);
//
// // Pick how we're going to visit blocks
// RecursiveVisitor visitor;
// //FAWE start - provide extent for preloading, min/max y
// if (recursive) {
// visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), minY, maxY, this);
// } else {
// visitor = new DownwardVisitor(mask, replace, origin.getBlockY(), (int) (radius * 2 + 1), minY, maxY, this);
// }
// //FAWE end
//
// BlockVector3 pos = session.getPlacementPosition(actor);
// int affected = editSession.res(pos, pattern, radius, depth, true);
// actor.print(Caption.of("worldedit.fillr.created", TextComponent.of(affected)));
// return affected;
// }
public static List<Map.Entry<URI, String>> filesToEntry(final File root, final List<File> files, final UUID uuid) {
return files.stream()
.map(input -> { // Keep this functional, as transform is evaluated lazily

View File

@ -426,23 +426,7 @@ public interface Player extends Entity, Actor {
cancel(true);
LocalSession session = getSession();
if (Settings.settings().CLIPBOARD.USE_DISK && Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
ClipboardHolder holder = session.getExistingClipboard();
if (holder != null) {
for (Clipboard clipboard : holder.getClipboards()) {
DiskOptimizedClipboard doc;
if (clipboard instanceof DiskOptimizedClipboard) {
doc = (DiskOptimizedClipboard) clipboard;
} else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) {
doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent();
} else {
continue;
}
Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> {
doc.close(); // Ensure closed before deletion
doc.getFile().delete();
});
}
}
session.deleteClipboardOnDisk();
} else if (Settings.settings().CLIPBOARD.USE_DISK) {
Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null));
} else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
@ -464,22 +448,7 @@ public interface Player extends Entity, Actor {
Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd"
);
try {
if (file.exists() && file.length() > 5) {
LocalSession session = getSession();
try {
if (session.getClipboard() != null) {
return;
}
} catch (EmptyClipboardException ignored) {
}
DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit(
getUniqueId(),
() -> DiskOptimizedClipboard.loadFromFile(file)
).get();
Clipboard clip = doc.toClipboard();
ClipboardHolder holder = new ClipboardHolder(clip);
session.setClipboard(holder);
}
getSession().loadClipboardFromDisk(file);
} catch (FaweClipboardVersionMismatchException e) {
print(e.getComponent());
} catch (RuntimeException e) {

View File

@ -42,6 +42,7 @@ import com.fastasyncworldedit.core.extension.factory.parser.pattern.OffsetPatter
import com.fastasyncworldedit.core.extension.factory.parser.pattern.PerlinPatternParser;
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomFullClipboardPatternParser;
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomOffsetPatternParser;
import com.fastasyncworldedit.core.extension.factory.parser.pattern.TypeSwapPatternParser;
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RelativePatternParser;
import com.fastasyncworldedit.core.extension.factory.parser.pattern.RichPatternParser;
@ -133,6 +134,7 @@ public final class PatternFactory extends AbstractFactory<Pattern> {
register(new SimplexPatternParser(worldEdit));
register(new SolidRandomOffsetPatternParser(worldEdit));
register(new SurfaceRandomOffsetPatternParser(worldEdit));
register(new TypeSwapPatternParser(worldEdit));
register(new VoronoiPatternParser(worldEdit));
//FAWE end
}

View File

@ -19,10 +19,9 @@
package com.sk89q.worldedit.extension.platform;
import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue;
import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
@ -68,7 +67,7 @@ public abstract class AbstractNonPlayerActor implements Actor {
// Queue for async tasks
private final AtomicInteger runningCount = new AtomicInteger();
private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue((thread, throwable) -> {
private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue((thread, throwable) -> {
while (throwable.getCause() != null) {
throwable = throwable.getCause();
}
@ -82,7 +81,7 @@ public abstract class AbstractNonPlayerActor implements Actor {
throwable.printStackTrace();
}
}
});
}, this::getUniqueId);
/**
* Run a task either async, or on the current thread.

View File

@ -25,7 +25,7 @@ import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.fastasyncworldedit.core.regions.FaweMaskManager;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.WEManager;
import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue;
import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.WorldEdit;
@ -81,7 +81,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
// Queue for async tasks
private final AtomicInteger runningCount = new AtomicInteger();
private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue(
private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue(
(thread, throwable) -> {
while (throwable.getCause() != null) {
throwable = throwable.getCause();
@ -96,7 +96,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
throwable.printStackTrace();
}
}
});
}, this::getUniqueId);
public AbstractPlayerActor(Map<String, Object> meta) {
this.meta = meta;
@ -709,7 +709,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
@Override
public void checkPermission(String permission) throws AuthorizationException {
if (!hasPermission(permission)) {
throw new AuthorizationException();
throw new AuthorizationException(Caption.of("fawe.error.no-perm", permission));
}
}

View File

@ -45,8 +45,8 @@ import com.sk89q.worldedit.world.item.ItemTypes;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@ -354,7 +354,7 @@ public class SessionManager {
@Subscribe
public void onConfigurationLoad(ConfigurationLoadEvent event) {
LocalConfiguration config = event.getConfiguration();
File dir = new File(config.getWorkingDirectoryPath().toFile(), "sessions");
Path dir = config.getWorkingDirectoryPath().resolve("sessions");
store = new JsonFileSessionStore(dir);
}

View File

@ -36,6 +36,10 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkNotNull;
@ -49,20 +53,31 @@ public class JsonFileSessionStore implements SessionStore {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private final Gson gson;
private final File dir;
private final Path dir;
/**
* Create a new session store.
*
* @param dir the directory
* @deprecated Use {@link #JsonFileSessionStore(Path)}
*/
@Deprecated
public JsonFileSessionStore(File dir) {
this(dir.toPath());
}
/**
* Create a new session store.
*
* @param dir the directory
*/
public JsonFileSessionStore(File dir) {
public JsonFileSessionStore(Path dir) {
checkNotNull(dir);
if (!dir.isDirectory()) {
if (!dir.mkdirs()) {
LOGGER.warn("Failed to create directory '" + dir.getPath() + "' for sessions");
}
try {
Files.createDirectories(dir);
} catch (IOException e) {
LOGGER.warn("Failed to create directory '" + dir + "' for sessions", e);
}
this.dir = dir;
@ -77,21 +92,19 @@ public class JsonFileSessionStore implements SessionStore {
* @param id the ID
* @return the file
*/
private File getPath(UUID id) {
private Path getPath(UUID id) {
checkNotNull(id);
return new File(dir, id + ".json");
return dir.resolve(id + ".json");
}
@Override
public LocalSession load(UUID id) throws IOException {
File file = getPath(id);
try (Closer closer = Closer.create()) {
FileReader fr = closer.register(new FileReader(file));
BufferedReader br = closer.register(new BufferedReader(fr));
LocalSession session = gson.fromJson(br, LocalSession.class);
Path file = getPath(id);
try (var reader = Files.newBufferedReader(file)) {
LocalSession session = gson.fromJson(reader, LocalSession.class);
if (session == null) {
LOGGER.warn("Loaded a null session from {}, creating new session", file);
if (!file.delete()) {
if (!Files.deleteIfExists(file)) {
LOGGER.warn("Failed to delete corrupted session {}", file);
}
session = new LocalSession();
@ -99,7 +112,7 @@ public class JsonFileSessionStore implements SessionStore {
return session;
} catch (JsonParseException e) {
throw new IOException(e);
} catch (FileNotFoundException e) {
} catch (NoSuchFileException e) {
return new LocalSession();
}
}
@ -107,29 +120,26 @@ public class JsonFileSessionStore implements SessionStore {
@Override
public void save(UUID id, LocalSession session) throws IOException {
checkNotNull(session);
File finalFile = getPath(id);
File tempFile = new File(finalFile.getParentFile(), finalFile.getName() + ".tmp");
Path finalFile = getPath(id);
Path tempFile = finalFile.getParent().resolve(finalFile.getFileName() + ".tmp");
try (Closer closer = Closer.create()) {
FileWriter fr = closer.register(new FileWriter(tempFile));
BufferedWriter bw = closer.register(new BufferedWriter(fr));
gson.toJson(session, bw);
try (var writer = Files.newBufferedWriter(tempFile)) {
gson.toJson(session, writer);
} catch (JsonIOException e) {
throw new IOException(e);
}
if (finalFile.exists()) {
if (!finalFile.delete()) {
LOGGER.warn("Failed to delete " + finalFile.getPath() + " so the .tmp file can replace it");
}
}
if (tempFile.length() == 0) {
if (Files.size(tempFile) == 0) {
throw new IllegalStateException("Gson wrote zero bytes");
}
if (!tempFile.renameTo(finalFile)) {
LOGGER.warn("Failed to rename temporary session file to " + finalFile.getPath());
try {
Files.move(
tempFile, finalFile,
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING
);
} catch (IOException e) {
LOGGER.warn("Failed to rename temporary session file to " + finalFile.toAbsolutePath(), e);
}
}

View File

@ -86,7 +86,7 @@ public class BlockType implements Keyed, Pattern {
* a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use
* {@link BlockTypes#get(String)} instead.
*/
@Deprecated(since = "TODO")
@Deprecated(since = "2.7.0")
//FAWE end
public BlockType(String id) {
this(id, null);
@ -98,7 +98,7 @@ public class BlockType implements Keyed, Pattern {
* a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use
* {@link BlockTypes#get(String)} instead.
*/
@Deprecated(since = "TODO")
@Deprecated(since = "2.7.0")
//FAWE end
public BlockType(String id, Function<BlockState, BlockState> values) {
// If it has no namespace, assume minecraft.

View File

@ -43,7 +43,7 @@ public class ItemType implements RegistryItem, Keyed {
private final String id;
@SuppressWarnings("deprecation")
private final LazyReference<String> name = LazyReference.from(() -> {
private transient final LazyReference<String> name = LazyReference.from(() -> {
String name = GuavaUtil.firstNonNull(
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS)
.getRegistries().getItemRegistry().getName(this),
@ -51,18 +51,18 @@ public class ItemType implements RegistryItem, Keyed {
);
return name.isEmpty() ? getId() : name;
});
private final LazyReference<Component> richName = LazyReference.from(() ->
private transient final LazyReference<Component> richName = LazyReference.from(() ->
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS)
.getRegistries().getItemRegistry().getRichName(this)
);
private final LazyReference<ItemMaterial> itemMaterial = LazyReference.from(() ->
private transient final LazyReference<ItemMaterial> itemMaterial = LazyReference.from(() ->
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS)
.getRegistries().getItemRegistry().getMaterial(this)
);
//FAWE start
private BlockType blockType;
private boolean initBlockType;
private BaseItem defaultState;
private transient BlockType blockType;
private transient boolean initBlockType;
private transient BaseItem defaultState;
//FAWE end
public ItemType(String id) {

View File

@ -489,6 +489,8 @@
"worldedit.jumpto.none": "No block in sight (or too far away)!",
"worldedit.up.obstructed": "You would hit something above you.",
"worldedit.up.moved": "Woosh!",
"worldedit.cone.invalid-radius": "You must either specify 1 or 2 radius values.",
"worldedit.cone.created": "{0} blocks have been created.",
"worldedit.cyl.invalid-radius": "You must either specify 1 or 2 radius values.",
"worldedit.cyl.created": "{0} blocks have been created.",
"worldedit.hcyl.thickness-too-large": "Thickness cannot be larger than x or z radii.",