mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 17:27:38 +00:00
This commit is contained in:
commit
9d489791ee
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -32,7 +32,6 @@ body:
|
||||
- '1.19.4'
|
||||
- '1.18.2'
|
||||
- '1.17.1'
|
||||
- '1.16.5'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
2
.github/renovate.json
vendored
2
.github/renovate.json
vendored
@ -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"
|
||||
],
|
||||
|
@ -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 {
|
||||
|
@ -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") {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
5
gradlew
vendored
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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"))
|
||||
|
@ -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/");
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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 += "]";
|
||||
}
|
||||
|
@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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/"))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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.",
|
||||
|
Loading…
Reference in New Issue
Block a user