Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,27 @@ import java.util.List;
|
||||
public class CommandBrush implements Brush {
|
||||
|
||||
private final String command;
|
||||
private final boolean print;
|
||||
|
||||
/**
|
||||
* New instance
|
||||
*
|
||||
* @deprecated Use {@link CommandBrush#CommandBrush(String, boolean)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public CommandBrush(String command) {
|
||||
this.command = command.charAt(0) == '/' ? "/" + command : command;
|
||||
this(command, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* New instance
|
||||
*
|
||||
* @param command command to run, or commands split by ';'
|
||||
* @param print if output should be printed to the actor for the run commands
|
||||
*/
|
||||
public CommandBrush(String command, boolean print) {
|
||||
this.command = command;
|
||||
this.print = print;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,7 +54,7 @@ public class CommandBrush implements Brush {
|
||||
position.subtract(radius, radius, radius),
|
||||
position.add(radius, radius, radius)
|
||||
);
|
||||
String replaced = command.replace("{x}", position.getBlockX() + "")
|
||||
String replaced = command.replace("{x}", Integer.toString(position.getBlockX()))
|
||||
.replace("{y}", Integer.toString(position.getBlockY()))
|
||||
.replace("{z}", Integer.toString(position.getBlockZ()))
|
||||
.replace("{world}", editSession.getWorld().getName())
|
||||
@@ -46,21 +64,22 @@ public class CommandBrush implements Brush {
|
||||
if (!(actor instanceof Player player)) {
|
||||
throw FaweCache.PLAYER_ONLY;
|
||||
}
|
||||
//Use max world height to allow full coverage of the world height
|
||||
Location face = player.getBlockTraceFace(editSession.getWorld().getMaxY(), true);
|
||||
if (face == null) {
|
||||
position = position.add(0, 1, 1);
|
||||
} else {
|
||||
position = position.add(face.getDirection().toBlockPoint());
|
||||
}
|
||||
player.setSelection(selector);
|
||||
AsyncPlayer wePlayer = new SilentPlayerWrapper(new LocationMaskedPlayerWrapper(
|
||||
AsyncPlayer wePlayer = new LocationMaskedPlayerWrapper(
|
||||
player,
|
||||
new Location(player.getExtent(), position.toVector3())
|
||||
));
|
||||
);
|
||||
if (!print) {
|
||||
wePlayer = new SilentPlayerWrapper(wePlayer);
|
||||
}
|
||||
List<String> cmds = StringMan.split(replaced, ';');
|
||||
for (String cmd : cmds) {
|
||||
CommandEvent event = new CommandEvent(wePlayer, cmd);
|
||||
if (cmd.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
cmd = cmd.charAt(0) != '/' ? "/" + cmd : cmd;
|
||||
cmd = cmd.length() >1 && cmd.charAt(1) == '/' ? cmd.substring(1) : cmd;
|
||||
CommandEvent event = new CommandEvent(wePlayer, cmd, editSession);
|
||||
PlatformCommandManager.getInstance().handleCommandOnCurrentThread(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package com.fastasyncworldedit.core.command.tool.brush;
|
||||
import com.fastasyncworldedit.core.FaweCache;
|
||||
import com.fastasyncworldedit.core.math.LocalBlockVectorSet;
|
||||
import com.fastasyncworldedit.core.util.StringMan;
|
||||
import com.fastasyncworldedit.core.wrappers.AsyncPlayer;
|
||||
import com.fastasyncworldedit.core.wrappers.LocationMaskedPlayerWrapper;
|
||||
import com.fastasyncworldedit.core.wrappers.SilentPlayerWrapper;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
@@ -13,7 +15,7 @@ import com.sk89q.worldedit.extension.platform.PlatformCommandManager;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -43,7 +45,7 @@ public class ScatterCommand extends ScatterBrush {
|
||||
position.subtract(radius, radius, radius),
|
||||
position.add(radius, radius, radius)
|
||||
);
|
||||
String replaced = command.replace("{x}", position.getBlockX() + "")
|
||||
String replaced = command.replace("{x}", Integer.toString(position.getBlockX()))
|
||||
.replace("{y}", Integer.toString(position.getBlockY()))
|
||||
.replace("{z}", Integer.toString(position.getBlockZ()))
|
||||
.replace("{world}", editSession.getWorld().getName())
|
||||
@@ -55,41 +57,22 @@ public class ScatterCommand extends ScatterBrush {
|
||||
}
|
||||
player.setSelection(selector);
|
||||
List<String> cmds = StringMan.split(replaced, ';');
|
||||
AsyncPlayer wePlayer = new LocationMaskedPlayerWrapper(
|
||||
player,
|
||||
new Location(player.getExtent(), position.toVector3())
|
||||
);
|
||||
if (!print) {
|
||||
wePlayer = new SilentPlayerWrapper(wePlayer);
|
||||
}
|
||||
for (String cmd : cmds) {
|
||||
Player p = print ?
|
||||
new LocationMaskedPlayerWrapper(player, player.getLocation().setPosition(position.toVector3()), false) :
|
||||
new ScatterCommandPlayerWrapper(player, position);
|
||||
CommandEvent event = new CommandEvent(p, cmd, editSession);
|
||||
if (cmd.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
cmd = cmd.charAt(0) != '/' ? "/" + cmd : cmd;
|
||||
cmd = cmd.length() >1 && cmd.charAt(1) == '/' ? cmd.substring(1) : cmd;
|
||||
CommandEvent event = new CommandEvent(wePlayer, cmd, editSession);
|
||||
PlatformCommandManager.getInstance().handleCommandOnCurrentThread(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ScatterCommandPlayerWrapper extends LocationMaskedPlayerWrapper {
|
||||
|
||||
ScatterCommandPlayerWrapper(Player player, BlockVector3 position) {
|
||||
super(player, player.getLocation().setPosition(position.toVector3()), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(Component component) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printDebug(String msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printError(String msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printRaw(String msg) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package com.fastasyncworldedit.core.concurrent;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
/**
|
||||
* Allows for reentrant behaviour of a wrapped {@link StampedLock}. Will not count the number of times it is re-entered.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class ReentrantWrappedStampedLock implements Lock {
|
||||
|
||||
private final StampedLock parent = new StampedLock();
|
||||
private volatile Thread owner;
|
||||
private volatile long stamp = 0;
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
if (Thread.currentThread() == owner) {
|
||||
return;
|
||||
}
|
||||
stamp = parent.writeLock();
|
||||
owner = Thread.currentThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockInterruptibly() throws InterruptedException {
|
||||
if (Thread.currentThread() == owner) {
|
||||
return;
|
||||
}
|
||||
stamp = parent.writeLockInterruptibly();
|
||||
owner = Thread.currentThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryLock() {
|
||||
if (Thread.currentThread() == owner) {
|
||||
return true;
|
||||
}
|
||||
if (parent.isWriteLocked()) {
|
||||
return false;
|
||||
}
|
||||
stamp = parent.writeLock();
|
||||
owner = Thread.currentThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryLock(final long time, @NotNull final TimeUnit unit) throws InterruptedException {
|
||||
if (Thread.currentThread() == owner) {
|
||||
return true;
|
||||
}
|
||||
if (!parent.isWriteLocked()) {
|
||||
stamp = parent.writeLock();
|
||||
owner = Thread.currentThread();
|
||||
return true;
|
||||
}
|
||||
stamp = parent.tryWriteLock(time, unit);
|
||||
owner = Thread.currentThread();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
if (owner != Thread.currentThread()) {
|
||||
throw new IllegalCallerException("The lock should only be unlocked by the owning thread when a stamp is not supplied");
|
||||
}
|
||||
unlock(stamp);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Condition newCondition() {
|
||||
throw new UnsupportedOperationException("Conditions are not supported by StampedLock");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stamp associated with the current lock. 0 if the wrapped {@link StampedLock} is not write-locked. This method is
|
||||
* thread-checking.
|
||||
*
|
||||
* @return lock stam[ or 0 if not locked.
|
||||
* @throws IllegalCallerException if the {@link StampedLock} is write-locked and the calling thread is not the lock owner
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public long getStampChecked() {
|
||||
if (stamp != 0 && owner != Thread.currentThread()) {
|
||||
throw new IllegalCallerException("The stamp should be be acquired by a thread that does not own the lock");
|
||||
}
|
||||
return stamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock the wrapped {@link StampedLock} using the given stamp. This can be called by any thread.
|
||||
*
|
||||
* @param stamp Stamp to unlock with
|
||||
* @throws IllegalMonitorStateException if the given stamp does not match the lock's stamp
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public void unlock(final long stamp) {
|
||||
parent.unlockWrite(stamp);
|
||||
this.stamp = 0;
|
||||
owner = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the lock is currently held.
|
||||
*
|
||||
* @return true if the lock is currently held.
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public boolean isLocked() {
|
||||
return owner == null && this.stamp == 0 && parent.isWriteLocked(); // Be verbose
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class RichMaskParser extends FaweParser<Mask> {
|
||||
@Override
|
||||
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
|
||||
if (input.isEmpty()) {
|
||||
throw new SuggestInputParseException("No input provided", "", () -> Stream
|
||||
throw new SuggestInputParseException(Caption.of("fawe.error.no-input-provided"), () -> Stream
|
||||
.of("#", ",", "&")
|
||||
.map(n -> n + ":")
|
||||
.collect(Collectors.toList())
|
||||
@@ -95,7 +95,6 @@ public class RichMaskParser extends FaweParser<Mask> {
|
||||
"https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns"
|
||||
))
|
||||
)),
|
||||
full,
|
||||
() -> {
|
||||
if (full.length() == 1) {
|
||||
return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions(""));
|
||||
@@ -148,6 +147,7 @@ public class RichMaskParser extends FaweParser<Mask> {
|
||||
try {
|
||||
builder.addRegex(full);
|
||||
} catch (InputParseException ignored) {
|
||||
builder.clear();
|
||||
context.setPreferringWildcard(false);
|
||||
context.setRestricted(false);
|
||||
BaseBlock block = worldEdit.getBlockFactory().parseFromInput(full, context);
|
||||
@@ -162,7 +162,6 @@ public class RichMaskParser extends FaweParser<Mask> {
|
||||
"https://intellectualsites.github.io/fastasyncworldedit-documentation/masks/masks"
|
||||
))
|
||||
)),
|
||||
full,
|
||||
() -> {
|
||||
if (full.length() == 1) {
|
||||
return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions(""));
|
||||
|
||||
@@ -47,8 +47,7 @@ public class RichPatternParser extends FaweParser<Pattern> {
|
||||
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
|
||||
if (input.isEmpty()) {
|
||||
throw new SuggestInputParseException(
|
||||
"No input provided",
|
||||
"",
|
||||
Caption.of("fawe.error.no-input-provided"),
|
||||
() -> Stream
|
||||
.concat(Stream.of("#", ",", "&"), BlockTypes.getNameSpaces().stream().map(n -> n + ":"))
|
||||
.collect(Collectors.toList())
|
||||
@@ -88,7 +87,6 @@ public class RichPatternParser extends FaweParser<Pattern> {
|
||||
"https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns"
|
||||
))
|
||||
)),
|
||||
full,
|
||||
() -> {
|
||||
if (full.length() == 1) {
|
||||
return new ArrayList<>(worldEdit.getPatternFactory().getSuggestions(""));
|
||||
@@ -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 += "]";
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
|
||||
BlockState state = BlockTypesCache.states[block];
|
||||
if (blockedBlocks != null) {
|
||||
if (blockedBlocks.contains(state.getBlockType().getId())) {
|
||||
blocks[i] = 0;
|
||||
blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
|
||||
}
|
||||
for (FuzzyBlockState fuzzy : blockedStates) {
|
||||
if (fuzzy.equalsFuzzy(state)) {
|
||||
blocks[i] = 0;
|
||||
blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
continue it;
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
|
||||
|
||||
@Override
|
||||
public ProcessorScope getScope() {
|
||||
return ProcessorScope.CHANGING_BLOCKS;
|
||||
return ProcessorScope.REMOVING_BLOCKS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public abstract class FaweRegionExtent extends ResettableExtent implements IBatc
|
||||
|
||||
@Override
|
||||
public ProcessorScope getScope() {
|
||||
return ProcessorScope.READING_SET_BLOCKS;
|
||||
return ProcessorScope.REMOVING_BLOCKS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ public class MultiRegionExtent extends FaweRegionExtent {
|
||||
set = intersection.processSet(chunk, get, set);
|
||||
}
|
||||
if (disallowedIntersection != null) {
|
||||
intersection.processSet(chunk, get, set);
|
||||
set = disallowedIntersection.processSet(chunk, get, set, true);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.sk89q.worldedit.world.biome.BiomeType;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
@@ -191,8 +192,8 @@ public class CPUOptimizedClipboard extends LinearClipboard {
|
||||
@Override
|
||||
public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) {
|
||||
char ordinal = block.getOrdinalChar();
|
||||
if (ordinal == 0) {
|
||||
ordinal = 1;
|
||||
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||
ordinal = BlockTypesCache.ReservedIDs.AIR;
|
||||
}
|
||||
states[index] = ordinal;
|
||||
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
@@ -269,8 +270,8 @@ public class MemoryOptimizedClipboard extends LinearClipboard {
|
||||
@Override
|
||||
public <B extends BlockStateHolder<B>> boolean setBlock(int index, B block) {
|
||||
int ordinal = block.getOrdinal();
|
||||
if (ordinal == 0) {
|
||||
ordinal = 1;
|
||||
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||
ordinal = BlockTypesCache.ReservedIDs.AIR;
|
||||
}
|
||||
setOrdinal(index, ordinal);
|
||||
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();
|
||||
|
||||
@@ -172,8 +172,8 @@ public class FastSchematicWriter implements ClipboardWriter {
|
||||
}
|
||||
|
||||
int ordinal = block.getOrdinal();
|
||||
if (ordinal == 0) {
|
||||
ordinal = 1;
|
||||
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||
ordinal = BlockTypesCache.ReservedIDs.AIR;
|
||||
}
|
||||
char value = palette[ordinal];
|
||||
if (value == Character.MAX_VALUE) {
|
||||
|
||||
@@ -41,8 +41,8 @@ public class DistrFilter extends ForkedFilter<DistrFilter> {
|
||||
@Override
|
||||
public final void applyBlock(FilterBlock block) {
|
||||
int ordinal = block.getOrdinal();
|
||||
if (ordinal == 0) {
|
||||
ordinal = 1;
|
||||
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||
ordinal = BlockTypesCache.ReservedIDs.AIR;
|
||||
}
|
||||
counter[ordinal]++;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -257,7 +257,7 @@ public class MultiBatchProcessor implements IBatchProcessor {
|
||||
for (IBatchProcessor processor : processors) {
|
||||
scope = Math.max(scope, processor.getScope().intValue());
|
||||
}
|
||||
return ProcessorScope.valueOf(0);
|
||||
return ProcessorScope.valueOf(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import com.sk89q.worldedit.function.mask.AbstractExtentMask;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class IdMask extends AbstractExtentMask implements ResettableMask {
|
||||
|
||||
private transient int id = -1;
|
||||
private final AtomicInteger id = new AtomicInteger(-1);
|
||||
|
||||
public IdMask(Extent extent) {
|
||||
super(extent);
|
||||
@@ -15,12 +17,9 @@ public class IdMask extends AbstractExtentMask implements ResettableMask {
|
||||
|
||||
@Override
|
||||
public boolean test(Extent extent, BlockVector3 vector) {
|
||||
if (id != -1) {
|
||||
return extent.getBlock(vector).getInternalBlockTypeId() == id;
|
||||
} else {
|
||||
id = extent.getBlock(vector).getInternalBlockTypeId();
|
||||
return true;
|
||||
}
|
||||
int blockID = extent.getBlock(vector).getInternalBlockTypeId();
|
||||
int testId = id.compareAndExchange(-1, blockID);
|
||||
return blockID == testId || testId == -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,12 +29,12 @@ public class IdMask extends AbstractExtentMask implements ResettableMask {
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.id = -1;
|
||||
this.id.set(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mask copy() {
|
||||
return new IdMask(getExtent());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.fastasyncworldedit.core.function.pattern;
|
||||
|
||||
import com.fastasyncworldedit.core.extent.filter.block.FilterBlock;
|
||||
import com.fastasyncworldedit.core.util.StringMan;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Pattern that replaces blocks based on their ID, matching for an "input" and replacing with an "output" string. The "input"
|
||||
* string may be regex. Keeps as much of the block state as possible, excluding NBT data.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TypeSwapPattern extends AbstractExtentPattern {
|
||||
|
||||
private static final Pattern SPLITTER = Pattern.compile("[|,]");
|
||||
|
||||
private final String inputString;
|
||||
private final String outputString;
|
||||
private final String[] inputs;
|
||||
private Pattern inputPattern = null;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param extent extent to use
|
||||
* @param inputString string to replace. May be regex.
|
||||
* @param outputString string to replace with
|
||||
* @param allowRegex if regex should be allowed for input string matching
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public TypeSwapPattern(Extent extent, String inputString, String outputString, boolean allowRegex) {
|
||||
super(extent);
|
||||
this.inputString = inputString;
|
||||
this.outputString = outputString;
|
||||
if (!StringMan.isAlphanumericUnd(inputString)) {
|
||||
if (allowRegex) {
|
||||
this.inputPattern = Pattern.compile(inputString.replace(",", "|"));
|
||||
inputs = null;
|
||||
} else {
|
||||
inputs = SPLITTER.split(inputString);
|
||||
}
|
||||
} else {
|
||||
inputs = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
|
||||
BlockState existing = get.getBlock(extent);
|
||||
BlockState newBlock = getNewBlock(existing);
|
||||
if (newBlock == null) {
|
||||
return false;
|
||||
}
|
||||
return set.setBlock(extent, newBlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyBlock(final FilterBlock block) {
|
||||
BlockState existing = block.getBlock();
|
||||
BlockState newState = getNewBlock(existing);
|
||||
if (newState != null) {
|
||||
block.setBlock(newState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock applyBlock(final BlockVector3 position) {
|
||||
BaseBlock existing = position.getFullBlock(getExtent());
|
||||
BlockState newState = getNewBlock(existing.toBlockState());
|
||||
return newState == null ? existing : newState.toBaseBlock();
|
||||
}
|
||||
|
||||
private BlockState getNewBlock(BlockState existing) {
|
||||
String oldId = existing.getBlockType().getId();
|
||||
String newId = oldId;
|
||||
if (inputPattern != null) {
|
||||
newId = inputPattern.matcher(oldId).replaceAll(outputString);
|
||||
} else if (inputs != null && inputs.length > 0) {
|
||||
for (String input : inputs) {
|
||||
newId = newId.replace(input, outputString);
|
||||
}
|
||||
} else {
|
||||
newId = oldId.replace(inputString, outputString);
|
||||
}
|
||||
if (newId.equals(oldId)) {
|
||||
return null;
|
||||
}
|
||||
BlockType newType = BlockTypes.get(newId);
|
||||
if (newType == null) {
|
||||
return null;
|
||||
}
|
||||
return newType.getDefaultState().withProperties(existing);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.history.UndoContext;
|
||||
import com.sk89q.worldedit.history.change.Change;
|
||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
|
||||
public class MutableBiomeChange implements Change {
|
||||
|
||||
@@ -13,8 +14,8 @@ public class MutableBiomeChange implements Change {
|
||||
private int to;
|
||||
|
||||
public MutableBiomeChange() {
|
||||
this.from = 0;
|
||||
this.to = 0;
|
||||
this.from = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
this.to = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
}
|
||||
|
||||
public void setBiome(int x, int y, int z, int from, int to) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.sk89q.worldedit.extent.inventory.BlockBagException;
|
||||
import com.sk89q.worldedit.history.UndoContext;
|
||||
import com.sk89q.worldedit.history.change.Change;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
|
||||
public class MutableFullBlockChange implements Change {
|
||||
|
||||
@@ -39,14 +40,14 @@ public class MutableFullBlockChange implements Change {
|
||||
if (blockBag != null) {
|
||||
BlockState toState = BlockState.getFromOrdinal(to);
|
||||
if (fromState != toState) {
|
||||
if (allowFetch && from != 0) {
|
||||
if (allowFetch && from != BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||
try {
|
||||
blockBag.fetchPlacedBlock(fromState);
|
||||
} catch (BlockBagException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (allowStore && to != 0) {
|
||||
if (allowStore && to != BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||
try {
|
||||
blockBag.storeDroppedBlock(toState);
|
||||
} catch (BlockBagException ignored) {
|
||||
|
||||
@@ -39,19 +39,27 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* This batch processor writes changes to a concrete implementation.
|
||||
* {@link #processSet(IChunk, IChunkGet, IChunkSet)} is synchronized to guarantee consistency.
|
||||
* To avoid many blocking threads on this method, changes are enqueued in {@link #queue}.
|
||||
* This allows to keep other threads free for other work.
|
||||
*/
|
||||
public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
|
||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||
|
||||
private final World world;
|
||||
private final AtomicInteger lastException = new AtomicInteger();
|
||||
protected AtomicInteger waitingCombined = new AtomicInteger(0);
|
||||
protected AtomicInteger waitingAsync = new AtomicInteger(0);
|
||||
|
||||
protected boolean closed;
|
||||
private final Semaphore workerSemaphore = new Semaphore(1, false);
|
||||
private final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();
|
||||
protected volatile boolean closed;
|
||||
|
||||
public AbstractChangeSet(World world) {
|
||||
this.world = world;
|
||||
@@ -65,16 +73,11 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
waitingAsync.incrementAndGet();
|
||||
TaskManager.taskManager().async(() -> {
|
||||
waitingAsync.decrementAndGet();
|
||||
synchronized (waitingAsync) {
|
||||
waitingAsync.notifyAll();
|
||||
}
|
||||
try {
|
||||
close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.catching(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -82,20 +85,10 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
@Override
|
||||
public void flush() {
|
||||
try {
|
||||
if (!Fawe.isMainThread()) {
|
||||
while (waitingAsync.get() > 0) {
|
||||
synchronized (waitingAsync) {
|
||||
waitingAsync.wait(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (waitingCombined.get() > 0) {
|
||||
synchronized (waitingCombined) {
|
||||
waitingCombined.wait(1000);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
// drain with this thread too
|
||||
drainQueue(true);
|
||||
} catch (Exception e) {
|
||||
LOGGER.catching(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +118,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
|
||||
public final synchronized IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
|
||||
int bx = chunk.getX() << 4;
|
||||
int bz = chunk.getZ() << 4;
|
||||
|
||||
@@ -193,7 +186,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
}
|
||||
final int combinedFrom = from;
|
||||
final int combinedTo = blocksSet[index];
|
||||
if (combinedTo != 0) {
|
||||
if (combinedTo != BlockTypesCache.ReservedIDs.__RESERVED__) {
|
||||
add(xx, yy, zz, combinedFrom, combinedTo);
|
||||
}
|
||||
}
|
||||
@@ -306,12 +299,12 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
BaseBlock to = change.getCurrent();
|
||||
add(loc, from, to);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.catching(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return waitingCombined.get() == 0 && waitingAsync.get() == 0 && size() == 0;
|
||||
return queue.isEmpty() && workerSemaphore.availablePermits() == 1 && size() == 0;
|
||||
}
|
||||
|
||||
public void add(BlockVector3 loc, BaseBlock from, BaseBlock to) {
|
||||
@@ -353,7 +346,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
add(x, y, z, combinedFrom, combinedTo);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.catching(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +355,6 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
}
|
||||
|
||||
public Future<?> addWriteTask(final Runnable writeTask, final boolean completeNow) {
|
||||
AbstractChangeSet.this.waitingCombined.incrementAndGet();
|
||||
Runnable wrappedTask = () -> {
|
||||
try {
|
||||
writeTask.run();
|
||||
@@ -372,25 +364,55 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
} else {
|
||||
int hash = t.getMessage().hashCode();
|
||||
if (lastException.getAndSet(hash) != hash) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (AbstractChangeSet.this.waitingCombined.decrementAndGet() <= 0) {
|
||||
synchronized (AbstractChangeSet.this.waitingAsync) {
|
||||
AbstractChangeSet.this.waitingAsync.notifyAll();
|
||||
}
|
||||
synchronized (AbstractChangeSet.this.waitingCombined) {
|
||||
AbstractChangeSet.this.waitingCombined.notifyAll();
|
||||
LOGGER.catching(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (completeNow) {
|
||||
wrappedTask.run();
|
||||
return Futures.immediateCancelledFuture();
|
||||
return Futures.immediateVoidFuture();
|
||||
} else {
|
||||
return Fawe.instance().getQueueHandler().submit(wrappedTask);
|
||||
CompletableFuture<?> task = new CompletableFuture<>();
|
||||
queue.add(() -> {
|
||||
wrappedTask.run();
|
||||
task.complete(null);
|
||||
});
|
||||
// make sure changes are processed
|
||||
triggerWorker();
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerWorker() {
|
||||
if (workerSemaphore.availablePermits() == 0) {
|
||||
return; // fast path to avoid additional tasks: a worker is already draining the queue
|
||||
}
|
||||
// create a new worker to drain the current queue
|
||||
Fawe.instance().getQueueHandler().async(() -> drainQueue(false));
|
||||
}
|
||||
|
||||
private void drainQueue(boolean ignoreRunningState) {
|
||||
if (!workerSemaphore.tryAcquire()) {
|
||||
if (ignoreRunningState) {
|
||||
// ignoreRunningState means we want to block
|
||||
// even if another thread is already draining
|
||||
try {
|
||||
workerSemaphore.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else {
|
||||
return; // another thread is draining the queue already, ignore
|
||||
}
|
||||
}
|
||||
try {
|
||||
Runnable next;
|
||||
while ((next = queue.poll()) != null) { // process all tasks in the queue
|
||||
next.run();
|
||||
}
|
||||
} finally {
|
||||
workerSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ public class AbstractDelegateChangeSet extends AbstractChangeSet {
|
||||
public AbstractDelegateChangeSet(AbstractChangeSet parent) {
|
||||
super(parent.getWorld());
|
||||
this.parent = parent;
|
||||
this.waitingCombined = parent.waitingCombined;
|
||||
this.waitingAsync = parent.waitingAsync;
|
||||
}
|
||||
|
||||
public final AbstractChangeSet getParent() {
|
||||
|
||||
@@ -258,7 +258,7 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
if (blockSize > 0) {
|
||||
return false;
|
||||
}
|
||||
if (waitingCombined.get() != 0 || waitingAsync.get() != 0) {
|
||||
if (!super.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
flush();
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.limit;
|
||||
|
||||
import com.fastasyncworldedit.core.FaweCache;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
public class FaweLimit {
|
||||
@@ -114,10 +115,10 @@ public class FaweLimit {
|
||||
MAX.FAST_PLACEMENT = true;
|
||||
MAX.CONFIRM_LARGE = true;
|
||||
MAX.RESTRICT_HISTORY_TO_REGIONS = false;
|
||||
MAX.STRIP_NBT = null;
|
||||
MAX.STRIP_NBT = Collections.emptySet();
|
||||
MAX.UNIVERSAL_DISALLOWED_BLOCKS = false;
|
||||
MAX.DISALLOWED_BLOCKS = null;
|
||||
MAX.REMAP_PROPERTIES = null;
|
||||
MAX.DISALLOWED_BLOCKS = Collections.emptySet();
|
||||
MAX.REMAP_PROPERTIES = Collections.emptySet();
|
||||
}
|
||||
|
||||
public boolean MAX_CHANGES() {
|
||||
@@ -241,7 +242,7 @@ public class FaweLimit {
|
||||
&& FAST_PLACEMENT
|
||||
&& !RESTRICT_HISTORY_TO_REGIONS
|
||||
&& (STRIP_NBT == null || STRIP_NBT.isEmpty())
|
||||
&& !UNIVERSAL_DISALLOWED_BLOCKS
|
||||
// && !UNIVERSAL_DISALLOWED_BLOCKS --> do not include this, it effectively has no relevance
|
||||
&& (DISALLOWED_BLOCKS == null || DISALLOWED_BLOCKS.isEmpty())
|
||||
&& (REMAP_PROPERTIES == null || REMAP_PROPERTIES.isEmpty());
|
||||
}
|
||||
|
||||
@@ -9,7 +9,20 @@ import java.util.Map;
|
||||
|
||||
public class BlockVector3ChunkMap<T> implements IAdaptedMap<BlockVector3, T, Integer, T> {
|
||||
|
||||
private final Int2ObjectArrayMap<T> map = new Int2ObjectArrayMap<>();
|
||||
private final Int2ObjectArrayMap<T> map;
|
||||
|
||||
public BlockVector3ChunkMap() {
|
||||
map = new Int2ObjectArrayMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance that is a copy of an existing map
|
||||
*
|
||||
* @param map existing map to copy
|
||||
*/
|
||||
public BlockVector3ChunkMap(BlockVector3ChunkMap<T> map) {
|
||||
this.map = new Int2ObjectArrayMap<>(map.getParent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, T> getParent() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -15,12 +15,11 @@ import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
|
||||
@@ -40,7 +39,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
public BlockVector3ChunkMap<CompoundTag> tiles;
|
||||
public HashSet<CompoundTag> entities;
|
||||
public HashSet<UUID> entityRemoves;
|
||||
public Map<HeightMapType, int[]> heightMaps;
|
||||
public EnumMap<HeightMapType, int[]> heightMaps;
|
||||
private boolean fastMode = false;
|
||||
private int bitMask = -1;
|
||||
|
||||
@@ -93,7 +92,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
|
||||
@Override
|
||||
public Map<HeightMapType, int[]> getHeightMaps() {
|
||||
return heightMaps == null ? new HashMap<>() : heightMaps;
|
||||
return heightMaps == null ? new EnumMap<>(HeightMapType.class) : heightMaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,7 +176,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
@Override
|
||||
public void setHeightMap(HeightMapType type, int[] heightMap) {
|
||||
if (heightMaps == null) {
|
||||
heightMaps = new HashMap<>();
|
||||
heightMaps = new EnumMap<>(HeightMapType.class);
|
||||
}
|
||||
heightMaps.put(type, heightMap);
|
||||
}
|
||||
@@ -306,8 +305,12 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
|| (heightMaps != null && !heightMaps.isEmpty())) {
|
||||
return false;
|
||||
}
|
||||
//noinspection SimplifyStreamApiCallChains - this is faster than using #noneMatch
|
||||
return !IntStream.range(minSectionPosition, maxSectionPosition + 1).anyMatch(this::hasSection);
|
||||
for (int i = minSectionPosition; i <= maxSectionPosition; i++) {
|
||||
if (hasSection(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -316,6 +319,9 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
tiles = null;
|
||||
entities = null;
|
||||
entityRemoves = null;
|
||||
light = null;
|
||||
skyLight = null;
|
||||
heightMaps = null;
|
||||
super.reset();
|
||||
return null;
|
||||
}
|
||||
@@ -329,6 +335,62 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
return biomes != null && biomes[layer] != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadUnsafeCharBlocks createCopy() {
|
||||
char[][] blocksCopy = new char[sectionCount][];
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
if (blocks[i] != null) {
|
||||
blocksCopy[i] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER];
|
||||
System.arraycopy(blocks[i], 0, blocksCopy[i], 0, FaweCache.INSTANCE.BLOCKS_PER_LAYER);
|
||||
}
|
||||
}
|
||||
BiomeType[][] biomesCopy;
|
||||
if (biomes == null) {
|
||||
biomesCopy = null;
|
||||
} else {
|
||||
biomesCopy = new BiomeType[sectionCount][];
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
if (biomes[i] != null) {
|
||||
biomesCopy[i] = new BiomeType[biomes[i].length];
|
||||
System.arraycopy(biomes[i], 0, biomesCopy[i], 0, biomes[i].length);
|
||||
}
|
||||
}
|
||||
}
|
||||
char[][] lightCopy = createLightCopy(light, sectionCount);
|
||||
char[][] skyLightCopy = createLightCopy(skyLight, sectionCount);
|
||||
return new ThreadUnsafeCharBlocks(
|
||||
blocksCopy,
|
||||
minSectionPosition,
|
||||
maxSectionPosition,
|
||||
biomesCopy,
|
||||
sectionCount,
|
||||
lightCopy,
|
||||
skyLightCopy,
|
||||
tiles != null ? new BlockVector3ChunkMap<>(tiles) : null,
|
||||
entities != null ? new HashSet<>(entities) : null,
|
||||
entityRemoves != null ? new HashSet<>(entityRemoves) : null,
|
||||
heightMaps != null ? new EnumMap<>(heightMaps) : null,
|
||||
defaultOrdinal(),
|
||||
fastMode,
|
||||
bitMask
|
||||
);
|
||||
}
|
||||
|
||||
static char[][] createLightCopy(char[][] lightArr, int sectionCount) {
|
||||
if (lightArr == null) {
|
||||
return null;
|
||||
} else {
|
||||
char[][] lightCopy = new char[sectionCount][];
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
if (lightArr[i] != null) {
|
||||
lightCopy[i] = new char[lightArr[i].length];
|
||||
System.arraycopy(lightArr[i], 0, lightCopy[i], 0, lightArr[i].length);
|
||||
}
|
||||
}
|
||||
return lightCopy;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] load(final int layer) {
|
||||
updateSectionIndexRange(layer);
|
||||
@@ -348,67 +410,47 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
|
||||
if (layer < minSectionPosition) {
|
||||
int diff = minSectionPosition - layer;
|
||||
sectionCount += diff;
|
||||
char[][] tmpBlocks = new char[sectionCount][];
|
||||
Section[] tmpSections = new Section[sectionCount];
|
||||
Object[] tmpSectionLocks = new Object[sectionCount];
|
||||
System.arraycopy(blocks, 0, tmpBlocks, diff, blocks.length);
|
||||
System.arraycopy(sections, 0, tmpSections, diff, sections.length);
|
||||
System.arraycopy(sectionLocks, 0, tmpSectionLocks, diff, sections.length);
|
||||
for (int i = 0; i < diff; i++) {
|
||||
tmpSections[i] = EMPTY;
|
||||
tmpSectionLocks[i] = new Object();
|
||||
}
|
||||
blocks = tmpBlocks;
|
||||
sections = tmpSections;
|
||||
sectionLocks = tmpSectionLocks;
|
||||
minSectionPosition = layer;
|
||||
if (biomes != null) {
|
||||
BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64];
|
||||
System.arraycopy(biomes, 0, tmpBiomes, diff, biomes.length);
|
||||
biomes = tmpBiomes;
|
||||
}
|
||||
if (light != null) {
|
||||
char[][] tmplight = new char[sectionCount][];
|
||||
System.arraycopy(light, 0, tmplight, diff, light.length);
|
||||
light = tmplight;
|
||||
}
|
||||
if (skyLight != null) {
|
||||
char[][] tmplight = new char[sectionCount][];
|
||||
System.arraycopy(skyLight, 0, tmplight, diff, skyLight.length);
|
||||
skyLight = tmplight;
|
||||
}
|
||||
resizeSectionsArrays(diff, false); // prepend new layer(s)
|
||||
} else {
|
||||
int diff = layer - maxSectionPosition;
|
||||
sectionCount += diff;
|
||||
char[][] tmpBlocks = new char[sectionCount][];
|
||||
Section[] tmpSections = new Section[sectionCount];
|
||||
Object[] tmpSectionLocks = new Object[sectionCount];
|
||||
System.arraycopy(blocks, 0, tmpBlocks, 0, blocks.length);
|
||||
System.arraycopy(sections, 0, tmpSections, 0, sections.length);
|
||||
System.arraycopy(sectionLocks, 0, tmpSectionLocks, 0, sections.length);
|
||||
for (int i = sectionCount - diff; i < sectionCount; i++) {
|
||||
tmpSections[i] = EMPTY;
|
||||
tmpSectionLocks[i] = new Object();
|
||||
}
|
||||
blocks = tmpBlocks;
|
||||
sections = tmpSections;
|
||||
sectionLocks = tmpSectionLocks;
|
||||
maxSectionPosition = layer;
|
||||
if (biomes != null) {
|
||||
BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64];
|
||||
System.arraycopy(biomes, 0, tmpBiomes, 0, biomes.length);
|
||||
biomes = tmpBiomes;
|
||||
}
|
||||
if (light != null) {
|
||||
char[][] tmplight = new char[sectionCount][];
|
||||
System.arraycopy(light, 0, tmplight, 0, light.length);
|
||||
light = tmplight;
|
||||
}
|
||||
if (skyLight != null) {
|
||||
char[][] tmplight = new char[sectionCount][];
|
||||
System.arraycopy(skyLight, 0, tmplight, 0, skyLight.length);
|
||||
skyLight = tmplight;
|
||||
}
|
||||
resizeSectionsArrays(diff, true); // append new layer(s)
|
||||
}
|
||||
}
|
||||
|
||||
private void resizeSectionsArrays(int diff, boolean appendNew) {
|
||||
char[][] tmpBlocks = new char[sectionCount][];
|
||||
Section[] tmpSections = new Section[sectionCount];
|
||||
Object[] tmpSectionLocks = new Object[sectionCount];
|
||||
int destPos = appendNew ? 0 : diff;
|
||||
System.arraycopy(blocks, 0, tmpBlocks, destPos, blocks.length);
|
||||
System.arraycopy(sections, 0, tmpSections, destPos, sections.length);
|
||||
System.arraycopy(sectionLocks, 0, tmpSectionLocks, destPos, sections.length);
|
||||
int toFillFrom = appendNew ? sectionCount - diff : 0;
|
||||
int toFillTo = appendNew ? sectionCount : diff;
|
||||
for (int i = toFillFrom; i < toFillTo; i++) {
|
||||
tmpSections[i] = EMPTY;
|
||||
tmpSectionLocks[i] = new Object();
|
||||
}
|
||||
blocks = tmpBlocks;
|
||||
sections = tmpSections;
|
||||
sectionLocks = tmpSectionLocks;
|
||||
if (biomes != null) {
|
||||
BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64];
|
||||
System.arraycopy(biomes, 0, tmpBiomes, destPos, biomes.length);
|
||||
biomes = tmpBiomes;
|
||||
}
|
||||
if (light != null) {
|
||||
char[][] tmplight = new char[sectionCount][];
|
||||
System.arraycopy(light, 0, tmplight, destPos, light.length);
|
||||
light = tmplight;
|
||||
}
|
||||
if (skyLight != null) {
|
||||
char[][] tmplight = new char[sectionCount][];
|
||||
System.arraycopy(skyLight, 0, tmplight, destPos, skyLight.length);
|
||||
skyLight = tmplight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
package com.fastasyncworldedit.core.queue.implementation.blocks;
|
||||
|
||||
import com.fastasyncworldedit.core.Fawe;
|
||||
import com.fastasyncworldedit.core.FaweCache;
|
||||
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType;
|
||||
import com.fastasyncworldedit.core.math.BlockVector3ChunkMap;
|
||||
import com.fastasyncworldedit.core.queue.IBlocks;
|
||||
import com.fastasyncworldedit.core.queue.IChunkSet;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Equivalent to {@link CharSetBlocks} without any attempt to make thread-safe for improved performance.
|
||||
* This is currently only used as a "copy" of {@link CharSetBlocks} to provide to
|
||||
* {@link com.fastasyncworldedit.core.queue.IBatchProcessor} instances for processing without overlapping the continuing edit.
|
||||
*
|
||||
* @since 2.6.2
|
||||
*/
|
||||
public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks {
|
||||
|
||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||
|
||||
private final char defaultOrdinal;
|
||||
private char[][] blocks;
|
||||
private int minSectionPosition;
|
||||
private int maxSectionPosition;
|
||||
private int sectionCount;
|
||||
private BiomeType[][] biomes;
|
||||
private char[][] light;
|
||||
private char[][] skyLight;
|
||||
private BlockVector3ChunkMap<CompoundTag> tiles;
|
||||
private HashSet<CompoundTag> entities;
|
||||
private HashSet<UUID> entityRemoves;
|
||||
private Map<HeightMapType, int[]> heightMaps;
|
||||
private boolean fastMode;
|
||||
private int bitMask;
|
||||
|
||||
/**
|
||||
* New instance given the data stored in a {@link CharSetBlocks} instance.
|
||||
*
|
||||
* @since 2.6.2
|
||||
*/
|
||||
ThreadUnsafeCharBlocks(
|
||||
char[][] blocks,
|
||||
int minSectionPosition,
|
||||
int maxSectionPosition,
|
||||
BiomeType[][] biomes,
|
||||
int sectionCount,
|
||||
char[][] light,
|
||||
char[][] skyLight,
|
||||
BlockVector3ChunkMap<CompoundTag> tiles,
|
||||
HashSet<CompoundTag> entities,
|
||||
HashSet<UUID> entityRemoves,
|
||||
Map<HeightMapType, int[]> heightMaps,
|
||||
char defaultOrdinal,
|
||||
boolean fastMode,
|
||||
int bitMask
|
||||
) {
|
||||
this.blocks = blocks;
|
||||
this.minSectionPosition = minSectionPosition;
|
||||
this.maxSectionPosition = maxSectionPosition;
|
||||
this.biomes = biomes;
|
||||
this.sectionCount = sectionCount;
|
||||
this.light = light;
|
||||
this.skyLight = skyLight;
|
||||
this.tiles = tiles;
|
||||
this.entities = entities;
|
||||
this.entityRemoves = entityRemoves;
|
||||
this.heightMaps = heightMaps;
|
||||
this.defaultOrdinal = defaultOrdinal;
|
||||
this.fastMode = fastMode;
|
||||
this.bitMask = bitMask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSection(int layer) {
|
||||
layer -= minSectionPosition;
|
||||
return layer >= 0 && layer < blocks.length && blocks[layer] != null && blocks[layer].length == FaweCache.INSTANCE.BLOCKS_PER_LAYER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] load(int layer) {
|
||||
updateSectionIndexRange(layer);
|
||||
layer -= minSectionPosition;
|
||||
char[] arr = blocks[layer];
|
||||
if (arr == null) {
|
||||
arr = blocks[layer] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER];
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public char[] loadIfPresent(int layer) {
|
||||
if (layer < minSectionPosition || layer > maxSectionPosition) {
|
||||
return null;
|
||||
}
|
||||
layer -= minSectionPosition;
|
||||
return blocks[layer];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<BlockVector3, CompoundTag> getTiles() {
|
||||
return tiles == null ? Collections.emptyMap() : tiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getTile(int x, int y, int z) {
|
||||
return tiles == 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.fastasyncworldedit.core.util;
|
||||
|
||||
import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder;
|
||||
import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor;
|
||||
import com.fastasyncworldedit.core.queue.IBatchProcessor;
|
||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
public class ProcessorTraverser<T extends IBatchProcessor> {
|
||||
|
||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||
|
||||
private final T root;
|
||||
|
||||
public ProcessorTraverser(@Nonnull T root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public <U extends IBatchProcessor> @Nullable U find(Class<U> clazz) {
|
||||
try {
|
||||
Queue<IBatchProcessor> processors = new ArrayDeque<>();
|
||||
IBatchProcessor processor = root;
|
||||
do {
|
||||
if (clazz.isAssignableFrom(processor.getClass())) {
|
||||
return clazz.cast(processor);
|
||||
} else if (processor instanceof MultiBatchProcessor multiProcessor) {
|
||||
processors.addAll(multiProcessor.getBatchProcessors());
|
||||
} else if (processor instanceof BatchProcessorHolder holder) {
|
||||
processors.add(holder.getProcessor());
|
||||
}
|
||||
} while ((processor = processors.poll()) != null);
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Error traversing processors", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,7 +88,10 @@ public abstract class TaskManager {
|
||||
|
||||
/**
|
||||
* Run a bunch of tasks in parallel using the shared thread pool.
|
||||
*
|
||||
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "2.7.0")
|
||||
public void parallel(Collection<Runnable> runables) {
|
||||
for (Runnable run : runables) {
|
||||
pool.submit(run);
|
||||
@@ -101,8 +104,9 @@ public abstract class TaskManager {
|
||||
*
|
||||
* @param runnables the tasks to run
|
||||
* @param numThreads number of threads (null = config.yml parallel threads)
|
||||
* @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = true, since = "2.7.0")
|
||||
public void parallel(Collection<Runnable> runnables, @Nullable Integer numThreads) {
|
||||
if (runnables == null) {
|
||||
return;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SimpleRandomCollection<E> extends RandomCollection<E> {
|
||||
|
||||
@Override
|
||||
public E next(int x, int y, int z) {
|
||||
return map.ceilingEntry(getRandom().nextDouble(x, y, z)).getValue();
|
||||
return map.ceilingEntry(getRandom().nextDouble(x, y, z) * this.total).getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -203,6 +203,7 @@ public class ImageUtil {
|
||||
arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png";
|
||||
}
|
||||
URL url = new URL(arg);
|
||||
MainUtil.checkImageHost(url.toURI());
|
||||
BufferedImage img = MainUtil.readImage(url);
|
||||
if (img == null) {
|
||||
throw new IOException("Failed to read " + url + ", please try again later");
|
||||
@@ -218,7 +219,7 @@ public class ImageUtil {
|
||||
return MainUtil.readImage(file);
|
||||
}
|
||||
throw new InputParseException(Caption.of("fawe.error.invalid-image", TextComponent.of(arg)));
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new InputParseException(TextComponent.of(e.getMessage()));
|
||||
}
|
||||
}
|
||||
@@ -229,7 +230,9 @@ public class ImageUtil {
|
||||
if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) {
|
||||
arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png";
|
||||
}
|
||||
return new URL(arg).toURI();
|
||||
URI uri = new URI(arg);
|
||||
MainUtil.checkImageHost(uri);
|
||||
return uri;
|
||||
}
|
||||
if (arg.startsWith("file:/")) {
|
||||
arg = arg.replaceFirst("file:/+", "");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.fastasyncworldedit.core.util.task;
|
||||
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
|
||||
public class FaweForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
|
||||
|
||||
private final String nameFormat;
|
||||
|
||||
public FaweForkJoinWorkerThreadFactory(String nameFormat) {
|
||||
this.nameFormat = nameFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
|
||||
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
||||
worker.setName(String.format(nameFormat, worker.getPoolIndex()));
|
||||
return worker;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.fastasyncworldedit.core.util.task;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Executor service that queues tasks based on keys, executing tasks on a configurable {@link ThreadPoolExecutor}
|
||||
*
|
||||
* @param <K> Key type
|
||||
* @since 2.6.2
|
||||
*/
|
||||
public class KeyQueuedExecutorService<K> {
|
||||
|
||||
private final ExecutorService parent;
|
||||
private final Map<K, KeyRunner> keyQueue = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new {@link KeyQueuedExecutorService} instance
|
||||
*
|
||||
* @param parent Parent {@link ExecutorService} to use for actual task completion
|
||||
*/
|
||||
public KeyQueuedExecutorService(ExecutorService parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link ThreadPoolExecutor#shutdown()}
|
||||
*/
|
||||
public void shutdown() {
|
||||
parent.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link ThreadPoolExecutor#shutdownNow()}
|
||||
*/
|
||||
@Nonnull
|
||||
public List<Runnable> shutdownNow() {
|
||||
return parent.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link ThreadPoolExecutor#isShutdown()}
|
||||
*/
|
||||
public boolean isShutdown() {
|
||||
return parent.isShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link ThreadPoolExecutor#isTerminated()}
|
||||
*/
|
||||
public boolean isTerminated() {
|
||||
return parent.isTerminated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link ThreadPoolExecutor#awaitTermination(long, TimeUnit)}
|
||||
*/
|
||||
public boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) throws InterruptedException {
|
||||
return parent.awaitTermination(timeout, unit);
|
||||
}
|
||||
|
||||
protected <T> FutureTask<T> newTaskFor(Runnable runnable, T value) {
|
||||
return new FutureTask<>(runnable, value);
|
||||
}
|
||||
|
||||
protected <T> FutureTask<T> newTaskFor(Callable<T> callable) {
|
||||
return new FutureTask<>(callable);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public <T> Future<T> submit(@Nonnull K key, @Nonnull Callable<T> task) {
|
||||
FutureTask<T> ftask = newTaskFor(task);
|
||||
execute(key, ftask);
|
||||
return ftask;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public <T> Future<T> submit(@Nonnull K key, @Nonnull Runnable task, T result) {
|
||||
FutureTask<T> ftask = newTaskFor(task, result);
|
||||
execute(key, ftask);
|
||||
return ftask;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Future<?> submit(@Nonnull K key, @Nonnull Runnable task) {
|
||||
FutureTask<Void> ftask = newTaskFor(task, null);
|
||||
execute(key, ftask);
|
||||
return ftask;
|
||||
}
|
||||
|
||||
public void execute(@Nonnull K key, @Nonnull FutureTask<?> command) {
|
||||
synchronized (keyQueue) {
|
||||
boolean triggerRun = false;
|
||||
KeyRunner runner = keyQueue.get(key);
|
||||
if (runner == null) {
|
||||
runner = new KeyRunner(key);
|
||||
keyQueue.put(key, runner);
|
||||
triggerRun = true;
|
||||
}
|
||||
runner.add(command);
|
||||
if (triggerRun) {
|
||||
runner.triggerRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class KeyRunner {
|
||||
|
||||
private final Queue<FutureTask<?>> tasks = new ConcurrentLinkedQueue<>();
|
||||
private final K key;
|
||||
|
||||
private KeyRunner(K key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
void add(FutureTask<?> task) {
|
||||
if (!tasks.add(task)) {
|
||||
throw new RejectedExecutionException(rejection());
|
||||
}
|
||||
}
|
||||
|
||||
void triggerRun() {
|
||||
Runnable task = tasks.poll();
|
||||
if (task == null) {
|
||||
throw new RejectedExecutionException(rejection());
|
||||
}
|
||||
try {
|
||||
run(task);
|
||||
} catch (RejectedExecutionException e) {
|
||||
synchronized (keyQueue) {
|
||||
keyQueue.remove(key);
|
||||
}
|
||||
throw new RejectedExecutionException(rejection(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void run(Runnable task) {
|
||||
parent.execute(() -> {
|
||||
task.run();
|
||||
Runnable next = tasks.poll();
|
||||
if (next == null) {
|
||||
synchronized (keyQueue) {
|
||||
next = tasks.poll();
|
||||
if (next == null) {
|
||||
keyQueue.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next != null) {
|
||||
run(next);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String rejection() {
|
||||
return "Task for the key '" + key + "' rejected";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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, "");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,7 +82,10 @@ public class BiomeCommands {
|
||||
aliases = {"biomels", "/biomelist", "/listbiomes"},
|
||||
desc = "Gets all biomes available."
|
||||
)
|
||||
@CommandPermissions("worldedit.biome.list")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.biome.list",
|
||||
queued = false
|
||||
)
|
||||
public void biomeList(
|
||||
Actor actor,
|
||||
@ArgFlag(name = 'p', desc = "Page number.", def = "1")
|
||||
|
||||
@@ -134,6 +134,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.util.List;
|
||||
@@ -310,11 +311,11 @@ public class BrushCommands {
|
||||
},
|
||||
desc = "Join multiple objects together in a curve",
|
||||
descFooter =
|
||||
"Click to select some objects,click the same block twice to connect the objects.\n"
|
||||
+ "Insufficient brush radius, or clicking the the wrong spot will result in undesired shapes. The shapes must be simple lines or loops.\n"
|
||||
+ "Pic1: http://i.imgur.com/CeRYAoV.jpg -> http://i.imgur.com/jtM0jA4.png\n"
|
||||
+ "Pic2: http://i.imgur.com/bUeyc72.png -> http://i.imgur.com/tg6MkcF.png"
|
||||
+ "Tutorial: https://www.planetminecraft.com/blog/fawe-tutorial/"
|
||||
"""
|
||||
Click to select some objects,click the same block twice to connect the objects.
|
||||
Insufficient brush radius, or clicking the the wrong spot will result in undesired shapes. The shapes must be simple lines or loops.
|
||||
Pic1: http://i.imgur.com/CeRYAoV.jpg -> http://i.imgur.com/jtM0jA4.png
|
||||
Pic2: http://i.imgur.com/bUeyc72.png -> http://i.imgur.com/tg6MkcF.pngTutorial: https://www.planetminecraft.com/blog/fawe-tutorial/"""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.spline")
|
||||
public void splineBrush(
|
||||
@@ -337,9 +338,10 @@ public class BrushCommands {
|
||||
"vaesweep"
|
||||
},
|
||||
desc = "Sweep your clipboard content along a curve",
|
||||
descFooter = "Sweeps your clipboard content along a curve.\n"
|
||||
+ "Define a curve by selecting the individual points with a brush\n"
|
||||
+ "Set [copies] to a value > 0 if you want to have your selection pasted a limited amount of times equally spaced on the curve"
|
||||
descFooter = """
|
||||
Sweeps your clipboard content along a curve.
|
||||
Define a curve by selecting the individual points with a brush
|
||||
Set [copies] to a value > 0 if you want to have your selection pasted a limited amount of times equally spaced on the curve"""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.sweep")
|
||||
public void sweepBrush(
|
||||
@@ -520,11 +522,9 @@ public class BrushCommands {
|
||||
@Switch(name = 'a', desc = "Use image Alpha") boolean alpha,
|
||||
@Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut
|
||||
)
|
||||
throws WorldEditException, IOException {
|
||||
throws WorldEditException, IOException, URISyntaxException {
|
||||
URL url = new URL(imageURL);
|
||||
if (!url.getHost().equalsIgnoreCase("i.imgur.com")) {
|
||||
throw new IOException("Only i.imgur.com links are allowed!");
|
||||
}
|
||||
MainUtil.checkImageHost(url.toURI());
|
||||
BufferedImage image = MainUtil.readImage(url);
|
||||
worldEdit.checkMaxBrushRadius(radius);
|
||||
if (yscale != 1) {
|
||||
@@ -636,10 +636,10 @@ public class BrushCommands {
|
||||
@Command(
|
||||
name = "layer",
|
||||
desc = "Replaces terrain with a layer.",
|
||||
descFooter = "Replaces terrain with a layer.\n"
|
||||
+ "Example: /br layer 5 oak_planks,orange_stained_glass,magenta_stained_glass,black_wool - Places several " +
|
||||
"layers on a surface\n"
|
||||
+ "Pic: https://i.imgur.com/XV0vYoX.png"
|
||||
descFooter = """
|
||||
Replaces terrain with a layer.
|
||||
Example: /br layer 5 oak_planks,orange_stained_glass,magenta_stained_glass,black_wool - Places several layers on a surface
|
||||
Pic: https://i.imgur.com/XV0vYoX.png"""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.layer")
|
||||
public void surfaceLayer(
|
||||
@@ -658,11 +658,11 @@ public class BrushCommands {
|
||||
@Command(
|
||||
name = "splatter",
|
||||
desc = "Splatter a pattern on a surface",
|
||||
descFooter = "Sets a bunch of blocks randomly on a surface.\n"
|
||||
+ "Pic: https://i.imgur.com/hMD29oO.png\n"
|
||||
+ "Example: /br splatter stone,dirt 30 15\n"
|
||||
+ "Note: The seeds define how many splotches there are, recursion defines how large, "
|
||||
+ "solid defines whether the pattern is applied per seed, else per block."
|
||||
descFooter = """
|
||||
Sets a bunch of blocks randomly on a surface.
|
||||
Pic: https://i.imgur.com/hMD29oO.png
|
||||
Example: /br splatter stone,dirt 30 15
|
||||
Note: The seeds define how many splotches there are, recursion defines how large, solid defines whether the pattern is applied per seed, else per block."""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.splatter")
|
||||
public void splatterBrush(
|
||||
@@ -691,9 +691,10 @@ public class BrushCommands {
|
||||
"scommand"
|
||||
},
|
||||
desc = "Run commands at random points on a surface",
|
||||
descFooter = "Run commands at random points on a surface\n"
|
||||
+ " - Your selection will be expanded to the specified size around each point\n"
|
||||
+ " - Placeholders: {x}, {y}, {z}, {world}, {size}"
|
||||
descFooter = """
|
||||
Run commands at random points on a surface
|
||||
- Your selection will be expanded to the specified size around each point
|
||||
- Placeholders: {x}, {y}, {z}, {world}, {size}"""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.scattercommand")
|
||||
public void scatterCommandBrush(
|
||||
@@ -723,9 +724,10 @@ public class BrushCommands {
|
||||
name = "height",
|
||||
aliases = {"heightmap"},
|
||||
desc = "Raise or lower terrain using a heightmap",
|
||||
descFooter = "This brush raises and lowers land.\n"
|
||||
+ "Note: Use a negative yscale to reduce height\n"
|
||||
+ "Snow Pic: https://i.imgur.com/Hrzn0I4.png"
|
||||
descFooter = """
|
||||
This brush raises and lowers land.
|
||||
Note: Use a negative yscale to reduce height
|
||||
Snow Pic: https://i.imgur.com/Hrzn0I4.png"""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.height")
|
||||
public void heightBrush(
|
||||
@@ -890,9 +892,11 @@ public class BrushCommands {
|
||||
"copypasta"
|
||||
},
|
||||
desc = "Copy Paste brush",
|
||||
descFooter = "Left click the base of an object to copy.\n" + "Right click to paste\n"
|
||||
+ "Note: Works well with the clipboard scroll action\n"
|
||||
+ "Video: https://www.youtube.com/watch?v=RPZIaTbqoZw"
|
||||
descFooter = """
|
||||
Left click the base of an object to copy.
|
||||
Right click to paste
|
||||
Note: Works well with the clipboard scroll action
|
||||
Video: https://www.youtube.com/watch?v=RPZIaTbqoZw"""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.copy")
|
||||
public void copy(
|
||||
@@ -914,9 +918,10 @@ public class BrushCommands {
|
||||
name = "command",
|
||||
aliases = {"cmd"},
|
||||
desc = "Command brush",
|
||||
descFooter = "Run the commands at the clicked position.\n"
|
||||
+ " - Your selection will be expanded to the specified size around each point\n"
|
||||
+ " - Placeholders: {x}, {y}, {z}, {world}, {size}"
|
||||
descFooter = """
|
||||
Run the commands at the clicked position.
|
||||
- Your selection will be expanded to the specified size around each point
|
||||
- Placeholders: {x}, {y}, {z}, {world}, {size}"""
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.command")
|
||||
public void command(
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -149,14 +149,11 @@ public class GeneralCommands {
|
||||
String arg0 = args.get(0).toLowerCase(Locale.ENGLISH);
|
||||
String flipped;
|
||||
switch (arg0) {
|
||||
case "on":
|
||||
flipped = "off";
|
||||
break;
|
||||
case "off":
|
||||
flipped = "on";
|
||||
break;
|
||||
default:
|
||||
case "on" -> flipped = "off";
|
||||
case "off" -> flipped = "on";
|
||||
default -> {
|
||||
return TextComponent.of("There is no replacement for //fast " + arg0);
|
||||
}
|
||||
}
|
||||
return CommandUtil.createNewCommandReplacementText("//perf " + flipped);
|
||||
}
|
||||
@@ -362,7 +359,10 @@ public class GeneralCommands {
|
||||
descFooter = "This is dependent on platform implementation. " +
|
||||
"Not all platforms support watchdog hooks, or contain a watchdog."
|
||||
)
|
||||
@CommandPermissions("worldedit.watchdog")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.watchdog",
|
||||
queued = false
|
||||
)
|
||||
public void watchdog(
|
||||
Actor actor, LocalSession session,
|
||||
@Arg(desc = "The mode to set the watchdog hook to", def = "")
|
||||
@@ -424,7 +424,10 @@ public class GeneralCommands {
|
||||
aliases = {"/searchitem", "/l", "/search"},
|
||||
desc = "Search for an item"
|
||||
)
|
||||
@CommandPermissions("worldedit.searchitem")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.searchitem",
|
||||
queued = false
|
||||
)
|
||||
public void searchItem(
|
||||
Actor actor,
|
||||
@Switch(name = 'b', desc = "Only search for blocks")
|
||||
@@ -573,7 +576,10 @@ public class GeneralCommands {
|
||||
aliases = {"tips"},
|
||||
desc = "Toggle FAWE tips"
|
||||
)
|
||||
@CommandPermissions("fawe.tips")
|
||||
@CommandPermissions(
|
||||
value = "fawe.tips",
|
||||
queued = false
|
||||
)
|
||||
public void tips(Actor actor, LocalSession session) throws WorldEditException {
|
||||
if (actor.togglePermission("fawe.tips")) {
|
||||
actor.print(Caption.of("fawe.info.worldedit.toggle.tips.on"));
|
||||
@@ -627,7 +633,6 @@ public class GeneralCommands {
|
||||
String command = "/searchitem " + (blocksOnly ? "-b " : "") + (itemsOnly ? "-i " : "") + "-p %page% " + search;
|
||||
Map<String, Component> results = new TreeMap<>();
|
||||
String idMatch = search.replace(' ', '_');
|
||||
String nameMatch = search.toLowerCase(Locale.ROOT);
|
||||
for (ItemType searchType : ItemType.REGISTRY) {
|
||||
if (blocksOnly && !searchType.hasBlockType()) {
|
||||
continue;
|
||||
|
||||
@@ -65,6 +65,7 @@ import org.jetbrains.annotations.Range;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -119,18 +120,15 @@ public class GenerationCommands {
|
||||
final double radiusX;
|
||||
final double radiusZ;
|
||||
switch (radii.size()) {
|
||||
case 1:
|
||||
radiusX = radiusZ = Math.max(1, radii.get(0));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0));
|
||||
case 2 -> {
|
||||
radiusX = Math.max(1, radii.get(0));
|
||||
radiusZ = Math.max(1, radii.get(1));
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
default -> {
|
||||
actor.print(Caption.of("worldedit.cyl.invalid-radius"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
worldEdit.checkMaxRadius(radiusX);
|
||||
worldEdit.checkMaxRadius(radiusZ);
|
||||
@@ -169,18 +167,15 @@ public class GenerationCommands {
|
||||
final double radiusX;
|
||||
final double radiusZ;
|
||||
switch (radii.size()) {
|
||||
case 1:
|
||||
radiusX = radiusZ = Math.max(1, radii.get(0));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0));
|
||||
case 2 -> {
|
||||
radiusX = Math.max(1, radii.get(0));
|
||||
radiusZ = Math.max(1, radii.get(1));
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
default -> {
|
||||
actor.print(Caption.of("worldedit.cyl.invalid-radius"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
worldEdit.checkMaxRadius(radiusX);
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -223,7 +223,10 @@ public class HistorySubCommands {
|
||||
aliases = {"summary", "summarize"},
|
||||
desc = "Summarize an edit"
|
||||
)
|
||||
@CommandPermissions("worldedit.history.info")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.history.info",
|
||||
queued = false
|
||||
)
|
||||
public synchronized void summary(
|
||||
Player player, RollbackDatabase database, Arguments arguments,
|
||||
@Arg(desc = "Player uuid/name")
|
||||
@@ -314,8 +317,7 @@ public class HistorySubCommands {
|
||||
public Component apply(@Nullable Supplier<? extends ChangeSet> input) {
|
||||
ChangeSet edit = input.get();
|
||||
|
||||
if (edit instanceof RollbackOptimizedHistory) {
|
||||
RollbackOptimizedHistory rollback = (RollbackOptimizedHistory) edit;
|
||||
if (edit instanceof RollbackOptimizedHistory rollback) {
|
||||
|
||||
UUID uuid = rollback.getUUID();
|
||||
int index = rollback.getIndex();
|
||||
@@ -368,7 +370,10 @@ public class HistorySubCommands {
|
||||
aliases = {"inspect", "search", "near"},
|
||||
desc = "Find nearby edits"
|
||||
)
|
||||
@CommandPermissions("worldedit.history.find")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.history.find",
|
||||
queued = false
|
||||
)
|
||||
public synchronized void find(
|
||||
Player player, World world, RollbackDatabase database, Arguments arguments,
|
||||
@ArgFlag(name = 'u', def = "", desc = "String user")
|
||||
@@ -429,7 +434,10 @@ public class HistorySubCommands {
|
||||
aliases = {"distribution"},
|
||||
desc = "View block distribution for an edit"
|
||||
)
|
||||
@CommandPermissions("worldedit.history.distr")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.history.distr",
|
||||
queued = false
|
||||
)
|
||||
public void distr(
|
||||
Player player, LocalSession session, RollbackDatabase database, Arguments arguments,
|
||||
@Arg(desc = "Player uuid/name")
|
||||
@@ -468,7 +476,10 @@ public class HistorySubCommands {
|
||||
name = "list",
|
||||
desc = "List your history"
|
||||
)
|
||||
@CommandPermissions("worldedit.history.list")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.history.list",
|
||||
queued = false
|
||||
)
|
||||
public void list(
|
||||
Player player, LocalSession session, RollbackDatabase database, Arguments arguments,
|
||||
@Arg(desc = "Player uuid/name")
|
||||
@@ -476,7 +487,6 @@ public class HistorySubCommands {
|
||||
@ArgFlag(name = 'p', desc = "Page to view.", def = "")
|
||||
Integer page
|
||||
) {
|
||||
int index = session.getHistoryIndex();
|
||||
List<Supplier<? extends ChangeSet>> history = Lists.transform(
|
||||
session.getHistory(),
|
||||
(Function<ChangeSet, Supplier<ChangeSet>>) input -> () -> input
|
||||
|
||||
@@ -60,7 +60,10 @@ public class NavigationCommands {
|
||||
aliases = {"!", "/unstuck"},
|
||||
desc = "Escape from being stuck inside a block"
|
||||
)
|
||||
@CommandPermissions("worldedit.navigation.unstuck")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.navigation.unstuck",
|
||||
queued = false
|
||||
)
|
||||
public void unstuck(Player player) throws WorldEditException {
|
||||
player.findFreePosition();
|
||||
player.print(Caption.of("worldedit.unstuck.moved"));
|
||||
@@ -71,7 +74,10 @@ public class NavigationCommands {
|
||||
aliases = {"asc", "/asc", "/ascend"},
|
||||
desc = "Go up a floor"
|
||||
)
|
||||
@CommandPermissions("worldedit.navigation.ascend")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.navigation.ascend",
|
||||
queued = false
|
||||
)
|
||||
public void ascend(
|
||||
Player player,
|
||||
@Arg(desc = "# of levels to ascend", def = "1")
|
||||
@@ -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 = "")
|
||||
|
||||
@@ -140,7 +140,10 @@ public class RegionCommands {
|
||||
name = "/test",
|
||||
desc = "test region"
|
||||
)
|
||||
@CommandPermissions("worldedit.region.test")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.region.test",
|
||||
queued = false
|
||||
)
|
||||
@Logging(REGION)
|
||||
public void test(
|
||||
Actor actor, EditSession editSession,
|
||||
@@ -175,7 +178,10 @@ public class RegionCommands {
|
||||
aliases = "/nbt",
|
||||
desc = "View nbt info for a block"
|
||||
)
|
||||
@CommandPermissions("worldedit.nbtinfo")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.nbtinfo",
|
||||
queued = false
|
||||
)
|
||||
public void nbtinfo(Player player, EditSession editSession) {
|
||||
Location pos = player.getBlockTrace(128);
|
||||
if (pos == null) {
|
||||
@@ -228,13 +234,12 @@ public class RegionCommands {
|
||||
@Switch(name = 'h', desc = "Generate only a shell")
|
||||
boolean shell
|
||||
) throws WorldEditException {
|
||||
if (!(region instanceof CuboidRegion)) {
|
||||
if (!(region instanceof CuboidRegion cuboidregion)) {
|
||||
actor.print(Caption.of("worldedit.line.cuboid-only"));
|
||||
return 0;
|
||||
}
|
||||
checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
|
||||
|
||||
CuboidRegion cuboidregion = (CuboidRegion) region;
|
||||
BlockVector3 pos1 = cuboidregion.getPos1();
|
||||
BlockVector3 pos2 = cuboidregion.getPos2();
|
||||
int blocksChanged = editSession.drawLine(pattern, pos1, pos2, thickness, !shell);
|
||||
@@ -261,13 +266,12 @@ public class RegionCommands {
|
||||
@Switch(name = 'h', desc = "Generate only a shell")
|
||||
boolean shell
|
||||
) throws WorldEditException {
|
||||
if (!(region instanceof ConvexPolyhedralRegion)) {
|
||||
if (!(region instanceof ConvexPolyhedralRegion cpregion)) {
|
||||
actor.print(Caption.of("worldedit.curve.invalid-type"));
|
||||
return 0;
|
||||
}
|
||||
checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
|
||||
|
||||
ConvexPolyhedralRegion cpregion = (ConvexPolyhedralRegion) region;
|
||||
List<BlockVector3> vectors = new ArrayList<>(cpregion.getVertices());
|
||||
|
||||
int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell);
|
||||
@@ -468,7 +472,10 @@ public class RegionCommands {
|
||||
desc = "Bypass region restrictions",
|
||||
descFooter = "Bypass region restrictions"
|
||||
)
|
||||
@CommandPermissions("fawe.admin")
|
||||
@CommandPermissions(
|
||||
value = "fawe.admin",
|
||||
queued = false
|
||||
)
|
||||
public void wea(Actor actor) throws WorldEditException {
|
||||
if (actor.togglePermission("fawe.bypass")) {
|
||||
actor.print(Caption.of("fawe.info.worldedit.bypassed"));
|
||||
@@ -697,7 +704,7 @@ public class RegionCommands {
|
||||
actor.print(Caption.of("fawe.regen.time"));
|
||||
//FAWE end
|
||||
RegenOptions options = RegenOptions.builder()
|
||||
.seed(!randomSeed ? seed : new Long(ThreadLocalRandom.current().nextLong()))
|
||||
.seed(!randomSeed ? seed : Long.valueOf(ThreadLocalRandom.current().nextLong()))
|
||||
.regenBiomes(regenBiomes)
|
||||
.biomeType(biomeType)
|
||||
.build();
|
||||
@@ -718,9 +725,10 @@ public class RegionCommands {
|
||||
@Command(
|
||||
name = "/deform",
|
||||
desc = "Deforms a selected region with an expression",
|
||||
descFooter = "The expression is executed for each block and is expected\n"
|
||||
+ "to modify the variables x, y and z to point to a new block\n"
|
||||
+ "to fetch. For details, see https://ehub.to/we/expr"
|
||||
descFooter = """
|
||||
The expression is executed for each block and is expected
|
||||
to modify the variables x, y and z to point to a new block
|
||||
to fetch. For details, see https://ehub.to/we/expr"""
|
||||
)
|
||||
@CommandPermissions("worldedit.region.deform")
|
||||
@Logging(ALL)
|
||||
@@ -794,9 +802,10 @@ public class RegionCommands {
|
||||
@Command(
|
||||
name = "/hollow",
|
||||
desc = "Hollows out the object contained in this selection",
|
||||
descFooter = "Hollows out the object contained in this selection.\n"
|
||||
+ "Optionally fills the hollowed out part with the given block.\n"
|
||||
+ "Thickness is measured in manhattan distance."
|
||||
descFooter = """
|
||||
Hollows out the object contained in this selection.
|
||||
Optionally fills the hollowed out part with the given block.
|
||||
Thickness is measured in manhattan distance."""
|
||||
)
|
||||
@CommandPermissions("worldedit.region.hollow")
|
||||
@Logging(REGION)
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.io.schematic.MinecraftStructure;
|
||||
import com.fastasyncworldedit.core.util.MainUtil;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
@@ -46,6 +45,7 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||
import com.sk89q.worldedit.math.transform.AffineTransform;
|
||||
import com.sk89q.worldedit.math.transform.Transform;
|
||||
import com.sk89q.worldedit.session.ClipboardHolder;
|
||||
import com.sk89q.worldedit.util.formatting.component.ErrorFormat;
|
||||
@@ -90,6 +90,8 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.fastasyncworldedit.core.util.ReflectionUtils.as;
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -314,7 +314,10 @@ public class SelectionCommands {
|
||||
name = "/wand",
|
||||
desc = "Get the wand object"
|
||||
)
|
||||
@CommandPermissions("worldedit.wand")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.wand",
|
||||
queued = false
|
||||
)
|
||||
public void wand(
|
||||
Player player, LocalSession session,
|
||||
@Switch(name = 'n', desc = "Get a navigation wand") boolean navWand
|
||||
@@ -348,7 +351,10 @@ public class SelectionCommands {
|
||||
aliases = {"/toggleeditwand"},
|
||||
desc = "Remind the user that the wand is now a tool and can be unbound with /tool none."
|
||||
)
|
||||
@CommandPermissions("worldedit.wand.toggle")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.wand.toggle",
|
||||
queued = false
|
||||
)
|
||||
public void toggleWand(Player player) {
|
||||
player.print(
|
||||
Caption.of(
|
||||
@@ -499,7 +505,10 @@ public class SelectionCommands {
|
||||
name = "/size",
|
||||
desc = "Get information about the selection"
|
||||
)
|
||||
@CommandPermissions("worldedit.selection.size")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.selection.size",
|
||||
queued = false
|
||||
)
|
||||
public void size(
|
||||
Actor actor, World world, LocalSession session,
|
||||
@Switch(name = 'c', desc = "Get clipboard info instead")
|
||||
@@ -734,6 +743,7 @@ public class SelectionCommands {
|
||||
box.appendCommand("sphere", Caption.of("worldedit.select.sphere.description"), "//sel sphere");
|
||||
box.appendCommand("cyl", Caption.of("worldedit.select.cyl.description"), "//sel cyl");
|
||||
box.appendCommand("convex", Caption.of("worldedit.select.convex.description"), "//sel convex");
|
||||
|
||||
//FAWE start
|
||||
box.appendCommand("polyhedral", Caption.of("fawe.selection.sel.polyhedral"), "//sel polyhedral");
|
||||
box.appendCommand("fuzzy[=<mask>]", Caption.of("fawe.selection.sel.fuzzy-instruction"), "//sel fuzzy[=<mask>]");
|
||||
|
||||
@@ -98,7 +98,10 @@ public class SnapshotCommands {
|
||||
name = "list",
|
||||
desc = "List snapshots"
|
||||
)
|
||||
@CommandPermissions("worldedit.snapshots.list")
|
||||
@CommandPermissions(
|
||||
value = "worldedit.snapshots.list",
|
||||
queued = false
|
||||
)
|
||||
void list(
|
||||
Actor actor, World world,
|
||||
@ArgFlag(name = 'p', desc = "Page of results to return", def = "1")
|
||||
@@ -127,8 +130,7 @@ public class SnapshotCommands {
|
||||
TextComponent.of(world.getName())
|
||||
));
|
||||
|
||||
if (config.snapshotDatabase instanceof FileSystemSnapshotDatabase) {
|
||||
FileSystemSnapshotDatabase db = (FileSystemSnapshotDatabase) config.snapshotDatabase;
|
||||
if (config.snapshotDatabase instanceof FileSystemSnapshotDatabase db) {
|
||||
Path root = db.getRoot();
|
||||
if (Files.isDirectory(root)) {
|
||||
WorldEdit.logger.info("No snapshots were found for world '"
|
||||
|
||||
@@ -95,7 +95,7 @@ public class SnapshotUtilCommands {
|
||||
if (snapshotName != null) {
|
||||
URI uri = resolveSnapshotName(config, snapshotName);
|
||||
Optional<Snapshot> snapOpt = config.snapshotDatabase.getSnapshot(uri);
|
||||
if (!snapOpt.isPresent()) {
|
||||
if (snapOpt.isEmpty()) {
|
||||
actor.print(Caption.of("worldedit.restore.not-available"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public class ToolUtilCommands {
|
||||
@Arg(desc = "The range of the brush")
|
||||
int range
|
||||
) throws WorldEditException {
|
||||
session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()).setRange(range);
|
||||
session.getBrushTool(player).setRange(range);
|
||||
player.print(Caption.of("worldedit.tool.range.set"));
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public class ToolUtilCommands {
|
||||
) throws WorldEditException {
|
||||
we.checkMaxBrushRadius(size);
|
||||
|
||||
session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()).setSize(size);
|
||||
session.getBrushTool(player).setSize(size);
|
||||
player.print(Caption.of("worldedit.tool.size.set"));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.fastasyncworldedit.core.util.StringMan;
|
||||
import com.fastasyncworldedit.core.util.TaskManager;
|
||||
import com.fastasyncworldedit.core.util.image.ImageUtil;
|
||||
import com.fastasyncworldedit.core.util.task.DelegateConsumer;
|
||||
import com.google.common.base.Function;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.IncompleteRegionException;
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
@@ -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();
|
||||
|
||||
@@ -27,9 +27,13 @@ import com.sk89q.worldedit.function.operation.Operation;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.math.transform.AffineTransform;
|
||||
import com.sk89q.worldedit.math.transform.Transform;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.session.ClipboardHolder;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class ClipboardBrush implements Brush {
|
||||
|
||||
private final ClipboardHolder holder;
|
||||
@@ -38,6 +42,9 @@ public class ClipboardBrush implements Brush {
|
||||
private final boolean pasteEntities;
|
||||
private final boolean pasteBiomes;
|
||||
private final Mask sourceMask;
|
||||
//FAWE start - random rotation
|
||||
private final boolean randomRotate;
|
||||
//FAWE end
|
||||
|
||||
public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin) {
|
||||
this.holder = holder;
|
||||
@@ -46,23 +53,48 @@ public class ClipboardBrush implements Brush {
|
||||
this.pasteBiomes = false;
|
||||
this.pasteEntities = false;
|
||||
this.sourceMask = null;
|
||||
//FAWE start - random rotation
|
||||
this.randomRotate = false;
|
||||
//FAWE end
|
||||
}
|
||||
|
||||
public ClipboardBrush(
|
||||
ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities,
|
||||
boolean pasteBiomes, Mask sourceMask
|
||||
) {
|
||||
//FAWE start - random rotation
|
||||
this(holder, ignoreAirBlocks, usingOrigin, pasteEntities, pasteBiomes, sourceMask, false);
|
||||
}
|
||||
|
||||
public ClipboardBrush(
|
||||
ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities,
|
||||
boolean pasteBiomes, Mask sourceMask, boolean randomRotate
|
||||
) {
|
||||
//FAWE end
|
||||
this.holder = holder;
|
||||
this.ignoreAirBlocks = ignoreAirBlocks;
|
||||
this.usingOrigin = usingOrigin;
|
||||
this.pasteEntities = pasteEntities;
|
||||
this.pasteBiomes = pasteBiomes;
|
||||
this.sourceMask = sourceMask;
|
||||
this.randomRotate = randomRotate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws
|
||||
MaxChangedBlocksException {
|
||||
//FAWE start - random rotation
|
||||
Transform originalTransform = holder.getTransform();
|
||||
Transform transform = new AffineTransform();
|
||||
if (this.randomRotate) {
|
||||
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
|
||||
transform = ((AffineTransform) transform).rotateY(rotate);
|
||||
if (originalTransform != null) {
|
||||
transform = originalTransform.combine(transform);
|
||||
}
|
||||
}
|
||||
holder.setTransform(transform);
|
||||
//FAWE end
|
||||
Clipboard clipboard = holder.getClipboard();
|
||||
Region region = clipboard.getRegion();
|
||||
BlockVector3 centerOffset = region.getCenter().toBlockPoint().subtract(clipboard.getOrigin());
|
||||
@@ -77,6 +109,10 @@ public class ClipboardBrush implements Brush {
|
||||
.build();
|
||||
|
||||
Operations.completeLegacy(operation);
|
||||
//FAWE start - random rotation
|
||||
// reset transform
|
||||
holder.setTransform(originalTransform);
|
||||
//FAWE end
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ConfirmHandler implements CommandCallListener {
|
||||
}
|
||||
Optional<Actor> actorOpt = parameters.injectedValue(Key.of(Actor.class));
|
||||
|
||||
if (!actorOpt.isPresent()) {
|
||||
if (actorOpt.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Actor actor = actorOpt.get();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,9 @@
|
||||
|
||||
package com.sk89q.worldedit.extension.platform;
|
||||
|
||||
import com.fastasyncworldedit.core.configuration.Caption;
|
||||
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
||||
import com.fastasyncworldedit.core.util.TaskManager;
|
||||
import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue;
|
||||
import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.internal.cui.CUIEvent;
|
||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||
@@ -68,7 +67,7 @@ public abstract class AbstractNonPlayerActor implements Actor {
|
||||
|
||||
// Queue for async tasks
|
||||
private final AtomicInteger runningCount = new AtomicInteger();
|
||||
private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue((thread, throwable) -> {
|
||||
private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue((thread, throwable) -> {
|
||||
while (throwable.getCause() != null) {
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
@@ -82,7 +81,7 @@ public abstract class AbstractNonPlayerActor implements Actor {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, this::getUniqueId);
|
||||
|
||||
/**
|
||||
* Run a task either async, or on the current thread.
|
||||
|
||||
@@ -25,7 +25,7 @@ import com.fastasyncworldedit.core.math.MutableBlockVector3;
|
||||
import com.fastasyncworldedit.core.regions.FaweMaskManager;
|
||||
import com.fastasyncworldedit.core.util.TaskManager;
|
||||
import com.fastasyncworldedit.core.util.WEManager;
|
||||
import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue;
|
||||
import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
@@ -81,7 +81,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
|
||||
// Queue for async tasks
|
||||
private final AtomicInteger runningCount = new AtomicInteger();
|
||||
private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue(
|
||||
private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue(
|
||||
(thread, throwable) -> {
|
||||
while (throwable.getCause() != null) {
|
||||
throwable = throwable.getCause();
|
||||
@@ -96,7 +96,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, this::getUniqueId);
|
||||
|
||||
public AbstractPlayerActor(Map<String, Object> meta) {
|
||||
this.meta = meta;
|
||||
@@ -709,7 +709,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
@Override
|
||||
public void checkPermission(String permission) throws AuthorizationException {
|
||||
if (!hasPermission(permission)) {
|
||||
throw new AuthorizationException();
|
||||
throw new AuthorizationException(Caption.of("fawe.error.no-perm", permission));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -321,10 +321,24 @@ public class PlatformManager {
|
||||
return queryCapability(Capability.CONFIGURATION).getConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current supported {@link SideEffect}s.
|
||||
*
|
||||
* @return the supported side effects
|
||||
* @throws NoCapablePlatformException thrown if no platform is capable
|
||||
*/
|
||||
public Collection<SideEffect> getSupportedSideEffects() {
|
||||
return queryCapability(Capability.WORLD_EDITING).getSupportedSideEffects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initialized state of the Platform.
|
||||
* {@return if the platform manager is initialized}
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return initialized.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* You shouldn't have been calling this anyways, but this is now deprecated. Either don't
|
||||
* fire this event at all, or fire the new event via the event bus if you're a platform.
|
||||
|
||||
@@ -266,8 +266,8 @@ public class SpongeSchematicReader extends NBTSchematicReader {
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
values.put("id", id);
|
||||
//FAWE end
|
||||
values.put("id", values.get("Id"));
|
||||
values.remove("Id");
|
||||
values.remove("Pos");
|
||||
if (fixer != null) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ public final class Functions {
|
||||
);
|
||||
handle = handle.asType(handle.type().changeReturnType(Number.class));
|
||||
handle = filterReturnValue(handle, DOUBLE_VALUE);
|
||||
handle = handle.asType(handle.type().wrap());
|
||||
}
|
||||
// return vararg-ity
|
||||
if (wasVarargs) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.fastasyncworldedit.core.queue.IChunkSet;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
import com.sk89q.worldedit.world.storage.ChunkStore;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -823,14 +824,15 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
|
||||
boolean trimX = lowerX != 0 || upperX != 15;
|
||||
boolean trimZ = lowerZ != 0 || upperZ != 15;
|
||||
|
||||
if (!(trimX || trimZ)) {
|
||||
return set;
|
||||
}
|
||||
|
||||
for (int layer = get.getMinSectionPosition(); layer < get.getMaxSectionPosition(); layer++) {
|
||||
if (!set.hasSection(layer)) {
|
||||
continue;
|
||||
}
|
||||
char[] arr = Objects.requireNonNull(set.loadIfPresent(layer)); // This shouldn't be null if above is true
|
||||
if (!(trimX || trimZ)) {
|
||||
continue;
|
||||
}
|
||||
int indexY = 0;
|
||||
for (int y = 0; y < 16; y++, indexY += 256) { // For each y layer within a chunk section
|
||||
int index;
|
||||
@@ -839,14 +841,14 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
|
||||
for (int z = 0; z < lowerZ; z++) {
|
||||
// null the z values
|
||||
for (int x = 0; x < 16; x++, index++) {
|
||||
arr[index] = 0;
|
||||
arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
}
|
||||
}
|
||||
index = indexY + upperZi;
|
||||
for (int z = upperZ + 1; z < 16; z++) {
|
||||
// null the z values
|
||||
for (int x = 0; x < 16; x++, index++) {
|
||||
arr[index] = 0;
|
||||
arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -855,11 +857,11 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
|
||||
for (int z = lowerZ; z <= upperZ; z++, index += 16) {
|
||||
for (int x = 0; x < lowerX; x++) {
|
||||
// null the x values
|
||||
arr[index + x] = 0;
|
||||
arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
}
|
||||
for (int x = upperX + 1; x < 16; x++) {
|
||||
// null the x values
|
||||
arr[index + x] = 0;
|
||||
arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -925,7 +927,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
|
||||
for (int z = lowerZ; z <= upperZ; z++) {
|
||||
// null the z values
|
||||
for (int x = 0; x < 16; x++, index++) {
|
||||
arr[index] = 0;
|
||||
arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -934,7 +936,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
|
||||
for (int z = lowerZ; z <= upperZ; z++, index += 16) {
|
||||
for (int x = lowerX; x <= upperX; x++) {
|
||||
// null the x values
|
||||
arr[index + x] = 0;
|
||||
arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user