Merge remote-tracking branch 'upstream/main'

This commit is contained in:
2023-08-19 12:43:25 +02:00
224 changed files with 4739 additions and 10834 deletions

View File

@@ -11,8 +11,8 @@ applyPlatformAndCoreConfiguration()
dependencies {
constraints {
implementation("org.yaml:snakeyaml") {
version { strictly("1.33") }
implementation(libs.snakeyaml) {
version { strictly("2.0") }
because("Bukkit provides SnakeYaml")
}
}
@@ -24,17 +24,17 @@ dependencies {
// Minecraft expectations
implementation(libs.fastutil)
implementation("com.google.guava:guava")
implementation("com.google.code.gson:gson")
implementation(libs.guava)
implementation(libs.gson)
// Platform expectations
implementation("org.yaml:snakeyaml")
implementation(libs.snakeyaml)
// Logging
implementation("org.apache.logging.log4j:log4j-api")
implementation(libs.log4jApi)
// Plugins
compileOnly("com.plotsquared:PlotSquared-Core") { isTransitive = false }
compileOnly(libs.plotSquaredCore) { isTransitive = false }
// ensure this is on the classpath for the AP
annotationProcessor(libs.guava)
@@ -45,11 +45,11 @@ dependencies {
compileOnly(libs.truezip)
implementation(libs.findbugs)
implementation(libs.rhino)
compileOnly("net.kyori:adventure-api")
compileOnly(libs.adventureApi)
compileOnlyApi(libs.adventureNbt)
compileOnlyApi("net.kyori:adventure-text-minimessage")
compileOnlyApi(libs.adventureMiniMessage)
implementation(libs.zstd) { isTransitive = false }
compileOnly("com.intellectualsites.paster:Paster")
compileOnly(libs.paster)
compileOnly(libs.lz4Java) { isTransitive = false }
compileOnly(libs.sparsebitset)
compileOnly(libs.parallelgzip) { isTransitive = false }

View File

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

View File

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

View File

@@ -20,27 +20,29 @@
package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.internal.Constants;
import com.sk89q.worldedit.util.gson.GsonUtil;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Represents a sign block.
*
* @deprecated WorldEdit does not handle interpreting NBT,
* deprecated for removal without replacement
*/
@Deprecated(forRemoval = true)
public class SignBlock extends BaseBlock {
private String[] text;
private static String EMPTY = "{\"text\":\"\"}";
private static final String EMPTY = "{\"text\":\"\"}";
/**
* Construct the sign with text.
@@ -64,6 +66,11 @@ public class SignBlock extends BaseBlock {
this.text = text;
}
private boolean isLegacy() {
int dataVersion = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataVersion();
return dataVersion < Constants.DATA_VERSION_MC_1_20;
}
/**
* Get the text.
*
@@ -98,10 +105,17 @@ public class SignBlock extends BaseBlock {
@Override
public CompoundTag getNbtData() {
Map<String, Tag> values = new HashMap<>();
values.put("Text1", new StringTag(text[0]));
values.put("Text2", new StringTag(text[1]));
values.put("Text3", new StringTag(text[2]));
values.put("Text4", new StringTag(text[3]));
if (isLegacy()) {
values.put("Text1", new StringTag(text[0]));
values.put("Text2", new StringTag(text[1]));
values.put("Text3", new StringTag(text[2]));
values.put("Text4", new StringTag(text[3]));
} else {
ListTag messages = new ListTag(StringTag.class, Arrays.stream(text).map(StringTag::new).collect(Collectors.toList()));
Map<String, Tag> frontTextTag = new HashMap<>();
frontTextTag.put("messages", messages);
values.put("front_text", new CompoundTag(frontTextTag));
}
return new CompoundTag(values);
}
@@ -122,24 +136,33 @@ public class SignBlock extends BaseBlock {
throw new RuntimeException(String.format("'%s' tile entity expected", getNbtId()));
}
t = values.get("Text1");
if (t instanceof StringTag) {
text[0] = ((StringTag) t).getValue();
}
if (isLegacy()) {
t = values.get("Text1");
if (t instanceof StringTag) {
text[0] = ((StringTag) t).getValue();
}
t = values.get("Text2");
if (t instanceof StringTag) {
text[1] = ((StringTag) t).getValue();
}
t = values.get("Text2");
if (t instanceof StringTag) {
text[1] = ((StringTag) t).getValue();
}
t = values.get("Text3");
if (t instanceof StringTag) {
text[2] = ((StringTag) t).getValue();
}
t = values.get("Text3");
if (t instanceof StringTag) {
text[2] = ((StringTag) t).getValue();
}
t = values.get("Text4");
if (t instanceof StringTag) {
text[3] = ((StringTag) t).getValue();
t = values.get("Text4");
if (t instanceof StringTag) {
text[3] = ((StringTag) t).getValue();
}
} else {
CompoundTag frontTextTag = (CompoundTag) values.get("front_text");
ListTag messagesTag = frontTextTag.getListTag("messages");
for (int i = 0; i < messagesTag.getValue().size(); i++) {
StringTag tag = (StringTag) messagesTag.getValue().get(i);
text[i] = tag.getValue();
}
}
}

View File

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

View File

@@ -171,52 +171,68 @@ public enum FaweCache implements Trimable {
public static final FaweBlockBagException BLOCK_BAG = new FaweBlockBagException();
public static final FaweException MANUAL = new FaweException(
Caption.of("fawe.cancel.reason.manual"),
Type.MANUAL
Type.MANUAL,
false
);
public static final FaweException NO_REGION = new FaweException(
Caption.of("fawe.cancel.reason.no.region"),
Type.NO_REGION
Type.NO_REGION,
false
);
public static final FaweException OUTSIDE_REGION = new FaweException(
Caption.of(
"fawe.cancel.reason.outside.region"),
Type.OUTSIDE_REGION,
true
);
public static final FaweException OUTSIDE_SAFE_REGION = new FaweException(
Caption.of(
"fawe.cancel.reason.outside.safe.region"),
Type.OUTSIDE_REGION
);
public static final FaweException MAX_CHECKS = new FaweException(
Caption.of("fawe.cancel.reason.max" + ".checks"),
Type.MAX_CHECKS
Type.MAX_CHECKS,
true
);
public static final FaweException MAX_CHANGES = new FaweException(
Caption.of("fawe.cancel.reason.max" + ".changes"),
Type.MAX_CHANGES
Type.MAX_CHANGES,
false
);
public static final FaweException LOW_MEMORY = new FaweException(
Caption.of("fawe.cancel.reason.low" + ".memory"),
Type.LOW_MEMORY
Type.LOW_MEMORY,
false
);
public static final FaweException MAX_ENTITIES = new FaweException(
Caption.of(
"fawe.cancel.reason.max.entities"),
Type.MAX_ENTITIES
Type.MAX_ENTITIES,
true
);
public static final FaweException MAX_TILES = new FaweException(Caption.of(
"fawe.cancel.reason.max.tiles",
Type.MAX_TILES
Type.MAX_TILES,
true
));
public static final FaweException MAX_ITERATIONS = new FaweException(
Caption.of(
"fawe.cancel.reason.max.iterations"),
Type.MAX_ITERATIONS
Type.MAX_ITERATIONS,
true
);
public static final FaweException PLAYER_ONLY = new FaweException(
Caption.of(
"fawe.cancel.reason.player-only"),
Type.PLAYER_ONLY
Type.PLAYER_ONLY,
false
);
public static final FaweException ACTOR_REQUIRED = new FaweException(
Caption.of(
"fawe.cancel.reason.actor-required"),
Type.ACTOR_REQUIRED
Type.ACTOR_REQUIRED,
false
);
/*

View File

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

View File

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

View File

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

View File

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

View File

@@ -102,11 +102,10 @@ public class Settings extends Config {
public FaweLimit getLimit(Actor actor) {
FaweLimit limit;
if (actor.hasPermission("fawe.limit.*") || actor.hasPermission("fawe.bypass")) {
limit = FaweLimit.MAX.copy();
} else {
limit = new FaweLimit();
if (actor.hasPermission("fawe.bypass") || actor.hasPermission("fawe.limit.unlimited")) {
return FaweLimit.MAX.copy();
}
limit = new FaweLimit();
ArrayList<String> keys = new ArrayList<>(LIMITS.getSections());
if (keys.remove("default")) {
keys.add("default");
@@ -313,6 +312,12 @@ public class Settings extends Config {
" - Any blacklist regions are likely to override any internal allowed regions."
})
public boolean WORLDGUARD_REGION_BLACKLIST = false;
@Comment({
"Restrict all edits to within the safe chunk limits of +/- 30 million blocks",
" - Edits outside this range may induce crashing",
" - Forcefully prevents any edit outside this range"
})
public boolean RESTRICT_TO_SAFE_RANGE = true;
}
@@ -394,6 +399,7 @@ public class Settings extends Config {
"Where block properties are specified, any blockstate with the property will be disallowed (e.g. all directions",
"of a waterlogged fence). For blocking/remapping of all occurrences of a property like waterlogged, see",
"remap-properties below.",
"To generate a blank list, substitute the default content with a set of square brackets [] instead.",
"Example block property blocking:",
" - \"minecraft:conduit[waterlogged=true]\"",
" - \"minecraft:piston[extended=false,facing=west]\"",
@@ -462,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)")
@@ -487,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;
@@ -509,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",
@@ -520,18 +511,10 @@ public class Settings extends Config {
" - A smaller value will reduce memory usage",
" - A value too small may break some operations (deform?)",
" - Values smaller than the configurated parallel-threads are not accepted",
" - It is recommended this option be at least 2x greater than parallel-threads"
" - It is recommended this option be at least 4x greater than parallel-threads"
})
public int TARGET_SIZE = 64;
@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;
public int TARGET_SIZE = 8 * Runtime.getRuntime().availableProcessors();
@Comment({
"Increase or decrease queue intensity (ms) [-50,50]:",
@@ -560,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"
@@ -578,7 +554,8 @@ public class Settings extends Config {
@Comment({"Display constant titles about the progress of a user's edit",
" - false = disabled",
" - title = Display progress titles",
" - chat = Display progress in chat"
" - chat = Display progress in chat",
" - Currently not implemented"
})
public String DISPLAY = "false";
@Comment("How often edit progress is displayed")
@@ -616,10 +593,11 @@ public class Settings extends Config {
public boolean PERSISTENT_BRUSHES = true;
@Comment({
"[SAFE] Keep entities that are positioned in non-air blocks when editing an area",
"Might cause client-side FPS lag in some situations"
"[SAFE] Keep entities that are positioned in non-air blocks when editing an area (default: true)",
" - Might cause client-side FPS lag in some situations",
" - Requires fast-placement to be true"
})
public boolean KEEP_ENTITIES_IN_BLOCKS = false;
public boolean KEEP_ENTITIES_IN_BLOCKS = true;
@Comment({
"[SAFE] Attempt to remove entities from the world if they were not present in the expected chunk (default: true)",
@@ -631,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;
@@ -671,6 +649,14 @@ public class Settings extends Config {
})
public int MAX_IMAGE_SIZE = 8294400;
@Comment({
"Whitelist of hostnames to allow images to be downloaded from",
" - Adding '*' to the list will allow any host, but this is NOT adviseable",
" - Crash exploits exist with malformed images",
" - See: https://medium.com/chargebee-engineering/perils-of-parsing-pixel-flood-attack-on-java-imageio-a97aeb06637d"
})
public List<String> ALLOWED_IMAGE_HOSTS = new ArrayList<>(Collections.singleton(("i.imgur.com")));
}
public static class EXTENT {
@@ -694,7 +680,7 @@ public class Settings extends Config {
public static class TICK_LIMITER {
@Comment("Enable the limiter")
public boolean ENABLED = true;
public boolean ENABLED = false;
@Comment("The interval in ticks")
public int INTERVAL = 20;
@Comment("Max falling blocks per interval (per chunk)")
@@ -703,12 +689,6 @@ public class Settings extends Config {
public int PHYSICS_MS = 10;
@Comment("Max item spawns per interval (per chunk)")
public int ITEMS = 256;
@Comment({
"Whether fireworks can load chunks",
" - Fireworks usually travel vertically so do not load any chunks",
" - Horizontal fireworks can be hacked in to crash a server"
})
public boolean FIREWORKS_LOAD_CHUNKS = false;
}

View File

@@ -1,6 +1,7 @@
package com.fastasyncworldedit.core.configuration.file;
import com.fastasyncworldedit.core.configuration.serialization.ConfigurationSerialization;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.Node;
@@ -12,6 +13,7 @@ import java.util.Map;
public class YamlConstructor extends SafeConstructor {
public YamlConstructor() {
super(new LoaderOptions());
yamlConstructors.put(Tag.MAP, new ConstructCustomObject());
}

View File

@@ -3,6 +3,7 @@ package com.fastasyncworldedit.core.configuration.file;
import com.fastasyncworldedit.core.configuration.ConfigurationSection;
import com.fastasyncworldedit.core.configuration.serialization.ConfigurationSerializable;
import com.fastasyncworldedit.core.configuration.serialization.ConfigurationSerialization;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.representer.Representer;
@@ -12,6 +13,7 @@ import java.util.Map;
public class YamlRepresenter extends Representer {
public YamlRepresenter() {
super(new DumperOptions());
this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection());
this.multiRepresenters.put(ConfigurationSerializable.class, new RepresentConfigurationSerializable());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.Countable;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BaseBlock;
@@ -34,19 +35,46 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
public class LimitExtent extends AbstractDelegateExtent {
private final FaweLimit limit;
private final boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
private final Consumer<Component> onErrorMessage;
/**
* Create a new instance.
*
* @param extent the extent
* @param limit the limit
*/
public LimitExtent(Extent extent, FaweLimit limit) {
this(extent, limit, c -> {
});
}
/**
* Create a new instance.
*
* @param extent the extent
* @param limit the limit
* @param onErrorMessage consumer to handle a component generated by exceptions
*/
public LimitExtent(Extent extent, FaweLimit limit, Consumer<Component> onErrorMessage) {
super(extent);
this.limit = limit;
this.onErrorMessage = onErrorMessage;
}
private void handleException(FaweException e) {
if (e.ignorable() || !limit.MAX_FAILS()) {
throw e;
}
if (!faweExceptionReasonsUsed[e.getType().ordinal()]) {
faweExceptionReasonsUsed[e.getType().ordinal()] = true;
onErrorMessage.accept(e.getComponent());
}
}
@Override
@@ -55,9 +83,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getEntities(region);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return Collections.emptyList();
}
}
@@ -68,9 +94,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getEntities();
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return Collections.emptyList();
}
}
@@ -83,9 +107,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.createEntity(location, entity);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return null;
}
}
@@ -98,9 +120,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.createEntity(location, entity, uuid);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return null;
}
}
@@ -112,9 +132,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
super.removeEntity(x, y, z, uuid);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
}
}
@@ -124,9 +142,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.regenerateChunk(x, z, type, seed);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return false;
}
}
@@ -137,9 +153,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getHighestTerrainBlock(x, z, minY, maxY);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -150,9 +164,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getHighestTerrainBlock(x, z, minY, maxY, filter);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -163,9 +175,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getNearestSurfaceLayer(x, z, y, minY, maxY);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -176,9 +186,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, ignoreAir);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -189,9 +197,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -202,9 +208,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -215,9 +219,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, mask);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -237,9 +239,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, ignoreAir);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return minY;
}
}
@@ -386,9 +386,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
filter.applyBlock(block.init(pos));
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
}
}
return filter;
@@ -404,9 +402,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getBlock(position);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return BlockTypes.AIR.getDefaultState();
}
}
@@ -417,9 +413,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getBlock(x, y, z);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return BlockTypes.AIR.getDefaultState();
}
}
@@ -430,9 +424,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getFullBlock(position);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return BlockTypes.AIR.getDefaultState().toBaseBlock();
}
}
@@ -443,9 +435,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getFullBlock(x, y, z);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return BlockTypes.AIR.getDefaultState().toBaseBlock();
}
}
@@ -456,9 +446,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getBiome(position);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return BiomeTypes.FOREST;
}
}
@@ -469,9 +457,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.getBiomeType(x, y, z);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return BiomeTypes.FOREST;
}
}
@@ -486,9 +472,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.setBlock(position, block);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return false;
}
}
@@ -502,9 +486,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.setBlock(x, y, z, block);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return false;
}
}
@@ -516,9 +498,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.setTile(x, y, z, tile);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return false;
}
}
@@ -529,9 +509,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.setBiome(position, biome);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return false;
}
}
@@ -542,9 +520,7 @@ public class LimitExtent extends AbstractDelegateExtent {
try {
return super.setBiome(x, y, z, biome);
} catch (FaweException e) {
if (e.getType() == FaweException.Type.MANUAL || !limit.MAX_FAILS()) {
throw e;
}
handleException(e);
return false;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,65 @@
package com.fastasyncworldedit.core.extent.processor;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IChunk;
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 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) {
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;
}
@Nullable
@Override
public Extent construct(final Extent child) {
throw new UnsupportedOperationException("Processing only");
}
@Override
public ProcessorScope getScope() {
// After block removal but before history
return ProcessorScope.CUSTOM;
}
}

