Various (important) changes

Added command tab completion for patterns
Tick limiter:
- Ignore redstone physics
- Per chunk limiting (rather than global)
- Notify console of any limiting
Changes default settings for low memory usage rather than speed:
- Doesn't effect any existing configs, just new installs
- Uses disk and database for undo/redo/clipboard
- Uses compression level 8 instead of 1
Fixes 1.8 queue failing on no isDirty field
Fixes rare chunk skipping
Fixes queue staging issue
Tweak undo/redo failure messages to provide more info
Added sand/gravel sphere message so that people don't ask me "why it no
work!?"
This commit is contained in:
Jesse Boyd 2016-12-09 01:47:50 +11:00
parent 3c75336c9a
commit 551b25baf6
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
24 changed files with 1418 additions and 106 deletions

View File

@ -3,8 +3,17 @@ package com.boydti.fawe.bukkit.v0;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.bukkit.FaweBukkit;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.IntegerPair;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.TaskManager;
import java.util.HashMap;
import java.util.HashSet;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@ -12,6 +21,7 @@ import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.entity.ItemSpawnEvent;
public class ChunkListener implements Listener {
public ChunkListener() {
if (Settings.TICK_LIMITER.ENABLED) {
Bukkit.getPluginManager().registerEvents(ChunkListener.this, Fawe.<FaweBukkit>imp().getPlugin());
@ -20,24 +30,116 @@ public class ChunkListener implements Listener {
public void run() {
physicsFreeze = false;
itemFreeze = false;
physicsLimit = Settings.TICK_LIMITER.PHYSICS;
itemLimit = Settings.TICK_LIMITER.ITEMS;
counter.clear();
lastZ = Integer.MIN_VALUE;
for (Long badChunk : badChunks) {
counter.put(badChunk, new IntegerPair(Settings.TICK_LIMITER.PHYSICS, Settings.TICK_LIMITER.ITEMS));
}
badChunks.clear();
}
}, 1);
}
}
private int physicsLimit = Integer.MAX_VALUE;
private int itemLimit = Integer.MAX_VALUE;
public static boolean physicsFreeze = false;
public static boolean itemFreeze = false;
private HashSet<Long> badChunks = new HashSet<>();
private HashMap<Long, IntegerPair> counter = new HashMap<>();
private int lastX = Integer.MIN_VALUE, lastZ = Integer.MIN_VALUE;
private IntegerPair lastCount;
public IntegerPair getCount(int cx, int cz) {
if (lastX == cx && lastZ == cz) {
return lastCount;
}
lastX = cx;
lastZ = cz;
long pair = MathMan.pairInt(cx, cz);
lastCount = counter.get(pair);
if (lastCount == null) {
lastCount = new IntegerPair(0,0);
counter.put(pair, lastCount);
}
return lastCount;
}
public void cleanup(Chunk chunk) {
for (Entity entity : chunk.getEntities()) {
if (entity.getType() == EntityType.DROPPED_ITEM) {
entity.remove();
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPhysics(BlockPhysicsEvent event) {
if (physicsFreeze) {
event.setCancelled(true);
} else if (physicsLimit-- < 0) {
physicsFreeze = true;
return;
}
int id = event.getChangedTypeId();
switch (id) {
case 23: // dispensor
case 158: // dropper
case 25:
// piston
case 29:
case 33:
// tnt
case 44:
// wire
case 55:
// door
case 96: // trapdoor
case 167:
case 107: // fence
case 183:
case 184:
case 185:
case 186:
case 187:
case 64: // door
case 71:
case 193:
case 194:
case 195:
case 196:
case 197:
// diode
case 93:
case 94:
// torch
case 75:
case 76:
// comparator
case 149:
case 150:
// lamp
case 123:
case 124:
// rail
case 27:
// BUD
case 73: // ore
case 74:
case 8: // water
case 9:
case 34: // piston
return;
}
Block block = event.getBlock();
int cx = block.getX() >> 4;
int cz = block.getZ() >> 4;
IntegerPair count = getCount(cx, cz);
if (++count.x >= Settings.TICK_LIMITER.PHYSICS) {
if (count.x == Settings.TICK_LIMITER.PHYSICS) {
badChunks.add(MathMan.pairInt(cx, cz));
Fawe.debug("[Tick Limiter] Detected and cancelled lag source at " + block.getLocation());
}
event.setCancelled(true);
return;
}
}
@ -45,8 +147,20 @@ public class ChunkListener implements Listener {
public void onItemSpawn(ItemSpawnEvent event) {
if (physicsFreeze) {
event.setCancelled(true);
} else if (itemLimit-- < 0) {
return;
}
Location loc = event.getLocation();
int cx = loc.getBlockX() >> 4;
int cz = loc.getBlockZ() >> 4;
IntegerPair count = getCount(cx, cz);
if (++count.z >= Settings.TICK_LIMITER.ITEMS) {
if (count.z == Settings.TICK_LIMITER.ITEMS) {
cleanup(loc.getChunk());
badChunks.add(MathMan.pairInt(cx, cz));
Fawe.debug("[Tick Limiter] Detected and cancelled lag source at " + loc);
}
event.setCancelled(true);
return;
}
}
}

View File

@ -48,15 +48,17 @@ public class BukkitQueue18R3 extends BukkitQueue_0<Chunk, ChunkSection[], ChunkS
fieldTickingBlockCount = ChunkSection.class.getDeclaredField("tickingBlockCount");
fieldNonEmptyBlockCount = ChunkSection.class.getDeclaredField("nonEmptyBlockCount");
fieldChunkMap = PlayerChunkMap.class.getDeclaredField("d");
isDirty = ChunkSection.class.getDeclaredField("isDirty");
fieldSection.setAccessible(true);
fieldTickingBlockCount.setAccessible(true);
fieldNonEmptyBlockCount.setAccessible(true);
fieldChunkMap.setAccessible(true);
isDirty.setAccessible(true);
} catch (Throwable e) {
e.printStackTrace();
}
try {
isDirty = ChunkSection.class.getDeclaredField("isDirty");
isDirty.setAccessible(true);
} catch (Throwable ignore) {}
}
public BukkitQueue18R3(final com.sk89q.worldedit.world.World world) {

View File

@ -83,7 +83,9 @@ import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.session.PasteBuilder;
import com.sk89q.worldedit.session.SessionManager;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.command.parametric.ParameterData;
import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
import com.sk89q.worldedit.util.command.parametric.ParametricCallable;
import com.sk89q.worldedit.world.registry.BundledBlockData;
import java.io.File;
import java.io.IOException;
@ -353,6 +355,8 @@ public class Fawe {
HistoryCommands.inject(); // Translations + rollback command
NavigationCommands.inject(); // Translations + thru fix
ParametricBuilder.inject(); // Translations
ParametricCallable.inject(); // Translations
ParameterData.inject(); // Translations
ToolUtilCommands.inject(); // Fixes + Translations
GeneralCommands.inject(); // Translations + gmask args
// Schematic

View File

@ -0,0 +1,17 @@
package com.boydti.fawe.command;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.internal.command.WorldEditBinding;
public class FaweBinding extends WorldEditBinding {
private final WorldEdit worldEdit;
public FaweBinding(WorldEdit worldEdit) {
super(worldEdit);
this.worldEdit = worldEdit;
}
public WorldEdit getWorldEdit() {
return worldEdit;
}
}

View File

@ -0,0 +1,49 @@
package com.boydti.fawe.command;
import com.boydti.fawe.util.MainUtil;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.factory.HashTagPatternParser;
import com.sk89q.worldedit.util.command.parametric.ParameterData;
import com.sk89q.worldedit.world.registry.BundledBlockData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PatternBinding extends FaweBinding {
private final WorldEdit worldEdit;
public PatternBinding(WorldEdit worldEdit) {
super(worldEdit);
this.worldEdit = worldEdit;
}
@Override
public List<String> getSuggestions(ParameterData parameter, String prefix) {
int index = prefix.lastIndexOf(",|%");
String start = index != -1 ? prefix.substring(0, index) : "";
String current = index != -1 ? prefix.substring(index) : prefix;
if (current.isEmpty()) {
return MainUtil.prepend(start, Arrays.asList(HashTagPatternParser.ALL_PATTERNS));
}
if (current.startsWith("#") || current.startsWith("=")) {
return new ArrayList<>();
}
if ("hand".startsWith(prefix)) {
return MainUtil.prepend(start, Arrays.asList("hand"));
}
if ("pos1".startsWith(prefix)) {
return MainUtil.prepend(start, Arrays.asList("pos1"));
}
if (current.contains("|")) {
return new ArrayList<>();
}
String[] split2 = current.split(":");
if (split2.length == 2 || current.endsWith(":")) {
start = (start.isEmpty() ? split2[0] : start + " " + split2[0]) + ":";
current = split2.length == 2 ? split2[1] : "";
return MainUtil.prepend(start, MainUtil.filter(current, BundledBlockData.getInstance().getBlockStates(split2[0])));
}
List<String> blocks = BundledBlockData.getInstance().getBlockNames(split2[0]);
return MainUtil.prepend(start, blocks);
}
}

View File

@ -0,0 +1,86 @@
package com.boydti.fawe.command;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.StringMan;
import com.sk89q.worldedit.extension.input.InputParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class SuggestInputParseException extends InputParseException {
private final String message;
private String prefix;
private ArrayList<String> suggestions = new ArrayList<>();
public SuggestInputParseException(String input, Collection<String> inputs) {
super("");
this.message = "Suggested input: " + StringMan.join(suggestions = getSuggestions(input, inputs), ", ");
this.prefix = "";
}
public SuggestInputParseException(String input, String... inputs) {
super("");
this.message = "Suggested input: " + StringMan.join(suggestions = getSuggestions(input, inputs), ", ");
this.prefix = "";
}
@Override
public String getMessage() {
return message;
}
public List<String> getSuggestions() {
return MainUtil.prepend(prefix, suggestions);
}
public void prepend(String input) {
this.prefix = input + prefix;
}
public static SuggestInputParseException get(Throwable e) {
if (e instanceof SuggestInputParseException) {
return (SuggestInputParseException) e;
}
Throwable cause = e.getCause();
if (cause == null) {
return null;
}
return get(cause);
}
private static ArrayList<String> getSuggestions(String input, Collection<String> inputs) {
ArrayList<String> suggestions = new ArrayList<>();
if (input != null) {
String tmp = input.toLowerCase();
for (String s : inputs) {
if (s.startsWith(tmp)) {
suggestions.add(s);
}
}
}
if (suggestions.isEmpty()) {
suggestions.addAll(inputs);
} return suggestions;
}
private static ArrayList<String> getSuggestions(String input, String... inputs) {
ArrayList<String> suggestions = new ArrayList<>();
if (input != null) {
String tmp = input.toLowerCase();
for (String s : inputs) {
if (s.startsWith(tmp)) {
suggestions.add(s);
}
}
}
if (suggestions.isEmpty()) {
for (String s : inputs) {
suggestions.add(s);
}
}
return suggestions;
}
}

View File

@ -62,9 +62,9 @@ public enum BBC {
COMMAND_TREE("%s0 trees created.", "WorldEdit.Tree"),
COMMAND_FLORA("%s0 flora created.", "WorldEdit.Flora"),
COMMAND_HISTORY_CLEAR("History cleared", "WorldEdit.History"),
COMMAND_REDO_FAIL("Nothing left to redo.", "WorldEdit.History"),
COMMAND_REDO_ERROR("Nothing left to redo. (See also `/inspect` and `/frb`)", "WorldEdit.History"),
COMMAND_REDO_SUCCESS("Redo successful.", "WorldEdit.History"),
COMMAND_UNDO_FAIL("Nothing left to undo.", "WorldEdit.History"),
COMMAND_UNDO_ERROR("Nothing left to undo. (See also `/inspect` and `/frb`)", "WorldEdit.History"),
COMMAND_UNDO_SUCCESS("Undo successful.", "WorldEdit.History"),
OPERATION("Operation queued (%s0)", "WorldEdit.Operation"),
@ -91,6 +91,7 @@ public enum BBC {
BRUSH_EXTINGUISHER("Extinguisher equipped (%s0).", "WorldEdit.Brush"),
BRUSH_GRAVITY("Gravity brush equipped (%s0)", "WorldEdit.Brush"),
BRUSH_HEIGHT("Height brush equipped (%s0)", "WorldEdit.Brush"),
BRUSH_TRY_OTHER("&cFAWE adds other, more suitable brushes e.g.\n&8 - &7//br height [radius=5] [#clipboard|file=null] [rotation=0] [yscale=1.00]", "WorldEdit.Brush"),
BRUSH_COPY("Copy brush equipped (%s0)", "WorldEdit.Brush"),
BRUSH_COMMAND("Command brush equipped (%s0)", "WorldEdit.Brush"),
BRUSH_HEIGHT_INVALID("Invalid height map file (%s0)", "WorldEdit.Brush"),

View File

@ -107,12 +107,12 @@ public class Settings extends Config {
" - Unlimited undo",
" - Enables the rollback command"
})
public static boolean USE_DISK = false;
public static boolean USE_DISK = true;
@Comment({
"Use a database to store disk storage summaries:",
" - Faster lookups and rollback from disk",
})
public static boolean USE_DATABASE = false;
public static boolean USE_DATABASE = true;
@Comment({
"Record history with dispatching:",
" - Faster as it avoids duplicate block checks",
@ -133,7 +133,7 @@ public class Settings extends Config {
"9 = 1 x high, 1 x medium, 3 x fast (best compression)",
"NOTE: If using disk, do some compression (3+) as smaller files save faster"
})
public static int COMPRESSION_LEVEL = 1;
public static int COMPRESSION_LEVEL = 8;
@Comment({
"The buffer size for compression:",
" - Larger = better ratio but uses more upfront memory"
@ -242,19 +242,20 @@ public class Settings extends Config {
public static boolean DEBUG = true;
}
@Comment("Generic tick limiter (not necessarily WorldEdit related, but still useful)")
@Comment("Generic tick limiter (not necessarily WorldEdit related, but useful to stop abuse)")
public static class TICK_LIMITER {
@Comment("Enable the limiter")
public static boolean ENABLED = true;
@Comment("Max physics per tick")
public static int PHYSICS = 500000;
@Comment("Max item spawns per tick")
public static int ITEMS = 50000;
@Comment("Max physics per tick (per chunk)")
public static int PHYSICS = 4096;
@Comment("Max item spawns per tick (per chunk)")
public static int ITEMS = 512;
}
public static class CLIPBOARD {
@Comment("Store the clipboard on disk instead of memory")
public static boolean USE_DISK = false;
public static boolean USE_DISK = true;
@Comment({
"Compress the clipboard to reduce the size:",
" - TODO: Buffered random access with compression is not implemented on disk yet",

View File

@ -50,7 +50,7 @@ public class RollbackDatabase {
this.prefix = "";
this.worldName = Fawe.imp().getWorldName(world);
this.world = world;
this.dbLocation = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.PATHS.HISTORY + File.separator + world + File.separator + "summary.db");
this.dbLocation = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.PATHS.HISTORY + File.separator + world.getName() + File.separator + "summary.db");
connection = openConnection();
CREATE_TABLE = "CREATE TABLE IF NOT EXISTS `" + prefix + "edits` (`player` BLOB(16) NOT NULL,`id` INT NOT NULL,`x1` INT NOT NULL,`y1` INT NOT NULL,`z1` INT NOT NULL,`x2` INT NOT NULL,`y2` INT NOT NULL,`z2` INT NOT NULL,`time` INT NOT NULL, PRIMARY KEY (player, id))";
INSERT_EDIT = "INSERT OR REPLACE INTO `" + prefix + "edits` (`player`,`id`,`x1`,`y1`,`z1`,`x2`,`y2`,`z2`,`time`) VALUES(?,?,?,?,?,?,?,?,?)";

View File

@ -4,6 +4,7 @@ import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.SetQueue;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@ -22,7 +23,7 @@ public class DefaultFaweQueueMap implements IFaweQueueMap {
/**
* Map of chunks in the queue
*/
public ConcurrentHashMap<Long, FaweChunk> blocks = new ConcurrentHashMap<Long, FaweChunk>(8, 0.9f, 1) {
public final ConcurrentHashMap<Long, FaweChunk> blocks = new ConcurrentHashMap<Long, FaweChunk>(8, 0.9f, 1) {
@Override
public FaweChunk put(Long key, FaweChunk value) {
if (parent.getProgressTask() != null) {
@ -73,7 +74,9 @@ public class DefaultFaweQueueMap implements IFaweQueueMap {
return lastWrappedChunk;
}
long pair = MathMan.pairInt(cx, cz);
return this.blocks.get(pair);
FaweChunk chunk = this.blocks.get(pair);
lastWrappedChunk = chunk;
return chunk;
}
@Override
@ -100,16 +103,14 @@ public class DefaultFaweQueueMap implements IFaweQueueMap {
return parent.getFaweChunk(cx, cz);
}
private FaweChunk lastWrappedChunk;
private volatile FaweChunk lastWrappedChunk;
private int lastX = Integer.MIN_VALUE;
private int lastZ = Integer.MIN_VALUE;
@Override
public boolean next(int amount, ExecutorCompletionService pool, long time) {
lastWrappedChunk = null;
lastX = Integer.MIN_VALUE;
lastZ = Integer.MIN_VALUE;
try {
boolean skip = parent.getStage() == SetQueue.QueueStage.INACTIVE;
int added = 0;
Iterator<Map.Entry<Long, FaweChunk>> iter = blocks.entrySet().iterator();
if (amount == 1) {
@ -117,6 +118,9 @@ public class DefaultFaweQueueMap implements IFaweQueueMap {
do {
if (iter.hasNext()) {
FaweChunk chunk = iter.next().getValue();
if (skip && chunk == lastWrappedChunk) {
continue;
}
iter.remove();
parent.start(chunk);
chunk.call();
@ -125,35 +129,43 @@ public class DefaultFaweQueueMap implements IFaweQueueMap {
break;
}
} while (System.currentTimeMillis() - start < time);
return !blocks.isEmpty();
}
boolean result = true;
// amount = 8;
for (int i = 0; i < amount && (result = iter.hasNext()); i++, added++) {
Map.Entry<Long, FaweChunk> item = iter.next();
FaweChunk chunk = item.getValue();
parent.start(chunk);
pool.submit(chunk);
iter.remove();
}
// if result, then submitted = amount
if (result) {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < time && result) {
if (result = iter.hasNext()) {
Map.Entry<Long, FaweChunk> item = iter.next();
FaweChunk chunk = item.getValue();
parent.start(chunk);
pool.submit(chunk);
iter.remove();
FaweChunk fc = ((FaweChunk) pool.take().get());
parent.end(fc);
} else {
boolean result = true;
// amount = 8;
for (int i = 0; i < amount && (result = iter.hasNext()); i++, added++) {
Map.Entry<Long, FaweChunk> item = iter.next();
FaweChunk chunk = item.getValue();
if (skip && chunk == lastWrappedChunk) {
i--;
added--;
continue;
}
iter.remove();
parent.start(chunk);
pool.submit(chunk);
}
// if result, then submitted = amount
if (result) {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < time && result) {
if (result = iter.hasNext()) {
Map.Entry<Long, FaweChunk> item = iter.next();
FaweChunk chunk = item.getValue();
if (skip && chunk == lastWrappedChunk) {
continue;
}
iter.remove();
parent.start(chunk);
pool.submit(chunk);
FaweChunk fc = ((FaweChunk) pool.take().get());
parent.end(fc);
}
}
}
}
for (int i = 0; i < added; i++) {
FaweChunk fc = ((FaweChunk) pool.take().get());
parent.end(fc);
for (int i = 0; i < added; i++) {
FaweChunk fc = ((FaweChunk) pool.take().get());
parent.end(fc);
}
}
} catch (Throwable e) {
e.printStackTrace();

View File

@ -233,7 +233,7 @@ public abstract class MappedFaweQueue<WORLD, CHUNK, SECTION> extends FaweQueue {
@Override
public int size() {
int size = map.size();
if (size == 0 && SetQueue.IMP.getStage(this) != SetQueue.QueueStage.INACTIVE) {
if (size == 0 && getStage() != SetQueue.QueueStage.INACTIVE) {
runTasks();
}
return size;

View File

@ -5,6 +5,7 @@ import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.SetQueue;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.Collection;
@ -141,10 +142,8 @@ public class WeakFaweQueueMap implements IFaweQueueMap {
@Override
public boolean next(int amount, ExecutorCompletionService pool, long time) {
lastWrappedChunk = null;
lastX = Integer.MIN_VALUE;
lastZ = Integer.MIN_VALUE;
try {
boolean skip = parent.getStage() == SetQueue.QueueStage.INACTIVE;
int added = 0;
Iterator<Map.Entry<Long, Reference<FaweChunk>>> iter = blocks.entrySet().iterator();
if (amount == 1) {
@ -154,6 +153,9 @@ public class WeakFaweQueueMap implements IFaweQueueMap {
Map.Entry<Long, Reference<FaweChunk>> entry = iter.next();
Reference<FaweChunk> chunkReference = entry.getValue();
FaweChunk chunk = chunkReference.get();
if (skip && chunk == lastWrappedChunk) {
continue;
}
iter.remove();
if (chunk != null) {
parent.start(chunk);
@ -174,6 +176,11 @@ public class WeakFaweQueueMap implements IFaweQueueMap {
Map.Entry<Long, Reference<FaweChunk>> item = iter.next();
Reference<FaweChunk> chunkReference = item.getValue();
FaweChunk chunk = chunkReference.get();
if (skip && chunk == lastWrappedChunk) {
i--;
added--;
continue;
}
iter.remove();
if (chunk != null) {
parent.start(chunk);
@ -192,6 +199,9 @@ public class WeakFaweQueueMap implements IFaweQueueMap {
Map.Entry<Long, Reference<FaweChunk>> item = iter.next();
Reference<FaweChunk> chunkReference = item.getValue();
FaweChunk chunk = chunkReference.get();
if (skip && chunk == lastWrappedChunk) {
continue;
}
iter.remove();
if (chunk != null) {
parent.start(chunk);

View File

@ -37,6 +37,7 @@ public abstract class FaweQueue {
private long modified = System.currentTimeMillis();
private RunnableVal2<FaweChunk, FaweChunk> changeTask;
private RunnableVal2<ProgressType, Integer> progressTask;
private SetQueue.QueueStage stage;
public FaweQueue(String world) {
this.world = world;
@ -388,6 +389,14 @@ public abstract class FaweQueue {
flush(10000);
}
public SetQueue.QueueStage getStage() {
return stage;
}
public void setStage(SetQueue.QueueStage stage) {
this.stage = stage;
}
/**
* Lock the thread until the queue is empty
*/
@ -397,7 +406,7 @@ public abstract class FaweQueue {
SetQueue.IMP.flush(this);
} else {
if (enqueue()) {
while (!isEmpty() && SetQueue.IMP.isStage(this, SetQueue.QueueStage.ACTIVE)) {
while (!isEmpty() && getStage() == SetQueue.QueueStage.ACTIVE) {
synchronized (this) {
try {
this.wait(time);

View File

@ -20,7 +20,9 @@ import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -55,6 +57,51 @@ public class MainUtil {
Fawe.debug(s);
}
public static List<String> filter(String prefix, List<String> suggestions) {
if (prefix.isEmpty()) {
return suggestions;
}
if (suggestions.getClass() != ArrayList.class) {
suggestions = new ArrayList<>(suggestions);
}
Iterator<String> iter = suggestions.iterator();
while (iter.hasNext()) {
if (!iter.next().startsWith(prefix)) {
iter.remove();
}
}
return suggestions;
}
public static List<String> prepend(String start, List<String> suggestions) {
if (start.isEmpty()) {
return suggestions;
}
suggestions = new ArrayList<>(suggestions);
for (int i = 0; i < suggestions.size(); i++) {
suggestions.set(i, start + suggestions.get(i));
}
return suggestions;
}
public static <T> T[] joinArrayGeneric(T[]... arrays) {
int length = 0;
for (T[] array : arrays) {
length += array.length;
}
//T[] result = new T[length];
final T[] result = (T[]) Array.newInstance(arrays[0].getClass().getComponentType(), length);
int offset = 0;
for (T[] array : arrays) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
public static long getTotalSize(Path path) {
final AtomicLong size = new AtomicLong(0);
traverse(path, new RunnableVal2<Path, BasicFileAttributes>() {

View File

@ -6,6 +6,7 @@ import com.boydti.fawe.object.FaweQueue;
import com.sk89q.worldedit.world.World;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedDeque;
@ -115,7 +116,8 @@ public class SetQueue {
boolean parallel = Settings.QUEUE.PARALLEL_THREADS > 1;
queue.startSet(parallel);
try {
if (!queue.next(Settings.QUEUE.PARALLEL_THREADS, getCompleterService(), time)) {
if (!queue.next(Settings.QUEUE.PARALLEL_THREADS, getCompleterService(), time) && queue.getStage() == QueueStage.ACTIVE) {
queue.setStage(QueueStage.NONE);
queue.runTasks();
}
} catch (Throwable e) {
@ -143,12 +145,7 @@ public class SetQueue {
}
public QueueStage getStage(FaweQueue queue) {
if (activeQueues.contains(queue)) {
return QueueStage.ACTIVE;
} else if (inactiveQueues.contains(queue)) {
return QueueStage.INACTIVE;
}
return QueueStage.NONE;
return queue.getStage();
}
public boolean isStage(FaweQueue queue, QueueStage stage) {
@ -164,6 +161,7 @@ public class SetQueue {
}
public boolean enqueue(FaweQueue queue) {
queue.setStage(QueueStage.ACTIVE);
inactiveQueues.remove(queue);
if (queue.size() > 0) {
if (!activeQueues.contains(queue)) {
@ -176,6 +174,7 @@ public class SetQueue {
}
public void dequeue(FaweQueue queue) {
queue.setStage(QueueStage.NONE);
inactiveQueues.remove(queue);
activeQueues.remove(queue);
queue.runTasks();
@ -189,16 +188,17 @@ public class SetQueue {
}
public Collection<FaweQueue> getActiveQueues() {
return activeQueues;
return Collections.unmodifiableCollection(activeQueues);
}
public Collection<FaweQueue> getInactiveQueues() {
return inactiveQueues;
return Collections.unmodifiableCollection(inactiveQueues);
}
public FaweQueue getNewQueue(World world, boolean fast, boolean autoqueue) {
FaweQueue queue = Fawe.imp().getNewQueue(world, fast);
if (autoqueue) {
queue.setStage(QueueStage.INACTIVE);
inactiveQueues.add(queue);
}
return queue;
@ -207,6 +207,7 @@ public class SetQueue {
public FaweQueue getNewQueue(String world, boolean fast, boolean autoqueue) {
FaweQueue queue = Fawe.imp().getNewQueue(world, fast);
if (autoqueue) {
queue.setStage(QueueStage.INACTIVE);
inactiveQueues.add(queue);
}
return queue;
@ -221,6 +222,7 @@ public class SetQueue {
completer = new ExecutorCompletionService(pool);
MainUtil.handleError(e);
} finally {
queue.setStage(QueueStage.NONE);
queue.runTasks();
}
}
@ -233,6 +235,7 @@ public class SetQueue {
queue.setModified(now);
return queue;
} else {
queue.setStage(QueueStage.NONE);
queue.runTasks();
activeQueues.poll();
}
@ -249,6 +252,7 @@ public class SetQueue {
total += queue.size();
if (queue.size() == 0) {
if (age > Settings.QUEUE.DISCARD_AFTER_MS) {
queue.setStage(QueueStage.NONE);
queue.runTasks();
iter.remove();
}
@ -282,6 +286,7 @@ public class SetQueue {
activeQueues.add(queue);
return set;
} else {
queue.setStage(QueueStage.NONE);
queue.runTasks();
}
}
@ -304,6 +309,7 @@ public class SetQueue {
if (diff > Settings.QUEUE.DISCARD_AFTER_MS) {
// These edits never finished
for (FaweQueue queue : tmp) {
queue.setStage(QueueStage.NONE);
queue.runTasks();
}
inactiveQueues.clear();

View File

@ -205,6 +205,14 @@ public class BrushCommands {
} else {
tool.setBrush(new SphereBrush(), "worldedit.brush.sphere");
}
if (fill instanceof BlockPattern) {
BaseBlock block = ((BlockPattern) fill).getBlock();
switch (block.getId()) {
case BlockID.SAND:
case BlockID.GRAVEL:
BBC.BRUSH_TRY_OTHER.send(player);
}
}
player.print(BBC.BRUSH_SPHERE.f(radius));
}

View File

@ -213,7 +213,7 @@ public class HistoryCommands {
BBC.COMMAND_UNDO_SUCCESS.send(player);
worldEdit.flushBlockBag(player, undone);
} else {
BBC.COMMAND_UNDO_FAIL.send(player);
BBC.COMMAND_UNDO_ERROR.send(player);
break;
}
}
@ -248,7 +248,7 @@ public class HistoryCommands {
BBC.COMMAND_REDO_SUCCESS.send(player);
worldEdit.flushBlockBag(player, redone);
} else {
BBC.COMMAND_REDO_FAIL.send(player);
BBC.COMMAND_REDO_ERROR.send(player);
}
}
}

View File

@ -22,9 +22,13 @@ package com.sk89q.worldedit.command;
import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.example.NMSMappedFaweQueue;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweLocation;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.SetQueue;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandPermissions;
@ -44,6 +48,7 @@ import com.sk89q.worldedit.function.mask.ExistingBlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.NoiseFilter2D;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.BlockPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.pattern.Patterns;
import com.sk89q.worldedit.function.visitor.LayerVisitor;
@ -64,6 +69,7 @@ import com.sk89q.worldedit.util.command.binding.Range;
import com.sk89q.worldedit.util.command.binding.Switch;
import com.sk89q.worldedit.util.command.binding.Text;
import com.sk89q.worldedit.util.command.parametric.Optional;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@ -280,6 +286,77 @@ public class RegionCommands {
BBC.VISITOR_BLOCK.send(player, affected);
}
@Command(
aliases = { "/set" },
usage = "[pattern]",
desc = "Set all blocks within selection",
min = 1,
max = 1
)
@CommandPermissions("worldedit.region.set")
@Logging(REGION)
public void set(Player player, EditSession editSession, LocalSession session, @Selection Region selection, Pattern to) throws WorldEditException {
if (selection instanceof CuboidRegion && editSession.hasFastMode() && to instanceof BlockPattern) {
try {
CuboidRegion cuboid = (CuboidRegion) selection;
RegionWrapper current = new RegionWrapper(cuboid.getMinimumPoint(), cuboid.getMaximumPoint());
Field field = to.getClass().getDeclaredField("pattern");
field.setAccessible(true);
Pattern pattern = (Pattern) field.get(to);
BaseBlock block = ((BlockPattern) pattern).getBlock();
final FaweQueue queue = editSession.getQueue();
final int minY = cuboid.getMinimumY();
final int maxY = cuboid.getMaximumY();
final int id = block.getId();
final byte data = (byte) block.getData();
final FaweChunk<?> fc = queue.getFaweChunk(0, 0);
fc.fillCuboid(0, 15, minY, maxY, 0, 15, id, data);
fc.optimize();
int bcx = (current.minX) >> 4;
int bcz = (current.minZ) >> 4;
int tcx = (current.maxX) >> 4;
int tcz = (current.maxZ) >> 4;
// [chunkx, chunkz, pos1x, pos1z, pos2x, pos2z, isedge]
MainUtil.chunkTaskSync(current, new RunnableVal<int[]>() {
@Override
public void run(int[] value) {
FaweChunk newChunk;
if (value[6] == 0) {
newChunk = fc.copy(true);
newChunk.setLoc(queue, value[0], value[1]);
} else {
int bx = value[2] & 15;
int tx = value[4] & 15;
int bz = value[3] & 15;
int tz = value[5] & 15;
if (bx == 0 && tx == 15 && bz == 0 && tz == 15) {
newChunk = fc.copy(true);
newChunk.setLoc(queue, value[0], value[1]);
} else {
newChunk = queue.getFaweChunk(value[0], value[1]);
newChunk.fillCuboid(value[2] & 15, value[4] & 15, minY, maxY, value[3] & 15, value[5] & 15, id, data);
}
}
newChunk.addToQueue();
}
});
queue.enqueue();
long start = System.currentTimeMillis();
BBC.OPERATION.send(player, BBC.VISITOR_BLOCK.format(cuboid.getArea()));
queue.flush();
BBC.ACTION_COMPLETE.send(player, (System.currentTimeMillis() - start) / 1000d);
return;
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
int affected = editSession.setBlocks(selection, Patterns.wrap(to));
BBC.OPERATION.send(player, affected);
}
@Command(
aliases = { "/overlay" },
usage = "<block>",

View File

@ -1,6 +1,9 @@
package com.sk89q.worldedit.extension.factory;
import com.boydti.fawe.command.SuggestInputParseException;
import com.boydti.fawe.object.pattern.*;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.StringMan;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.LocalSession;
@ -21,10 +24,30 @@ import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.world.registry.BundledBlockData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class HashTagPatternParser extends InputParser<Pattern> {
public class HashTagPatternParser extends InputParser<Pattern>{
public static final String[] EXPRESSION_PATTERN = new String[] { "=<expression>" };
public static final String[] BLOCK_PATTERN = new String[] { "<block>" };
public static final String[] SIMPLE_PATTERNS = new String[] {
"#existing", "#fullcopy", "#clipboard",
};
public static final String[] DELEGATE_PATTERNS = new String[] {
"#linear3d:", "#linear:", "#spread:", "#solidspread:", "#surfacespread:", "#offset:", "#mask:", "#!x:", "#!y:", "#!z:", "#relative:", "#id:", "#data:",
};
public static final String[] MISC_PATTERNS = new String[] {
"hand", "pos1",
};
public static final String[] ALL_PATTERNS = MainUtil.joinArrayGeneric(BLOCK_PATTERN, SIMPLE_PATTERNS, DELEGATE_PATTERNS, MISC_PATTERNS);
public HashTagPatternParser(WorldEdit worldEdit) {
super(worldEdit);
@ -50,8 +73,21 @@ public class HashTagPatternParser extends InputParser<Pattern> {
return result;
}
private Pattern catchSuggestion(String currentInput, String nextInput, ParserContext context) throws InputParseException{
try {
return parseFromInput(nextInput, context);
} catch (SuggestInputParseException e) {
e.prepend(currentInput.substring(0, currentInput.length() - nextInput.length()));
throw e;
}
}
@Override
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
if (input.isEmpty()) {
throw new SuggestInputParseException(input, ALL_PATTERNS);
}
switch (input.toLowerCase().charAt(0)) {
case '#': {
switch (input) {
@ -90,43 +126,52 @@ public class HashTagPatternParser extends InputParser<Pattern> {
}
}
String[] split2 = input.split(":");
if (split2.length > 1) {
if (split2.length > 1 || input.endsWith(":")) {
String rest = input.substring(split2[0].length() + 1);
switch (split2[0].toLowerCase()) {
case "#id": {
return new IdPattern(Request.request().getEditSession(), parseFromInput(rest, context));
return new IdPattern(Request.request().getEditSession(), catchSuggestion(input, rest, context));
}
case "#data": {
return new DataPattern(Request.request().getEditSession(), parseFromInput(rest, context));
return new DataPattern(Request.request().getEditSession(), catchSuggestion(input, rest, context));
}
case "#~":
case "#r":
case "#relative":
case "#rel": {
return new RelativePattern(parseFromInput(rest, context));
return new RelativePattern(catchSuggestion(input, rest, context));
}
case "#!x":
case "#nx":
case "#nox": {
return new NoXPattern(parseFromInput(rest, context));
return new NoXPattern(catchSuggestion(input, rest, context));
}
case "#!y":
case "#ny":
case "#noy": {
return new NoYPattern(parseFromInput(rest, context));
return new NoYPattern(catchSuggestion(input, rest, context));
}
case "#!z":
case "#nz":
case "#noz": {
return new NoZPattern(parseFromInput(rest, context));
return new NoZPattern(catchSuggestion(input, rest, context));
}
case "#mask": {
List<String> split3 = split(rest, ':');
if (split3.size() != 3) {
throw new InputParseException("The correct format is #mask:<mask>:<pattern-if>:<pattern-else>");
int len = split3.size();
if (len != 3) {
if (len <= 3) {
if (split3.get(len - 1).endsWith(":")) {
throw new SuggestInputParseException(null, ALL_PATTERNS);
}
String[] args = new String[]{"<mask>", "<pattern-if>", "<pattern-else>"};
throw new SuggestInputParseException(input, input + ":" + StringMan.join(Arrays.copyOfRange(args, len, 3), ":"));
} else {
throw new SuggestInputParseException(input, "#mask:<mask>:<pattern-if>:<pattern-else>");
}
}
Pattern primary = parseFromInput(split3.get(1), context);
Pattern secondary = parseFromInput(split3.get(2), context);
Pattern primary = catchSuggestion(input, split3.get(1), context);
Pattern secondary = catchSuggestion(input, split3.get(2), context);
PatternExtent extent = new PatternExtent(primary);
Request request = Request.request();
request.setExtent(extent);
@ -136,7 +181,7 @@ public class HashTagPatternParser extends InputParser<Pattern> {
MaskFactory factory = worldEdit.getMaskFactory();
Mask mask = factory.parseFromInput(split3.get(0), context);
if (mask == null | primary == null || secondary == null) {
throw new InputParseException("The correct format is #mask:<mask>;<pattern-if>;<pattern-else> (null provided)");
throw new SuggestInputParseException(input, "#mask:<mask>:<pattern-if>:<pattern-else>");
}
return new MaskedPattern(mask, extent, secondary);
}
@ -146,10 +191,10 @@ public class HashTagPatternParser extends InputParser<Pattern> {
int y = (int) Math.abs(Expression.compile(split2[2]).evaluate());
int z = (int) Math.abs(Expression.compile(split2[3]).evaluate());
rest = rest.substring(split2[1].length() + split2[2].length() + split2[3].length() + 3);
Pattern pattern = parseFromInput(rest, context);
Pattern pattern = catchSuggestion(input, rest, context);
return new OffsetPattern(pattern, x, y, z);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #offset:<dx>:<dy>:<dz>:<pattern>");
throw new SuggestInputParseException(input, "#offset:<dx>:<dy>:<dz>:<pattern>");
}
case "#surfacespread": {
try {
@ -157,10 +202,10 @@ public class HashTagPatternParser extends InputParser<Pattern> {
int y = (int) Math.abs(Expression.compile(split2[2]).evaluate());
int z = (int) Math.abs(Expression.compile(split2[3]).evaluate());
rest = rest.substring(split2[1].length() + split2[2].length() + split2[3].length() + 3);
Pattern pattern = parseFromInput(rest, context);
Pattern pattern = catchSuggestion(input, rest, context);
return new SurfaceRandomOffsetPattern(pattern, x, y, z);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #spread:<dx>:<dy>:<dz>:<pattern>");
throw new SuggestInputParseException(input, "#surfacespread:<dx>:<dy>:<dz>:<pattern>");
}
}
case "#solidspread": {
@ -169,10 +214,10 @@ public class HashTagPatternParser extends InputParser<Pattern> {
int y = (int) Math.abs(Expression.compile(split2[2]).evaluate());
int z = (int) Math.abs(Expression.compile(split2[3]).evaluate());
rest = rest.substring(split2[1].length() + split2[2].length() + split2[3].length() + 3);
Pattern pattern = parseFromInput(rest, context);
Pattern pattern = catchSuggestion(input, rest, context);
return new SolidRandomOffsetPattern(pattern, x, y, z);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #spread:<dx>:<dy>:<dz>:<pattern>");
throw new SuggestInputParseException(input, "#solidspread:<dx>:<dy>:<dz>:<pattern>");
}
}
case "#randomoffset":
@ -182,37 +227,39 @@ public class HashTagPatternParser extends InputParser<Pattern> {
int y = (int) Math.abs(Expression.compile(split2[2]).evaluate());
int z = (int) Math.abs(Expression.compile(split2[3]).evaluate());
rest = rest.substring(split2[1].length() + split2[2].length() + split2[3].length() + 3);
Pattern pattern = parseFromInput(rest, context);
Pattern pattern = catchSuggestion(input, rest, context);
return new RandomOffsetPattern(pattern, x, y, z);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #spread:<dx>:<dy>:<dz>:<pattern>");
throw new SuggestInputParseException(input, "#spread:<dx>:<dy>:<dz>:<pattern>");
}
}
case "#l":
case "#linear": {
ArrayList<Pattern> patterns = new ArrayList<>();
for (String token : split(rest, ',')) {
patterns.add(parseFromInput(token, context));
patterns.add(catchSuggestion(input, token, context));
}
if (patterns.isEmpty()) {
throw new InputParseException("No blocks provided for linear pattern e.g. [stone,wood");
throw new SuggestInputParseException(null, ALL_PATTERNS);
}
return new LinearBlockPattern(patterns.toArray(new Pattern[patterns.size()]));
}
case "#l3d":
case "#linear3D": {
case "#linear3d": {
ArrayList<Pattern> patterns = new ArrayList<>();
for (String token : split(rest, ',')) {
patterns.add(parseFromInput(token, context));
patterns.add(catchSuggestion(input, token, context));
}
if (patterns.isEmpty()) {
throw new InputParseException("No blocks provided for linear pattern e.g. [stone,wood");
throw new SuggestInputParseException(null, ALL_PATTERNS);
}
return new Linear3DBlockPattern(patterns.toArray(new Pattern[patterns.size()]));
}
default:
throw new SuggestInputParseException(split2[0], DELEGATE_PATTERNS);
}
}
throw new InputParseException("Invalid, see: https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns");
throw new SuggestInputParseException(input, MainUtil.joinArrayGeneric(SIMPLE_PATTERNS, DELEGATE_PATTERNS));
}
case '=': {
try {
@ -226,10 +273,16 @@ public class HashTagPatternParser extends InputParser<Pattern> {
exp.setEnvironment(env);
return new ExpressionPattern(exp);
} catch (ExpressionException e) {
throw new InputParseException("Invalid expression: " + e.getMessage());
throw new SuggestInputParseException("=http://wiki.sk89q.com/wiki/WorldEdit/Expression_syntax");
}
}
default:
if (input.equals("<pattern>")) {
throw new SuggestInputParseException(input, ALL_PATTERNS);
}
if (input.equals("<block>")) {
throw new SuggestInputParseException(input, BundledBlockData.getInstance().getBlockNames());
}
List<String> items = split(input, ',');
if (items.size() == 1) {
return new BlockPattern(worldEdit.getBlockFactory().parseFromInput(items.get(0), context));
@ -247,11 +300,11 @@ public class HashTagPatternParser extends InputParser<Pattern> {
throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
} else {
chance = Expression.compile(p[0]).evaluate();
pattern = parseFromInput(p[1], context);
pattern = catchSuggestion(input, p[1], context);
}
} else {
chance = 1;
pattern = parseFromInput(token, context);
pattern = catchSuggestion(input, token, context);
}
randomPattern.add(pattern, chance);
}
@ -259,7 +312,6 @@ public class HashTagPatternParser extends InputParser<Pattern> {
throw new InputParseException("Invalid, see: https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns");
}
return randomPattern;
}
}

View File

@ -21,6 +21,7 @@ package com.sk89q.worldedit.extension.platform;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.command.AnvilCommands;
import com.boydti.fawe.command.PatternBinding;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FawePlayer;
@ -42,14 +43,21 @@ import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.*;
import com.sk89q.worldedit.command.argument.ReplaceParser;
import com.sk89q.worldedit.command.argument.TreeGeneratorParser;
import com.sk89q.worldedit.command.composition.*;
import com.sk89q.worldedit.command.composition.ApplyCommand;
import com.sk89q.worldedit.command.composition.DeformCommand;
import com.sk89q.worldedit.command.composition.PaintCommand;
import com.sk89q.worldedit.command.composition.ShapedBrushCommand;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.event.platform.CommandSuggestionEvent;
import com.sk89q.worldedit.function.factory.Deform;
import com.sk89q.worldedit.function.factory.Deform.Mode;
import com.sk89q.worldedit.history.changeset.ChangeSet;
import com.sk89q.worldedit.internal.command.*;
import com.sk89q.worldedit.internal.command.ActorAuthorizer;
import com.sk89q.worldedit.internal.command.CommandLoggingHandler;
import com.sk89q.worldedit.internal.command.UserCommandCompleter;
import com.sk89q.worldedit.internal.command.WorldEditBinding;
import com.sk89q.worldedit.internal.command.WorldEditExceptionConverter;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.InvalidUsageException;
@ -120,9 +128,11 @@ public final class CommandManager {
builder.setAuthorizer(new ActorAuthorizer());
builder.setDefaultCompleter(new UserCommandCompleter(platformManager));
builder.addBinding(new WorldEditBinding(worldEdit));
builder.addBinding(new PatternBinding(worldEdit), com.sk89q.worldedit.function.pattern.Pattern.class);
builder.addInvokeListener(new LegacyCommandsHandler());
builder.addInvokeListener(new CommandLoggingHandler(worldEdit, commandLog));
dispatcher = new CommandGraph().builder(builder).commands()
.registerMethods(new AnvilCommands(worldEdit)) // Added
.registerMethods(new BiomeCommands(worldEdit))
@ -139,7 +149,7 @@ public final class CommandManager {
.registerMethods(new ToolUtilCommands(worldEdit))
.registerMethods(new ToolCommands(worldEdit))
.registerMethods(new UtilityCommands(worldEdit))
.register(adapt(new SelectionCommand(new ApplyCommand(new ReplaceParser(), "Set all blocks within selection"), "worldedit.region.set")), "/set").group("worldedit", "we")
.group("worldedit", "we")
.describeAs("WorldEdit commands")
.registerMethods(new WorldEditCommands(worldEdit)).parent().group("schematic", "schem", "/schematic", "/schem")
.describeAs("Schematic commands for saving/loading areas")

View File

@ -0,0 +1,197 @@
/*
* 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 Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.worldedit.util.command.SimpleParameter;
import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
import com.sk89q.worldedit.util.command.binding.Range;
import com.sk89q.worldedit.util.command.binding.Text;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
/**
* Describes a parameter in detail.
*/
public class ParameterData extends SimpleParameter {
private Binding binding;
private Annotation classifier;
private Annotation[] modifiers;
private Type type;
/**
* Get the binding associated with this parameter.
*
* @return the binding
*/
public Binding getBinding() {
return binding;
}
/**
* Set the binding associated with this parameter.
*
* @param binding the binding
*/
public void setBinding(Binding binding) {
this.binding = binding;
}
/**
* Set the main type of this parameter.
*
* <p>The type is normally that is used to determine which binding is used
* for a particular method's parameter.</p>
*
* @return the main type
* @see #getClassifier() which can override the type
*/
public Type getType() {
return type;
}
/**
* Set the main type of this parameter.
*
* @param type the main type
*/
public void setType(Type type) {
this.type = type;
}
/**
* Get the classifier annotation.
*
* <p>Normally, the type determines what binding is called, but classifiers
* take precedence if one is found (and registered with
* {@link ParametricBuilder#addBinding(Binding, Type...)}).
* An example of a classifier annotation is {@link Text}.</p>
*
* @return the classifier annotation, null is possible
*/
public Annotation getClassifier() {
return classifier;
}
/**
* Set the classifier annotation.
*
* @param classifier the classifier annotation, null is possible
*/
public void setClassifier(Annotation classifier) {
this.classifier = classifier;
}
/**
* Get a list of modifier annotations.
*
* <p>Modifier annotations are not considered in the process of choosing a binding
* for a method parameter, but they can be used to modify the behavior of a binding.
* An example of a modifier annotation is {@link Range}, which can restrict
* numeric values handled by {@link PrimitiveBindings} to be within a range. The list
* of annotations may contain a classifier and other unrelated annotations.</p>
*
* @return a list of annotations
*/
public Annotation[] getModifiers() {
return modifiers;
}
/**
* Set the list of modifiers.
*
* @param modifiers a list of annotations
*/
public void setModifiers(Annotation[] modifiers) {
this.modifiers = modifiers;
}
/**
* Return the number of arguments this binding consumes.
*
* @return -1 if unknown or unavailable
*/
public int getConsumedCount() {
return getBinding().getConsumedCount(this);
}
/**
* Get whether this parameter is entered by the user.
*
* @return true if this parameter is entered by the user.
*/
public boolean isUserInput() {
return getBinding().getBehavior(this) != BindingBehavior.PROVIDES;
}
/**
* Get whether this parameter consumes non-flag arguments.
*
* @return true if this parameter consumes non-flag arguments
*/
public boolean isNonFlagConsumer() {
return getBinding().getBehavior(this) != BindingBehavior.PROVIDES && !isValueFlag();
}
/**
* Validate this parameter and its binding.
*/
public void validate(Method method, int parameterIndex) throws ParametricException {
// We can't have indeterminate consumers without @Switches otherwise
// it may screw up parameter processing for later bindings
BindingBehavior behavior = getBinding().getBehavior(this);
boolean indeterminate = (behavior == BindingBehavior.INDETERMINATE);
if (!isValueFlag() && indeterminate) {
throw new ParametricException(
"@Switch missing for indeterminate consumer\n\n" +
"Notably:\nFor the type " + type + ", the binding " +
getBinding().getClass().getCanonicalName() +
"\nmay or may not consume parameters (isIndeterminateConsumer(" + type + ") = true)" +
"\nand therefore @Switch(flag) is required for parameter #" + parameterIndex + " of \n" +
method.toGenericString());
}
// getConsumedCount() better return -1 if the BindingBehavior is not CONSUMES
if (behavior != BindingBehavior.CONSUMES && binding.getConsumedCount(this) != -1) {
throw new ParametricException(
"getConsumedCount() does not return -1 for binding " +
getBinding().getClass().getCanonicalName() +
"\neven though its behavior type is " + behavior.name() +
"\nfor parameter #" + parameterIndex + " of \n" +
method.toGenericString());
}
// getConsumedCount() should not return 0 if the BindingBehavior is not PROVIDES
if (behavior != BindingBehavior.PROVIDES && binding.getConsumedCount(this) == 0) {
throw new ParametricException(
"getConsumedCount() must not return 0 for binding " +
getBinding().getClass().getCanonicalName() +
"\nwhen its behavior type is " + behavior.name() + " and not PROVIDES " +
"\nfor parameter #" + parameterIndex + " of \n" +
method.toGenericString());
}
}
public static Class<?> inject() {
return ParameterData.class;
}
}

View File

@ -0,0 +1,569 @@
/*
* 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 Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.boydti.fawe.command.SuggestInputParseException;
import com.google.common.primitives.Chars;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import com.sk89q.minecraft.util.commands.SuggestionContext;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.InvalidUsageException;
import com.sk89q.worldedit.util.command.MissingParameterException;
import com.sk89q.worldedit.util.command.Parameter;
import com.sk89q.worldedit.util.command.SimpleDescription;
import com.sk89q.worldedit.util.command.UnconsumedParameterException;
import com.sk89q.worldedit.util.command.binding.Switch;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The implementation of a {@link CommandCallable} for the {@link ParametricBuilder}.
*/
public class ParametricCallable implements CommandCallable {
private final ParametricBuilder builder;
private final Object object;
private final Method method;
private final ParameterData[] parameters;
private final Set<Character> valueFlags = new HashSet<Character>();
private final boolean anyFlags;
private final Set<Character> legacyFlags = new HashSet<Character>();
private final SimpleDescription description = new SimpleDescription();
private final CommandPermissions commandPermissions;
/**
* Create a new instance.
*
* @param builder the parametric builder
* @param object the object to invoke on
* @param method the method to invoke
* @param definition the command definition annotation
* @throws ParametricException thrown on an error
*/
public ParametricCallable(ParametricBuilder builder, Object object, Method method, Command definition) throws ParametricException {
this.builder = builder;
this.object = object;
this.method = method;
Annotation[][] annotations = method.getParameterAnnotations();
String[] names = builder.getParanamer().lookupParameterNames(method, false);
Type[] types = method.getGenericParameterTypes();
parameters = new ParameterData[types.length];
List<Parameter> userParameters = new ArrayList<Parameter>();
// This helps keep tracks of @Nullables that appear in the middle of a list
// of parameters
int numOptional = 0;
// Set permission hint
CommandPermissions permHint = method.getAnnotation(CommandPermissions.class);
if (permHint != null) {
description.setPermissions(Arrays.asList(permHint.value()));
}
// Go through each parameter
for (int i = 0; i < types.length; i++) {
Type type = types[i];
ParameterData parameter = new ParameterData();
parameter.setType(type);
parameter.setModifiers(annotations[i]);
// Search for annotations
for (Annotation annotation : annotations[i]) {
if (annotation instanceof Switch) {
parameter.setFlag(((Switch) annotation).value(), type != boolean.class);
} else if (annotation instanceof Optional) {
parameter.setOptional(true);
String[] value = ((Optional) annotation).value();
if (value.length > 0) {
parameter.setDefaultValue(value);
}
// Special annotation bindings
} else if (parameter.getBinding() == null) {
parameter.setBinding(builder.getBindings().get(annotation.annotationType()));
parameter.setClassifier(annotation);
}
}
parameter.setName(names.length > 0 ? names[i] : generateName(type, parameter.getClassifier(), i));
// Track all value flags
if (parameter.isValueFlag()) {
valueFlags.add(parameter.getFlag());
}
// No special @annotation binding... let's check for the type
if (parameter.getBinding() == null) {
parameter.setBinding(builder.getBindings().get(type));
// Don't know how to parse for this type of value
if (parameter.getBinding() == null) {
throw new ParametricException("Don't know how to handle the parameter type '" + type + "' in\n" + method.toGenericString());
}
}
// Do some validation of this parameter
parameter.validate(method, i + 1);
// Keep track of optional parameters
if (parameter.isOptional() && parameter.getFlag() == null) {
numOptional++;
} else {
if (numOptional > 0 && parameter.isNonFlagConsumer()) {
if (parameter.getConsumedCount() < 0) {
throw new ParametricException(
"Found an parameter using the binding " +
parameter.getBinding().getClass().getCanonicalName() +
"\nthat does not know how many arguments it consumes, but " +
"it follows an optional parameter\nMethod: " +
method.toGenericString());
}
}
}
parameters[i] = parameter;
// Make a list of "real" parameters
if (parameter.isUserInput()) {
userParameters.add(parameter);
}
}
// Gather legacy flags
anyFlags = definition.anyFlags();
legacyFlags.addAll(Chars.asList(definition.flags().toCharArray()));
// Finish description
description.setDescription(!definition.desc().isEmpty() ? definition.desc() : null);
description.setHelp(!definition.help().isEmpty() ? definition.help() : null);
description.overrideUsage(!definition.usage().isEmpty() ? definition.usage() : null);
for (InvokeListener listener : builder.getInvokeListeners()) {
listener.updateDescription(object, method, parameters, description);
}
// Set parameters
description.setParameters(userParameters);
// Get permissions annotation
commandPermissions = method.getAnnotation(CommandPermissions.class);
}
@Override
public Object call(String stringArguments, CommandLocals locals, String[] parentCommands) throws CommandException {
// Test permission
if (!testPermission(locals)) {
throw new CommandPermissionsException();
}
String calledCommand = parentCommands.length > 0 ? parentCommands[parentCommands.length - 1] : "_";
String[] split = CommandContext.split(calledCommand + " " + stringArguments);
CommandContext context = new CommandContext(split, getValueFlags(), false, locals);
// Provide help if -? is specified
if (context.hasFlag('?')) {
throw new InvalidUsageException(null, this, true);
}
Object[] args = new Object[parameters.length];
ContextArgumentStack arguments = new ContextArgumentStack(context);
ParameterData parameter = null;
try {
// preProcess handlers
List<InvokeHandler> handlers = new ArrayList<InvokeHandler>();
for (InvokeListener listener : builder.getInvokeListeners()) {
InvokeHandler handler = listener.createInvokeHandler();
handlers.add(handler);
handler.preProcess(object, method, parameters, context);
}
// Collect parameters
for (int i = 0; i < parameters.length; i++) {
parameter = parameters[i];
if (mayConsumeArguments(i, arguments)) {
// Parse the user input into a method argument
ArgumentStack usedArguments = getScopedContext(parameter, arguments);
try {
args[i] = parameter.getBinding().bind(parameter, usedArguments, false);
} catch (MissingParameterException e) {
// Not optional? Then we can't execute this command
if (!parameter.isOptional()) {
throw e;
}
args[i] = getDefaultValue(i, arguments);
}
} else {
args[i] = getDefaultValue(i, arguments);
}
}
// Check for unused arguments
checkUnconsumed(arguments);
// preInvoke handlers
for (InvokeHandler handler : handlers) {
handler.preInvoke(object, method, parameters, args, context);
}
// Execute!
method.invoke(object, args);
// postInvoke handlers
for (InvokeHandler handler : handlers) {
handler.postInvoke(handler, method, parameters, args, context);
}
} catch (MissingParameterException e) {
throw new InvalidUsageException("Too few parameters!", this);
} catch (UnconsumedParameterException e) {
throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), this);
} catch (ParameterException e) {
assert parameter != null;
String name = parameter.getName();
throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), this);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof CommandException) {
throw (CommandException) e.getCause();
}
throw new WrappedCommandException(e);
} catch (Throwable t) {
throw new WrappedCommandException(t);
}
return true;
}
@Override
public List<String> getSuggestions(String arguments, CommandLocals locals) throws CommandException {
String[] split = CommandContext.split("ignored" + " " + arguments);
CommandContext context = new CommandContext(split, getValueFlags(), !arguments.endsWith(" "), locals);
ContextArgumentStack scoped = new ContextArgumentStack(context);
SuggestionContext suggestable = context.getSuggestionContext();
// For /command -f |
// For /command -f flag|
if (suggestable.forFlag()) {
for (int i = 0; i < parameters.length; i++) {
ParameterData parameter = parameters[i];
if (parameter.getFlag() == suggestable.getFlag()) {
String prefix = context.getFlag(parameter.getFlag());
if (prefix == null) {
prefix = "";
}
return parameter.getBinding().getSuggestions(parameter, prefix);
}
}
// This should not happen
return new ArrayList<String>();
}
int consumerIndex = 0;
ParameterData lastConsumer = null;
String lastConsumed = null;
for (int i = 0; i < parameters.length; i++) {
ParameterData parameter = parameters[i];
if (parameter.getFlag() != null) {
continue; // We already handled flags
}
try {
scoped.mark();
parameter.getBinding().bind(parameter, scoped, true);
if (scoped.wasConsumed()) {
lastConsumer = parameter;
lastConsumed = context.getString(scoped.position() - 1);
consumerIndex++;
}
} catch (MissingParameterException e) {
// For /command value1 |value2
// For /command |value1 value2
if (suggestable.forHangingValue()) {
return parameter.getBinding().getSuggestions(parameter, "");
} else {
// For /command value1| value2
if (lastConsumer != null) {
return lastConsumer.getBinding().getSuggestions(lastConsumer, lastConsumed);
// For /command| value1 value2
// This should never occur
} else {
throw new RuntimeException("Invalid suggestion context");
}
}
} catch (ParameterException | InvocationTargetException e) {
SuggestInputParseException suggestion = SuggestInputParseException.get(e);
if (suggestion != null) {
return suggestion.getSuggestions();
}
if (suggestable.forHangingValue()) {
String name = getDescription().getParameters().get(consumerIndex).getName();
throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), this);
} else {
return parameter.getBinding().getSuggestions(parameter, "");
}
}
}
// For /command value1 value2 |
if (suggestable.forHangingValue()) {
// There's nothing that we can suggest because there's no more parameters
// to add on, and we can't change the previous parameter
return new ArrayList<String>();
} else {
// For /command value1 value2|
if (lastConsumer != null) {
return lastConsumer.getBinding().getSuggestions(lastConsumer, lastConsumed);
// This should never occur
} else {
throw new RuntimeException("Invalid suggestion context");
}
}
}
/**
* Get a list of value flags used by this command.
*
* @return a list of value flags
*/
public Set<Character> getValueFlags() {
return valueFlags;
}
@Override
public SimpleDescription getDescription() {
return description;
}
@Override
public boolean testPermission(CommandLocals locals) {
if (commandPermissions != null) {
for (String perm : commandPermissions.value()) {
if (builder.getAuthorizer().testPermission(locals, perm)) {
return true;
}
}
return false;
} else {
return true;
}
}
/**
* Get the right {@link ArgumentStack}.
*
* @param parameter the parameter
* @param existing the existing scoped context
* @return the context to use
*/
public static ArgumentStack getScopedContext(Parameter parameter, ArgumentStack existing) {
if (parameter.getFlag() != null) {
CommandContext context = existing.getContext();
if (parameter.isValueFlag()) {
return new StringArgumentStack(context, context.getFlag(parameter.getFlag()), false);
} else {
String v = context.hasFlag(parameter.getFlag()) ? "true" : "false";
return new StringArgumentStack(context, v, true);
}
}
return existing;
}
/**
* Get whether a parameter is allowed to consume arguments.
*
* @param i the index of the parameter
* @param scoped the scoped context
* @return true if arguments may be consumed
*/
public boolean mayConsumeArguments(int i, ContextArgumentStack scoped) {
CommandContext context = scoped.getContext();
ParameterData parameter = parameters[i];
// Flag parameters: Always consume
// Required non-flag parameters: Always consume
// Optional non-flag parameters:
// - Before required parameters: Consume if there are 'left over' args
// - At the end: Always consumes
if (parameter.isOptional()) {
if (parameter.getFlag() != null) {
return !parameter.isValueFlag() || context.hasFlag(parameter.getFlag());
} else {
int numberFree = context.argsLength() - scoped.position();
for (int j = i; j < parameters.length; j++) {
if (parameters[j].isNonFlagConsumer() && !parameters[j].isOptional()) {
// We already checked if the consumed count was > -1
// when we created this object
numberFree -= parameters[j].getConsumedCount();
}
}
// Skip this optional parameter
if (numberFree < 1) {
return false;
}
}
}
return true;
}
/**
* Get the default value for a parameter.
*
* @param i the index of the parameter
* @param scoped the scoped context
* @return a value
* @throws ParameterException on an error
* @throws CommandException on an error
*/
public Object getDefaultValue(int i, ContextArgumentStack scoped) throws ParameterException, CommandException, InvocationTargetException {
CommandContext context = scoped.getContext();
ParameterData parameter = parameters[i];
String[] defaultValue = parameter.getDefaultValue();
if (defaultValue != null) {
try {
return parameter.getBinding().bind(parameter, new StringArgumentStack(context, defaultValue, false), false);
} catch (MissingParameterException e) {
throw new ParametricException(
"The default value of the parameter using the binding " +
parameter.getBinding().getClass() + " in the method\n" +
method.toGenericString() + "\nis invalid");
}
}
return null;
}
/**
* Check to see if all arguments, including flag arguments, were consumed.
*
* @param scoped the argument scope
* @throws UnconsumedParameterException thrown if parameters were not consumed
*/
public void checkUnconsumed(ContextArgumentStack scoped) throws UnconsumedParameterException {
CommandContext context = scoped.getContext();
String unconsumed;
String unconsumedFlags = getUnusedFlags(context);
if ((unconsumed = scoped.getUnconsumed()) != null) {
throw new UnconsumedParameterException(unconsumed + " " + unconsumedFlags);
}
if (unconsumedFlags != null) {
throw new UnconsumedParameterException(unconsumedFlags);
}
}
/**
* Get any unused flag arguments.
*
* @param context the command context
*/
public String getUnusedFlags(CommandContext context) {
if (!anyFlags) {
Set<Character> unusedFlags = null;
for (char flag : context.getFlags()) {
boolean found = false;
if (legacyFlags.contains(flag)) {
break;
}
for (ParameterData parameter : parameters) {
Character paramFlag = parameter.getFlag();
if (paramFlag != null && flag == paramFlag) {
found = true;
break;
}
}
if (!found) {
if (unusedFlags == null) {
unusedFlags = new HashSet<Character>();
}
unusedFlags.add(flag);
}
}
if (unusedFlags != null) {
StringBuilder builder = new StringBuilder();
for (Character flag : unusedFlags) {
builder.append("-").append(flag).append(" ");
}
return builder.toString().trim();
}
}
return null;
}
/**
* Generate a name for a parameter.
*
* @param type the type
* @param classifier the classifier
* @param index the index
* @return a generated name
*/
public static String generateName(Type type, Annotation classifier, int index) {
if (classifier != null) {
return classifier.annotationType().getSimpleName().toLowerCase();
} else {
if (type instanceof Class<?>) {
return ((Class<?>) type).getSimpleName().toLowerCase();
} else {
return "unknown" + index;
}
}
}
public static Class<?> inject() {
return ParametricCallable.class;
}
}

View File

@ -36,10 +36,13 @@ import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@ -60,6 +63,7 @@ public class BundledBlockData {
private static final BundledBlockData INSTANCE = new BundledBlockData();
private final Map<String, BlockEntry> idMap = new HashMap<String, BlockEntry>();
private final Map<String, BlockEntry> localizedMap = new HashMap<String, BlockEntry>();
private final BlockEntry[] legacyMap = new BlockEntry[4096];
@ -95,6 +99,41 @@ public class BundledBlockData {
}
}
public Set<String> getBlockNames() {
return localizedMap.keySet();
}
public List<String> getBlockNames(String partial) {
partial = partial.toLowerCase();
List<String> blocks = new ArrayList<>();
for (Map.Entry<String, BlockEntry> entry : localizedMap.entrySet()) {
String key = entry.getKey();
if (key.startsWith(partial)) {
blocks.add(key);
}
}
return blocks;
}
public List<String> getBlockStates(String id) {
BlockEntry block = localizedMap.get(id);
if (block == null || block.states == null || block.states.isEmpty()) {
return Arrays.asList("0", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15");
}
ArrayList<String> blocks = new ArrayList<>();
if (block.states != null) {
for (Map.Entry<String, FaweState> entry : block.states.entrySet()) {
FaweState state = entry.getValue();
if (state.values != null) {
for (Map.Entry<String, FaweStateValue> stateValueEntry : state.values.entrySet()) {
blocks.add(stateValueEntry.getKey());
}
}
}
}
return blocks;
}
public boolean add(BlockEntry entry, boolean overwrite) {
if (entry == null) {
return false;
@ -162,6 +201,7 @@ public class BundledBlockData {
}
idMap.put(entry.id, entry);
localizedMap.put(entry.localizedName.toLowerCase().replace(" ", "_"), entry);
legacyMap[entry.legacyId] = entry;
return true;
}
@ -249,6 +289,7 @@ public class BundledBlockData {
public int legacyId;
public String id;
public String unlocalizedName;
public String localizedName;
public List<String> aliases;
public Map<String, FaweState> states = new HashMap<String, FaweState>();
public FaweBlockMaterial material = new FaweBlockMaterial();

View File

@ -415,7 +415,7 @@ public class Sniper {
if (count > 0) {
BBC.COMMAND_UNDO_SUCCESS.send(fp);
} else {
BBC.COMMAND_UNDO_FAIL.send(fp);
BBC.COMMAND_UNDO_ERROR.send(fp);
}
}