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:
parent
3c75336c9a
commit
551b25baf6
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
17
core/src/main/java/com/boydti/fawe/command/FaweBinding.java
Normal file
17
core/src/main/java/com/boydti/fawe/command/FaweBinding.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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"),
|
||||
|
@ -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",
|
||||
|
@ -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(?,?,?,?,?,?,?,?,?)";
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>() {
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>",
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user