View File

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

View File

@@ -3,11 +3,12 @@ package com.fastasyncworldedit.core.extent.processor;
/**
* The scope of a processor.
* Order in which processors are executed:
* - ADDING_BLOCKS (processors that strictly ADD blocks to an edit ONLY)
* - CHANGING_BLOCKS (processors that strictly ADD or CHANGE blocks being set)
* - REMOVING_BLOCKS (processors that string ADD, CHANGE or REMOVE blocks being set)
* - ADDING_BLOCKS (processors that may ADD blocks to an edit ONLY)
* - CHANGING_BLOCKS (processors that may ADD or CHANGE blocks being set)
* - REMOVING_BLOCKS (processors that may ADD, CHANGE or REMOVE blocks being set)
* - CUSTOM (processors that do not specify a SCOPE)
* - READING_SET_BLOCKS (processors that do not alter blocks at all, and read the blocks that are actually going to set, e.g. history processors)
* - READING_SET_BLOCKS (processors that do not alter blocks at all, and read the blocks that are actually going to set, e.g.
* history processors). There is no guarantee that changes made here will be stored in history.
*/
public enum ProcessorScope {
ADDING_BLOCKS(0),

View File

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

View File

@@ -20,12 +20,11 @@ import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -36,10 +35,12 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
public class BlockMaskBuilder {
private static final Operator GREATER = (a, b) -> a > b;
@@ -63,58 +64,97 @@ public class BlockMaskBuilder {
this.bitSets = bitSets;
}
private boolean filterRegex(BlockType blockType, PropertyKey key, String regex) {
private boolean handleRegex(BlockType blockType, PropertyKey key, String regex, FuzzyStateAllowingBuilder builder) {
Property<Object> property = blockType.getProperty(key);
if (property == null) {
return false;
}
List<Object> values = property.getValues();
boolean result = false;
List<Object> values = property.getValues();
for (int i = 0; i < values.size(); i++) {
Object value = values.get(i);
if (!value.toString().matches(regex) && has(blockType, property, i)) {
filter(blockType, property, i);
if (values.get(i).toString().matches(regex)) {
builder.allow(property, i);
result = true;
}
}
return result;
}
private boolean filterOperator(BlockType blockType, PropertyKey key, Operator operator, CharSequence value) {
private boolean handleOperator(
BlockType blockType,
PropertyKey key,
Operator operator,
CharSequence stringValue,
FuzzyStateAllowingBuilder builder
) {
Property<Object> property = blockType.getProperty(key);
if (property == null) {
return false;
}
int index = property.getIndexFor(value);
int index = property.getIndexFor(stringValue);
List<Object> values = property.getValues();
boolean result = false;
for (int i = 0; i < values.size(); i++) {
if (!operator.test(index, i) && has(blockType, property, i)) {
filter(blockType, property, i);
if (operator.test(index, i)) {
builder.allow(property, i);
result = true;
}
}
return result;
}
private boolean filterRegexOrOperator(BlockType type, PropertyKey key, Operator operator, CharSequence value) {
boolean result = false;
if (!type.hasProperty(key)) {
if (operator == EQUAL) {
result = bitSets[type.getInternalId()] != null;
remove(type);
private boolean handleRegexOrOperator(
BlockType type,
PropertyKey key,
Operator operator,
CharSequence value,
FuzzyStateAllowingBuilder builder
) {
if (!type.hasProperty(key) && operator == EQUAL) {
return false;
}
if (value.length() == 0) {
return false;
}
if ((operator == EQUAL || operator == EQUAL_OR_NULL) && !StringMan.isAlphanumericUnd(value)) {
return handleRegex(type, key, value.toString(), builder);
} else {
return handleOperator(type, key, operator, value, builder);
}
}
private void add(FuzzyStateAllowingBuilder builder) {
long[] states = bitSets[builder.getType().getInternalId()];
if (states == ALL) {
bitSets[builder.getType().getInternalId()] = states = FastBitSet.create(builder.getType().getMaxStateId() + 1);
FastBitSet.unsetAll(states);
}
applyRecursive(0, builder.getType().getInternalId(), builder, states);
}
private void applyRecursive(
int propertiesIndex,
int state,
FuzzyStateAllowingBuilder builder,
long[] states
) {
AbstractProperty<?> current = (AbstractProperty<?>) builder.getType().getProperties().get(propertiesIndex);
List<?> values = current.getValues();
if (propertiesIndex + 1 < builder.getType().getProperties().size()) {
for (int i = 0; i < values.size(); i++) {
if (builder.allows(current) || builder.allows(current, i)) {
int newState = current.modifyIndex(state, i);
applyRecursive(propertiesIndex + 1, newState, builder, states);
}
}
} else {
if (value.length() == 0) {
return result;
}
if ((operator == EQUAL || operator == EQUAL_OR_NULL) && !StringMan.isAlphanumericUnd(value)) {
result = filterRegex(type, key, value.toString());
} else {
result = filterOperator(type, key, operator, value);
for (int i = 0; i < values.size(); i++) {
if (builder.allows(current) || builder.allows(current, i)) {
int index = current.modifyIndex(state, i) >> BlockTypesCache.BIT_OFFSET;
FastBitSet.set(states, index);
}
}
}
return result;
}
public BlockMaskBuilder addRegex(final String input) throws InputParseException {
@@ -130,26 +170,28 @@ public class BlockMaskBuilder {
charSequence.setString(input);
charSequence.setSubstring(0, propStart);
BlockType type = null;
List<BlockType> blockTypeList = null;
List<BlockType> blockTypeList;
List<FuzzyStateAllowingBuilder> builders;
if (StringMan.isAlphanumericUnd(charSequence)) {
type = BlockTypes.parse(charSequence.toString());
BlockType type = BlockTypes.parse(charSequence.toString());
blockTypeList = Collections.singletonList(type);
builders = Collections.singletonList(new FuzzyStateAllowingBuilder(type));
add(type);
} else {
String regex = charSequence.toString();
blockTypeList = new ArrayList<>();
for (BlockType myType : BlockTypesCache.values) {
if (myType.getId().matches(regex)) {
blockTypeList.add(myType);
add(myType);
builders = new ArrayList<>();
Pattern pattern = Pattern.compile("(minecraft:)?" + regex);
for (BlockType type : BlockTypesCache.values) {
if (pattern.matcher(type.getId()).find()) {
blockTypeList.add(type);
builders.add(new FuzzyStateAllowingBuilder(type));
add(type);
}
}
if (blockTypeList.isEmpty()) {
throw new InputParseException(Caption.of("fawe.error.no-block-found", TextComponent.of(input)));
}
if (blockTypeList.size() == 1) {
type = blockTypeList.get(0);
}
}
// Empty string
charSequence.setSubstring(0, 0);
@@ -169,48 +211,33 @@ public class BlockMaskBuilder {
}
case ']', ',' -> {
charSequence.setSubstring(last, i);
if (key == null && PropertyKey.getByName(charSequence) == null) {
if (key == null && (key = PropertyKey.getByName(charSequence)) == null) {
suggest(
input,
charSequence.toString(),
type != null ? Collections.singleton(type) : blockTypeList
blockTypeList
);
}
if (operator == null) {
throw new SuggestInputParseException(
"No operator for " + input,
"",
Caption.of("fawe.error.no-operator-for-input", input),
() -> Arrays.asList("=", "~", "!", "<", ">", "<=", ">=")
);
}
boolean filtered = false;
if (type != null) {
filtered = filterRegexOrOperator(type, key, operator, charSequence);
} else {
for (BlockType myType : blockTypeList) {
filtered |= filterRegexOrOperator(myType, key, operator, charSequence);
for (int index = 0; index < blockTypeList.size(); index++) {
if (!handleRegexOrOperator(
blockTypeList.get(index),
key,
operator,
charSequence,
builders.get(index)
)) {
// If we cannot find a matching property for all to mask, do not mask the block
blockTypeList.remove(index);
builders.remove(index);
index--;
}
}
if (!filtered) {
String value = charSequence.toString();
final PropertyKey fKey = key;
Collection<BlockType> types = type != null ? Collections.singleton(type) : blockTypeList;
throw new SuggestInputParseException("No value for " + input, input, () -> {
HashSet<String> values = new HashSet<>();
types.stream().filter(t -> t.hasProperty(fKey)).forEach(t -> {
Property<Object> p = t.getProperty(fKey);
for (int j = 0; j < p.getValues().size(); j++) {
if (has(t, p, j)) {
String o = p.getValues().get(j).toString();
if (o.startsWith(value)) {
values.add(o);
}
}
}
});
return new ArrayList<>(values);
});
}
// Reset state
key = null;
operator = null;
@@ -236,7 +263,7 @@ public class BlockMaskBuilder {
suggest(
input,
charSequence.toString(),
type != null ? Collections.singleton(type) : blockTypeList
blockTypeList
);
}
}
@@ -246,13 +273,18 @@ public class BlockMaskBuilder {
}
}
}
for (FuzzyStateAllowingBuilder builder : builders) {
if (builder.allows()) {
add(builder);
}
}
} else {
if (StringMan.isAlphanumericUnd(input)) {
add(BlockTypes.parse(input));
} else {
boolean success = false;
for (BlockType myType : BlockTypesCache.values) {
if (myType.getId().matches(input)) {
if (myType.getId().matches("(minecraft:)?" + input)) {
add(myType);
success = true;
}
@@ -276,18 +308,8 @@ public class BlockMaskBuilder {
return this;
}
private <T> boolean has(BlockType type, Property<T> property, int index) {
AbstractProperty<T> prop = (AbstractProperty<T>) property;
long[] states = bitSets[type.getInternalId()];
if (states == null) {
return false;
}
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
return (states == ALL || FastBitSet.get(states, localI));
}
private void suggest(String input, String property, Collection<BlockType> finalTypes) throws InputParseException {
throw new SuggestInputParseException(input + " does not have: " + property, input, () -> {
throw new SuggestInputParseException(Caption.of("worldedit.error.parser.unknown-property", property, input), () -> {
Set<PropertyKey> keys = PropertyKeySet.empty();
finalTypes.forEach(t -> t.getProperties().forEach(p -> keys.add(p.getKey())));
return keys.stream().map(PropertyKey::getName)
@@ -382,42 +404,6 @@ public class BlockMaskBuilder {
return this;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> BlockMaskBuilder filter(
Predicate<BlockType> typePredicate,
BiPredicate<BlockType, Map.Entry<Property<T>, T>> allowed
) {
for (int i = 0; i < bitSets.length; i++) {
long[] states = bitSets[i];
if (states == null) {
continue;
}
BlockType type = BlockTypes.get(i);
if (!typePredicate.test(type)) {
bitSets[i] = null;
continue;
}
List<AbstractProperty<?>> properties = (List<AbstractProperty<?>>) type.getProperties();
for (AbstractProperty<?> prop : properties) {
List<?> values = prop.getValues();
for (int j = 0; j < values.size(); j++) {
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == ALL || FastBitSet.get(states, localI)) {
if (!allowed.test(type, new AbstractMap.SimpleEntry(prop, values.get(j)))) {
if (states == ALL) {
bitSets[i] = states = FastBitSet.create(type.getMaxStateId() + 1);
FastBitSet.setAll(states);
}
FastBitSet.clear(states, localI);
reset(false);
}
}
}
}
}
return this;
}
public BlockMaskBuilder add(BlockType type) {
bitSets[type.getInternalId()] = ALL;
return this;
@@ -479,117 +465,6 @@ public class BlockMaskBuilder {
return this;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public BlockMaskBuilder addAll(
Predicate<BlockType> typePredicate,
BiPredicate<BlockType, Map.Entry<Property<?>, ?>> propPredicate
) {
for (int i = 0; i < bitSets.length; i++) {
long[] states = bitSets[i];
if (states == ALL) {
continue;
}
BlockType type = BlockTypes.get(i);
if (!typePredicate.test(type)) {
continue;
}
for (AbstractProperty<?> prop : (List<AbstractProperty<?>>) type.getProperties()) {
List<?> values = prop.getValues();
for (int j = 0; j < values.size(); j++) {
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == null || !FastBitSet.get(states, localI)) {
if (propPredicate.test(type, new AbstractMap.SimpleEntry(prop, values.get(j)))) {
if (states == null) {
bitSets[i] = states = FastBitSet.create(type.getMaxStateId() + 1);
}
FastBitSet.set(states, localI);
reset(false);
}
}
}
}
}
return this;
}
public <T> BlockMaskBuilder add(BlockType type, Property<T> property, int index) {
AbstractProperty<T> prop = (AbstractProperty<T>) property;
long[] states = bitSets[type.getInternalId()];
if (states == ALL) {
return this;
}
List<T> values = property.getValues();
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == null || !FastBitSet.get(states, localI)) {
if (states == null) {
bitSets[type.getInternalId()] = states = FastBitSet.create(type.getMaxStateId() + 1);
}
set(type, states, property, index);
reset(false);
}
return this;
}
public <T> BlockMaskBuilder filter(BlockType type, Property<T> property, int index) {
AbstractProperty<T> prop = (AbstractProperty<T>) property;
long[] states = bitSets[type.getInternalId()];
if (states == null) {
return this;
}
List<T> values = property.getValues();
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == ALL || FastBitSet.get(states, localI)) {
if (states == ALL) {
bitSets[type.getInternalId()] = states = FastBitSet.create(type.getMaxStateId() + 1);
FastBitSet.setAll(states);
}
clear(type, states, property, index);
reset(false);
}
return this;
}
private void applyRecursive(List<Property> properties, int propertiesIndex, int state, long[] states, boolean set) {
AbstractProperty current = (AbstractProperty) properties.get(propertiesIndex);
List values = current.getValues();
if (propertiesIndex + 1 < properties.size()) {
for (int i = 0; i < values.size(); i++) {
int newState = current.modifyIndex(state, i);
applyRecursive(properties, propertiesIndex + 1, newState, states, set);
}
} else {
for (int i = 0; i < values.size(); i++) {
int index = current.modifyIndex(state, i) >> BlockTypesCache.BIT_OFFSET;
if (set) {
FastBitSet.set(states, index);
} else {
FastBitSet.clear(states, index);
}
}
}
}
private void set(BlockType type, long[] bitSet, Property property, int index) {
FastBitSet.set(bitSet, index);
if (type.getProperties().size() > 1) {
ArrayList<Property> properties = new ArrayList<>(type.getProperties());
properties.remove(property);
int state = ((AbstractProperty) property).modifyIndex(type.getInternalId(), index);
applyRecursive(properties, 0, state, bitSet, true);
}
}
private void clear(BlockType type, long[] bitSet, Property property, int index) {
FastBitSet.clear(bitSet, index);
if (type.getProperties().size() > 1) {
ArrayList<Property> properties = new ArrayList<>(type.getProperties());
properties.remove(property);
int state = ((AbstractProperty) property).modifyIndex(type.getInternalId(), index);
applyRecursive(properties, 0, state, bitSet, false);
}
}
public BlockMaskBuilder optimize() {
if (!optimizedStates) {
for (int i = 0; i < bitSets.length; i++) {
@@ -668,4 +543,56 @@ public class BlockMaskBuilder {
}
private static class FuzzyStateAllowingBuilder {
private final BlockType type;
private final Map<Property<?>, List<Integer>> masked = new HashMap<>();
private FuzzyStateAllowingBuilder(BlockType type) {
this.type = type;
}
private BlockType getType() {
return this.type;
}
private List<Property<?>> getMaskedProperties() {
return masked
.entrySet()
.stream()
.filter(e -> !e.getValue().isEmpty())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
private void allow(Property<?> property, int index) {
checkNotNull(property);
if (!type.hasProperty(property.getKey())) {
throw new IllegalArgumentException(String.format(
"Property %s cannot be applied to block type %s",
property.getName(),
type.getId()
));
}
masked.computeIfAbsent(property, k -> new ArrayList<>()).add(index);
}
private boolean allows() {
//noinspection SimplifyStreamApiCallChains - Marginally faster like this
return !masked.isEmpty() && !masked.values().stream().anyMatch(List::isEmpty);
}
private boolean allows(Property<?> property) {
return !masked.containsKey(property);
}
private boolean allows(Property<?> property, int index) {
if (!masked.containsKey(property)) {
return true;
}
return masked.get(property).contains(index);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,7 +50,7 @@ public class SimpleChangeSetSummary implements ChangeSetSummary {
public Map<BlockState, Integer> getBlocks() {
HashMap<BlockState, Integer> map = new HashMap<>();
for (int i = 0; i < blocks.length; i++) {
if (blocks[i] != 0) {
if (blocks[i] != BlockTypesCache.ReservedIDs.__RESERVED__) {
BlockState state = BlockTypesCache.states[i];
map.put(state, blocks[i]);
}

View File

@@ -14,6 +14,7 @@ public class FaweException extends RuntimeException {
private final Component message;
private final Type type;
private final boolean ignorable;
/**
* New instance. Defaults to {@link FaweException.Type#OTHER}.
@@ -33,8 +34,19 @@ public class FaweException extends RuntimeException {
* New instance of a given {@link FaweException.Type}
*/
public FaweException(Component reason, Type type) {
this(reason, type, false);
}
/**
* 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 2.7.0
*/
public FaweException(Component reason, Type type, boolean ignorable) {
this.message = reason;
this.type = type;
this.ignorable = ignorable;
}
@Override
@@ -55,6 +67,15 @@ public class FaweException extends RuntimeException {
return type;
}
/**
* If an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent}
*
* @since 2.7.0
*/
public boolean ignorable() {
return ignorable;
}
public static FaweException get(Throwable e) {
if (e instanceof FaweException) {
return (FaweException) e;
@@ -80,6 +101,7 @@ public class FaweException extends RuntimeException {
MANUAL,
NO_REGION,
OUTSIDE_REGION,
OUTSIDE_SAFE_REGION,
MAX_CHECKS,
MAX_CHANGES,
LOW_MEMORY,

View File

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

View File

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

View File

@@ -69,6 +69,10 @@ public class FastBitSet {
Arrays.fill(bits, -1L);
}
public static void unsetAll(long[] bits) {
Arrays.fill(bits, 0);
}
public static void and(long[] bits, final long[] other) {
final int end = Math.min(other.length, bits.length);
for (int i = 0; i < end; ++i) {

View File

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

View File

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

View File

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

View File

@@ -193,6 +193,7 @@ public class ParallelQueueExtent extends PassthroughExtent {
public int setBlocks(Set<BlockVector3> vset, Pattern pattern) {
if (vset instanceof Region) {
this.changes = setBlocks((Region) vset, pattern);
return this.changes;
}
// TODO optimize parallel
for (BlockVector3 blockVector3 : vset) {

View File

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

View File

@@ -83,17 +83,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
this.maxY = maxY;
}
/**
* Safety check to ensure that the thread being used matches the one being initialized on. - Can
* be removed later
*/
private void checkThread() {
if (Thread.currentThread() != currentThread && currentThread != null) {
throw new UnsupportedOperationException(
"This class must be used from a single thread. Use multiple queues for concurrent operations");
}
}
@Override
public void enableQueue() {
enabledQueue = true;
@@ -154,10 +143,10 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
return;
}
if (!this.chunks.isEmpty()) {
getChunkLock.lock();
for (IChunk chunk : this.chunks.values()) {
chunk.recycle();
}
getChunkLock.lock();
this.chunks.clear();
getChunkLock.unlock();
}
@@ -233,9 +222,21 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
*/
private <V extends Future<V>> V submitUnchecked(IQueueChunk chunk) {
if (chunk.isEmpty()) {
chunk.recycle();
Future result = Futures.immediateFuture(null);
return (V) result;
if (chunk instanceof ChunkHolder<?> holder) {
long age = holder.initAge();
// Ensure we've given time for the chunk to be used - it was likely used for a reason!
if (age < 5) {
try {
Thread.sleep(5 - age);
} catch (InterruptedException ignored) {
}
}
}
if (chunk.isEmpty()) {
chunk.recycle();
Future result = Futures.immediateFuture(null);
return (V) result;
}
}
if (Fawe.isMainThread()) {
@@ -275,8 +276,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
* Get a new IChunk from either the pool, or create a new one<br> + Initialize it at the
* coordinates
*
* @param chunkX
* @param chunkZ
* @param chunkX X chunk coordinate
* @param chunkZ Z chunk coordinate
* @return IChunk
*/
private ChunkHolder poolOrCreate(int chunkX, int chunkZ) {
@@ -293,7 +294,9 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
if (pair == lastPair) {
return lastChunk;
}
if (!processGet(x, z)) {
if (!processGet(x, z) || (Settings.settings().REGION_RESTRICTIONS_OPTIONS.RESTRICT_TO_SAFE_RANGE
// if any chunk coord is outside 30 million blocks
&& (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000))) {
lastPair = pair;
lastChunk = NullChunk.getInstance();
return NullChunk.getInstance();
@@ -309,19 +312,11 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
// If queueing is enabled AND either of the following
// - memory is low & queue size > num threads + 8
// - queue size > target size and primary queue has less than num threads submissions
if (enabledQueue && ((lowMem && size > Settings.settings().QUEUE.PARALLEL_THREADS + 8) || (size > Settings.settings().QUEUE.TARGET_SIZE && Fawe
.instance()
.getQueueHandler()
.isUnderutilized()))) {
int targetSize = lowMem ? Settings.settings().QUEUE.PARALLEL_THREADS + 8 : Settings.settings().QUEUE.TARGET_SIZE;
if (enabledQueue && size > targetSize && (lowMem || Fawe.instance().getQueueHandler().isUnderutilized())) {
chunk = chunks.removeFirst();
final Future future = submitUnchecked(chunk);
if (future != null && !future.isDone()) {
final int targetSize;
if (lowMem) {
targetSize = Settings.settings().QUEUE.PARALLEL_THREADS + 8;
} else {
targetSize = Settings.settings().QUEUE.TARGET_SIZE;
}
pollSubmissions(targetSize, lowMem);
submissions.add(future);
}
@@ -457,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);
@@ -473,7 +469,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
}
}
}
getChunkLock.lock();
chunks.clear();
getChunkLock.unlock();
}

View File

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

View File

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

View File

@@ -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,10 +44,10 @@ 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 IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes)
private IChunkSet chunkSet; // The blocks to be set to the chunkExisting
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
private IBlockDelegate delegate; // delegate handles the abstraction of the chunk layers
private IQueueExtent<? extends IChunk> extent; // the parent queue extent which has this chunk
private int chunkX;
@@ -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,14 @@ 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 {
return this.call(chunkSet, () -> {
this.delegate = NULL;
chunkSet = null;
calledLock.unlock(stamp);
IChunkSet copy = chunkSet.createCopy();
chunkSet = null;
return this.call(copy, () -> {
// Do nothing
});
} catch (Throwable t) {
calledLock.unlock();
@@ -1073,6 +1082,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
} else {
finalizer = finalize;
}
calledLock.unlock();
return get.call(set, finalizer);
}
return null;

View File

@@ -110,6 +110,14 @@ public class AsyncPreloader implements Preloader, Runnable {
Iterator<BlockVector2> chunksIter = chunks.iterator();
while (chunksIter.hasNext() && pair.getValue() == chunks) { // Ensure the queued load is still valid
BlockVector2 chunk = chunksIter.next();
if (Settings.settings().REGION_RESTRICTIONS_OPTIONS.RESTRICT_TO_SAFE_RANGE) {
int x = chunk.getX();
int z = chunk.getZ();
// if any chunk coord is outside 30 million blocks
if (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000) {
continue;
}
}
queueLoad(world, chunk);
}
}

View File

@@ -4,12 +4,15 @@ import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.regions.Region;
import javax.annotation.Nonnull;
import java.util.Objects;
public class FaweMask implements IDelegateRegion {
private final Region region;
public FaweMask(Region region) {
this.region = region;
public FaweMask(@Nonnull Region region) {
this.region = Objects.requireNonNull(region);
}
@Override
@@ -17,10 +20,30 @@ public class FaweMask implements IDelegateRegion {
return region;
}
/**
* Test if the mask is still valid
*
* @param player player to test
* @param type type of mask
* @return if still valid
*/
public boolean isValid(Player player, FaweMaskManager.MaskType type) {
return false;
}
/**
* Test if the mask is still valid
*
* @param player player to test
* @param type type of mask
* @param notify if the player should be notified
* @return if still valid
* @since 2.7.0
*/
public boolean isValid(Player player, FaweMaskManager.MaskType type, boolean notify) {
return isValid(player, type);
}
@Override
public Region clone() {
throw new UnsupportedOperationException("Clone not supported");

View File

@@ -1,7 +1,6 @@
package com.fastasyncworldedit.core.regions;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.regions.filter.RegionFilter;
import com.sk89q.worldedit.entity.Player;
import java.util.Locale;
@@ -28,6 +27,15 @@ public abstract class FaweMaskManager {
*/
public abstract FaweMask getMask(final Player player, MaskType type, boolean isWhitelist);
/**
* Get a {@link FaweMask} for the given player and {@link MaskType}. If isWhitelist is false, will return a "blacklist" mask.
*
* @since 2.7.0
*/
public FaweMask getMask(final Player player, MaskType type, boolean isWhitelist, boolean notify) {
return getMask(player, type, isWhitelist);
}
public boolean isExclusive() {
return Settings.settings().REGION_RESTRICTIONS_OPTIONS.EXCLUSIVE_MANAGERS.contains(this.key);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,13 @@ import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.net.URL;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class UpdateNotification {
@@ -28,38 +34,54 @@ public class UpdateNotification {
*/
public static void doUpdateCheck() {
if (Settings.settings().ENABLED_COMPONENTS.UPDATE_NOTIFICATIONS) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new URL("https://ci.athion.net/job/FastAsyncWorldEdit/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://www.spigotmc.org/resources/13932/""",
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 && !faweVersion.snapshot) {
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.
*

View File

@@ -108,67 +108,69 @@ public class WEManager {
}
player.setMeta("lastMaskWorld", world);
Set<FaweMask> masks = player.getMeta("lastMask");
Set<Region> backupRegions = new HashSet<>();
Set<Region> regions = new HashSet<>();
if (masks == null || !isWhitelist) {
masks = new HashSet<>();
} else {
synchronized (masks) {
boolean removed = false;
boolean inMask = false;
if (!masks.isEmpty()) {
Iterator<FaweMask> iterator = masks.iterator();
while (iterator.hasNext()) {
FaweMask mask = iterator.next();
if (mask.isValid(player, type)) {
if (mask.isValid(player, type, false)) {
Region region = mask.getRegion();
if (region.contains(loc.toBlockPoint())) {
regions.add(region);
} else {
removed = true;
backupRegions.add(region);
}
inMask |= region.contains(loc.toBlockPoint());
regions.add(region);
} else {
player.print(Caption.of("fawe.error.region-mask-invalid", mask.getClass().getSimpleName()));
if (Settings.settings().ENABLED_COMPONENTS.DEBUG) {
player.printDebug(Caption.of("fawe.error.region-mask-invalid", mask.getClass().getSimpleName()));
}
removed = true;
iterator.remove();
}
}
}
if (!removed) {
if (!removed && inMask) {
return regions.toArray(new Region[0]);
}
masks.clear();
}
}
for (FaweMaskManager manager : managers) {
if (player.hasPermission("fawe." + manager.getKey())) {
try {
if (manager.isExclusive() && !masks.isEmpty()) {
continue;
}
final FaweMask mask = manager.getMask(player, FaweMaskManager.MaskType.getDefaultMaskType(), isWhitelist);
if (mask != null) {
regions.add(mask.getRegion());
masks.add(mask);
if (manager.isExclusive()) {
break;
synchronized (masks) {
for (FaweMaskManager manager : managers) {
if (player.hasPermission("fawe." + manager.getKey())) {
try {
if (manager.isExclusive() && !masks.isEmpty()) {
continue;
}
final FaweMask mask = manager.getMask(
player,
FaweMaskManager.MaskType.getDefaultMaskType(),
isWhitelist,
masks.isEmpty()
);
if (mask != null) {
regions.add(mask.getRegion());
masks.add(mask);
if (manager.isExclusive()) {
break;
}
}
} catch (Throwable e) {
e.printStackTrace();
}
} catch (Throwable e) {
e.printStackTrace();
} else {
player.printError(TextComponent.of("Missing permission " + "fawe." + manager.getKey()));
}
} else {
player.printError(TextComponent.of("Missing permission " + "fawe." + manager.getKey()));
}
}
if (isWhitelist) {
regions.addAll(backupRegions);
if (!masks.isEmpty()) {
player.setMeta("lastMask", masks);
} else {
player.deleteMeta("lastMask");
if (isWhitelist) {
if (!masks.isEmpty()) {
player.setMeta("lastMask", masks);
} else {
player.deleteMeta("lastMask");
}
}
}
return regions.toArray(new Region[0]);

View File

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

View File

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

View File

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

View File

@@ -45,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 {

View File

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

View File

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

View File

@@ -105,7 +105,7 @@ public class YAMLProcessor extends YAMLNode {
// pre-1.32 snakeyaml
}
yaml = new Yaml(new SafeConstructor(), representer, dumperOptions, loaderOptions);
yaml = new Yaml(new SafeConstructor(new LoaderOptions()), representer, dumperOptions, loaderOptions);
this.file = file;
}
@@ -310,6 +310,7 @@ public class YAMLProcessor extends YAMLNode {
private static class FancyRepresenter extends Representer {
private FancyRepresenter() {
super(new DumperOptions());
this.nullRepresenter = o -> representScalar(Tag.NULL, "");
}

View File

@@ -30,6 +30,7 @@ import com.fastasyncworldedit.core.extent.ResettableExtent;
import com.fastasyncworldedit.core.extent.SingleRegionExtent;
import com.fastasyncworldedit.core.extent.SourceMaskExtent;
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
import com.fastasyncworldedit.core.extent.processor.ExtentBatchProcessorHolder;
import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.function.SurfaceRegionFunction;
@@ -55,6 +56,7 @@ import com.fastasyncworldedit.core.queue.implementation.preloader.Preloader;
import com.fastasyncworldedit.core.util.ExtentTraverser;
import com.fastasyncworldedit.core.util.MaskTraverser;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ProcessorTraverser;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.collection.BlockVector3Set;
import com.fastasyncworldedit.core.util.task.RunnableVal;
@@ -524,9 +526,17 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
* @return mask, may be null
*/
public Mask getMask() {
//FAWE start - ExtendTraverser & MaskingExtents
ExtentTraverser<MaskingExtent> maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class);
return maskingExtent != null ? maskingExtent.get().getMask() : null;
//FAWE start - ExtentTraverser & MaskingExtents
MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class);
if (maskingExtent == null) {
ExtentBatchProcessorHolder processorExtent =
new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class);
if (processorExtent != null) {
maskingExtent =
new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class);
}
}
return maskingExtent != null ? maskingExtent.getMask() : null;
//FAWE end
}
@@ -609,23 +619,31 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
//FAWE start - use MaskingExtent & ExtentTraverser
/**
* Set a mask.
* Set a mask. Combines with any existing masks, set null to clear existing masks.
*
* @param mask mask or null
*/
public void setMask(Mask mask) {
public void setMask(@Nullable Mask mask) {
if (mask == null) {
mask = Masks.alwaysTrue();
} else {
new MaskTraverser(mask).reset(this);
}
ExtentTraverser<MaskingExtent> maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class);
if (maskingExtent != null && maskingExtent.get() != null) {
Mask oldMask = maskingExtent.get().getMask();
MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class);
if (maskingExtent == null && mask != Masks.alwaysTrue()) {
ExtentBatchProcessorHolder processorExtent =
new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class);
if (processorExtent != null) {
maskingExtent =
new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class);
}
}
if (maskingExtent != null) {
Mask oldMask = maskingExtent.getMask();
if (oldMask instanceof ResettableMask) {
((ResettableMask) oldMask).reset();
}
maskingExtent.get().setMask(mask);
maskingExtent.setMask(mask);
} else if (mask != Masks.alwaysTrue()) {
addProcessor(new MaskingExtent(getExtent(), mask));
}
@@ -2161,10 +2179,12 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
final int ceilRadiusX = (int) Math.ceil(radiusX);
final int ceilRadiusZ = (int) Math.ceil(radiusZ);
double xSqr;
double zSqr;
double distanceSq;
double xSqr, zSqr, distanceSq;
double xn, zn;
double dx2, dz2;
double nextXn = 0;
double nextZn, nextMinZn;
int xx, x_x, zz, z_z, yy;
if (thickness != 0) {
double nextMinXn = 0;
@@ -2172,19 +2192,18 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
final double minInvRadiusZ = 1 / (radiusZ - thickness);
forX:
for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
double dx2 = nextMinXn * nextMinXn;
xn = nextXn;
dx2 = nextMinXn * nextMinXn;
nextXn = (x + 1) * invRadiusX;
nextMinXn = (x + 1) * minInvRadiusX;
double nextZn = 0;
double nextMinZn = 0;
nextZn = 0;
nextMinZn = 0;
xSqr = xn * xn;
xx = px + x;
x_x = px - x;
forZ:
for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
double dz2 = nextMinZn * nextMinZn;
nextZn = (z + 1) * invRadiusZ;
nextMinZn = (z + 1) * minInvRadiusZ;
zn = nextZn;
zSqr = zn * zn;
distanceSq = xSqr + zSqr;
if (distanceSq > 1) {
@@ -2193,16 +2212,23 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
}
break forZ;
}
dz2 = nextMinZn * nextMinZn;
nextZn = (z + 1) * invRadiusZ;
nextMinZn = (z + 1) * minInvRadiusZ;
if ((dz2 + nextMinXn * nextMinXn <= 1) && (nextMinZn * nextMinZn + dx2 <= 1)) {
continue;
}
zz = pz + z;
z_z = pz - z;
for (int y = 0; y < height; ++y) {
this.setBlock(mutableBlockVector3.setComponents(px + x, py + y, pz + z), block);
this.setBlock(mutableBlockVector3.setComponents(px - x, py + y, pz + z), block);
this.setBlock(mutableBlockVector3.setComponents(px + x, py + y, pz - z), block);
this.setBlock(mutableBlockVector3.setComponents(px - x, py + y, pz - z), block);
yy = py + y;
this.setBlock(xx, yy, zz, block);
this.setBlock(x_x, yy, zz, block);
this.setBlock(xx, yy, z_z, block);
this.setBlock(x_x, yy, z_z, block);
}
}
}
@@ -2210,14 +2236,17 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
//FAWE end
forX:
for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
xn = nextXn;
nextXn = (x + 1) * invRadiusX;
double nextZn = 0;
nextZn = 0;
xSqr = xn * xn;
// FAWE start
xx = px + x;
x_x = px - x;
//FAWE end
forZ:
for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
nextZn = (z + 1) * invRadiusZ;
zn = nextZn;
zSqr = zn * zn;
distanceSq = xSqr + zSqr;
if (distanceSq > 1) {
@@ -2227,18 +2256,27 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
break forZ;
}
// FAWE start
nextZn = (z + 1) * invRadiusZ;
//FAWE end
if (!filled) {
if ((zSqr + nextXn * nextXn <= 1) && (nextZn * nextZn + xSqr <= 1)) {
continue;
}
}
//FAWE start
zz = pz + z;
z_z = pz - z;
//FAWE end
for (int y = 0; y < height; ++y) {
//FAWE start - mutable
this.setBlock(mutableBlockVector3.setComponents(px + x, py + y, pz + z), block);
this.setBlock(mutableBlockVector3.setComponents(px - x, py + y, pz + z), block);
this.setBlock(mutableBlockVector3.setComponents(px + x, py + y, pz - z), block);
this.setBlock(mutableBlockVector3.setComponents(px - x, py + y, pz - z), block);
//FAWE start
yy = py + y;
this.setBlock(xx, yy, zz, block);
this.setBlock(x_x, yy, zz, block);
this.setBlock(xx, yy, z_z, block);
this.setBlock(x_x, yy, z_z, block);
//FAWE end
}
}
@@ -2250,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.
*
@@ -2293,7 +2415,6 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
int px = pos.getBlockX();
int py = pos.getBlockY();
int pz = pos.getBlockZ();
MutableBlockVector3 mutable = new MutableBlockVector3();
final int ceilRadiusX = (int) Math.ceil(radiusX);
final int ceilRadiusY = (int) Math.ceil(radiusY);
@@ -2301,31 +2422,43 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
double threshold = 0.5;
double dx, dy, dz, dxy, dxz, dyz, dxyz;
int xx, x_x, yy, y_y, zz, z_z;
double xnx, yny, znz;
double nextXn = 0;
double dx;
double dy;
double dz;
double dxy;
double dxyz;
double nextYn, nextZn;
double nextXnSq, nextYnSq, nextZnSq;
double xn, yn, zn;
forX:
for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
xn = nextXn;
dx = xn * xn;
nextXn = (x + 1) * invRadiusX;
double nextYn = 0;
nextXnSq = nextXn * nextXn;
nextYn = 0;
xx = px + x;
x_x = px - x;
xnx = x * nx;
forY:
for (int y = 0; y <= ceilRadiusY; ++y) {
final double yn = nextYn;
yn = nextYn;
dy = yn * yn;
dxy = dx + dy;
nextYn = (y + 1) * invRadiusY;
double nextZn = 0;
nextYnSq = nextYn * nextYn;
nextZn = 0;
yy = py + y;
y_y = py - y;
yny = y * ny;
forZ:
for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
zn = nextZn;
dz = zn * zn;
dxyz = dxy + dz;
dxz = dx + dz;
dyz = dy + dz;
nextZn = (z + 1) * invRadiusZ;
nextZnSq = nextZn * nextZn;
if (dxyz > 1) {
if (z == 0) {
if (y == 0) {
@@ -2336,34 +2469,37 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
break forZ;
}
if (!filled) {
if (nextXn * nextXn + dy + dz <= 1 && nextYn * nextYn + dx + dz <= 1 && nextZn * nextZn + dx + dy <= 1) {
if (nextXnSq + dyz <= 1 && nextYnSq + dxz <= 1 && nextZnSq + dxy <= 1) {
continue;
}
}
zz = pz + z;
z_z = pz - z;
znz = z * nz;
if (Math.abs((x) * nx + (y) * ny + (z) * nz) < threshold) {
setBlock(mutable.setComponents(px + x, py + y, pz + z), block);
if (Math.abs(xnx + yny + znz) < threshold) {
setBlock(xx, yy, zz, block);
}
if (Math.abs((-x) * nx + (y) * ny + (z) * nz) < threshold) {
setBlock(mutable.setComponents(px - x, py + y, pz + z), block);
if (Math.abs(-xnx + yny + znz) < threshold) {
setBlock(x_x, yy, zz, block);
}
if (Math.abs((x) * nx + (-y) * ny + (z) * nz) < threshold) {
setBlock(mutable.setComponents(px + x, py - y, pz + z), block);
if (Math.abs(xnx - yny + znz) < threshold) {
setBlock(xx, y_y, zz, block);
}
if (Math.abs((x) * nx + (y) * ny + (-z) * nz) < threshold) {
setBlock(mutable.setComponents(px + x, py + y, pz - z), block);
if (Math.abs(xnx + yny - znz) < threshold) {
setBlock(xx, yy, z_z, block);
}
if (Math.abs((-x) * nx + (-y) * ny + (z) * nz) < threshold) {
setBlock(mutable.setComponents(px - x, py - y, pz + z), block);
if (Math.abs(-xnx - yny + znz) < threshold) {
setBlock(x_x, y_y, zz, block);
}
if (Math.abs((x) * nx + (-y) * ny + (-z) * nz) < threshold) {
setBlock(mutable.setComponents(px + x, py - y, pz - z), block);
if (Math.abs(xnx - yny - znz) < threshold) {
setBlock(xx, y_y, z_z, block);
}
if (Math.abs((-x) * nx + (y) * ny + (-z) * nz) < threshold) {
setBlock(mutable.setComponents(px - x, py + y, pz - z), block);
if (Math.abs(-xnx + yny - znz) < threshold) {
setBlock(x_x, yy, z_z, block);
}
if (Math.abs((-x) * nx + (-y) * ny + (-z) * nz) < threshold) {
setBlock(mutable.setComponents(px - x, py - y, pz - z), block);
if (Math.abs(-xnx - yny - znz) < threshold) {
setBlock(x_x, y_y, z_z, block);
}
}
}
@@ -2418,29 +2554,38 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
final int ceilRadiusZ = (int) Math.ceil(radiusZ);
//FAWE start
int yy;
//FAWE end
double nextXn = 0;
double nextYn, nextZn;
double nextXnSq, nextYnSq, nextZnSq;
double xn, yn, zn, dx, dy, dz;
double dxy, dxz, dyz, dxyz;
int xx, x_x, yy, zz, z_z;
forX:
for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
double dx = xn * xn;
xn = nextXn;
dx = xn * xn;
nextXn = (x + 1) * invRadiusX;
double nextZn = 0;
nextXnSq = nextXn * nextXn;
xx = px + x;
x_x = px - x;
nextZn = 0;
forZ:
for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
double dz = zn * zn;
double dxz = dx + dz;
zn = nextZn;
dz = zn * zn;
dxz = dx + dz;
nextZn = (z + 1) * invRadiusZ;
double nextYn = 0;
nextZnSq = nextZn * nextZn;
zz = pz + z;
z_z = pz - z;
nextYn = 0;
forY:
for (int y = 0; y <= ceilRadiusY; ++y) {
final double yn = nextYn;
double dy = yn * yn;
double dxyz = dxz + dy;
yn = nextYn;
dy = yn * yn;
dxyz = dxz + dy;
nextYn = (y + 1) * invRadiusY;
if (dxyz > 1) {
@@ -2453,40 +2598,45 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
break forY;
}
nextYnSq = nextYn * nextYn;
dxy = dx + dy;
dyz = dy + dz;
if (!filled) {
if (nextXn * nextXn + dy + dz <= 1 && nextYn * nextYn + dx + dz <= 1 && nextZn * nextZn + dx + dy <= 1) {
if (nextXnSq + dyz <= 1 && nextYnSq + dxz <= 1 && nextZnSq + dxy <= 1) {
continue;
}
}
//FAWE start
yy = py + y;
if (yy <= maxY) {
this.setBlock(px + x, py + y, pz + z, block);
this.setBlock(xx, yy, zz, block);
if (x != 0) {
this.setBlock(px - x, py + y, pz + z, block);
this.setBlock(x_x, yy, zz, block);
}
if (z != 0) {
this.setBlock(px + x, py + y, pz - z, block);
this.setBlock(xx, yy, z_z, block);
if (x != 0) {
this.setBlock(px - x, py + y, pz - z, block);
this.setBlock(x_x, yy, z_z, block);
}
}
}
if (y != 0 && (yy = py - y) >= minY) {
this.setBlock(px + x, yy, pz + z, block);
this.setBlock(xx, yy, zz, block);
if (x != 0) {
this.setBlock(px - x, yy, pz + z, block);
this.setBlock(x_x, yy, zz, block);
}
if (z != 0) {
this.setBlock(px + x, yy, pz - z, block);
this.setBlock(xx, yy, z_z, block);
if (x != 0) {
this.setBlock(px - x, yy, pz - z, block);
this.setBlock(x_x, yy, z_z, block);
}
}
}
}
}
}
//FAWE end
return changes;
//FAWE end
@@ -2509,17 +2659,22 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
int bz = position.getZ();
int height = size;
int yy, xx, x_x, zz, z_z;
for (int y = 0; y <= height; ++y) {
size--;
yy = y + by;
for (int x = 0; x <= size; ++x) {
xx = bx + x;
x_x = bx - x;
for (int z = 0; z <= size; ++z) {
zz = bz + z;
z_z = bz - z;
if ((filled && z <= size && x <= size) || z == size || x == size) {
setBlock(x + bx, y + by, z + bz, block);
setBlock(-x + bx, y + by, z + bz, block);
setBlock(x + bx, y + by, -z + bz, block);
setBlock(-x + bx, y + by, -z + bz, block);
setBlock(xx, yy, zz, block);
setBlock(x_x, yy, zz, block);
setBlock(xx, yy, z_z, block);
setBlock(x_x, yy, z_z, block);
}
}
}
@@ -2938,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);
}
}
};
@@ -3774,19 +3930,28 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
int radiusSqr = (int) (size * size);
int sizeInt = (int) size * 2;
int xx, yy, zz;
double distance;
double noise;
if (sphericity == 1) {
double nx, ny, nz;
double d1, d2;
for (int x = -sizeInt; x <= sizeInt; x++) {
double nx = seedX + x * distort;
double d1 = x * x * modX;
nx = seedX + x * distort;
d1 = x * x * modX;
xx = px + x;
for (int y = -sizeInt; y <= sizeInt; y++) {
double d2 = d1 + y * y * modY;
double ny = seedY + y * distort;
d2 = d1 + y * y * modY;
ny = seedY + y * distort;
yy = py + y;
for (int z = -sizeInt; z <= sizeInt; z++) {
double nz = seedZ + z * distort;
double distance = d2 + z * z * modZ;
double noise = amplitude * SimplexNoise.noise(nx, ny, nz);
nz = seedZ + z * distort;
distance = d2 + z * z * modZ;
zz = pz + z;
noise = amplitude * SimplexNoise.noise(nx, ny, nz);
if (distance + distance * noise < radiusSqr) {
setBlock(px + x, py + y, pz + z, pattern);
setBlock(xx, yy, zz, pattern);
}
}
}
@@ -3803,39 +3968,48 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
MutableVector3 mutable = new MutableVector3();
double roughness = 1 - sphericity;
int x;
int y;
int z;
double xScaled;
double yScaled;
double zScaled;
double manDist;
double distSqr;
for (int xr = -sizeInt; xr <= sizeInt; xr++) {
xx = px + xr;
for (int yr = -sizeInt; yr <= sizeInt; yr++) {
yy = py + yr;
for (int zr = -sizeInt; zr <= sizeInt; zr++) {
zz = pz + zr;
// pt == mutable as it's a MutableVector3
// so it must be set each time
mutable.mutX(xr);
mutable.mutY(yr);
mutable.mutZ(zr);
mutable.setComponents(xr, yr, zr);
Vector3 pt = transform.apply(mutable);
int x = MathMan.roundInt(pt.getX());
int y = MathMan.roundInt(pt.getY());
int z = MathMan.roundInt(pt.getZ());
x = MathMan.roundInt(pt.getX());
y = MathMan.roundInt(pt.getY());
z = MathMan.roundInt(pt.getZ());
double xScaled = Math.abs(x) * modX;
double yScaled = Math.abs(y) * modY;
double zScaled = Math.abs(z) * modZ;
double manDist = xScaled + yScaled + zScaled;
double distSqr = x * x * modX + z * z * modZ + y * y * modY;
xScaled = Math.abs(x) * modX;
yScaled = Math.abs(y) * modY;
zScaled = Math.abs(z) * modZ;
manDist = xScaled + yScaled + zScaled;
distSqr = x * x * modX + z * z * modZ + y * y * modY;
double distance = Math.sqrt(distSqr) * sphericity + MathMan.max(
distance = Math.sqrt(distSqr) * sphericity + MathMan.max(
manDist,
xScaled * manScaleX,
yScaled * manScaleY,
zScaled * manScaleZ
) * roughness;
double noise = amplitude * SimplexNoise.noise(
noise = amplitude * SimplexNoise.noise(
seedX + x * distort,
seedZ + z * distort,
seedZ + z * distort
);
if (distance + distance * noise < r) {
setBlock(px + xr, py + yr, pz + zr, pattern);
setBlock(xx, yy, zz, pattern);
}
}
}

View File

@@ -32,6 +32,7 @@ import com.fastasyncworldedit.core.extent.NullExtent;
import com.fastasyncworldedit.core.extent.SingleRegionExtent;
import com.fastasyncworldedit.core.extent.SlowExtent;
import com.fastasyncworldedit.core.extent.StripNBTExtent;
import com.fastasyncworldedit.core.extent.processor.EntityInBlockRemovingProcessor;
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightmapProcessor;
import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
@@ -65,6 +66,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Identifiable;
import com.sk89q.worldedit.util.eventbus.EventBus;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.world.World;
import org.apache.logging.log4j.Logger;
@@ -75,6 +77,7 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
/**
* A builder-style factory for {@link EditSession EditSessions}.
@@ -96,7 +99,6 @@ public final class EditSessionBuilder {
private RelightMode relightMode;
private Relighter relighter;
private Boolean wnaMode;
private AbstractChangeSet changeTask;
private Extent bypassHistory;
private Extent bypassAll;
private Extent extent;
@@ -519,7 +521,6 @@ public final class EditSessionBuilder {
changeSet = new BlockBagChangeSet(changeSet, blockBag, limit.INVENTORY_MODE == 1);
}
if (combineStages) {
changeTask = changeSet;
this.extent = extent.enableHistory(changeSet);
} else {
this.extent = new HistoryExtent(extent, changeSet);
@@ -531,36 +532,18 @@ public final class EditSessionBuilder {
}
if (allowedRegions == null && Settings.settings().REGION_RESTRICTIONS) {
if (actor != null && !actor.hasPermission("fawe.bypass.regions")) {
if (actor instanceof Player) {
Player player = (Player) actor;
if (actor instanceof Player player) {
allowedRegions = player.getAllowedRegions();
}
}
}
if (disallowedRegions == null && Settings.settings().REGION_RESTRICTIONS && Settings.settings().REGION_RESTRICTIONS_OPTIONS.ALLOW_BLACKLISTS) {
if (actor != null && !actor.hasPermission("fawe.bypass.regions")) {
if (actor instanceof Player) {
Player player = (Player) actor;
if (actor instanceof Player player) {
disallowedRegions = player.getDisallowedRegions();
}
}
}
FaweRegionExtent regionExtent = null;
if (disallowedRegions != null) { // Always use MultiRegionExtent if we have blacklist regions
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, disallowedRegions);
} else if (allowedRegions == null) {
allowedRegions = new Region[]{RegionWrapper.GLOBAL()};
} else {
if (allowedRegions.length == 0) {
regionExtent = new NullExtent(this.extent, FaweCache.NO_REGION);
} else {
if (allowedRegions.length == 1) {
regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]);
} else {
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null);
}
}
}
// There's no need to do the below (and it'll also just be a pain to implement) if we're not placing chunks
if (placeChunks) {
if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) {
@@ -570,6 +553,11 @@ public final class EditSessionBuilder {
queue.addProcessor(new RelightProcessor(relighter));
}
queue.addProcessor(new HeightmapProcessor(world.getMinY(), world.getMaxY()));
if (!Settings.settings().EXPERIMENTAL.KEEP_ENTITIES_IN_BLOCKS) {
queue.addProcessor(new EntityInBlockRemovingProcessor());
}
IBatchProcessor platformProcessor = WorldEdit
.getInstance()
.getPlatformManager()
@@ -589,23 +577,18 @@ public final class EditSessionBuilder {
} else {
relighter = NullRelighter.INSTANCE;
}
if (limit != null && !limit.isUnlimited() && regionExtent != null) {
this.extent = new LimitExtent(regionExtent, limit);
} else if (limit != null && !limit.isUnlimited()) {
this.extent = new LimitExtent(this.extent, limit);
} else if (regionExtent != null) {
this.extent = regionExtent;
}
if (this.limit != null && this.limit.STRIP_NBT != null && !this.limit.STRIP_NBT.isEmpty()) {
StripNBTExtent ext = new StripNBTExtent(this.extent, this.limit.STRIP_NBT);
if (placeChunks) {
queue.addProcessor(new StripNBTExtent(this.extent, this.limit.STRIP_NBT));
} else {
this.extent = new StripNBTExtent(this.extent, this.limit.STRIP_NBT);
queue.addProcessor(ext);
}
if (!placeChunks || !combineStages) {
this.extent = ext;
}
}
if (this.limit != null && !this.limit.isUnlimited()) {
Set<String> limitBlocks = new HashSet<>();
if ((getActor() == null || getActor().hasPermission("worldedit.anyblock")) && this.limit.UNIVERSAL_DISALLOWED_BLOCKS) {
if (getActor() != null && !getActor().hasPermission("worldedit.anyblock") && this.limit.UNIVERSAL_DISALLOWED_BLOCKS) {
limitBlocks.addAll(WorldEdit.getInstance().getConfiguration().disallowedBlocks);
}
if (this.limit.DISALLOWED_BLOCKS != null && !this.limit.DISALLOWED_BLOCKS.isEmpty()) {
@@ -613,13 +596,47 @@ public final class EditSessionBuilder {
}
Set<PropertyRemap<?>> remaps = this.limit.REMAP_PROPERTIES;
if (!limitBlocks.isEmpty() || (remaps != null && !remaps.isEmpty())) {
DisallowedBlocksExtent ext = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps);
if (placeChunks) {
queue.addProcessor(new DisallowedBlocksExtent(this.extent, limitBlocks, remaps));
} else {
this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps);
queue.addProcessor(ext);
}
if (!placeChunks || !combineStages) {
this.extent = ext;
}
}
}
FaweRegionExtent regionExtent = null;
// Always use MultiRegionExtent if we have blacklist regions
if (allowedRegions != null && allowedRegions.length == 0) {
regionExtent = new NullExtent(this.extent, FaweCache.NO_REGION);
} else if (disallowedRegions != null && disallowedRegions.length != 0) {
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, disallowedRegions);
} else if (allowedRegions == null) {
allowedRegions = new Region[]{RegionWrapper.GLOBAL()};
} else if (allowedRegions.length == 1) {
regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]);
} else {
regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null);
}
if (regionExtent != null) {
if (placeChunks) {
queue.addProcessor(regionExtent);
}
if (!placeChunks || !combineStages) {
this.extent = regionExtent;
}
}
Consumer<Component> onErrorMessage;
if (getActor() != null) {
onErrorMessage = c -> getActor().print(Caption.of("fawe.error.occurred-continuing", c));
} else {
onErrorMessage = c -> {
};
}
if (limit != null && !limit.isUnlimited()) {
this.extent = new LimitExtent(this.extent, limit, onErrorMessage);
}
this.extent = wrapExtent(this.extent, eventBus, event, EditSession.Stage.BEFORE_HISTORY);
}
return this;
@@ -690,7 +707,7 @@ public final class EditSessionBuilder {
* Get the change set that will be used for history
*/
public AbstractChangeSet getChangeTask() {
return changeTask;
return changeSet;
}
/**

View File

@@ -96,6 +96,7 @@ public abstract class LocalConfiguration {
public boolean allowSymlinks = false;
public boolean serverSideCUI = true;
public boolean extendedYLimit = false;
public boolean commandBlockSupport = false;
public String defaultLocaleName = "default";
public Locale defaultLocale = Locale.getDefault();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -426,25 +426,11 @@ 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;
}
WorldEdit.getInstance().getExecutorService().submit(() -> {
doc.close(); // Ensure closed before deletion
doc.getFile().delete();
});
}
}
} else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT || Settings.settings().CLIPBOARD.USE_DISK) {
WorldEdit.getInstance().getExecutorService().submit(() -> session.setClipboard(null));
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) {
session.setClipboard(null);
}
if (Settings.settings().HISTORY.DELETE_ON_LOGOUT) {
session.clearHistory();
@@ -462,19 +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 = DiskOptimizedClipboard.loadFromFile(file);
Clipboard clip = doc.toClipboard();
ClipboardHolder holder = new ClipboardHolder(clip);
session.setClipboard(holder);
}
getSession().loadClipboardFromDisk(file);
} catch (FaweClipboardVersionMismatchException e) {
print(e.getComponent());
} catch (RuntimeException e) {

View File

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

View File

@@ -0,0 +1,49 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.extension.platform;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.util.Location;
public abstract class AbstractCommandBlockActor extends AbstractNonPlayerActor implements Locatable {
protected static final String UUID_PREFIX = "CMD";
private final Location location;
public AbstractCommandBlockActor(Location location) {
this.location = location;
}
@Override
public Location getLocation() {
return this.location;
}
@Override
public boolean setLocation(Location location) {
// Can't move a CommandBlock
return false;
}
@Override
public Extent getExtent() {
return this.location.getExtent();
}
}

View File

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

View File

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

View File

@@ -678,31 +678,26 @@ public final class PlatformCommandManager {
Actor actor = event.getActor();
String args = event.getArguments();
TaskManager.taskManager().taskNow(() -> {
if (!Fawe.isMainThread()) {
Thread.currentThread().setName("FAWE Thread for player: " + actor.getName());
int space0 = args.indexOf(' ');
String arg0 = space0 == -1 ? args : args.substring(0, space0);
Optional<Command> optional = commandManager.getCommand(arg0);
if (optional.isEmpty()) {
return;
}
Command cmd = optional.get();
PermissionCondition queued = cmd.getCondition().as(PermissionCondition.class).orElse(null);
if (queued != null && !queued.isQueued()) {
TaskManager.taskManager().taskNow(() -> handleCommandOnCurrentThread(event), Fawe.isMainThread());
return;
} else {
actor.decline();
}
actor.runAction(() -> {
SessionKey key = actor.getSessionKey();
if (key.isActive()) {
PlatformCommandManager.this.handleCommandOnCurrentThread(event);
}
int space0 = args.indexOf(' ');
String arg0 = space0 == -1 ? args : args.substring(0, space0);
Optional<Command> optional = commandManager.getCommand(arg0);
if (!optional.isPresent()) {
return;
}
Command cmd = optional.get();
PermissionCondition queued = cmd.getCondition().as(PermissionCondition.class).orElse(null);
if (queued != null && !queued.isQueued()) {
handleCommandOnCurrentThread(event);
return;
} else {
actor.decline();
}
actor.runAction(() -> {
SessionKey key = actor.getSessionKey();
if (key.isActive()) {
PlatformCommandManager.this.handleCommandOnCurrentThread(event);
}
}, false, true);
}, Fawe.isMainThread());
}, false, true);
}
public void handleCommandOnCurrentThread(CommandEvent event) {

View File

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

View File

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

View File

@@ -555,10 +555,11 @@ public class BlockTransformExtent extends ResettableExtent {
int transformedId = transformState(state, transform);
BlockState transformed = BlockState.getFromInternalId(transformedId);
if (block.hasNbtData()) {
boolean baseBlock = block instanceof BaseBlock;
if (baseBlock && block.hasNbtData()) {
return (B) transformBaseBlockNBT(transformed, block.getNbtData(), transform);
}
return (B) (block instanceof BaseBlock ? transformed.toBaseBlock() : transformed);
return (B) (baseBlock? transformed.toBaseBlock() : transformed);
//FAWE end
}

View File

@@ -160,17 +160,7 @@ public class ExtentEntityCopy implements EntityFunction {
// Remove
if (isRemoving() && success) {
//FAWE start
UUID uuid = null;
if (tag.containsKey("UUID")) {
int[] arr = tag.getIntArray("UUID");
uuid = new UUID((long) arr[0] << 32 | (arr[1] & 0xFFFFFFFFL), (long) arr[2] << 32 | (arr[3] & 0xFFFFFFFFL));
} else if (tag.containsKey("UUIDMost")) {
uuid = new UUID(tag.getLong("UUIDMost"), tag.getLong("UUIDLeast"));
} else if (tag.containsKey("WorldUUIDMost")) {
uuid = new UUID(tag.getLong("WorldUUIDMost"), tag.getLong("WorldUUIDLeast"));
} else if (tag.containsKey("PersistentIDMSB")) {
uuid = new UUID(tag.getLong("PersistentIDMSB"), tag.getLong("PersistentIDLSB"));
}
UUID uuid = entity.getState().getNbtData().getUUID();
if (uuid != null) {
if (source != null) {
source.removeEntity(

View File

@@ -229,6 +229,7 @@ public abstract class BreadthFirstSearch implements Operation {
*/
public void visit(BlockVector3 position) {
if (!visited.contains(position)) {
isVisitable(position, position); // Ignore this, just to initialize mask on this point
queue.add(position);
visited.add(position);
}

View File

@@ -81,4 +81,9 @@ public final class Constants {
*/
public static final int DATA_VERSION_MC_1_19 = 3105;
/**
* The DataVersion for Minecraft 1.20
*/
public static final int DATA_VERSION_MC_1_20 = 3463;
}

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