Copy paste/merge FAWE classes to this WorldEdit fork

- so certain people can look at the diff and complain about my sloppy code :(

Signed-off-by: Jesse Boyd <jessepaleg@gmail.com>
This commit is contained in:
Jesse Boyd
2018-08-13 00:03:07 +10:00
parent a920c77cb8
commit a629d15c74
994 changed files with 117583 additions and 10745 deletions

View File

@ -0,0 +1,585 @@
package com.boydti.fawe;
import com.boydti.fawe.command.Cancel;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Commands;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.brush.visualization.VisualQueue;
import com.boydti.fawe.regions.general.plot.PlotSquaredFeature;
import com.boydti.fawe.util.*;
import com.boydti.fawe.util.chat.ChatManager;
import com.boydti.fawe.util.chat.PlainChatManager;
import com.boydti.fawe.util.cui.CUI;
import com.boydti.fawe.util.metrics.BStats;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.command.*;
import com.sk89q.worldedit.command.composition.SelectionCommand;
import com.sk89q.worldedit.command.tool.*;
import com.sk89q.worldedit.command.tool.brush.GravityBrush;
import com.sk89q.worldedit.event.extent.EditSessionEvent;
import com.sk89q.worldedit.extension.factory.DefaultMaskParser;
import com.sk89q.worldedit.extension.factory.DefaultTransformParser;
import com.sk89q.worldedit.extension.factory.HashTagPatternParser;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.CommandManager;
import com.sk89q.worldedit.extension.platform.PlatformManager;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.extent.MaskingExtent;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.SchematicReader;
import com.sk89q.worldedit.extent.inventory.BlockBagExtent;
import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
import com.sk89q.worldedit.function.CombinedRegionFunction;
import com.sk89q.worldedit.function.block.BlockReplace;
import com.sk89q.worldedit.function.block.ExtentBlockCopy;
import com.sk89q.worldedit.function.entity.ExtentEntityCopy;
import com.sk89q.worldedit.function.mask.*;
import com.sk89q.worldedit.function.operation.ChangeSetExecutor;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.*;
import com.sk89q.worldedit.function.visitor.*;
import com.sk89q.worldedit.internal.command.WorldEditBinding;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.runtime.*;
import com.sk89q.worldedit.math.convolution.HeightMap;
import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.CylinderRegion;
import com.sk89q.worldedit.regions.EllipsoidRegion;
import com.sk89q.worldedit.regions.selector.*;
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
import com.sk89q.worldedit.session.ClipboardHolder;
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.SimpleCommandMapping;
import com.sk89q.worldedit.util.command.SimpleDispatcher;
import com.sk89q.worldedit.util.command.fluent.DispatcherNode;
import com.sk89q.worldedit.util.command.parametric.*;
import com.sk89q.worldedit.util.formatting.Fragment;
import com.sk89q.worldedit.util.formatting.component.CommandListBox;
import com.sk89q.worldedit.util.formatting.component.CommandUsageBox;
import com.sk89q.worldedit.util.formatting.component.MessageBox;
import com.sk89q.worldedit.world.biome.BaseBiome;
import javax.annotation.Nullable;
import javax.management.InstanceAlreadyExistsException;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* [ WorldEdit action]
* |
* \|/
* [ EditSession ] - The change is processed (area restrictions, change limit, block type)
* |
* \|/
* [Block change] - A block change from some location
* |
* \|/
* [ Set Queue ] - The SetQueue manages the implementation specific queue
* |
* \|/
* [ Fawe Queue] - A queue of chunks - check if the queue has the chunk for a change
* |
* \|/
* [ Fawe Chunk Implementation ] - Otherwise create a new FaweChunk object which is a wrapper around the Chunk object
* |
* \|/
* [ Execution ] - When done, the queue then sets the blocks for the chunk, performs lighting updates and sends the chunk packet to the clients
* <p>
* Why it's faster:
* - The chunk is modified directly rather than through the API
* \ Removes some overhead, and means some processing can be done async
* - Lighting updates are performed on the chunk level rather than for every block
* \ e.g. A blob of stone: only the visible blocks need to have the lighting calculated
* - Block changes are sent with a chunk packet
* \ A chunk packet is generally quicker to create and smaller for large world edits
* - No physics updates
* \ Physics updates are slow, and are usually performed on each block
* - Block data shortcuts
* \ Some known blocks don't need to have the data set or accessed (e.g. air is never going to have data)
* - Remove redundant extents
* \ Up to 11 layers of extents can be removed
* - History bypassing
* \ FastMode bypasses history and means blocks in the world don't need to be checked and recorded
*/
public class Fawe {
/**
* The FAWE instance;
*/
private static Fawe INSTANCE;
/**
* TPS timer
*/
private final FaweTimer timer;
private FaweVersion version;
private VisualQueue visualQueue;
private Updater updater;
private TextureUtil textures;
private DefaultTransformParser transformParser;
private ChatManager chatManager = new PlainChatManager();
private BStats stats;
/**
* Get the implementation specific class
*
* @return
*/
@SuppressWarnings("unchecked")
public static <T extends IFawe> T imp() {
return INSTANCE != null ? (T) INSTANCE.IMP : null;
}
/**
* Get the implementation independent class
*
* @return
*/
public static Fawe get() {
return INSTANCE;
}
/**
* Setup Fawe
*
* @param implementation
* @throws InstanceAlreadyExistsException
*/
public static void set(final IFawe implementation) throws InstanceAlreadyExistsException, IllegalArgumentException {
if (INSTANCE != null) {
throw new InstanceAlreadyExistsException("FAWE has already been initialized with: " + INSTANCE.IMP);
}
if (implementation == null) {
throw new IllegalArgumentException("Implementation may not be null.");
}
INSTANCE = new Fawe(implementation);
}
public static void debugPlain(String s) {
if (INSTANCE != null) {
INSTANCE.IMP.debug(s);
} else {
System.out.println(BBC.stripColor(BBC.color(s)));
}
}
/**
* Write something to the console
*
* @param s
*/
public static void debug(Object s) {
debugPlain(BBC.PREFIX.original() + " " + s);
}
/**
* The platform specific implementation
*/
private final IFawe IMP;
private Thread thread = Thread.currentThread();
private Fawe(final IFawe implementation) {
this.INSTANCE = this;
this.IMP = implementation;
this.thread = Thread.currentThread();
/*
* Implementation dependent stuff
*/
this.setupConfigs();
TaskManager.IMP = this.IMP.getTaskManager();
TaskManager.IMP.async(new Runnable() {
@Override
public void run() {
MainUtil.deleteOlder(MainUtil.getFile(IMP.getDirectory(), Settings.IMP.PATHS.HISTORY), TimeUnit.DAYS.toMillis(Settings.IMP.HISTORY.DELETE_AFTER_DAYS), false);
MainUtil.deleteOlder(MainUtil.getFile(IMP.getDirectory(), Settings.IMP.PATHS.CLIPBOARD), TimeUnit.DAYS.toMillis(Settings.IMP.CLIPBOARD.DELETE_AFTER_DAYS), false);
}
});
if (Settings.IMP.METRICS) {
try {
this.stats = new BStats();
this.IMP.startMetrics();
TaskManager.IMP.later(new Runnable() {
@Override
public void run() {
stats.start();
}
}, 1);
} catch (Throwable ignore) {
ignore.printStackTrace();
}
}
this.setupCommands();
/*
* Instance independent stuff
*/
this.setupMemoryListener();
this.timer = new FaweTimer();
Fawe.this.IMP.setupVault();
File jar = MainUtil.getJarFile();
// TODO FIXME remove extrablocks.json
// File extraBlocks = MainUtil.copyFile(jar, "extrablocks.json", null);
// if (extraBlocks != null && extraBlocks.exists()) {
// TaskManager.IMP.task(() -> {
// try {
// BundledBlockData.getInstance().loadFromResource();
// BundledBlockData.getInstance().add(extraBlocks.toURI().toURL(), true);
// } catch (Throwable ignore) {
// ignore.printStackTrace();
// Fawe.debug("Invalid format: extrablocks.json");
// }
// });
// }
// Delayed worldedit setup
TaskManager.IMP.later(() -> {
try {
transformParser = new DefaultTransformParser(getWorldEdit());
visualQueue = new VisualQueue(3);
WEManager.IMP.managers.addAll(Fawe.this.IMP.getMaskManagers());
WEManager.IMP.managers.add(new PlotSquaredFeature());
Fawe.debug("Plugin 'PlotSquared' found. Using it now.");
} catch (Throwable e) {}
}, 0);
TaskManager.IMP.repeat(timer, 1);
if (!Settings.IMP.UPDATE.equalsIgnoreCase("false")) {
// Delayed updating
updater = new Updater();
TaskManager.IMP.async(() -> update());
TaskManager.IMP.repeatAsync(() -> update(), 36000);
}
}
public void onDisable() {
if (stats != null) {
stats.close();
}
}
private boolean update() {
if (updater != null) {
updater.getUpdate(IMP.getPlatform(), getVersion());
return true;
}
return false;
}
public CUI getCUI(Actor actor) {
FawePlayer<Object> fp = FawePlayer.wrap(actor);
CUI cui = fp.getMeta("CUI");
if (cui == null) {
cui = Fawe.imp().getCUI(fp);
if (cui != null) {
synchronized (fp) {
CUI tmp = fp.getMeta("CUI");
if (tmp == null) {
fp.setMeta("CUI", cui);
} else {
cui = tmp;
}
}
}
}
return cui;
}
public ChatManager getChatManager() {
return chatManager;
}
public void setChatManager(ChatManager chatManager) {
checkNotNull(chatManager);
this.chatManager = chatManager;
}
// @Deprecated
// public boolean isJava8() {
// return isJava8;
// }
public DefaultTransformParser getTransformParser() {
return transformParser;
}
/**
* The FAWE updater class
* - Use to get basic update information (changelog/version etc)
*
* @return
*/
public Updater getUpdater() {
return updater;
}
public TextureUtil getCachedTextureUtil(boolean randomize, int min, int max) {
TextureUtil tu = getTextureUtil();
try {
tu = min == 0 && max == 100 ? tu : new CleanTextureUtil(tu, min, max);
tu = randomize ? new RandomTextureUtil(tu) : new CachedTextureUtil(tu);
} catch (FileNotFoundException neverHappens) {
neverHappens.printStackTrace();
}
return tu;
}
public TextureUtil getTextureUtil() {
TextureUtil tmp = textures;
if (tmp == null) {
synchronized (this) {
tmp = textures;
if (tmp == null) {
try {
textures = tmp = new TextureUtil();
tmp.loadModTextures();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return tmp;
}
/**
* The FaweTimer is a useful class for monitoring TPS
*
* @return FaweTimer
*/
public FaweTimer getTimer() {
return timer;
}
/**
* The visual queue is used to queue visualizations
*
* @return
*/
public VisualQueue getVisualQueue() {
return visualQueue;
}
/**
* The FAWE version
* - Unofficial jars may be lacking version information
*
* @return FaweVersion
*/
public
@Nullable
FaweVersion getVersion() {
return version;
}
public double getTPS() {
return timer.getTPS();
}
private void setupCommands() {
this.IMP.setupCommand("fcancel", new Cancel());
}
public void setupConfigs() {
MainUtil.copyFile(MainUtil.getJarFile(), "de/message.yml", null);
MainUtil.copyFile(MainUtil.getJarFile(), "ru/message.yml", null);
MainUtil.copyFile(MainUtil.getJarFile(), "ru/commands.yml", null);
MainUtil.copyFile(MainUtil.getJarFile(), "tr/message.yml", null);
MainUtil.copyFile(MainUtil.getJarFile(), "es/message.yml", null);
MainUtil.copyFile(MainUtil.getJarFile(), "es/commands.yml", null);
// Setting up config.yml
File file = new File(this.IMP.getDirectory(), "config.yml");
Settings.IMP.PLATFORM = IMP.getPlatform().replace("\"", "");
try {
InputStream stream = getClass().getResourceAsStream("/fawe.properties");
java.util.Scanner scanner = new java.util.Scanner(stream).useDelimiter("\\A");
String versionString = scanner.next().trim();
scanner.close();
this.version = new FaweVersion(versionString);
Settings.IMP.DATE = new Date(100 + version.year, version.month, version.day).toGMTString();
Settings.IMP.BUILD = "https://ci.athion.net/job/FastAsyncWorldEdit/" + version.build;
Settings.IMP.COMMIT = "https://github.com/boy0001/FastAsyncWorldedit/commit/" + Integer.toHexString(version.hash);
} catch (Throwable ignore) {}
try {
Settings.IMP.reload(file);
// Setting up message.yml
String lang = Objects.toString(Settings.IMP.LANGUAGE);
BBC.load(new File(this.IMP.getDirectory(), (lang.isEmpty() ? "" : lang + File.separator) + "message.yml"));
Commands.load(new File(INSTANCE.IMP.getDirectory(), "commands.yml"));
} catch (Throwable e) {
debug("====== Failed to load config ======");
debug("Please validate your yaml files:");
debug("====================================");
e.printStackTrace();
debug("====================================");
}
}
public WorldEdit getWorldEdit() {
return WorldEdit.getInstance();
}
public static void setupInjector() {
/*
* Modify the sessions
* - EditSession supports custom queue and a lot of optimizations
* - LocalSession supports VirtualPlayers and undo on disk
*/
if (!Settings.IMP.EXPERIMENTAL.DISABLE_NATIVES) {
try {
com.github.luben.zstd.util.Native.load();
} catch (Throwable e) {
if (Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL > 6 || Settings.IMP.HISTORY.COMPRESSION_LEVEL > 6) {
Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL);
Settings.IMP.HISTORY.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.HISTORY.COMPRESSION_LEVEL);
debug("====== ZSTD COMPRESSION BINDING NOT FOUND ======");
debug(e);
debug("===============================================");
debug("FAWE will work but won't compress data as much");
debug("===============================================");
}
}
try {
net.jpountz.util.Native.load();
} catch (Throwable e) {
e.printStackTrace();
debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======");
debug(e);
debug("===============================================");
debug("FAWE will work but compression will be slower");
debug(" - Try updating your JVM / OS");
debug(" - Report this issue if you cannot resolve it");
debug("===============================================");
}
}
try {
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432");
boolean x86OS = arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64") ? false : true;
boolean x86JVM = System.getProperty("sun.arch.data.model").equals("32");
if (x86OS != x86JVM) {
debug("====== UPGRADE TO 64-BIT JAVA ======");
debug("You are running 32-bit Java on a 64-bit machine");
debug(" - This is only a recommendation");
debug("====================================");
}
} catch (Throwable ignore) {}
}
private void setupMemoryListener() {
if (Settings.IMP.MAX_MEMORY_PERCENT < 1 || Settings.IMP.MAX_MEMORY_PERCENT > 99) {
return;
}
try {
final MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
final NotificationEmitter ne = (NotificationEmitter) memBean;
ne.addNotificationListener(new NotificationListener() {
@Override
public void handleNotification(final Notification notification, final Object handback) {
final long heapSize = Runtime.getRuntime().totalMemory();
final long heapMaxSize = Runtime.getRuntime().maxMemory();
if (heapSize < heapMaxSize) {
return;
}
MemUtil.memoryLimitedTask();
}
}, null, null);
final List<MemoryPoolMXBean> memPools = ManagementFactory.getMemoryPoolMXBeans();
for (final MemoryPoolMXBean mp : memPools) {
if (mp.isUsageThresholdSupported()) {
final MemoryUsage mu = mp.getUsage();
final long max = mu.getMax();
if (max < 0) {
continue;
}
final long alert = (max * Settings.IMP.MAX_MEMORY_PERCENT) / 100;
mp.setUsageThreshold(alert);
}
}
} catch (Throwable e) {
debug("====== MEMORY LISTENER ERROR ======");
MainUtil.handleError(e, false);
debug("===================================");
debug("FAWE needs access to the JVM memory system:");
debug(" - Change your Java security settings");
debug(" - Disable this with `max-memory-percent: -1`");
debug("===================================");
}
}
/**
* Get the main thread
*
* @return
*/
public Thread getMainThread() {
return this.thread;
}
public static boolean isMainThread() {
return INSTANCE != null ? INSTANCE.thread == Thread.currentThread() : true;
}
/**
* Sets the main thread to the current thread
*
* @return
*/
public Thread setMainThread() {
return this.thread = Thread.currentThread();
}
private ConcurrentHashMap<String, FawePlayer> players = new ConcurrentHashMap<>(8, 0.9f, 1);
private ConcurrentHashMap<UUID, FawePlayer> playersUUID = new ConcurrentHashMap<>(8, 0.9f, 1);
public <T> void register(FawePlayer<T> player) {
players.put(player.getName(), player);
playersUUID.put(player.getUUID(), player);
}
public <T> void unregister(String name) {
FawePlayer player = players.remove(name);
if (player != null) playersUUID.remove(player.getUUID());
}
public FawePlayer getCachedPlayer(String name) {
return players.get(name);
}
public FawePlayer getCachedPlayer(UUID uuid) {
return playersUUID.get(uuid);
}
public Collection<FawePlayer> getCachedPlayers() {
return players.values();
}
}

View File

@ -0,0 +1,564 @@
package com.boydti.fawe;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.example.NMSMappedFaweQueue;
import com.boydti.fawe.example.NMSRelighter;
import com.boydti.fawe.object.FaweLocation;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.object.schematic.Schematic;
import com.boydti.fawe.regions.FaweMaskManager;
import com.boydti.fawe.util.EditSessionBuilder;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MemUtil;
import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.TaskManager;
import com.boydti.fawe.util.WEManager;
import com.boydti.fawe.wrappers.WorldWrapper;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extension.factory.DefaultMaskParser;
import com.sk89q.worldedit.extension.factory.DefaultTransformParser;
import com.sk89q.worldedit.extension.factory.HashTagPatternParser;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.CommandManager;
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.internal.registry.AbstractFactory;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The FaweAPI class offers a few useful functions.<br>
* - This class is not intended to replace the WorldEdit API<br>
* - With FAWE installed, you can use the EditSession and other WorldEdit classes from an async thread.<br>
* <br>
* FaweAPI.[some method]
*/
public class FaweAPI {
/**
* Offers a lot of options for building an EditSession
*
* @param world
* @return A new EditSessionBuilder
* @see com.boydti.fawe.util.EditSessionBuilder
*/
public static EditSessionBuilder getEditSessionBuilder(World world) {
return new EditSessionBuilder(world);
}
/**
* The TaskManager has some useful methods for doing things asynchronously
*
* @return TaskManager
*/
public static TaskManager getTaskManager() {
return TaskManager.IMP;
}
/**
* Add a custom mask for use in e.g {@literal //mask #id:<input>}
*
* @param methods The class with a bunch of mask methods
* @return true if the mask was registered
* @see com.sk89q.worldedit.command.MaskCommands
*/
public static boolean registerMasks(Object methods) {
DefaultMaskParser parser = getParser(DefaultMaskParser.class);
if (parser != null) parser.register(methods);
return parser != null;
}
/**
* Add a custom material for use in e.g {@literal //material #id:<input>}
*
* @param methods The class with a bunch of pattern methods
* @return true if the mask was registered
* @see com.sk89q.worldedit.command.PatternCommands
*/
public static boolean registerPatterns(Object methods) {
HashTagPatternParser parser = getParser(HashTagPatternParser.class);
if (parser != null) parser.register(methods);
return parser != null;
}
/**
* Add a custom transform for use in
*
* @param methods The class with a bunch of transform methods
* @return true if the transform was registered
* @see com.sk89q.worldedit.command.TransformCommands
*/
public static boolean registerTransforms(Object methods) {
DefaultTransformParser parser = Fawe.get().getTransformParser();
if (parser != null) parser.register(methods);
return parser != null;
}
public static <T> T getParser(Class<T> parserClass) {
try {
Field field = AbstractFactory.class.getDeclaredField("parsers");
field.setAccessible(true);
ArrayList<InputParser> parsers = new ArrayList<>();
parsers.addAll((List<InputParser>) field.get(WorldEdit.getInstance().getMaskFactory()));
parsers.addAll((List<InputParser>) field.get(WorldEdit.getInstance().getBlockFactory()));
parsers.addAll((List<InputParser>) field.get(WorldEdit.getInstance().getItemFactory()));
parsers.addAll((List<InputParser>) field.get(WorldEdit.getInstance().getPatternFactory()));
for (InputParser parser : parsers) {
if (parserClass.isAssignableFrom(parser.getClass())) {
return (T) parser;
}
}
return null;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* Create a command with the provided aliases and register all methods of the class as sub commands.<br>
* - You should try to register commands during startup
* - If no aliases are specified, all methods become root commands
*
* @param clazz The class containing all the sub command methods
* @param aliases The aliases to give the command (or none)
*/
public static void registerCommands(Object clazz, String... aliases) {
CommandManager.getInstance().registerCommands(clazz, aliases);
}
/**
* Wrap some object into a FawePlayer<br>
* - org.bukkit.entity.Player
* - org.spongepowered.api.entity.living.player
* - com.sk89q.worldedit.entity.Player
* - String (name)
* - UUID (player UUID)
*
* @param obj
* @return
*/
public static FawePlayer wrapPlayer(Object obj) {
return FawePlayer.wrap(obj);
}
public static FaweQueue createQueue(String worldName, boolean autoqueue) {
return SetQueue.IMP.getNewQueue(worldName, true, autoqueue);
}
/**
* You can either use a FaweQueue or an EditSession to change blocks<br>
* - The FaweQueue skips a bit of overhead so it's faster<br>
* - The WorldEdit EditSession can do a lot more<br>
* Remember to enqueue it when you're done!<br>
*
* @param world The name of the world
* @param autoqueue If it should start dispatching before you enqueue it.
* @return
* @see com.boydti.fawe.object.FaweQueue#enqueue()
*/
public static FaweQueue createQueue(World world, boolean autoqueue) {
return SetQueue.IMP.getNewQueue(world, true, autoqueue);
}
public static World getWorld(String worldName) {
Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING);
List<? extends World> worlds = platform.getWorlds();
for (World current : worlds) {
if (Fawe.imp().getWorldName(current).equals(worldName)) {
return WorldWrapper.wrap((AbstractWorld) current);
}
}
for (World current : worlds) {
if (current.getName().equals(worldName)) {
return WorldWrapper.wrap((AbstractWorld) current);
}
}
return null;
}
/**
* Upload the clipboard to the configured web interface
*
* @param clipboard The clipboard (may not be null)
* @param format The format to use (some formats may not be supported)
* @return The download URL or null
*/
public static URL upload(final Clipboard clipboard, final ClipboardFormat format) {
return format.uploadAnonymous(clipboard);
}
/**
* Just forwards to ClipboardFormat.SCHEMATIC.load(file)
*
* @param file
* @return
* @see com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat
* @see com.boydti.fawe.object.schematic.Schematic
*/
public static Schematic load(File file) throws IOException {
return ClipboardFormat.SCHEMATIC.load(file);
}
/**
* Get a list of supported protection plugin masks.
*
* @return Set of FaweMaskManager
*/
public static Set<FaweMaskManager> getMaskManagers() {
return new HashSet<>(WEManager.IMP.managers);
}
/**
* Check if the server has more than the configured low memory threshold
*
* @return True if the server has limited memory
*/
public static boolean isMemoryLimited() {
return MemUtil.isMemoryLimited();
}
/**
* Use ThreadLocalRandom instead
*
* @return
*/
@Deprecated
public static PseudoRandom getFastRandom() {
return new PseudoRandom();
}
/**
* Get a player's allowed WorldEdit region
*
* @param player
* @return
*/
public static Region[] getRegions(FawePlayer player) {
return WEManager.IMP.getMask(player);
}
/**
* Cancel the edit with the following extent<br>
* - The extent must be the one being used by an EditSession, otherwise an error may be thrown <br>
* - Insert an extent into the EditSession using the EditSessionEvent: http://wiki.sk89q.com/wiki/WorldEdit/API/Hooking_EditSession <br>
*
* @param extent
* @param reason
* @see com.sk89q.worldedit.EditSession#getRegionExtent() To get the FaweExtent for an EditSession
*/
public static void cancelEdit(Extent extent, BBC reason) {
try {
WEManager.IMP.cancelEdit(extent, reason);
} catch (WorldEditException ignore) {
}
}
public static void addMaskManager(FaweMaskManager maskMan) {
WEManager.IMP.managers.add(maskMan);
}
/**
* Get the DiskStorageHistory object representing a File
*
* @param file
* @return
*/
public static DiskStorageHistory getChangeSetFromFile(File file) {
if (!file.exists() || file.isDirectory()) {
throw new IllegalArgumentException("Not a file!");
}
if (!file.getName().toLowerCase().endsWith(".bd")) {
throw new IllegalArgumentException("Not a BD file!");
}
if (Settings.IMP.HISTORY.USE_DISK) {
throw new IllegalArgumentException("History on disk not enabled!");
}
String[] path = file.getPath().split(File.separator);
if (path.length < 3) {
throw new IllegalArgumentException("Not in history directory!");
}
String worldName = path[path.length - 3];
String uuidString = path[path.length - 2];
World world = getWorld(worldName);
if (world == null) {
throw new IllegalArgumentException("Corresponding world does not exist: " + worldName);
}
UUID uuid;
try {
uuid = UUID.fromString(uuidString);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid UUID from file path: " + uuidString);
}
DiskStorageHistory history = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
return history;
}
/**
* Used in the RollBack to generate a list of DiskStorageHistory objects<br>
* - Note: An edit outside the radius may be included if it overlaps with an edit inside that depends on it.
*
* @param origin - The origin location
* @param user - The uuid (may be null)
* @param radius - The radius from the origin of the edit
* @param timediff - The max age of the file in milliseconds
* @param shallow - If shallow is true, FAWE will only read the first Settings.IMP.BUFFER_SIZE bytes to obtain history info<br>
* Reading only part of the file will result in unreliable bounds info for large edits
* @return
*/
public static List<DiskStorageHistory> getBDFiles(FaweLocation origin, UUID user, int radius, long timediff, boolean shallow) {
File history = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + origin.world);
if (!history.exists()) {
return new ArrayList<>();
}
long now = System.currentTimeMillis();
ArrayList<File> files = new ArrayList<>();
for (File userFile : history.listFiles()) {
if (!userFile.isDirectory()) {
continue;
}
UUID userUUID;
try {
userUUID = UUID.fromString(userFile.getName());
} catch (IllegalArgumentException e) {
continue;
}
if (user != null && !userUUID.equals(user)) {
continue;
}
ArrayList<Integer> ids = new ArrayList<>();
for (File file : userFile.listFiles()) {
if (file.getName().endsWith(".bd")) {
if (timediff >= Integer.MAX_VALUE || now - file.lastModified() <= timediff) {
files.add(file);
if (files.size() > 2048) {
return null;
}
}
}
}
}
World world = origin.getWorld();
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File a, File b) {
String aName = a.getName();
String bName = b.getName();
int aI = Integer.parseInt(aName.substring(0, aName.length() - 3));
int bI = Integer.parseInt(bName.substring(0, bName.length() - 3));
long value = aI - bI;
return value == 0 ? 0 : value < 0 ? -1 : 1;
}
});
RegionWrapper bounds = new RegionWrapper(origin.x - radius, origin.x + radius, origin.z - radius, origin.z + radius);
RegionWrapper boundsPlus = new RegionWrapper(bounds.minX - 64, bounds.maxX + 512, bounds.minZ - 64, bounds.maxZ + 512);
HashSet<RegionWrapper> regionSet = new HashSet<RegionWrapper>(Arrays.asList(bounds));
ArrayList<DiskStorageHistory> result = new ArrayList<>();
for (File file : files) {
UUID uuid = UUID.fromString(file.getParentFile().getName());
DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
DiskStorageHistory.DiskStorageSummary summary = dsh.summarize(boundsPlus, shallow);
RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ);
boolean encompassed = false;
boolean isIn = false;
for (RegionWrapper allowed : regionSet) {
isIn = isIn || allowed.intersects(region);
if (encompassed = allowed.isIn(region.minX, region.maxX) && allowed.isIn(region.minZ, region.maxZ)) {
break;
}
}
if (isIn) {
result.add(0, dsh);
if (!encompassed) {
regionSet.add(region);
}
if (shallow && result.size() > 64) {
return result;
}
}
}
return result;
}
/**
* The DiskStorageHistory class is what FAWE uses to represent the undo on disk.
*
* @param world
* @param uuid
* @param index
* @return
* @see com.boydti.fawe.object.changeset.DiskStorageHistory#toEditSession(com.boydti.fawe.object.FawePlayer)
*/
public static DiskStorageHistory getChangeSetFromDisk(World world, UUID uuid, int index) {
return new DiskStorageHistory(world, uuid, index);
}
/**
* Compare two versions
*
* @param version
* @param major
* @param minor
* @param minor2
* @return true if version is >= major, minor, minor2
*/
public static boolean checkVersion(final int[] version, final int major, final int minor, final int minor2) {
return (version[0] > major) || ((version[0] == major) && (version[1] > minor)) || ((version[0] == major) && (version[1] == minor) && (version[2] >= minor2));
}
@Deprecated
public static int fixLighting(String world, Region selection) {
return fixLighting(world, selection, FaweQueue.RelightMode.ALL);
}
@Deprecated
public static int fixLighting(String world, Region selection, final FaweQueue.RelightMode mode) {
return fixLighting(world, selection, null, mode);
}
@Deprecated
public static int fixLighting(String world, Region selection, @Nullable FaweQueue queue, final FaweQueue.RelightMode mode) {
return fixLighting(getWorld(world), selection, queue, mode);
}
/**
* Fix the lighting in a selection<br>
* - First removes all lighting, then relights
* - Relights in parallel (if enabled) for best performance<br>
* - Also resends chunks<br>
*
* @param world
* @param selection (assumes cuboid)
* @return
*/
public static int fixLighting(World world, Region selection, @Nullable FaweQueue queue, final FaweQueue.RelightMode mode) {
final Vector bot = selection.getMinimumPoint();
final Vector top = selection.getMaximumPoint();
final int minX = bot.getBlockX() >> 4;
final int minZ = bot.getBlockZ() >> 4;
final int maxX = top.getBlockX() >> 4;
final int maxZ = top.getBlockZ() >> 4;
int count = 0;
if (queue == null) {
queue = SetQueue.IMP.getNewQueue(world, true, false);
}
// Remove existing lighting first
if (queue instanceof NMSMappedFaweQueue) {
final NMSMappedFaweQueue nmsQueue = (NMSMappedFaweQueue) queue;
NMSRelighter relighter = new NMSRelighter(nmsQueue);
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
relighter.addChunk(x, z, null, 65535);
count++;
}
}
if (mode != FaweQueue.RelightMode.NONE) {
boolean sky = nmsQueue.hasSky();
if (sky) {
relighter.fixSkyLighting();
}
relighter.fixBlockLighting();
} else {
relighter.removeLighting();
}
relighter.sendChunks();
}
return count;
}
/**
* Set a task to run when the global queue (SetQueue class) is empty
*
* @param whenDone
*/
public static void addTask(final Runnable whenDone) {
SetQueue.IMP.addEmptyTask(whenDone);
}
/**
* Have a task run when the server is low on memory (configured threshold)
*
* @param run
*/
public static void addMemoryLimitedTask(Runnable run) {
MemUtil.addMemoryLimitedTask(run);
}
/**
* Have a task run when the server is no longer low on memory (configured threshold)
*
* @param run
*/
public static void addMemoryPlentifulTask(Runnable run) {
MemUtil.addMemoryPlentifulTask(run);
}
/**
* @return
* @see BBC
*/
public static BBC[] getTranslations() {
return BBC.values();
}
/**
* @see #getEditSessionBuilder(com.sk89q.worldedit.world.World)
* @deprecated
*/
@Deprecated
public static EditSession getNewEditSession(@Nonnull FawePlayer player) {
if (player == null) {
throw new IllegalArgumentException("Player may not be null");
}
return player.getNewEditSession();
}
/**
* @see #getEditSessionBuilder(com.sk89q.worldedit.world.World)
* @deprecated
*/
@Deprecated
public static EditSession getNewEditSession(World world) {
return WorldEdit.getInstance().getEditSessionFactory().getEditSession(world, -1);
}
}

View File

@ -0,0 +1,218 @@
package com.boydti.fawe;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.lang.reflect.Field;
import java.util.*;
public class FaweCache {
/**
* [ y | z | x ] => index
*/
public final static short[][][] CACHE_I = new short[256][16][16];
/**
* [ y | z | x ] => index
*/
public final static short[][][] CACHE_J = new short[256][16][16];
/**
* [ i | j ] => x
*/
public final static byte[][] CACHE_X = new byte[16][];
/**
* [ i | j ] => y
*/
public final static short[][] CACHE_Y = new short[16][4096];
/**
* [ i | j ] => z
*/
public final static byte[][] CACHE_Z = new byte[16][];
/**
* Immutable biome cache
*/
public final static BaseBiome[] CACHE_BIOME = new BaseBiome[256];
public static final BaseBiome getBiome(int id) {
return CACHE_BIOME[id];
}
static {
for (int i = 0; i < 256; i++) {
CACHE_BIOME[i] = new BaseBiome(i) {
@Override
public void setId(int id) {
throw new IllegalStateException("Cannot set id");
}
};
}
CACHE_X[0] = new byte[4096];
CACHE_Z[0] = new byte[4096];
for (int y = 0; y < 16; y++) {
CACHE_X[y] = CACHE_X[0];
CACHE_Z[y] = CACHE_Z[0];
}
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
final short j = (short) (((y & 0xF) << 8) | (z << 4) | x);
CACHE_X[0][j] = (byte) x;
CACHE_Z[0][j] = (byte) z;
}
}
}
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 256; y++) {
final short i = (short) (y >> 4);
final short j = (short) (((y & 0xF) << 8) | (z << 4) | x);
CACHE_I[y][z][x] = i;
CACHE_J[y][z][x] = j;
CACHE_Y[i][j] = (short) y;
}
}
}
}
public static Map<String, Object> asMap(Object... pairs) {
HashMap<String, Object> map = new HashMap<String, Object>(pairs.length >> 1);
for (int i = 0; i < pairs.length; i += 2) {
String key = (String) pairs[i];
Object value = pairs[i + 1];
map.put(key, value);
}
return map;
}
public static ShortTag asTag(short value) {
return new ShortTag(value);
}
public static IntTag asTag(int value) {
return new IntTag(value);
}
public static DoubleTag asTag(double value) {
return new DoubleTag(value);
}
public static ByteTag asTag(byte value) {
return new ByteTag(value);
}
public static FloatTag asTag(float value) {
return new FloatTag(value);
}
public static LongTag asTag(long value) {
return new LongTag(value);
}
public static ByteArrayTag asTag(byte[] value) {
return new ByteArrayTag(value);
}
public static IntArrayTag asTag(int[] value) {
return new IntArrayTag(value);
}
public static LongArrayTag asTag(long[] value) {
return new LongArrayTag(value);
}
public static StringTag asTag(String value) {
return new StringTag(value);
}
public static CompoundTag asTag(Map<String, Object> value) {
HashMap<String, Tag> map = new HashMap<>();
for (Map.Entry<String, Object> entry : value.entrySet()) {
Object child = entry.getValue();
Tag tag = asTag(child);
map.put(entry.getKey(), tag);
}
return new CompoundTag(map);
}
public static Tag asTag(Object value) {
if (value instanceof Integer) {
return asTag((int) value);
} else if (value instanceof Short) {
return asTag((short) value);
} else if (value instanceof Double) {
return asTag((double) value);
} else if (value instanceof Byte) {
return asTag((byte) value);
} else if (value instanceof Float) {
return asTag((float) value);
} else if (value instanceof Long) {
return asTag((long) value);
} else if (value instanceof String) {
return asTag((String) value);
} else if (value instanceof Map) {
return asTag((Map) value);
} else if (value instanceof Collection) {
return asTag((Collection) value);
} else if (value instanceof Object[]) {
return asTag((Object[]) value);
} else if (value instanceof byte[]) {
return asTag((byte[]) value);
} else if (value instanceof int[]) {
return asTag((int[]) value);
} else if (value instanceof long[]) {
return asTag((long[]) value);
} else if (value instanceof Tag) {
return (Tag) value;
} else if (value instanceof Boolean) {
return asTag((byte) ((boolean) value ? 1 : 0));
} else if (value == null) {
System.out.println("Invalid nbt: " + value);
return null;
} else {
Class<? extends Object> clazz = value.getClass();
if (clazz.getName().startsWith("com.intellectualcrafters.jnbt")) {
try {
if (clazz.getName().equals("com.intellectualcrafters.jnbt.EndTag")) {
return new EndTag();
}
Field field = clazz.getDeclaredField("value");
field.setAccessible(true);
return asTag(field.get(value));
} catch (Throwable e) {
e.printStackTrace();
}
}
System.out.println("Invalid nbt: " + value);
return null;
}
}
public static ListTag asTag(Object... values) {
Class clazz = null;
List<Tag> list = new ArrayList<>(values.length);
for (Object value : values) {
Tag tag = asTag(value);
if (clazz == null) {
clazz = tag.getClass();
}
list.add(tag);
}
if (clazz == null) clazz = EndTag.class;
return new ListTag(clazz, list);
}
public static ListTag asTag(Collection values) {
Class clazz = null;
List<Tag> list = new ArrayList<>(values.size());
for (Object value : values) {
Tag tag = asTag(value);
if (clazz == null) {
clazz = tag.getClass();
}
list.add(tag);
}
if (clazz == null) clazz = EndTag.class;
return new ListTag(clazz, list);
}
}

View File

@ -0,0 +1,32 @@
package com.boydti.fawe;
public class FaweVersion {
public final int year, month, day, hash, build, major, minor, patch;
public FaweVersion(String version) {
String[] split = version.substring(version.indexOf('=') + 1).split("-");
if (split[0].equals("unknown")) {
this.year = month = day = hash = build = major = minor = patch = 0;
return;
}
String[] date = split[0].split("\\.");
this.year = Integer.parseInt(date[0]);
this.month = Integer.parseInt(date[1]);
this.day = Integer.parseInt(date[2]);
this.hash = Integer.parseInt(split[1], 16);
this.build = Integer.parseInt(split[2]);
String[] semver = split[3].split("\\.");
this.major = Integer.parseInt(semver[0]);
this.minor = Integer.parseInt(semver[1]);
this.patch = Integer.parseInt(semver[2]);
}
@Override
public String toString() {
return "FastAsyncWorldEdit-" + year + "." + month + "." + day + "-" + Integer.toHexString(hash) + "-" + build;
}
public boolean isNewer(FaweVersion other) {
return other.build < this.build && (this.major > other.major || (this.major == other.major && this.minor > other.minor) || (this.major == other.major && this.minor == other.minor && this.patch > other.patch));
}
}

View File

@ -0,0 +1,68 @@
package com.boydti.fawe;
import com.boydti.fawe.object.FaweCommand;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.regions.FaweMaskManager;
import com.boydti.fawe.util.TaskManager;
import com.boydti.fawe.util.cui.CUI;
import com.boydti.fawe.util.gui.FormBuilder;
import com.boydti.fawe.util.image.ImageViewer;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.util.Collection;
import java.util.UUID;
public interface IFawe {
public void debug(final String s);
public File getDirectory();
public void setupCommand(final String label, final FaweCommand cmd);
public FawePlayer wrap(final Object obj);
public void setupVault();
public TaskManager getTaskManager();
public FaweQueue getNewQueue(World world, boolean fast);
public FaweQueue getNewQueue(String world, boolean fast);
public String getWorldName(World world);
public Collection<FaweMaskManager> getMaskManagers();
public void startMetrics();
default CUI getCUI(FawePlayer player) { return null; }
default ImageViewer getImageViewer(FawePlayer player) { return null; }
public default void registerPacketListener() {}
default int getPlayerCount() {
return Fawe.get().getCachedPlayers().size();
}
public String getPlatformVersion();
public boolean isOnlineMode();
public String getPlatform();
public UUID getUUID(String name);
public String getName(UUID uuid);
public Object getBlocksHubApi();
public default String getDebugInfo() {
return "";
}
public default FormBuilder getFormBuilder() {
return null;
}
}

View File

@ -0,0 +1,693 @@
package com.boydti.fawe.command;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.jnbt.anvil.*;
import com.boydti.fawe.jnbt.anvil.filters.*;
import com.boydti.fawe.jnbt.anvil.history.IAnvilHistory;
import com.boydti.fawe.jnbt.anvil.history.NullAnvilHistory;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal4;
import com.boydti.fawe.object.changeset.AnvilHistory;
import com.boydti.fawe.object.clipboard.remap.ClipboardRemapper;
import com.boydti.fawe.object.mask.FaweBlockMatcher;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.StringMan;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.pattern.RandomPattern;
import com.sk89q.worldedit.internal.annotation.Selection;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.command.binding.Switch;
import com.sk89q.worldedit.util.command.parametric.Optional;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;
import java.util.function.Consumer;
import static com.google.common.base.Preconditions.checkNotNull;
@Command(aliases = {"/anvil"}, desc = "Manipulate billions of blocks: [More Info](https://github.com/boy0001/FastAsyncWorldedit/wiki/Anvil-API)")
public class AnvilCommands {
private final WorldEdit worldEdit;
/**
* Create a new instance.
*
* @param worldEdit reference to WorldEdit
*/
public AnvilCommands(WorldEdit worldEdit) {
checkNotNull(worldEdit);
this.worldEdit = worldEdit;
}
/**
* Run safely on an unloaded world (no selection)
*
* @param player
* @param folder
* @param filter
* @param <G>
* @param <T>
* @return
*/
@Deprecated
public static <G, T extends MCAFilter<G>> T runWithWorld(Player player, String folder, T filter, boolean force) {
return runWithWorld(player, folder, filter, force, false);
}
@Deprecated
public static <G, T extends MCAFilter<G>> T runWithWorld(Player player, String folder, T filter, boolean force, boolean unsafe) {
boolean copy = false;
if (FaweAPI.getWorld(folder) != null) {
if (!force) {
BBC.WORLD_IS_LOADED.send(player);
return null;
}
copy = true;
}
FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false);
MCAQueue queue = new MCAQueue(defaultQueue);
if (copy && !unsafe) {
return queue.filterCopy(filter, RegionWrapper.GLOBAL());
} else {
return queue.filterWorld(filter);
}
}
/**
* Run safely on an existing world within a selection
*
* @param player
* @param editSession
* @param selection
* @param filter
* @param <G>
* @param <T>
* @return
*/
@Deprecated
public static <G, T extends MCAFilter<G>> T runWithSelection(Player player, EditSession editSession, Region selection, T filter) {
if (!(selection instanceof CuboidRegion)) {
BBC.NO_REGION.send(player);
return null;
}
CuboidRegion cuboid = (CuboidRegion) selection;
RegionWrapper wrappedRegion = new RegionWrapper(cuboid.getMinimumPoint(), cuboid.getMaximumPoint());
String worldName = Fawe.imp().getWorldName(editSession.getWorld());
FaweQueue tmp = SetQueue.IMP.getNewQueue(worldName, true, false);
MCAQueue queue = new MCAQueue(tmp);
FawePlayer<Object> fp = FawePlayer.wrap(player);
fp.checkAllowedRegion(selection);
recordHistory(fp, editSession.getWorld(), iAnvilHistory -> {
queue.filterCopy(filter, wrappedRegion, iAnvilHistory);
});
return filter;
}
public static void recordHistory(FawePlayer fp, World world, Consumer<IAnvilHistory> run) {
LocalSession session = fp.getSession();
if (session == null || session.hasFastMode()) {
run.accept(new NullAnvilHistory());
} else {
AnvilHistory history = new AnvilHistory(Fawe.imp().getWorldName(world), fp.getUUID());
run.accept(history);
session.remember(fp.getPlayer(), world, history, fp.getLimit());
}
}
// @Command(
// aliases = {"replaceall", "rea", "repall"},
// usage = "<folder> [from-block] <to-block>",
// desc = "Replace all blocks in the selection with another",
// help = "Replace all blocks in the selection with another\n" +
// "The -d flag disabled wildcard data matching\n",
// flags = "df",
// min = 2,
// max = 4
// )
// @CommandPermissions("worldedit.anvil.replaceall")
// public void replaceAll(Player player, String folder, @Optional String from, String to, @Switch('d') boolean useData) throws WorldEditException {
// final FaweBlockMatcher matchFrom;
// if (from == null) {
// matchFrom = FaweBlockMatcher.NOT_AIR;
// } else {
// if (from.contains(":")) {
// useData = true; //override d flag, if they specified data they want it
// }
// matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData);
// }
// final FaweBlockMatcher matchTo = FaweBlockMatcher.setBlocks(worldEdit.getBlocks(player, to, true));
// ReplaceSimpleFilter filter = new ReplaceSimpleFilter(matchFrom, matchTo);
// ReplaceSimpleFilter result = runWithWorld(player, folder, filter, true);
// if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
// }
@Command(
aliases = {"remapall"},
usage = "<folder>",
help = "Remap the world between MCPE/PC values",
desc = "Remap the world between MCPE/PC values",
min = 1,
max = 1
)
@CommandPermissions("worldedit.anvil.remapall")
public void remapall(Player player, String folder) throws WorldEditException {
ClipboardRemapper mapper;
ClipboardRemapper.RemapPlatform from;
ClipboardRemapper.RemapPlatform to;
if (Fawe.imp().getPlatform().equalsIgnoreCase("nukkit")) {
from = ClipboardRemapper.RemapPlatform.PC;
to = ClipboardRemapper.RemapPlatform.PE;
} else {
from = ClipboardRemapper.RemapPlatform.PE;
to = ClipboardRemapper.RemapPlatform.PC;
}
RemapFilter filter = new RemapFilter(from, to);
RemapFilter result = runWithWorld(player, folder, filter, true);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"deleteallunvisited", "delunvisited" },
usage = "<folder> <age-ticks> [file-age=60000]",
desc = "Delete all chunks which haven't been occupied",
help = "Delete all chunks which haven't been occupied for `age-ticks` (20t = 1s) and \n" +
"Have not been accessed since `file-duration` (ms) after creation and\n" +
"Have not been used in the past `chunk-inactivity` (ms)" +
"The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`",
min = 2,
max = 3
)
@CommandPermissions("worldedit.anvil.deleteallunvisited")
public void deleteAllUnvisited(Player player, String folder, int inhabitedTicks, @Optional("60000") int fileDurationMillis) throws WorldEditException {
long chunkInactivityMillis = fileDurationMillis; // Use same value for now
DeleteUninhabitedFilter filter = new DeleteUninhabitedFilter(fileDurationMillis, inhabitedTicks, chunkInactivityMillis);
DeleteUninhabitedFilter result = runWithWorld(player, folder, filter, true);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"deleteallunclaimed", "delallunclaimed" },
usage = "<age-ticks> [file-age=60000]",
desc = "(Supports: WG, P2, GP) Delete all chunks which haven't been occupied AND claimed",
help = "(Supports: WG, P2, GP) Delete all chunks which aren't claimed AND haven't been occupied for `age-ticks` (20t = 1s) and \n" +
"Have not been accessed since `file-duration` (ms) after creation and\n" +
"Have not been used in the past `chunk-inactivity` (ms)" +
"The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`",
min = 1,
max = 3
)
@CommandPermissions("worldedit.anvil.deleteallunclaimed")
public void deleteAllUnclaimed(Player player, int inhabitedTicks, @Optional("60000") int fileDurationMillis, @Switch('d') boolean debug) throws WorldEditException {
String folder = Fawe.imp().getWorldName(player.getWorld());
long chunkInactivityMillis = fileDurationMillis; // Use same value for now
DeleteUnclaimedFilter filter = new DeleteUnclaimedFilter(player.getWorld(), fileDurationMillis, inhabitedTicks, chunkInactivityMillis);
if (debug) filter.enableDebug();
DeleteUnclaimedFilter result = runWithWorld(player, folder, filter, true);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"deleteunclaimed"},
usage = "<age-ticks> [file-age=60000]",
desc = "(Supports: WG, P2, GP) Delete all chunks which haven't been occupied AND claimed",
help = "(Supports: WG, P2, GP) Delete all chunks which aren't claimed AND haven't been occupied for `age-ticks` (20t = 1s) and \n" +
"Have not been accessed since `file-duration` (ms) after creation and\n" +
"Have not been used in the past `chunk-inactivity` (ms)" +
"The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`",
min = 1,
max = 3
)
@CommandPermissions("worldedit.anvil.deleteunclaimed")
public void deleteUnclaimed(Player player, EditSession editSession, @Selection Region selection, int inhabitedTicks, @Optional("60000") int fileDurationMillis, @Switch('d') boolean debug) throws WorldEditException {
String folder = Fawe.imp().getWorldName(player.getWorld());
long chunkInactivityMillis = fileDurationMillis; // Use same value for now
DeleteUnclaimedFilter filter = new DeleteUnclaimedFilter(player.getWorld(), fileDurationMillis, inhabitedTicks, chunkInactivityMillis);
if (debug) filter.enableDebug();
DeleteUnclaimedFilter result = runWithSelection(player, editSession, selection, filter);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"deletealloldregions", "deloldreg" },
usage = "<folder> <time>",
desc = "Delete regions which haven't been accessed in a certain amount of time",
help = "Delete regions which haven't been accessed in a certain amount of time\n" +
"You can use seconds (s), minutes (m), hours (h), days (d), weeks (w), years (y)\n" +
"(months are not a unit of time)\n" +
"E.g. 8h5m12s\n",
min = 2,
max = 3
)
@CommandPermissions("worldedit.anvil.deletealloldregions")
public void deleteAllOldRegions(Player player, String folder, String time) throws WorldEditException {
long duration = MainUtil.timeToSec(time) * 1000l;
DeleteOldFilter filter = new DeleteOldFilter(duration);
DeleteOldFilter result = runWithWorld(player, folder, filter, true);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"trimallplots", },
desc = "Trim chunks in a Plot World",
help = "Trim chunks in a Plot World\n" +
"Unclaimed chunks will be deleted\n" +
"Unmodified chunks will be deleted\n" +
"Use -v to also delete unvisited chunks\n"
)
@CommandPermissions("worldedit.anvil.trimallplots")
public void trimAllPlots(Player player, @Switch('v') boolean deleteUnvisited) throws WorldEditException {
String folder = Fawe.imp().getWorldName(player.getWorld());
int visitTime = deleteUnvisited ? 1 : -1;
PlotTrimFilter filter = new PlotTrimFilter(player.getWorld(), 0, visitTime, 600000);
// PlotTrimFilter result = runWithWorld(player, folder, filter, true);
FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false);
MCAQueue queue = new MCAQueue(defaultQueue);
PlotTrimFilter result = queue.filterWorld(filter);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"deletebiomechunks", },
desc = "Delete chunks matching a specific biome"
)
@CommandPermissions("worldedit.anvil.trimallair")
public void deleteBiome(Player player, String folder, BaseBiome biome, @Switch('u') boolean unsafe) {
DeleteBiomeFilterSimple filter = new DeleteBiomeFilterSimple(biome);
DeleteBiomeFilterSimple result = runWithWorld(player, folder, filter, true, unsafe);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"trimallair", },
desc = "Trim all air in the world"
)
@CommandPermissions("worldedit.anvil.trimallair")
public void trimAllAir(Player player, String folder, @Switch('u') boolean unsafe) throws WorldEditException {
TrimAirFilter filter = new TrimAirFilter();
TrimAirFilter result = runWithWorld(player, folder, filter, true, unsafe);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"debugfixair", },
desc = "debug - do not use"
)
@CommandPermissions("worldedit.anvil.debugfixair")
public void debugfixair(Player player, String folder) throws WorldEditException {
DebugFixAir filter = new DebugFixAir();
DebugFixAir result = runWithWorld(player, folder, filter, true, true);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
@Command(
aliases = {"debugfixroads", },
desc = "debug - do not use"
)
@CommandPermissions("worldedit.anvil.debugfixroads")
public void debugfixroads(Player player, String folder) throws WorldEditException {
DebugFixP2Roads filter = new DebugFixP2Roads();
DebugFixP2Roads result = runWithWorld(player, folder, filter, true, true);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
// @Command(
// aliases = {"replaceallpattern", "reap", "repallpat"},
// usage = "<folder> [from-block] <to-pattern>",
// desc = "Replace all blocks in the selection with another",
// flags = "dm",
// min = 2,
// max = 4
// )
// @CommandPermissions("worldedit.anvil.replaceall")
// public void replaceAllPattern(Player player, String folder, @Optional String from, final Pattern to, @Switch('d') boolean useData, @Switch('m') boolean useMap) throws WorldEditException {
// MCAFilterCounter filter;
// if (useMap) {
// if (to instanceof RandomPattern) {
// List<String> split = StringMan.split(from, ',');
// filter = new MappedReplacePatternFilter(from, (RandomPattern) to, useData);
// } else {
// player.print(BBC.getPrefix() + "Must be a pattern list!");
// return;
// }
// } else {
// final FaweBlockMatcher matchFrom;
// if (from == null) {
// matchFrom = FaweBlockMatcher.NOT_AIR;
// } else {
// matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData || from.contains(":"));
// }
// filter = new ReplacePatternFilter(matchFrom, to);
// }
// MCAFilterCounter result = runWithWorld(player, folder, filter, true);
// if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
// }
//
// @Command(
// aliases = {"countall"},
// usage = "<folder> [hasSky] <id>",
// desc = "Count all blocks in a world",
// flags = "d",
// min = 2,
// max = 3
// )
// @CommandPermissions("worldedit.anvil.countall")
// public void countAll(Player player, EditSession editSession, String folder, String arg, @Switch('d') boolean useData) throws WorldEditException {
// Set<BaseBlock> searchBlocks = worldEdit.getBlocks(player, arg, true);
// MCAFilterCounter filter;
// if (useData || arg.contains(":")) { // Optimize for both cases
// CountFilter counter = new CountFilter();
// searchBlocks.forEach(counter::addBlock);
// filter = counter;
// } else {
// CountIdFilter counter = new CountIdFilter();
// searchBlocks.forEach(counter::addBlock);
// filter = counter;
// }
// MCAFilterCounter result = runWithWorld(player, folder, filter, true);
// if (result != null) player.print(BBC.getPrefix() + BBC.SELECTION_COUNT.format(result.getTotal()));
// }
@Command(
aliases = {"clear", "unset"},
desc = "Clear the chunks in a selection (delete without defrag)"
)
@CommandPermissions("worldedit.anvil.clear")
public void unset(Player player, EditSession editSession, @Selection Region selection) throws WorldEditException {
Vector bot = selection.getMinimumPoint();
Vector top = selection.getMaximumPoint();
RegionWrapper region = new RegionWrapper(bot, top);
MCAFilterCounter filter = new MCAFilterCounter() {
@Override
public MCAFile applyFile(MCAFile file) {
int X = file.getX();
int Z = file.getZ();
int bcx = X << 5;
int bcz = Z << 5;
int bx = X << 9;
int bz = Z << 9;
if (region.isIn(bx, bz) && region.isIn(bx + 511, bz + 511)) {
file.setDeleted(true);
get().add(512 * 512 * 256);
} else if (region.isInMCA(X, Z)) {
file.init();
final byte[] empty = new byte[4];
RandomAccessFile raf = file.getRandomAccessFile();
file.forEachChunk(new RunnableVal4<Integer, Integer, Integer, Integer>() {
@Override
public void run(Integer cx, Integer cz, Integer offset, Integer size) {
if (region.isInChunk(bcx + cx, bcz + cz)) {
int index = ((cx & 31) << 2) + ((cz & 31) << 7);
try {
raf.seek(index);
raf.write(empty);
get().add(16 * 16 * 256);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
file.clear();
}
return null;
}
};
MCAFilterCounter result = runWithSelection(player, editSession, selection, filter);
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
// @Command(
// aliases = {"count"},
// usage = "<ids>",
// desc = "Count blocks in a selection",
// flags = "d",
// min = 1,
// max = 2
// )
// @CommandPermissions("worldedit.anvil.count")
// public void count(Player player, EditSession editSession, @Selection Region selection, String arg, @Switch('d') boolean useData) throws WorldEditException {
// Set<BaseBlock> searchBlocks = worldEdit.getBlocks(player, arg, true);
// MCAFilterCounter filter;
// if (useData || arg.contains(":")) { // Optimize for both cases
// CountFilter counter = new CountFilter();
// searchBlocks.forEach(counter::addBlock);
// filter = counter;
// } else {
// CountIdFilter counter = new CountIdFilter();
// searchBlocks.forEach(counter::addBlock);
// filter = counter;
// }
// MCAFilterCounter result = runWithSelection(player, editSession, selection, filter);
// if (result != null) player.print(BBC.getPrefix() + BBC.SELECTION_COUNT.format(result.getTotal()));
// }
//
// @Command(
// aliases = {"distr"},
// desc = "Replace all blocks in the selection with another"
// )
// @CommandPermissions("worldedit.anvil.distr")
// public void distr(Player player, EditSession editSession, @Selection Region selection, @Switch('d') boolean useData) throws WorldEditException {
// long total = 0;
// long[] count;
// MCAFilter<long[]> counts;
// if (useData) {
// counts = runWithSelection(player, editSession, selection, new MCAFilter<long[]>() {
// @Override
// public void applyBlock(int x, int y, int z, BaseBlock block, long[] counts) {
// counts[block.getCombined()]++;
// }
//
// @Override
// public long[] init() {
// return new long[Character.MAX_VALUE + 1];
// }
// });
// count = new long[Character.MAX_VALUE + 1];
// } else {
// counts = runWithSelection(player, editSession, selection, new MCAFilter<long[]>() {
// @Override
// public void applyBlock(int x, int y, int z, BaseBlock block, long[] counts) {
// counts[block.getId()]++;
// }
//
// @Override
// public long[] init() {
// return new long[4096];
// }
// });
// count = new long[4096];
// }
// for (long[] value : counts) {
// for (int i = 0; i < value.length; i++) {
// count[i] += value[i];
// total += value[i];
// }
// }
// ArrayList<long[]> map = new ArrayList<>();
// for (int i = 0; i < count.length; i++) {
// if (count[i] != 0) map.add(new long[]{i, count[i]});
// }
// Collections.sort(map, new Comparator<long[]>() {
// @Override
// public int compare(long[] a, long[] b) {
// long vA = a[1];
// long vB = b[1];
// return (vA < vB) ? -1 : ((vA == vB) ? 0 : 1);
// }
// });
// if (useData) {
// for (long[] c : map) {
// BaseBlock block = FaweCache.CACHE_BLOCK[(int) c[0]];
// String name = BlockType.fromID(block.getId()).getName();
// String str = String.format("%-7s (%.3f%%) %s #%d:%d",
// String.valueOf(c[1]),
// ((c[1] * 10000) / total) / 100d,
// name == null ? "Unknown" : name,
// block.getType(), block.getData());
// player.print(BBC.getPrefix() + str);
// }
// } else {
// for (long[] c : map) {
// BlockType block = BlockType.fromID((int) c[0]);
// String str = String.format("%-7s (%.3f%%) %s #%d",
// String.valueOf(c[1]),
// ((c[1] * 10000) / total) / 100d,
// block == null ? "Unknown" : block.getName(), c[0]);
// player.print(BBC.getPrefix() + str);
// }
// }
// }
//
// @Command(
// aliases = {"replace", "r"},
// usage = "[from-block] <to-block>",
// desc = "Replace all blocks in the selection with another"
// )
// @CommandPermissions("worldedit.anvil.replace")
// public void replace(Player player, EditSession editSession, @Selection Region selection, @Optional String from, String to, @Switch('d') boolean useData) throws WorldEditException {
// final FaweBlockMatcher matchFrom;
// if (from == null) {
// matchFrom = FaweBlockMatcher.NOT_AIR;
// } else {
// matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData || from.contains(":"));
// }
// final FaweBlockMatcher matchTo = FaweBlockMatcher.setBlocks(worldEdit.getBlocks(player, to, true));
// ReplaceSimpleFilter filter = new ReplaceSimpleFilter(matchFrom, matchTo);
// MCAFilterCounter result = runWithSelection(player, editSession, selection, filter);
// if (result != null) {
// player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
// }
// }
//
// @Command(
// aliases = {"replacepattern", "preplace", "rp"},
// usage = "[from-mask] <to-pattern>",
// desc = "Replace all blocks in the selection with a pattern"
// )
// @CommandPermissions("worldedit.anvil.replace")
// // Player player, String folder, @Optional String from, final Pattern to, @Switch('d') boolean useData, @Switch('m') boolean useMap
// public void replacePattern(Player player, EditSession editSession, @Selection Region selection, @Optional String from, final Pattern to, @Switch('d') boolean useData, @Switch('m') boolean useMap) throws WorldEditException {
// MCAFilterCounter filter;
// if (useMap) {
// if (to instanceof RandomPattern) {
// List<String> split = StringMan.split(from, ',');
// filter = new MappedReplacePatternFilter(from, (RandomPattern) to, useData);
// } else {
// player.print(BBC.getPrefix() + "Must be a pattern list!");
// return;
// }
// } else {
// final FaweBlockMatcher matchFrom;
// if (from == null) {
// matchFrom = FaweBlockMatcher.NOT_AIR;
// } else {
// matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData || from.contains(":"));
// }
// filter = new ReplacePatternFilter(matchFrom, to);
// }
// MCAFilterCounter result = runWithSelection(player, editSession, selection, filter);
// if (result != null) {
// player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
// }
// }
@Command(
aliases = {"set"},
usage = "<to-pattern>",
desc = "Set all blocks in the selection with a pattern"
)
@CommandPermissions("worldedit.anvil.set")
// Player player, String folder, @Optional String from, final Pattern to, @Switch('d') boolean useData, @Switch('m') boolean useMap
public void set(Player player, EditSession editSession, @Selection Region selection, final Pattern to) throws WorldEditException {
MCAFilterCounter filter = new SetPatternFilter(to);
MCAFilterCounter result = runWithSelection(player, editSession, selection, filter);
if (result != null) {
player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
}
@Command(
aliases = {"removelayers"},
usage = "<id>",
desc = "Removes matching chunk layers",
help = "Remove if all the selected layers in a chunk match the provided id"
)
@CommandPermissions("worldedit.anvil.removelayer")
public void removeLayers(Player player, EditSession editSession, @Selection Region selection, int id) throws WorldEditException {
Vector min = selection.getMinimumPoint();
Vector max = selection.getMaximumPoint();
int minY = min.getBlockY();
int maxY = max.getBlockY();
RemoveLayerFilter filter = new RemoveLayerFilter(minY, maxY, id);
MCAFilterCounter result = runWithSelection(player, editSession, selection, filter);
if (result != null) {
player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
}
@Command(
aliases = {"copy"},
desc = "Lazily copy chunks to your anvil clipboard"
)
@CommandPermissions("worldedit.anvil.copychunks")
public void copy(Player player, LocalSession session, EditSession editSession, @Selection Region selection) throws WorldEditException {
if (!(selection instanceof CuboidRegion)) {
BBC.NO_REGION.send(player);
return;
}
CuboidRegion cuboid = (CuboidRegion) selection;
String worldName = Fawe.imp().getWorldName(editSession.getWorld());
FaweQueue tmp = SetQueue.IMP.getNewQueue(worldName, true, false);
MCAQueue queue = new MCAQueue(tmp);
Vector origin = session.getPlacementPosition(player);
MCAClipboard clipboard = new MCAClipboard(queue, cuboid, origin);
FawePlayer fp = FawePlayer.wrap(player);
fp.setMeta(FawePlayer.METADATA_KEYS.ANVIL_CLIPBOARD, clipboard);
BBC.COMMAND_COPY.send(player, selection.getArea());
}
// @Command(
// aliases = {"paste"},
// desc = "Paste chunks from your anvil clipboard",
// help =
// "Paste the chunks from your anvil clipboard.\n" +
// "The -c flag will align the paste to the chunks.",
// flags = "c"
//
// )
// @CommandPermissions("worldedit.anvil.pastechunks")
// public void paste(Player player, LocalSession session, EditSession editSession, @Switch('c') boolean alignChunk) throws WorldEditException, IOException {
// FawePlayer fp = FawePlayer.wrap(player);
// MCAClipboard clipboard = fp.getMeta(FawePlayer.METADATA_KEYS.ANVIL_CLIPBOARD);
// if (clipboard == null) {
// fp.sendMessage(BBC.getPrefix() + "You must first use `//anvil copy`");
// return;
// }
// CuboidRegion cuboid = clipboard.getRegion();
// RegionWrapper copyRegion = new RegionWrapper(cuboid.getMinimumPoint(), cuboid.getMaximumPoint());
// final Vector offset = player.getPosition().subtract(clipboard.getOrigin());
// if (alignChunk) {
// offset.setComponents((offset.getBlockX() >> 4) << 4, offset.getBlockY(), (offset.getBlockZ() >> 4) << 4);
// }
// int oX = offset.getBlockX();
// int oZ = offset.getBlockZ();
// RegionWrapper pasteRegion = new RegionWrapper(copyRegion.minX + oX, copyRegion.maxX + oX, copyRegion.minZ + oZ, copyRegion.maxZ + oZ);
// String pasteWorldName = Fawe.imp().getWorldName(editSession.getWorld());
// FaweQueue tmpTo = SetQueue.IMP.getNewQueue(pasteWorldName, true, false);
// MCAQueue copyQueue = clipboard.getQueue();
// MCAQueue pasteQueue = new MCAQueue(tmpTo);
//
// fp.checkAllowedRegion(pasteRegion);
// recordHistory(fp, editSession.getWorld(), iAnvilHistory -> {
// try {
// pasteQueue.pasteRegion(copyQueue, copyRegion, offset, iAnvilHistory);
// } catch (IOException e) { throw new RuntimeException(e); }
// });
// BBC.COMMAND_PASTE.send(player, player.getPosition().toBlockVector());
// }
}

View File

@ -0,0 +1,100 @@
package com.boydti.fawe.command;
import com.boydti.fawe.config.Commands;
import com.boydti.fawe.jnbt.anvil.HeightMapMCAGenerator;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.changeset.CFIChangeSet;
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.CommandPermissions;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.MethodCommands;
import com.sk89q.worldedit.util.command.SimpleDispatcher;
import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
import java.io.IOException;
public class CFICommand extends MethodCommands {
private final CFICommands child;
private final SimpleDispatcher dispatcher;
public CFICommand(WorldEdit worldEdit, ParametricBuilder builder) {
super(worldEdit);
this.dispatcher = new SimpleDispatcher();
this.child = new CFICommands(worldEdit, dispatcher);
builder.registerMethodsAsCommands(dispatcher, child);
}
@Command(
aliases = {"cfi", "createfromimage"},
usage = "",
min = 0,
max = -1,
anyFlags = true,
desc = "Start CreateFromImage"
)
@CommandPermissions("worldedit.anvil.cfi")
public void cfi(FawePlayer fp, CommandContext context) throws CommandException, IOException {
CFICommands.CFISettings settings = child.getSettings(fp);
settings.popMessages(fp);
dispatch(fp, settings, context);
HeightMapMCAGenerator gen = settings.getGenerator();
if (gen != null && gen.isModified()) {
gen.update();
CFIChangeSet set = new CFIChangeSet(gen, fp.getUUID());
LocalSession session = fp.getSession();
session.remember(fp.getPlayer(), gen, set, fp.getLimit());
}
}
private void dispatch(FawePlayer fp, CFICommands.CFISettings settings, CommandContext context) throws CommandException {
if (!settings.hasGenerator()) {
switch (context.argsLength()) {
case 0: {
String hmCmd = child.alias() + " ";
if (settings.image == null) {
hmCmd += "image";
} else {
hmCmd = Commands.getAlias(CFICommands.class, "heightmap") + " " + settings.imageArg;
}
child.msg("What do you want to use as the base?").newline()
.text("&7[&aHeightMap&7]").cmdTip(hmCmd).text(" - A heightmap like ").text("&7[&athis&7]").linkTip("http://i.imgur.com/qCd30MR.jpg")
.newline()
.text("&7[&aEmpty&7]").cmdTip(child.alias() + " empty").text("- An empty map of a specific size")
.send(fp);
break;
}
default: {
String remaining = context.getJoinedStrings(0);
if (!dispatcher.contains(context.getString(0))) {
switch (context.argsLength()) {
case 1: {
String cmd = Commands.getAlias(CFICommands.class, "heightmap") + " " + context.getJoinedStrings(0);
dispatcher.call(cmd, context.getLocals(), new String[0]);
return;
}
case 2: {
String cmd = Commands.getAlias(CFICommands.class, "empty") + " " + context.getJoinedStrings(0);
dispatcher.call(cmd, context.getLocals(), new String[0]);
return;
}
}
}
dispatcher.call(remaining, context.getLocals(), new String[0]);
}
}
} else {
switch (context.argsLength()) {
case 0:
settings.setCategory("");
child.mainMenu(fp);
break;
default:
dispatcher.call(context.getJoinedStrings(0), context.getLocals(), new String[0]);
break;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
package com.boydti.fawe.command;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FaweCommand;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.brush.visualization.VirtualWorld;
import com.boydti.fawe.util.SetQueue;
import com.sk89q.worldedit.EditSession;
import java.util.Collection;
import java.util.UUID;
public class Cancel extends FaweCommand {
public Cancel() {
super("fawe.cancel", false);
}
@Override
public boolean execute(final FawePlayer player, final String... args) {
if (player == null) {
return false;
}
int cancelled = player.cancel(false);
BBC.WORLDEDIT_CANCEL_COUNT.send(player, cancelled);
return true;
}
}

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,106 @@
package com.boydti.fawe.command;
import com.boydti.fawe.util.StringMan;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.util.command.Dispatcher;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public abstract class FaweParser<T> extends InputParser<T> {
protected FaweParser(WorldEdit worldEdit) {
super(worldEdit);
}
public T 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;
}
}
public abstract Dispatcher getDispatcher();
public List<String> suggestRemaining(String input, String... expected) throws InputParseException {
List<String> remainder = StringMan.split(input, ':');
int len = remainder.size();
if (len != expected.length - 1) {
if (len <= expected.length - 1 && len != 0) {
if (remainder.get(len - 1).endsWith(":")) {
throw new SuggestInputParseException(null, StringMan.join(expected, ":"));
}
throw new SuggestInputParseException(null, expected[0] + ":" + input + ":" + StringMan.join(Arrays.copyOfRange(expected, len + 1, 3), ":"));
} else {
throw new SuggestInputParseException(null, StringMan.join(expected, ":"));
}
}
return remainder;
}
protected static class ParseEntry {
public boolean and;
public String input;
public String full;
public ParseEntry(String full, String input, boolean type) {
this.full = full;
this.input = input;
this.and = type;
}
@Override
public String toString() {
return input + " | " + and;
}
}
public List<Map.Entry<ParseEntry, List<String>>> parse(String toParse) throws InputParseException {
List<Map.Entry<ParseEntry, List<String>>> keys = new ArrayList<>();
List<String> inputs = new ArrayList<>();
List<Boolean> and = new ArrayList<>();
int last = 0;
outer:
for (int i = 0; i < toParse.length(); i++) {
char c = toParse.charAt(i);
switch (c) {
case ',':
case '&':
inputs.add(toParse.substring(last, i));
and.add(c == '&');
last = i + 1;
continue outer;
default:
if (StringMan.getMatchingBracket(c) != c) {
int next = StringMan.findMatchingBracket(toParse, i);
if (next != -1) {
i = next;
continue outer;
}
}
}
}
inputs.add(toParse.substring(last, toParse.length()));
for (int i = 0; i < inputs.size(); i++) {
String full = inputs.get(i);
String command = full;
List<String> args = new ArrayList<>();
while (command.charAt(command.length() - 1) == ']') {
int startPos = StringMan.findMatchingBracket(command, command.length() - 1);
if (startPos == -1) break;
String arg = command.substring(startPos + 1, command.length() - 1);
args.add(arg);
command = full.substring(0, startPos);
}
ParseEntry entry = new ParseEntry(full, command, i > 0 ? and.get(i - 1) : false);
keys.add(new AbstractMap.SimpleEntry<>(entry, args));
}
return keys;
}
}

View File

@ -0,0 +1,473 @@
package com.boydti.fawe.command;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.extent.NullExtent;
import com.boydti.fawe.object.extent.ResettableExtent;
import com.boydti.fawe.util.TextureUtil;
import com.boydti.fawe.util.image.ImageUtil;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.factory.DefaultTransformParser;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.command.binding.Range;
import com.sk89q.worldedit.util.command.binding.Text;
import com.sk89q.worldedit.util.command.binding.Validate;
import com.sk89q.worldedit.util.command.parametric.ArgumentStack;
import com.sk89q.worldedit.util.command.parametric.BindingBehavior;
import com.sk89q.worldedit.util.command.parametric.BindingHelper;
import com.sk89q.worldedit.util.command.parametric.BindingMatch;
import com.sk89q.worldedit.util.command.parametric.ParameterException;
import com.sk89q.worldedit.world.World;
import java.awt.image.BufferedImage;
import java.lang.annotation.Annotation;
import javax.annotation.Nullable;
public class FawePrimitiveBinding extends BindingHelper {
@BindingMatch(type = {Long.class, long.class},
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Long getLong(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
try {
Long v = Long.parseLong(context.next());
validate(v, modifiers);
return v;
} catch (NumberFormatException ignore) {
return null;
}
}
private static void validate(long number, Annotation[] modifiers) throws ParameterException {
for (Annotation modifier : modifiers) {
if (modifier instanceof Range) {
Range range = (Range) modifier;
if (number < range.min()) {
throw new ParameterException(
String.format(
"A valid value is greater than or equal to %s " +
"(you entered %s)", range.min(), number));
} else if (number > range.max()) {
throw new ParameterException(
String.format(
"A valid value is less than or equal to %s " +
"(you entered %s)", range.max(), number));
}
}
}
}
@BindingMatch(
type = {BufferedImage.class},
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true
)
public BufferedImage getImage(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
return ImageUtil.getImage(context.next());
}
@BindingMatch(
type = {TextureUtil.class},
behavior = BindingBehavior.PROVIDES
)
public TextureUtil getTexture(ArgumentStack context) {
Actor actor = context.getContext().getLocals().get(Actor.class);
if (actor == null) return Fawe.get().getCachedTextureUtil(true, 0, 100);
LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor);
return session.getTextureUtil();
}
@BindingMatch(
type = {Extent.class},
behavior = BindingBehavior.PROVIDES
)
public Extent getExtent(ArgumentStack context) throws ParameterException {
Extent extent = context.getContext().getLocals().get(EditSession.class);
if (extent != null) return extent;
extent = Request.request().getExtent();
if (extent != null) return extent;
Actor actor = context.getContext().getLocals().get(Actor.class);
if (actor == null) throw new ParameterException("No player to get a session for");
if (!(actor instanceof Player)) throw new ParameterException("Caller is not a player");
LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor);
EditSession editSession = session.createEditSession((Player) actor);
editSession.enableQueue();
context.getContext().getLocals().put(EditSession.class, editSession);
session.tellVersion(actor);
return editSession;
}
/**
* Gets an {@link com.boydti.fawe.object.FawePlayer} from a {@link ArgumentStack}.
*
* @param context the context
* @return a FawePlayer
* @throws ParameterException on other error
*/
@BindingMatch(type = FawePlayer.class,
behavior = BindingBehavior.PROVIDES)
public FawePlayer getFawePlayer(ArgumentStack context) throws ParameterException, InputParseException {
Actor sender = context.getContext().getLocals().get(Actor.class);
if (sender == null) {
throw new ParameterException("Missing 'Actor'");
} else {
return FawePlayer.wrap(sender);
}
}
/**
* Gets an {@link com.sk89q.worldedit.extent.Extent} from a {@link ArgumentStack}.
*
* @param context the context
* @return an extent
* @throws ParameterException on other error
*/
@BindingMatch(type = ResettableExtent.class,
behavior = BindingBehavior.PROVIDES)
public ResettableExtent getResettableExtent(ArgumentStack context) throws ParameterException, InputParseException {
String input = context.next();
if (input.equalsIgnoreCase("#null")) return new NullExtent();
DefaultTransformParser parser = Fawe.get().getTransformParser();
Actor actor = context.getContext().getLocals().get(Actor.class);
ParserContext parserContext = new ParserContext();
parserContext.setActor(context.getContext().getLocals().get(Actor.class));
if (actor instanceof Entity) {
Extent extent = ((Entity) actor).getExtent();
if (extent instanceof World) {
parserContext.setWorld((World) extent);
}
}
parserContext.setSession(WorldEdit.getInstance().getSessionManager().get(actor));
return parser.parseFromInput(input, parserContext);
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param text the text annotation
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(classifier = Text.class,
type = String.class,
behavior = BindingBehavior.CONSUMES,
consumedCount = -1,
provideModifiers = true)
public String getText(ArgumentStack context, Text text, Annotation[] modifiers)
throws ParameterException {
String v = context.remaining();
validate(v, modifiers);
return v;
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = String.class,
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public String getString(ArgumentStack context, Annotation[] modifiers)
throws ParameterException {
String v = context.next();
validate(v, modifiers);
return v;
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = {Boolean.class, boolean.class},
behavior = BindingBehavior.CONSUMES,
consumedCount = 1)
public Boolean getBoolean(ArgumentStack context) throws ParameterException {
return context.nextBoolean();
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = Vector.class,
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Vector getVector(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
String radiusString = context.next();
String[] radii = radiusString.split(",");
final double radiusX, radiusY, radiusZ;
switch (radii.length) {
case 1:
radiusX = radiusY = radiusZ = Math.max(1, FawePrimitiveBinding.parseNumericInput(radii[0]));
break;
case 3:
radiusX = Math.max(1, FawePrimitiveBinding.parseNumericInput(radii[0]));
radiusY = Math.max(1, FawePrimitiveBinding.parseNumericInput(radii[1]));
radiusZ = Math.max(1, FawePrimitiveBinding.parseNumericInput(radii[2]));
break;
default:
throw new ParameterException("You must either specify 1 or 3 radius values.");
}
return new Vector(radiusX, radiusY, radiusZ);
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = Vector2D.class,
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Vector2D getVector2D(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
String radiusString = context.next();
String[] radii = radiusString.split(",");
final double radiusX, radiusZ;
switch (radii.length) {
case 1:
radiusX = radiusZ = Math.max(1, FawePrimitiveBinding.parseNumericInput(radii[0]));
break;
case 2:
radiusX = Math.max(1, FawePrimitiveBinding.parseNumericInput(radii[0]));
radiusZ = Math.max(1, FawePrimitiveBinding.parseNumericInput(radii[1]));
break;
default:
throw new ParameterException("You must either specify 1 or 2 radius values.");
}
return new Vector2D(radiusX, radiusZ);
}
/**
* Try to parse numeric input as either a number or a mathematical expression.
*
* @param input input
* @return a number
* @throws ParameterException thrown on parse error
*/
public static
@Nullable
Double parseNumericInput(@Nullable String input) throws ParameterException {
if (input == null) {
return null;
}
try {
return Double.parseDouble(input);
} catch (NumberFormatException e1) {
try {
Expression expression = Expression.compile(input);
return expression.evaluate();
} catch (EvaluationException e) {
throw new ParameterException(String.format(
"Expected '%s' to be a valid number (or a valid mathematical expression)", input));
} catch (ExpressionException e) {
throw new ParameterException(String.format(
"Expected '%s' to be a number or valid math expression (error: %s)", input, e.getMessage()));
}
}
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = {Integer.class, int.class},
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Integer getInteger(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Double v = parseNumericInput(context.next());
if (v != null) {
int intValue = v.intValue();
validate(intValue, modifiers);
return intValue;
} else {
return null;
}
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = {Short.class, short.class},
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Short getShort(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Integer v = getInteger(context, modifiers);
if (v != null) {
return v.shortValue();
}
return null;
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = {Double.class, double.class},
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Double getDouble(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Double v = parseNumericInput(context.next());
if (v != null) {
validate(v, modifiers);
return v;
} else {
return null;
}
}
/**
* Gets a type from a {@link ArgumentStack}.
*
* @param context the context
* @param modifiers a list of modifiers
* @return the requested type
* @throws ParameterException on error
*/
@BindingMatch(type = {Float.class, float.class},
behavior = BindingBehavior.CONSUMES,
consumedCount = 1,
provideModifiers = true)
public Float getFloat(ArgumentStack context, Annotation[] modifiers) throws ParameterException {
Double v = getDouble(context, modifiers);
if (v != null) {
return v.floatValue();
}
return null;
}
/**
* Validate a number value using relevant modifiers.
*
* @param number the number
* @param modifiers the list of modifiers to scan
* @throws ParameterException on a validation error
*/
private static void validate(double number, Annotation[] modifiers)
throws ParameterException {
for (Annotation modifier : modifiers) {
if (modifier instanceof Range) {
Range range = (Range) modifier;
if (number < range.min()) {
throw new ParameterException(
String.format(
"A valid value is greater than or equal to %s " +
"(you entered %s)", range.min(), number));
} else if (number > range.max()) {
throw new ParameterException(
String.format(
"A valid value is less than or equal to %s " +
"(you entered %s)", range.max(), number));
}
}
}
}
/**
* Validate a number value using relevant modifiers.
*
* @param number the number
* @param modifiers the list of modifiers to scan
* @throws ParameterException on a validation error
*/
private static void validate(int number, Annotation[] modifiers)
throws ParameterException {
for (Annotation modifier : modifiers) {
if (modifier instanceof Range) {
Range range = (Range) modifier;
if (number < range.min()) {
throw new ParameterException(
String.format(
"A valid value is greater than or equal to %s " +
"(you entered %s)", range.min(), number));
} else if (number > range.max()) {
throw new ParameterException(
String.format(
"A valid value is less than or equal to %s " +
"(you entered %s)", range.max(), number));
}
}
}
}
/**
* Validate a string value using relevant modifiers.
*
* @param string the string
* @param modifiers the list of modifiers to scan
* @throws ParameterException on a validation error
*/
private static void validate(String string, Annotation[] modifiers)
throws ParameterException {
if (string == null) {
return;
}
for (Annotation modifier : modifiers) {
if (modifier instanceof Validate) {
Validate validate = (Validate) modifier;
if (!validate.regex().isEmpty()) {
if (!string.matches(validate.regex())) {
throw new ParameterException(
String.format(
"The given text doesn't match the right " +
"format (technically speaking, the 'format' is %s)",
validate.regex()));
}
}
}
}
}
}

View File

@ -0,0 +1,39 @@
package com.boydti.fawe.command;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.util.command.parametric.ParameterData;
import java.util.ArrayList;
import java.util.List;
public class MaskBinding extends FaweBinding {
private final WorldEdit worldEdit;
public MaskBinding(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(DefaultMaskParser.ALL_MASKS));
// }
// if (current.startsWith("#") || current.startsWith("=")) {
// return new ArrayList<>();
// }
// if (StringMan.isAlphanumeric(current.charAt(0) + "")) {
// 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);
// }
return new ArrayList<>();
}
}

View File

@ -0,0 +1,46 @@
package com.boydti.fawe.command;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.util.command.parametric.ParameterData;
import java.util.ArrayList;
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) {
return new ArrayList<>();
// 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,200 @@
package com.boydti.fawe.command;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweCommand;
import com.boydti.fawe.object.FaweLocation;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class Rollback extends FaweCommand {
public Rollback() {
super("fawe.rollback");
}
@Override
public boolean execute(final FawePlayer player, final String... args) {
if (!Settings.IMP.HISTORY.USE_DATABASE) {
BBC.SETTING_DISABLE.send(player, "history.use-database (Import with /frb #import )");
return false;
}
if (args.length != 3) {
BBC.COMMAND_SYNTAX.send(player, "/frb u:<uuid> r:<radius> t:<time>");
return false;
}
switch (args[0]) {
case "i":
case "info":
case "undo":
case "revert":
BBC.COMMAND_SYNTAX.send(player, "/frb u:<uuid> r:<radius> t:<time>");
return false;
}
if (args.length < 1) {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return false;
}
World world = player.getWorld();
switch (args[0]) {
default: {
BBC.COMMAND_SYNTAX.send(player, "/frb info u:<uuid> r:<radius> t:<time>");
return false;
}
case "i":
case "info": {
if (args.length < 2) {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return false;
}
player.deleteMeta(FawePlayer.METADATA_KEYS.ROLLBACK);
final FaweLocation origin = player.getLocation();
rollback(player, !player.hasPermission("fawe.rollback.deep"), Arrays.copyOfRange(args, 1, args.length), new RunnableVal<List<DiskStorageHistory>>() {
@Override
public void run(List<DiskStorageHistory> edits) {
long total = 0;
player.sendMessage("&d=| Username | Bounds | Distance | Changes | Age |=");
for (DiskStorageHistory edit : edits) {
DiskStorageHistory.DiskStorageSummary summary = edit.summarize(new RegionWrapper(origin.x, origin.x, origin.z, origin.z), !player.hasPermission("fawe.rollback.deep"));
RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ);
int distance = region.distance(origin.x, origin.z);
String name = Fawe.imp().getName(edit.getUUID());
long seconds = (System.currentTimeMillis() - edit.getBDFile().lastModified()) / 1000;
total += edit.getBDFile().length();
int size = summary.getSize();
Map<Integer, Double> percents = summary.getPercents();
StringBuilder percentString = new StringBuilder();
String prefix = "";
for (Map.Entry<Integer, Double> entry : percents.entrySet()) {
int id = entry.getKey();
BlockStateHolder state = null;
try {
state = BlockState.get(id);
} catch (Throwable ignore) {};
String itemName = state == null ? "#" + id : state.getAsString();
percentString.append(prefix).append(entry.getValue()).append("% ").append(itemName);
prefix = ", ";
}
player.sendMessage("&c" + name + " | " + region + " | " + distance + "m | " + size + " | " + MainUtil.secToTime(seconds));
player.sendMessage("&8 - &7(" + percentString + ")");
}
player.sendMessage("&d==================================================");
player.sendMessage("&dSize: " + (((double) (total / 1024)) / 1000) + "MB");
player.sendMessage("&dTo rollback: /frb undo");
player.sendMessage("&d==================================================");
player.setMeta(FawePlayer.METADATA_KEYS.ROLLBACK, edits);
}
});
break;
}
case "undo":
case "revert": {
if (!player.hasPermission("fawe.rollback.perform")) {
BBC.NO_PERM.send(player, "fawe.rollback.perform");
return false;
}
final List<DiskStorageHistory> edits = (List<DiskStorageHistory>) player.getMeta(FawePlayer.METADATA_KEYS.ROLLBACK);
player.deleteMeta(FawePlayer.METADATA_KEYS.ROLLBACK);
if (edits == null) {
BBC.COMMAND_SYNTAX.send(player, "/frb info u:<uuid> r:<radius> t:<time>");
return false;
}
final Runnable task = new Runnable() {
@Override
public void run() {
if (edits.size() == 0) {
player.sendMessage("Rollback complete!");
return;
}
DiskStorageHistory edit = edits.remove(0);
player.sendMessage("&d" + edit.getBDFile());
EditSession session = edit.toEditSession(null);
session.undo(session);
edit.deleteFiles();
session.getQueue().addNotifyTask(this);
}
};
task.run();
}
}
return true;
}
public void rollback(final FawePlayer player, final boolean shallow, final String[] args, final RunnableVal<List<DiskStorageHistory>> result) {
UUID user = null;
int radius = Integer.MAX_VALUE;
long time = Long.MAX_VALUE;
for (int i = 0; i < args.length; i++) {
String[] split = args[i].split(":");
if (split.length != 2) {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return;
}
switch (split[0].toLowerCase()) {
case "username":
case "user":
case "u": {
try {
if (split[1].length() > 16) {
user = UUID.fromString(split[1]);
} else {
user = Fawe.imp().getUUID(split[1]);
}
} catch (IllegalArgumentException e) {
}
if (user == null) {
player.sendMessage("&dInvalid user: " + split[1]);
return;
}
break;
}
case "r":
case "radius": {
if (!MathMan.isInteger(split[1])) {
player.sendMessage("&dInvalid radius: " + split[1]);
return;
}
radius = Integer.parseInt(split[1]);
break;
}
case "t":
case "time": {
time = MainUtil.timeToSec(split[1]) * 1000;
break;
}
default: {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return;
}
}
}
FaweLocation origin = player.getLocation();
List<DiskStorageHistory> edits = FaweAPI.getBDFiles(origin, user, radius, time, shallow);
if (edits == null) {
player.sendMessage("&cToo broad, try refining your search!");
return;
}
if (edits.size() == 0) {
player.sendMessage("&cNo edits found!");
return;
}
result.run(edits);
}
}

View File

@ -0,0 +1,88 @@
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 SuggestInputParseException prepend(String input) {
this.prefix = input + prefix;
return this;
}
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

@ -0,0 +1,744 @@
package com.boydti.fawe.config;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.configuration.MemorySection;
import com.boydti.fawe.configuration.file.YamlConfiguration;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.RunnableVal3;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.StringMan;
import com.boydti.fawe.util.chat.Message;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public enum BBC {
/*
* Things to note about this class:
* Can use multiple arguments %s, %s1, %s2, %s3 etc
*/
PREFIX("&8(&4&lFAWE&8)&r&7", "Info"),
FILE_DELETED("%s0 has been deleted.", "Info"),
SCHEMATIC_PASTING("&7The schematic is pasting. This cannot be undone.", "Info"),
LIGHTING_PROPOGATE_SELECTION("&7Lighting has been propogated in %s0 chunks. (Note: To remove light use //removelight)", "Info"),
UPDATED_LIGHTING_SELECTION("&7Lighting has been updated in %s0 chunks. (It may take a second for the packets to send)", "Info"),
SET_REGION("&7Selection set to your current allowed region", "Info"),
WORLDEDIT_COMMAND_LIMIT("&7Please wait until your current action completes", "Info"),
WORLDEDIT_DELAYED("&7Please wait while we process your FAWE action...", "Info"),
WORLDEDIT_RUN("&7Apologies for the delay. Now executing: %s", "Info"),
WORLDEDIT_COMPLETE("&7Edit completed.", "Info"),
REQUIRE_SELECTION_IN_MASK("&7%s of your selection is not within your mask. You can only make edits within allowed regions.", "Info"),
WORLDEDIT_VOLUME("&7You cannot select a volume of %current%. The maximum volume you can modify is %max%.", "Info"),
WORLDEDIT_ITERATIONS("&7You cannot iterate %current% times. The maximum number of iterations allowed is %max%.", "Info"),
WORLDEDIT_UNSAFE("&7Access to that command has been blocked", "Info"),
WORLDEDIT_DANGEROUS_WORLDEDIT("&cProcessed unsafe edit at %s0 by %s1", "Info"),
WORLDEDIT_EXTEND("&cYour edit may have extended outside your allowed region.", "Error"),
WORLDEDIT_TOGGLE_TIPS_ON("&7Disabled FAWE tips.", "Info"),
WORLDEDIT_TOGGLE_TIPS_OFF("&7Enabled FAWE tips.", "Info"),
WORLDEDIT_BYPASSED("&7Currently bypassing FAWE restriction.", "Info"),
WORLDEDIT_UNMASKED("&6Your FAWE edits are now unrestricted.", "Info"),
WORLDEDIT_RESTRICTED("&6Your FAWE edits are now restricted.", "Info"),
WORLDEDIT_OOM_ADMIN("&cPossible options:\n&8 - &7//fast\n&8 - &7Do smaller edits\n&8 - &7Allocate more memory\n&8 - &7Disable `max-memory-percent`", "Info"),
COMPRESSED("History compressed. Saved ~ %s0b (%s1x smaller)", "Info"),
WEB_UNAUTHORIZED("Only links from the configured web host is allowed: %s0", "Error"),
ACTION_COMPLETE("Action completed in %s0 seconds", "Info"),
GENERATING_LINK("Uploading %s, please wait...", "Web"),
GENERATING_LINK_FAILED("&cFailed to generate download link!", "Web"),
DOWNLOAD_LINK("%s", "Web"),
MASK_DISABLED("Global mask disabled", "WorldEdit.General"),
MASK("Global mask set", "WorldEdit.General"),
TEXTURE_DISABLED("Texturing reset", "WorldEdit.General"),
TEXTURE_SET("Set texturing to %s1", "WorldEdit.General"),
SOURCE_MASK_DISABLED("Global source mask disabled", "WorldEdit.General"),
SOURCE_MASK("Global source mask set", "WorldEdit.General"),
TRANSFORM_DISABLED("Global transform disabled", "WorldEdit.General"),
TRANSFORM("Global transform set", "WorldEdit.General"),
COMMAND_COPY("%s0 blocks were copied.", "WorldEdit.Copy"),
COMMAND_CUT_SLOW("%s0 blocks were cut.", "WorldEdit.Cut"),
COMMAND_CUT_LAZY("%s0 blocks will be removed on paste", "WorldEdit.Cut"),
COMMAND_PASTE("The clipboard has been pasted at %s0", "WorldEdit.Paste"),
COMMAND_ROTATE("The clipboard has been rotated", "WorldEdit.Rotate"),
COMMAND_FLIPPED("The clipboard has been flipped", "WorldEdit.Flip"),
COMMAND_REGEN_0("Region regenerated.", "WorldEdit.Regen"),
COMMAND_REGEN_1("Region regenerated.", "WorldEdit.Regen"),
COMMAND_REGEN_2("Region regenerated.", "WorldEdit.Regen"),
COMMAND_TREE("%s0 trees created.", "WorldEdit.Tree"),
COMMAND_PUMPKIN("%s0 pumpkin patches created.", "WorldEdit.Tree"),
COMMAND_FLORA("%s0 flora created.", "WorldEdit.Flora"),
COMMAND_HISTORY_CLEAR("History cleared", "WorldEdit.History"),
COMMAND_REDO_ERROR("Nothing left to redo. (See also `/inspect` and `/frb`)", "WorldEdit.History"),
COMMAND_HISTORY_OTHER_ERROR("Unable to find session for %s0.", "WorldEdit.History"),
COMMAND_REDO_SUCCESS("Redo successful.", "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"),
SELECTION_WAND("Left click: select pos #1; Right click: select pos #2", "WorldEdit.Selection"),
NAVIGATION_WAND_ERROR("&cNothing to pass through", "WorldEdit.Navigation"),
SELECTION_WAND_DISABLE("Edit wand disabled.", "WorldEdit.Selection"),
SELECTION_WAND_ENABLE("Edit wand enabled.", "WorldEdit.Selection"),
SELECTION_CHUNK("Chunk selected (%s0)", "WorldEdit.Selection"),
SELECTION_CHUNKS("Chunks selected (%s0) - (%s1)", "WorldEdit.Selection"),
SELECTION_CONTRACT("Region contracted %s0 blocks.", "WorldEdit.Selection"),
SELECTION_COUNT("Counted %s0 blocks.", "WorldEdit.Selection"),
SELECTION_DISTR("# total blocks: %s0", "WorldEdit.Selection"),
SELECTION_EXPAND("Region expanded %s0 blocks", "WorldEdit.Selection"),
SELECTION_EXPAND_VERT("Region expanded %s0 blocks (top to bottom)", "WorldEdit.Selection"),
SELECTION_INSET("Region inset", "WorldEdit.Selection"),
SELECTION_OUTSET("Region outset", "WorldEdit.Selection"),
SELECTION_SHIFT("Region shifted", "WorldEdit.Selection"),
SELECTION_CLEARED("Selection cleared", "WorldEdit.Selection"),
WORLD_IS_LOADED("The world shouldn't be in use when executing. Unload the world, or use use -f to override (save first)", "WorldEdit.Anvil"),
BRUSH_RESET("Reset your brush. (SHIFT + Click)", "WorldEdit.Brush"),
BRUSH_NONE("You aren't holding a brush!", "WorldEdit.Brush"),
BRUSH_SCROLL_ACTION_SET("Set scroll action to %s0", "WorldEdit.Brush"),
BRUSH_SCROLL_ACTION_UNSET("Removed scrol action", "WorldEdit.Brush"),
BRUSH_VISUAL_MODE_SET("Set visual mode to %s0", "WorldEdit.Brush"),
BRUSH_TARGET_MODE_SET("Set target mode to %s0", "WorldEdit.Brush"),
BRUSH_TARGET_MASK_SET("Set target mask to %s0", "WorldEdit.Brush"),
BRUSH_TARGET_OFFSET_SET("Set target offset to %s0", "WorldEdit.Brush"),
BRUSH_EQUIPPED("Equipped brush %s0", "WorldEdit.Brush"),
BRUSH_TRY_OTHER("&cThere are 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("Left click the base of an object to copy, right click to paste. Increase the brush radius if necessary.", "WorldEdit.Brush"),
BRUSH_HEIGHT_INVALID("Invalid height map file (%s0)", "WorldEdit.Brush"),
BRUSH_SMOOTH("Note: Use the blend brush if you want to smooth overhangs or caves.", "WorldEdit.Brush"),
BRUSH_SPLINE("Click to add a point, click the same spot to finish", "WorldEdit.Brush"),
BRUSH_LINE_PRIMARY("Added point %s0, click another position to create the line", "WorldEdit.Brush"),
BRUSH_CATENARY_DIRECTION("Added point %s0, click the direction you want to create the spline", "WorldEdit.Brush"),
BRUSH_LINE_SECONDARY("Created spline", "WorldEdit.Brush"),
BRUSH_SPLINE_PRIMARY_2("Added position, Click the same spot to join!", "WorldEdit.Brush"),
BRUSH_SPLINE_SECONDARY_ERROR("Not enough positions set!", "WorldEdit.Brush"),
BRUSH_SPLINE_SECONDARY("Created spline", "WorldEdit.Brush"),
BRUSH_SIZE("Brush size set", "WorldEdit.Brush"),
BRUSH_RANGE("Brush size set", "WorldEdit.Brush"),
BRUSH_MASK_DISABLED("Brush mask disabled", "WorldEdit.Brush"),
BRUSH_MASK("Brush mask set", "WorldEdit.Brush"),
BRUSH_SOURCE_MASK_DISABLED("Brush source mask disabled", "WorldEdit.Brush"),
BRUSH_SOURCE_MASK("Brush source mask set", "WorldEdit.Brush"),
BRUSH_TRANSFORM_DISABLED("Brush transform disabled", "WorldEdit.Brush"),
BRUSH_TRANSFORM("Brush transform set", "WorldEdit.Brush"),
BRUSH_MATERIAL("Brush material set", "WorldEdit.Brush"),
ROLLBACK_ELEMENT("Undoing %s0", "WorldEdit.Rollback"),
TOOL_INSPECT("Inspect tool bound to %s0.", "WorldEdit.Tool"),
TOOL_INSPECT_INFO("&7%s0 changed %s1 to %s2 %s3 ago", "WorldEdit.Tool"),
TOOL_INSPECT_INFO_FOOTER("&6Total: &7%s0 changes", "WorldEdit.Tool"),
TOOL_NONE("Tool unbound from your current item.", "WorldEdit.Tool"),
TOOL_INFO("Info tool bound to %s0.", "WorldEdit.Tool"),
TOOL_TREE("Tree tool bound to %s0.", "WorldEdit.Tool"),
TOOL_TREE_ERROR("Tree type %s0 is unknown.", "WorldEdit.Tool"),
TOOL_REPL("Block replacer tool bound to %s0.", "WorldEdit.Tool"),
TOOL_CYCLER("Block data cycler tool bound to %s0.", "WorldEdit.Tool"),
TOOL_FLOOD_FILL("Block flood fill tool bound to %s0.", "WorldEdit.Tool"),
TOOL_RANGE_ERROR("Maximum range: %s0.", "WorldEdit.Tool"),
TOOL_RADIUS_ERROR("Maximum allowed brush radius: %s0.", "WorldEdit.Tool"),
TOOL_DELTREE("Floating tree remover tool bound to %s0.", "WorldEdit.Tool"),
TOOL_FARWAND("Far wand tool bound to %s0.", "WorldEdit.Tool"),
TOOL_LRBUILD_BOUND("Long-range building tool bound to %s0.", "WorldEdit.Tool"),
TOOL_LRBUILD_INFO("Left-click set to %s0; right-click set to %s1.", "WorldEdit.Tool"),
SUPERPICKAXE_ENABLED("Super Pickaxe enabled.", "WorldEdit.Tool"),
SUPERPICKAXE_DISABLED("Super Pickaxe disabled.", "WorldEdit.Tool"),
SUPERPICKAXE_AREA_ENABLED("Mode changed. Left click with a pickaxe. // to disable.", "WorldEdit.Tool"),
SNAPSHOT_LOADED("Snapshot '%s0' loaded; now restoring...", "WorldEdit.Snapshot"),
SNAPSHOT_SET("Snapshot set to: %s0", "WorldEdit.Snapshot"),
SNAPSHOT_NEWEST("Now using newest snapshot.", "WorldEdit.Snapshot"),
SNAPSHOT_LIST_HEADER("Snapshots for world (%s0):", "WorldEdit.Snapshot"),
SNAPSHOT_LIST_FOOTER("Use /snap use [snapshot] or /snap use latest.", "WorldEdit.Snapshot"),
BIOME_LIST_HEADER("Biomes (page %s0/%s1):", "WorldEdit.Biome"),
BIOME_CHANGED("Biomes were changed in %s0 columns.", "WorldEdit.Biome"),
FAST_ENABLED("Fast mode enabled. History and edit restrictions will be bypassed.", "WorldEdit.General"),
FAST_DISABLED("Fast mode disabled", "WorldEdit.General"),
PLACE_ENABLED("Now placing at pos #1.", "WorldEdit.General"),
PLACE_DISABLED("Now placing at the block you stand in.", "WorldEdit.General"),
KILL_SUCCESS("Killed %s0 entities in a radius of %s1.", "WorldEdit.Utility"),
NOTHING_CONFIRMED("You have no actions pending confirmation.", "WorldEdit.Utility"),
SCHEMATIC_PROMPT_CLEAR("&7You may want to use &c%s0 &7to clear your current clipboard first", "Worldedit.Schematic"),
SCHEMATIC_SHOW("&7Displaying &a%s0&7 schematics from &a%s1&7:\n" +
"&8 - &aLeft click &7a structure to set your clipboard\n" +
"&8 - &aRight click &7to add a structure to your multi-clipboard\n" +
"&8 - &7Use &a%s2&7 to go back to the world", "Worldedit.Schematic"),
SCHEMATIC_FORMAT("Available formats (Name: Lookup names)", "Worldedit.Schematic"),
SCHEMATIC_MOVE_EXISTS("&c%s0 already exists", "Worldedit.Schematic"),
SCHEMATIC_MOVE_SUCCESS("&a%s0 -> %s1", "Worldedit.Schematic"),
SCHEMATIC_MOVE_FAILED("&a%s0 no moved: %s1", "Worldedit.Schematic"),
SCHEMATIC_LOADED("%s0 loaded. Paste it with //paste", "Worldedit.Schematic"),
SCHEMATIC_SAVED("%s0 saved.", "Worldedit.Schematic"),
SCHEMATIC_PAGE("Page must be %s", "WorldEdit.Schematic"),
SCHEMATIC_NONE("No files found.", "WorldEdit.Schematic"),
SCHEMATIC_LIST("Available files (Filename: Format) [%s0/%s1]:", "Worldedit.Schematic"),
SCHEMATIC_LIST_ELEM("&8 - &a%s0 &8- &7%s1", "Worldedit.Schematic"),
CLIPBOARD_URI_NOT_FOUND("You do not have %s0 loaded", "WorldEdit.Clipboard"),
CLIPBOARD_CLEARED("Clipboard cleared", "WorldEdit.Clipboard"),
CLIPBOARD_INVALID_FORMAT("Unknown clipboard format: %s0", "WorldEdit.Clipboard"),
VISITOR_BLOCK("%s0 blocks affected", "WorldEdit.Visitor"),
VISITOR_ENTITY("%s0 entities affected", "WorldEdit.Visitor"),
VISITOR_FLAT("%s0 columns affected", "WorldEdit.Visitor"),
SELECTOR_FUZZY_POS1("Region set and expanded from %s0 %s1.", "WorldEdit.Selector"),
SELECTOR_FUZZY_POS2("Added expansion of %s0 %s1.", "WorldEdit.Selector"),
SELECTOR_POS("pos%s0 set to %s1 (%s2).", "WorldEdit.Selector"),
SELECTOR_CENTER("Center set to %s0 (%s1).", "WorldEdit.Selector"),
SELECTOR_RADIUS("Radius set to %s0 (%s1).", "WorldEdit.Selector"),
SELECTOR_EXPANDED("Expanded region to %s0 (%s1)", "WorldEdit.Selector"),
SELECTOR_INVALID_COORDINATES("Invalid coordinates %s0", "WorldEdit.Selector"),
SELECTOR_ALREADY_SET("Position already set.", "WorldEdit.Selector"),
SELECTOR_SET_DEFAULT("Your default region selector is now %s0.", "WorldEdit.Selector"),
TIMEZONE_SET("Timezone set for this session to: %s0", "WorldEdit.Timezone"),
TIMEZONE_DISPLAY("The current time in that timezone is: %s0", "WorldEdit.Timezone"),
COMMAND_INVALID_SYNTAX("The command was not used properly (no more help available).", "WorldEdit.Command"),
COMMAND_CLARIFYING_BRACKET("&7Added clarifying bracket for &c%s0", "WorldEdit.Help"),
HELP_SUGGEST("&7Couldn't find %s0. Maybe try one of &c%s1 &7?", "WorldEdit.Help"),
HELP_HEADER_CATEGORIES("Command Types", "WorldEdit.Help"),
HELP_HEADER_SUBCOMMANDS("Subcommands", "WorldEdit.Help"),
HELP_HEADER_COMMAND("&cHelp for: &7%s0", "WorldEdit.Help"),
HELP_ITEM_ALLOWED("&a%s0&8 - &7%s1", "WorldEdit.Help"),
HELP_ITEM_DENIED("&c%s0&8 - &7%s1", "WorldEdit.Help"),
HELP_HEADER("Help: page %s0/%s1", "WorldEdit.Help"),
HELP_FOOTER("&7Wiki: https://git.io/vSKE5", "WorldEdit.Help"),
PAGE_FOOTER("Use %s0 to go to the next page", "WorldEdit.Utility"),
PROGRESS_MESSAGE("%s1/%s0 (%s2%) @%s3cps %s4s left", "Progress"),
PROGRESS_FINISHED("[ Done! ]", "Progress"),
COMMAND_SYNTAX("&cUsage: &7%s0", "Error"),
NO_PERM("&cYou are lacking the permission node: %s0", "Error"),
SETTING_DISABLE("&cLacking setting: %s0", "Error"),
BRUSH_NOT_FOUND("&cAvailable brushes: %s0", "Error"),
BRUSH_INCOMPATIBLE("&cBrush not compatible with this version", "Error"),
SCHEMATIC_NOT_FOUND("&cSchematic not found: &7%s0", "Error"),
NO_REGION("&cYou have no current allowed region", "Error"),
NO_MASK("&cYou have no current mask set", "Error"),
NOT_PLAYER("&cYou must be a player to perform this action!", "Error"),
PLAYER_NOT_FOUND("&cPlayer not found:&7 %s0", "Error"),
OOM(
"&8[&cCritical&8] &cDetected low memory i.e. < 1%. We will take the following actions:\n&8 - &7Terminate WE block placement\n&8 - &7Clear WE history\n&8 - &7Unload non essential chunks\n&8 - &7Kill entities\n&8 - &7Garbage collect\n&cIgnore this if trying to crash server.\n&7Note: Low memory is likely (but not necessarily) caused by WE",
"Error"),
WORLDEDIT_SOME_FAILS("&c%s0 blocks weren't placed because they were outside your allowed region.", "Error"),
WORLDEDIT_SOME_FAILS_BLOCKBAG("&cMissing blocks: %s0", "Error"),
WORLDEDIT_CANCEL_COUNT("&cCancelled %s0 edits.", "Cancel"),
WORLDEDIT_CANCEL_REASON_CONFIRM("&7Your selection is large (&c%s0 &7-> &c%s1&7, containing &c%s3&7 blocks). Use &c//confirm &7to execute &c%s2", "Cancel"),
WORLDEDIT_CANCEL_REASON("&cYour WorldEdit action was cancelled:&7 %s0&c.", "Cancel"),
WORLDEDIT_CANCEL_REASON_MANUAL("Manual cancellation", "Cancel"),
WORLDEDIT_CANCEL_REASON_LOW_MEMORY("Low memory", "Cancel"),
WORLDEDIT_CANCEL_REASON_MAX_CHANGES("Too many blocks changed", "Cancel"),
WORLDEDIT_CANCEL_REASON_MAX_CHECKS("Too many block checks", "Cancel"),
WORLDEDIT_CANCEL_REASON_MAX_TILES("Too many blockstates", "Cancel"),
WORLDEDIT_CANCEL_REASON_MAX_ENTITIES("Too many entities", "Cancel"),
WORLDEDIT_CANCEL_REASON_MAX_ITERATIONS("Max iterations", "Cancel"),
WORLDEDIT_CANCEL_REASON_OUTSIDE_LEVEL("Outside world", "Cancel"),
WORLDEDIT_CANCEL_REASON_OUTSIDE_REGION("Outside allowed region (bypass with /wea, or disable `region-restrictions` in config.yml)", "Cancel"),
WORLDEDIT_CANCEL_REASON_NO_REGION("No allowed region (bypass with /wea, or disable `region-restrictions` in config.yml)", "Cancel"),
WORLDEDIT_FAILED_LOAD_CHUNK("&cSkipped loading chunk: &7%s0;%s1&c. Try increasing chunk-wait.", "Cancel"),
ASCEND_FAIL("No free spot above you found.", "Navigation"),
ASCENDED_PLURAL("Ascended %s0 levels.", "Navigation"),
ASCENDED_SINGULAR("Ascended a level.", "Navigation"),
UNSTUCK("There you go!", "Navigation"),
DESCEND_FAIL("No free spot below you found.", "Navigation"),
DESCEND_PLURAL("Descended %s0 levels.", "Navigation"),
DESCEND_SINGULAR("Descended a level.", "Navigation"),
WHOOSH("Whoosh!", "Navigation"),
POOF("Poof!", "Navigation"),
THRU_FAIL("No free spot ahead of you found.", "Navigation"),
NO_BLOCK("No block in sight! (or too far)", "Navigation"),
UP_FAIL("You would hit something above you.", "Navigation"),
SEL_CUBOID("Cuboid: left click for point 1, right click for point 2", "Selection"),
SEL_CUBOID_EXTEND("Cuboid: left click for a starting point, right click to extend", "Selection"),
SEL_2D_POLYGON("2D polygon selector: Left/right click to add a point.", "Selection"),
SEL_ELLIPSIOD("Ellipsoid selector: left click=center, right click to extend", "Selection"),
SEL_SPHERE("Sphere selector: left click=center, right click to set radius", "Selection"),
SEL_CYLINDRICAL("Cylindrical selector: Left click=center, right click to extend.", "Selection"),
SEL_MAX("%s0 points maximum.", "Selection"),
SEL_FUZZY("Fuzzy selector: Left click to select all contingent blocks, right click to add. To select an air cavity, use //pos1.", "Selection"),
SEL_CONVEX_POLYHEDRAL("Convex polyhedral selector: Left click=First vertex, right click to add more.", "Selection"),
SEL_LIST("For a list of selection types use:&c //sel list", "Selection"),
SEL_MODES("Select one of the modes below:", "Selection"),
TIP_SEL_LIST("Tip: See the different selection modes with &c//sel list", "Tips"),
TIP_SELECT_CONNECTED("Tip: Select all connected blocks with //sel fuzzy", "Tips"),
TIP_SET_POS1("Tip: Use pos1 as a pattern with &c//set pos1", "Tips"),
TIP_FARWAND("Tip: Select distant points with &c//farwand", "Tips"),
// cut
TIP_LAZYCUT("&7Tip: It is safer to use &c//lazycut", "Tips"),
// set
TIP_FAST("&7Tip: Set fast and without undo using &c//fast", "Tips"),
TIP_CANCEL("&7Tip: You can &c//cancel &7an edit in progress", "Tips"),
TIP_MASK("&7Tip: Set a global destination mask with &c/gmask", "Tips"),
TIP_MASK_ANGLE("Tip: Replace upward slopes of 3-20 blocks using&c //replace /[-20][-3] bedrock", "Tips"),
TIP_SET_LINEAR("&7Tip: Set blocks linearly with&c //set #l3d[wood,bedrock]", "Tips"),
TIP_SURFACE_SPREAD("&7Tip: Spread a flat surface with&c //set #surfacespread[5][0][5][#existing]", "Tips"),
TIP_SET_HAND("&7Tip: Use your current hand with &c//set hand", "Tips"),
// replace
TIP_REPLACE_ID("&7Tip: Replace only the block id:&c //replace woodenstair #id[cobblestair]", "Tips"),
TIP_REPLACE_LIGHT("Tip: Remove light sources with&c //replace #brightness[1][15] 0", "Tips"),
TIP_TAB_COMPLETE("Tip: The replace command supports tab completion", "Tips"),
// clipboard
TIP_FLIP("Tip: Mirror with &c//flip", "Tips"),
TIP_DEFORM("Tip: Reshape with &c//deform", "Tips"),
TIP_TRANSFORM("Tip: Set a transform with &c//gtransform", "Tips"),
TIP_COPYPASTE("Tip: Paste on click with &c//br copypaste", "Tips"),
TIP_SOURCE_MASK("Tip: Set a source mask with &c/gsmask <mask>&7", "Tips"),
TIP_REPLACE_MARKER("Tip: Replace a block with your full clipboard using &c//replace wool #fullcopy", "Tips"),
TIP_PASTE("Tip: Place with &c//paste", "Tips"),
TIP_LAZYCOPY("Tip: lazycopy is faster", "Tips"),
TIP_DOWNLOAD("Tip: Try out &c//download", "Tips"),
TIP_ROTATE("Tip: Orientate with &c//rotate", "Tips"),
TIP_COPY_PATTERN("Tip: To use as a pattern try &c#copy", "Tips"),
// regen
TIP_REGEN_0("Tip: Use a biome with /regen [biome]", "Tips"),
TIP_REGEN_1("Tip: Use a seed with /regen [biome] [seed]", "Tips"),
TIP_BIOME_PATTERN("Tip: The &c#biome[forest]&7 pattern can be used in any command", "Tips"),
TIP_BIOME_MASK("Tip: Restrict to a biome with the `$jungle` mask", "Tips"),;
private static final HashMap<String, String> replacements = new HashMap<>();
static {
for (final char letter : "1234567890abcdefklmnor".toCharArray()) {
replacements.put("&" + letter, "\u00a7" + letter);
}
replacements.put("\\\\n", "\n");
replacements.put("\\n", "\n");
replacements.put("&-", "\n");
}
/**
* Translated
*/
private String s;
/**
* Default
*/
private String d;
/**
* What locale category should this translation fall under
*/
private String cat;
/**
* Should the string be prefixed?
*/
private boolean prefix;
/**
* Constructor for custom strings.
*/
BBC() {
/*
* use setCustomString();
*/
}
/**
* Constructor
*
* @param d default
* @param prefix use prefix
*/
BBC(final String d, final boolean prefix, final String cat) {
this.d = d;
if (this.s == null) {
this.s = d;
}
this.prefix = prefix;
this.cat = cat.toLowerCase();
}
/**
* Constructor
*
* @param d default
*/
BBC(final String d, final String cat) {
this(d, true, cat.toLowerCase());
}
public String f(final Object... args) {
return format(args);
}
public String format(final Object... args) {
String m = this.s;
for (int i = args.length - 1; i >= 0; i--) {
if (args[i] == null) {
continue;
}
m = m.replace("%s" + i, args[i].toString());
}
if (args.length > 0) {
m = m.replace("%s", args[0].toString());
}
return m;
}
public static void load(final File file) {
try {
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
final YamlConfiguration yml = YamlConfiguration.loadConfiguration(file);
final Set<String> keys = yml.getKeys(true);
final EnumSet<BBC> all = EnumSet.allOf(BBC.class);
final HashSet<String> allNames = new HashSet<>();
final HashSet<String> allCats = new HashSet<>();
final HashSet<String> toRemove = new HashSet<>();
for (final BBC c : all) {
allNames.add(c.name());
allCats.add(c.cat.toLowerCase());
}
final HashSet<BBC> captions = new HashSet<>();
boolean changed = false;
for (final String key : keys) {
final Object value = yml.get(key);
if (value instanceof MemorySection) {
continue;
}
final String[] split = key.split("\\.");
final String node = split[split.length - 1].toUpperCase();
final BBC caption = allNames.contains(node) ? valueOf(node) : null;
if (caption != null) {
if (!split[0].equalsIgnoreCase(caption.cat)) {
changed = true;
yml.set(key, null);
yml.set(caption.cat + "." + caption.name().toLowerCase(), value);
}
captions.add(caption);
caption.s = (String) value;
} else {
toRemove.add(key);
}
}
for (final String remove : toRemove) {
changed = true;
yml.set(remove, null);
}
for (final BBC caption : all) {
if (!captions.contains(caption)) {
changed = true;
yml.set(caption.cat + "." + caption.name().toLowerCase(), caption.d);
}
caption.s = StringMan.replaceFromMap(caption.s, replacements);
}
if (changed) {
yml.save(file);
}
} catch (final Exception e) {
MainUtil.handleError(e);
}
}
@Override
public String toString() {
return s();
}
public boolean isEmpty() {
return length() == 0;
}
public int length() {
return toString().length();
}
public static String color(String string) {
return StringMan.replaceFromMap(string, replacements);
}
public static String stripColor(String string) {
return StringMan.removeFromSet(string, replacements.keySet());
}
public String s() {
return this.s;
}
public Message m(Object... args) {
return new Message(this, args);
}
public String original() {
return d;
}
public boolean usePrefix() {
return this.prefix;
}
public String getCat() {
return this.cat;
}
public BBC or(BBC... others) {
int index = PseudoRandom.random.nextInt(others.length + 1);
return index == 0 ? this : others[index - 1];
}
public void send(Object actor, final Object... args) {
if (isEmpty()) {
return;
}
if (actor == null) {
Fawe.debug(this.format(args));
} else {
try {
Method method = actor.getClass().getMethod("print", String.class);
method.setAccessible(true);
method.invoke(actor, (PREFIX.isEmpty() ? "" : PREFIX.s() + " ") + this.format(args));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
public static String getPrefix() {
return (PREFIX.isEmpty() ? "" : PREFIX.s() + " ");
}
public void send(final FawePlayer<?> player, final Object... args) {
if (isEmpty()) {
return;
}
if (player == null) {
Fawe.debug(this.format(args));
} else {
player.sendMessage((PREFIX.isEmpty() ? "" : PREFIX.s() + " ") + this.format(args));
}
}
public static char getCode(String name) {
switch (name) {
case "BLACK":
return '0';
case "DARK_BLUE":
return '1';
case "DARK_GREEN":
return '2';
case "DARK_AQUA":
return '3';
case "DARK_RED":
return '4';
case "DARK_PURPLE":
return '5';
case "GOLD":
return '6';
case "GRAY":
return '7';
case "DARK_GRAY":
return '8';
case "BLUE":
return '9';
case "GREEN":
return 'a';
case "AQUA":
return 'b';
case "RED":
return 'c';
case "LIGHT_PURPLE":
return 'd';
case "YELLOW":
return 'e';
case "WHITE":
return 'f';
case "OBFUSCATED":
return 'k';
case "BOLD":
return 'l';
case "STRIKETHROUGH":
return 'm';
case "UNDERLINE":
return 'n';
case "ITALIC":
return 'o';
default:
case "RESET":
return 'r';
}
}
public static String getColorName(char code) {
switch (code) {
case '0':
return "BLACK";
case '1':
return "DARK_BLUE";
case '2':
return "DARK_GREEN";
case '3':
return "DARK_AQUA";
case '4':
return "DARK_RED";
case '5':
return "DARK_PURPLE";
case '6':
return "GOLD";
default:
case '7':
return "GRAY";
case '8':
return "DARK_GRAY";
case '9':
return "BLUE";
case 'a':
return "GREEN";
case 'b':
return "AQUA";
case 'c':
return "RED";
case 'd':
return "LIGHT_PURPLE";
case 'e':
return "YELLOW";
case 'f':
return "WHITE";
case 'k':
return "OBFUSCATED";
case 'l':
return "BOLD";
case 'm':
return "STRIKETHROUGH";
case 'n':
return "UNDERLINE";
case 'o':
return "ITALIC";
case 'r':
return "RESET";
}
}
private static Object[] append(StringBuilder builder, Map<String, Object> obj, String color, Map<String, Boolean> properties) {
Object[] style = new Object[] { color, properties };
for (Map.Entry<String, Object> entry : obj.entrySet()) {
switch (entry.getKey()) {
case "text":
String text = (String) entry.getValue();
String newColor = (String) obj.get("color");
String newBold = (String) obj.get("bold");
int index = builder.length();
if (!Objects.equals(color, newColor)) {
style[0] = newColor;
char code = BBC.getCode(newColor.toUpperCase());
builder.append('\u00A7').append(code);
}
for (Map.Entry<String, Object> entry2 : obj.entrySet()) {
if (StringMan.isEqualIgnoreCaseToAny(entry2.getKey(), "bold", "italic", "underlined", "strikethrough", "obfuscated")) {
boolean newValue = Boolean.valueOf((String) entry2.getValue());
if (properties.put(entry2.getKey(), newValue) != newValue) {
if (newValue) {
char code = BBC.getCode(entry2.getKey().toUpperCase());
builder.append('\u00A7').append(code);
} else {
builder.insert(index, '\u00A7').append('r');
if (Objects.equals(color, newColor) && newColor != null) {
builder.append('\u00A7').append(BBC.getCode(newColor.toUpperCase()));
}
}
}
}
}
builder.append(text);
break;
case "extra":
List<Map<String, Object>> list = (List<Map<String, Object>>) entry.getValue();
for (Map<String, Object> elem : list) {
elem.putIfAbsent("color", obj.get("color"));
for (Map.Entry<String, Object> entry2 : obj.entrySet()) {
if (StringMan.isEqualIgnoreCaseToAny(entry2.getKey(), "bold", "italic", "underlined", "strikethrough", "obfuscated")) {
elem.putIfAbsent(entry2.getKey(), entry2.getValue());
}
}
style = append(builder, elem, (String) style[0], (Map) style[1]);
}
}
}
return style;
}
public static String jsonToString(String text) {
Gson gson = new Gson();
StringBuilder builder = new StringBuilder();
Map<String, Object> obj = gson.fromJson(text, new TypeToken<Map<String, Object>>() {}.getType());
HashMap<String, Boolean> properties = new HashMap<>();
properties.put("bold", false);
properties.put("italic", false);
properties.put("underlined", false);
properties.put("strikethrough", false);
properties.put("obfuscated", false);
append(builder, obj, null, properties);
return builder.toString();
}
/**
* @param m
* @param runPart Part, Color, NewLine
*/
public static void splitMessage(String m, RunnableVal3<String, String, Boolean> runPart) {
m = color(m);
String color = "GRAY";
boolean newline = false;
for (String line : m.split("\n")) {
boolean hasColor = line.charAt(0) == '\u00A7';
String[] splitColor = line.split("\u00A7");
for (String part : splitColor) {
if (hasColor) {
color = getColorName(part.charAt(0));
part = part.substring(1);
}
runPart.run(part, color, newline);
hasColor = true;
}
newline = true;
}
}
}

View File

@ -0,0 +1,187 @@
package com.boydti.fawe.config;
import com.boydti.fawe.configuration.ConfigurationSection;
import com.boydti.fawe.configuration.file.YamlConfiguration;
import com.sk89q.minecraft.util.commands.Command;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Commands {
private static YamlConfiguration cmdConfig;
private static File cmdFile;
public static void load(File file) {
cmdFile = file;
try {
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
cmdConfig = YamlConfiguration.loadConfiguration(file);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Command fromArgs(String[] aliases, String usage, String desc, int min, int max, String flags, String help) {
return new Command() {
@Override
public Class<? extends Annotation> annotationType() {
return Command.class;
}
@Override
public String[] aliases() {
return aliases;
}
@Override
public String usage() {
return usage;
}
@Override
public String desc() {
return desc;
}
@Override
public int min() {
return min;
}
@Override
public int max() {
return max;
}
@Override
public String flags() {
return flags;
}
@Override
public String help() {
return help;
}
@Override
public boolean anyFlags() {
return !(flags.isEmpty() || flags.matches("[a-z]+"));
}
};
}
public static Command translate(Class clazz, final Command command) {
if (cmdConfig == null || command instanceof TranslatedCommand) {
return command;
}
return new TranslatedCommand(clazz.getSimpleName(), command);
}
public static String getAlias(Class clazz, String command) {
if (cmdConfig == null) {
return command;
}
List<String> aliases = cmdConfig.getStringList(clazz + "." + command + ".aliases");
if (aliases == null) {
aliases = cmdConfig.getStringList(command + ".aliases");
}
return (aliases == null || aliases.isEmpty()) ? command : aliases.get(0);
}
public static class TranslatedCommand implements Command {
private final String[] aliases;
private final String usage;
private final String desc;
private final String help;
private final Command command;
public TranslatedCommand(String clazz, Command command) {
String id = command.aliases()[0];
ConfigurationSection commands;
if (cmdConfig.contains(clazz + "." + id) || !cmdConfig.contains(id)) {
commands = cmdConfig.getConfigurationSection(clazz + "." + id);
} else {
commands = cmdConfig.getConfigurationSection(id);
}
boolean set = false;
if (commands == null) {
set = (commands = cmdConfig.createSection(clazz + "." + id)) != null;
}
HashMap<String, Object> options = new HashMap<>();
options.put("aliases", new ArrayList<String>(Arrays.asList(command.aliases())));
options.put("usage", command.usage());
options.put("desc", command.desc());
options.put("help", command.help());
for (Map.Entry<String, Object> entry : options.entrySet()) {
String key = entry.getKey();
if (!commands.contains(key)) {
commands.set(key, entry.getValue());
set = true;
}
}
if (set) {
try {
cmdConfig.save(cmdFile);
} catch (IOException e) {
e.printStackTrace();
}
}
this.aliases = commands.getStringList("aliases").toArray(new String[0]);
this.usage = commands.getString("usage");
this.desc = commands.getString("desc");
this.help = commands.getString("help");
this.command = command;
}
@Override
public Class<? extends Annotation> annotationType() {
return command.annotationType();
}
@Override
public String[] aliases() {
return aliases;
}
@Override
public String usage() {
return usage;
}
@Override
public String desc() {
return desc;
}
@Override
public int min() {
return command.min();
}
@Override
public int max() {
return command.max();
}
@Override
public String flags() {
return command.flags();
}
@Override
public String help() {
return help;
}
@Override
public boolean anyFlags() {
return command.anyFlags();
}
}
public static Class<Commands> inject() {
return Commands.class;
}
}

View File

@ -0,0 +1,456 @@
package com.boydti.fawe.config;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.configuration.MemorySection;
import com.boydti.fawe.configuration.file.YamlConfiguration;
import com.boydti.fawe.util.StringMan;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Config {
public Config() {
save(new PrintWriter(new ByteArrayOutputStream(0)), getClass(), this, 0);
}
/**
* Get the value for a node<br>
* Probably throws some error if you try to get a non existent key
*
* @param key
* @param <T>
* @return
*/
private <T> T get(String key, Class root) {
String[] split = key.split("\\.");
Object instance = getInstance(split, root);
if (instance != null) {
Field field = getField(split, instance);
if (field != null) {
try {
return (T) field.get(instance);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
Fawe.debug("Failed to get config option: " + key);
return null;
}
/**
* Set the value of a specific node<br>
* Probably throws some error if you supply non existing keys or invalid values
*
* @param key config node
* @param value value
*/
private void set(String key, Object value, Class root) {
String[] split = key.split("\\.");
Object instance = getInstance(split, root);
if (instance != null) {
Field field = getField(split, instance);
if (field != null) {
try {
if (field.getAnnotation(Final.class) != null) {
return;
}
if (field.getType() == String.class && !(value instanceof String)) {
value = value + "";
}
// TODO FIXME parsing using bindings
field.set(instance, value);
return;
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Fawe.debug("Failed to set config option: " + key + ": " + value + " | " + instance + " | " + root.getSimpleName() + ".yml");
}
public boolean load(File file) {
if (!file.exists()) {
return false;
}
YamlConfiguration yml = YamlConfiguration.loadConfiguration(file);
for (String key : yml.getKeys(true)) {
Object value = yml.get(key);
if (value instanceof MemorySection) {
continue;
}
set(key, value, getClass());
}
return true;
}
/**
* Set all values in the file (load first to avoid overwriting)
*
* @param file
*/
public void save(File file) {
Class<? extends Config> root = getClass();
try {
if (!file.exists()) {
File parent = file.getParentFile();
if (parent != null) {
file.getParentFile().mkdirs();
}
file.createNewFile();
}
PrintWriter writer = new PrintWriter(file);
Object instance = this;
save(writer, getClass(), instance, 0);
writer.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* Indicates that a field should be instantiated / created
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Create {
}
/**
* Indicates that a field cannot be modified
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Final {
}
/**
* Creates a comment
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Comment {
String[] value();
}
/**
* The names of any default blocks
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface BlockName {
String[] value();
}
/**
* Any field or class with is not part of the config
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Ignore {
}
@Ignore // This is not part of the config
public static class ConfigBlock<T> {
private HashMap<String, T> INSTANCES = new HashMap<>();
public T remove(String key) {
return INSTANCES.remove(key);
}
public T get(String key) {
return INSTANCES.get(key);
}
public void put(String key, T value) {
INSTANCES.put(key, value);
}
public Collection<T> getInstances() {
return INSTANCES.values();
}
public Collection<String> getSections() {
return INSTANCES.keySet();
}
private Map<String, T> getRaw() {
return INSTANCES;
}
}
/**
* Get the static fields in a section
*
* @param clazz
* @return
*/
private Map<String, Object> getFields(Class clazz) {
HashMap<String, Object> map = new HashMap<>();
for (Field field : clazz.getFields()) {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
try {
map.put(toNodeName(field.getName()), field.get(null));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return map;
}
private String toYamlString(Object value, String spacing) {
if (value instanceof List) {
Collection<?> listValue = (Collection<?>) value;
if (listValue.isEmpty()) {
return "[]";
}
StringBuilder m = new StringBuilder();
for (Object obj : listValue) {
m.append(System.lineSeparator() + spacing + "- " + toYamlString(obj, spacing));
}
return m.toString();
}
if (value instanceof String) {
String stringValue = (String) value;
if (stringValue.isEmpty()) {
return "''";
}
return "\"" + stringValue + "\"";
}
return value != null ? value.toString() : "null";
}
private void save(PrintWriter writer, Class clazz, final Object instance, int indent) {
try {
String CTRF = System.lineSeparator();
String spacing = StringMan.repeat(" ", indent);
HashMap<Class, Object> instances = new HashMap<>();
for (Field field : clazz.getFields()) {
if (field.getAnnotation(Ignore.class) != null) {
continue;
}
Class<?> current = field.getType();
if (field.getAnnotation(Ignore.class) != null) {
continue;
}
Comment comment = field.getAnnotation(Comment.class);
if (comment != null) {
for (String commentLine : comment.value()) {
writer.write(spacing + "# " + commentLine + CTRF);
}
}
if (current == ConfigBlock.class) {
current = (Class<?>) ((ParameterizedType) (field.getGenericType())).getActualTypeArguments()[0];
comment = current.getAnnotation(Comment.class);
if (comment != null) {
for (String commentLine : comment.value()) {
writer.write(spacing + "# " + commentLine + CTRF);
}
}
BlockName blockNames = current.getAnnotation(BlockName.class);
if (blockNames != null) {
writer.write(spacing + toNodeName(current.getSimpleName()) + ":" + CTRF);
ConfigBlock configBlock = (ConfigBlock) field.get(instance);
if (configBlock == null || configBlock.getInstances().isEmpty()) {
configBlock = new ConfigBlock();
field.set(instance, configBlock);
for (String blockName : blockNames.value()) {
configBlock.put(blockName, current.newInstance());
}
}
// Save each instance
for (Map.Entry<String, Object> entry : ((Map<String, Object>) configBlock.getRaw()).entrySet()) {
String key = entry.getKey();
writer.write(spacing + " " + toNodeName(key) + ":" + CTRF);
save(writer, current, entry.getValue(), indent + 4);
}
}
continue;
}
Create create = field.getAnnotation(Create.class);
if (create != null) {
Object value = field.get(instance);
setAccessible(field);
if (indent == 0) {
writer.write(CTRF);
}
comment = current.getAnnotation(Comment.class);
if (comment != null) {
for (String commentLine : comment.value()) {
writer.write(spacing + "# " + commentLine + CTRF);
}
}
writer.write(spacing + toNodeName(current.getSimpleName()) + ":" + CTRF);
if (value == null) {
field.set(instance, value = current.newInstance());
instances.put(current, value);
}
save(writer, current, value, indent + 2);
continue;
} else {
writer.write(spacing + toNodeName(field.getName() + ": ") + toYamlString(field.get(instance), spacing) + CTRF);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* Get the field for a specific config node
*
* @param split the node (split by period)
* @return
*/
private Field getField(String[] split, Class root) {
Object instance = getInstance(split, root);
if (instance == null) {
return null;
}
return getField(split, instance);
}
/**
* Get the field for a specific config node and instance<br>
* Note: As expiry can have multiple blocks there will be multiple instances
*
* @param split the node (split by period)
* @param instance the instance
* @return
*/
private Field getField(String[] split, Object instance) {
try {
Field field = instance.getClass().getField(toFieldName(split[split.length - 1]));
setAccessible(field);
return field;
} catch (Throwable e) {
Fawe.debug("Invalid config field: " + StringMan.join(split, ".") + " for " + toNodeName(instance.getClass().getSimpleName()));
return null;
}
}
private Object getInstance(Object instance, Class clazz) throws IllegalAccessException, InstantiationException {
try {
Field instanceField = clazz.getDeclaredField(clazz.getSimpleName());
} catch (Throwable ignore) {
}
return clazz.newInstance();
}
/**
* Get the instance for a specific config node
*
* @param split the node (split by period)
* @return The instance or null
*/
private Object getInstance(String[] split, Class root) {
try {
Class<?> clazz = root == null ? MethodHandles.lookup().lookupClass() : root;
Object instance = this;
while (split.length > 0) {
switch (split.length) {
case 1:
return instance;
default:
Class found = null;
Class<?>[] classes = clazz.getDeclaredClasses();
for (Class current : classes) {
if (StringMan.isEqual(current.getSimpleName(), toFieldName(split[0]))) {
found = current;
break;
}
}
try {
Field instanceField = clazz.getDeclaredField(toFieldName(split[0]));
setAccessible(instanceField);
if (instanceField.getType() != ConfigBlock.class) {
Object value = instanceField.get(instance);
if (value == null) {
value = found.newInstance();
instanceField.set(instance, value);
}
clazz = found;
instance = value;
split = Arrays.copyOfRange(split, 1, split.length);
continue;
}
ConfigBlock value = (ConfigBlock) instanceField.get(instance);
if (value == null) {
value = new ConfigBlock();
instanceField.set(instance, value);
}
instance = value.get(split[1]);
if (instance == null) {
instance = found.newInstance();
value.put(split[1], instance);
}
clazz = found;
split = Arrays.copyOfRange(split, 2, split.length);
continue;
} catch (NoSuchFieldException ignore) {
}
if (found != null) {
split = Arrays.copyOfRange(split, 1, split.length);
clazz = found;
instance = clazz.newInstance();
continue;
}
return null;
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
/**
* Translate a node to a java field name
*
* @param node
* @return
*/
private String toFieldName(String node) {
return node.toUpperCase().replaceAll("-", "_");
}
/**
* Translate a field to a config node
*
* @param field
* @return
*/
private String toNodeName(String field) {
return field.toLowerCase().replace("_", "-");
}
/**
* Set some field to be accesible
*
* @param field
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private void setAccessible(Field field) throws NoSuchFieldException, IllegalAccessException {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
}

View File

@ -0,0 +1,495 @@
package com.boydti.fawe.config;
import com.boydti.fawe.object.FaweLimit;
import com.boydti.fawe.object.FawePlayer;
import java.io.File;
import java.util.*;
public class Settings extends Config {
@Ignore
public static final Settings IMP = new Settings();
@Comment("These first 6 aren't configurable") // This is a comment
@Final // Indicates that this value isn't configurable
public final String ISSUES = "https://github.com/boy0001/FastAsyncWorldedit/issues";
@Final
public final String WIKI = "https://github.com/boy0001/FastAsyncWorldedit/wiki/";
@Final
public String DATE; // These values are set from FAWE before loading
@Final
public String BUILD; // These values are set from FAWE before loading
@Final
public String COMMIT; // These values are set from FAWE before loading
@Final
public String PLATFORM; // These values are set from FAWE before loading
@Comment({"Options: de, ru, tr",
"Create a PR to contribute a translation: https://github.com/boy0001/FastAsyncWorldedit/new/master/core/src/main/resources",})
public String LANGUAGE = "";
@Comment({"Enable or disable automatic updates",
" - true = update automatically in the background",
" - confirm = prompt an admin to confirm each update",
" - false = do not update the plugin"
})
public String UPDATE = "false";
@Comment("Send anonymous usage statistics")
public boolean METRICS = false;
@Comment({
"Set true to enable WorldEdit restrictions per region (e.g. PlotSquared or WorldGuard).",
"To be allowed to WorldEdit in a region, users need the appropriate",
"fawe.<plugin> permission. See the Permissions page for supported region plugins."
})
public boolean REGION_RESTRICTIONS = true;
@Comment("FAWE will skip chunks when there's not enough memory available")
public boolean PREVENT_CRASHES = false;
@Comment({
"FAWE will cancel non admin edits when memory consumption exceeds this %",
" - Bypass with `/wea` or `//fast` or `fawe.bypass`",
" - Disable with 100 or -1."
})
public int MAX_MEMORY_PERCENT = 95;
@Create
public CLIPBOARD CLIPBOARD;
@Create
public LIGHTING LIGHTING;
@Create
public TICK_LIMITER TICK_LIMITER;
@Create
public WEB WEB;
@Create
public EXTENT EXTENT;
@Create
public EXPERIMENTAL EXPERIMENTAL;
@Create
public QUEUE QUEUE;
@Create
public HISTORY HISTORY;
@Create
public PATHS PATHS;
@Create
public REGION_RESTRICTIONS_OPTIONS REGION_RESTRICTIONS_OPTIONS;
@Comment("Paths for various directories")
public static final class PATHS {
public String TOKENS = "tokens";
@Comment({
"Put any minecraft or mod jars for FAWE to be aware of block textures",
})
public String PATTERNS = "patterns";
public String MASKS = "masks";
public String TEXTURES = "textures";
public String HEIGHTMAP = "heightmap";
public String HISTORY = "history";
@Comment({
"Multiple servers can use the same clipboards",
" - Use a shared directory or NFS/Samba"
})
public String CLIPBOARD = "clipboard";
@Comment("Each player has their own sub directory for schematics")
public boolean PER_PLAYER_SCHEMATICS = true;
}
@Comment("Region restriction settings")
public static final class REGION_RESTRICTIONS_OPTIONS {
@Comment({
"What type of users are allowed to WorldEdit in a region",
" - MEMBER = Players added to a region",
" - OWNER = Players who own the region"
})
public String MODE = "MEMBER";
}
@Create // This value will be generated automatically
public ConfigBlock<LIMITS> LIMITS;
@Comment({
"The \"default\" limit group affects those without a specific limit permission.",
"To grant someone different limits, copy the default limits group",
"and give it a different name (e.g. newbie). Then give the user the limit ",
"permission node with that limit name (e.g. fawe.limit.newbie )"
})
@BlockName("default") // The name for the default block
public static class LIMITS extends ConfigBlock {
@Comment("Max actions that can be run concurrently (i.e. commands)")
public int MAX_ACTIONS = 1;
@Comment("Max number of block changes (e.g. by `//set stone`).")
public int MAX_CHANGES = 50000000;
@Comment("Max number of blocks checked (e.g. `//count stone` which doesn't change blocks)")
public int MAX_CHECKS = 50000000;
@Comment("Number of times a change can fail (e.g. if the player can't access that region)")
public int MAX_FAILS = 50000000;
@Comment("Allowed brush iterations (e.g. `//brush smooth`)")
public int MAX_ITERATIONS = 1000;
@Comment("Max allowed entities (e.g. cows)")
public int MAX_ENTITIES = 1337;
@Comment({
"Blockstates include Banner, Beacon, BrewingStand, Chest, CommandBlock, ",
"CreatureSpawner, Dispenser, Dropper, EndGateway, Furnace, Hopper, Jukebox, ",
"NoteBlock, Sign, Skull, Structure"
})
public int MAX_BLOCKSTATES = 1337;
@Comment({
"Maximum size of the player's history in Megabytes:",
" - History on disk or memory will be deleted",
})
public int MAX_HISTORY_MB = -1;
@Comment("Maximum time in milliseconds //calc can execute")
public int MAX_EXPRESSION_MS = 50;
@Comment({
"Cinematic block placement:",
" - Adds a delay to block placement (ms/block)",
" - Having an artificial delay will use more CPU/Memory",
})
public int SPEED_REDUCTION = 0;
@Comment({
"Place chunks instead of individual blocks:",
" - Disabling this will negatively impact performance",
" - Only disable this for compatibility or cinematic placement",
})
public boolean FAST_PLACEMENT = true;
@Comment({
"Should WorldEdit use inventory?",
"0 = No inventory usage (creative)",
"1 = Inventory for removing and placing (freebuild)",
"2 = Inventory for placing (survival)",
})
public int INVENTORY_MODE = 0;
@Comment({
"Should large edits require confirmation (>16384 chunks)",
})
public boolean CONFIRM_LARGE = true;
@Comment({
"List of blocks to strip nbt from",
})
public List<String> STRIP_NBT = new ArrayList<>();
}
public static class HISTORY {
@Comment({
"Should history be saved on disk:",
" - Frees up a lot of memory",
" - Persists restarts",
" - Unlimited undo",
" - Does not affect edit performance if `combine-stages`",
})
public boolean USE_DISK = true;
@Comment({
"Use a database to store disk storage summaries:",
" - Enables inspection and rollback",
" - Does not impact performance",
})
public boolean USE_DATABASE = true;
@Comment({
"Record history with dispatching:",
" - Much faster as it avoids duplicate block checks",
" - Slightly worse compression since dispatch order is different",
})
public boolean COMBINE_STAGES = true;
@Comment({
"Higher compression reduces the size of history at the expense of CPU",
"0 = Uncompressed byte array (fastest)",
"1 = 1 pass fast compressor (default)",
"2 = 2 x fast",
"3 = 3 x fast",
"4 = 1 x medium, 1 x fast",
"5 = 1 x medium, 2 x fast",
"6 = 1 x medium, 3 x fast",
"7 = 1 x high, 1 x medium, 1 x fast",
"8 = 1 x high, 1 x medium, 2 x fast",
"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 int COMPRESSION_LEVEL = 3;
@Comment({
"The buffer size for compression:",
" - Larger = better ratio but uses more upfront memory",
" - Must be in the range [64, 33554432]",
})
public int BUFFER_SIZE = 531441;
@Comment({
"The maximum time in milliseconds to wait for a chunk to load for an edit.",
" (50ms = 1 server tick, 0 = Fastest).",
" The default value of 100 should be safe for most cases.",
"",
"Actions which require loaded chunks (e.g. copy) which do not load in time",
" will use the last chunk as filler, which may appear as bands of duplicated blocks.",
"Actions usually wait about 25-50ms for the chunk to load, more if the server is lagging.",
"A value of 100ms does not force it to wait 100ms if the chunk loads in 10ms.",
"",
"This value is a timeout in case a chunk is never going to load (for whatever odd reason).",
"If the action times out, the operation continues by using the previous chunk as filler,",
" and displaying an error message. In this case, either copy a smaller section,",
" or increase chunk-wait-ms.",
"A value of 0 is faster simply because it doesn't bother loading the chunks or waiting.",
})
public int CHUNK_WAIT_MS = 1000;
@Comment("Delete history on disk after a number of days")
public int DELETE_AFTER_DAYS = 7;
@Comment("Delete history in memory on logout (does not effect disk)")
public boolean DELETE_ON_LOGOUT = true;
@Comment({
"If history should be enabled by default for plugins using WorldEdit:",
" - It is faster to have disabled",
" - Use of the FAWE API will not be effected"
})
public boolean ENABLE_FOR_CONSOLE = true;
@Comment({
"Should redo information be stored:",
" - History is about 20% larger",
" - Enables use of /redo",
})
public boolean STORE_REDO = true;
@Comment({
"Assumes all edits are smaller than 4096x256x4096:",
" - Reduces history size by ~10%",
})
public boolean SMALL_EDITS = false;
}
@Comment("This relates to how FAWE places chunks")
public static class QUEUE {
@Comment({
"This should equal the number of processors you have",
" - Set this to 1 if you need reliable `/timings`"
})
public int PARALLEL_THREADS = Math.max(1, Runtime.getRuntime().availableProcessors());
@Create
public static PROGRESS PROGRESS;
@Comment({
"When doing edits that effect more than this many chunks:",
" - FAWE will start placing before all calculations are finished",
" - A larger value will use slightly less CPU time",
" - A smaller value will reduce memory usage",
" - A value too small may break some operations (deform?)"
})
public int TARGET_SIZE = 64;
@Comment({
"Force FAWE to start placing chunks regardless of whether an edit is finished processing",
" - A larger value will use slightly less CPU time",
" - A smaller value will reduce memory usage",
" - A value too small may break some operations (deform?)"
})
public int MAX_WAIT_MS = 1000;
@Comment({
"Increase or decrease queue intensity (ms) [-50,50]:",
" 0 = balance of performance / stability",
" -10 = Allocate 10ms less for chunk placement",
"Too high will can cause lag spikes (you might be okay with this)",
"Too low will result in slow edits",
})
public int EXTRA_TIME_MS = 0;
@Comment({
"Loading the right amount of chunks beforehand can speed up operations",
" - Low values may result in FAWE waiting on requests to the main thread",
" - Higher values use more memory and isn't noticeably faster",
})
public int PRELOAD_CHUNKS = 32;
@Comment({
"Discard edits which have been idle for a certain amount of time (ms)",
" - E.g. A plugin creates an EditSession but never does anything with it",
" - This only applies to plugins improperly using WorldEdit's legacy API"
})
public int DISCARD_AFTER_MS = 60000;
public static class PROGRESS {
@Comment({"Display constant titles about the progress of a user's edit",
" - false = disabled",
" - title = Display progress titles",
" - chat = Display progress in chat"
})
public String DISPLAY = "false";
@Comment("How often edit progress is displayed")
public int INTERVAL = 1;
@Comment("Delay sending progress in milliseconds (so quick edits don't spam)")
public int DELAY = 5000;
}
}
@Comment({
"Experimental options, use at your own risk",
" - UNSAFE = Can cause permanent damage to the server",
" - SAFE = Can be buggy but unlikely to cause any damage"
})
public static class EXPERIMENTAL {
@Comment({
"[UNSAFE] Directly modify the region files. (OBSOLETE - USE ANVIL COMMANDS)",
" - IMPROPER USE CAN CAUSE WORLD CORRUPTION!",
})
public boolean ANVIL_QUEUE_MODE = false;
@Comment({
"[SAFE] Dynamically increase the number of chunks rendered",
" - Requires Paper: ci.destroystokyo.com/job/PaperSpigot/",
" - Set your server view distance to 1 (spigot.yml, server.properties)",
" - Based on tps and player movement",
" - Please provide feedback",
})
public int DYNAMIC_CHUNK_RENDERING = -1;
@Comment({
"Allows brushes to be persistent (default: true)",
})
public boolean PERSISTENT_BRUSHES = true;
@Comment({
"[SAFE] Enable CUI without needing the mod installed (Requires ProtocolLib)",
})
public boolean VANILLA_CUI = false;
@Comment({
"Disable using native libraries",
})
public boolean DISABLE_NATIVES = false;
@Comment({
"[SAFE] Keep entities that are positioned in non-air blocks when editing an area",
"Might cause client-side FPS lagg in some situations"
})
public boolean KEEP_ENTITIES_IN_BLOCKS = false;
@Comment({
"[SAFE] Experimental scripting support for Java 9",
" - "
})
public boolean MODERN_CRAFTSCRIPTS = false;
}
public static class WEB {
@Comment({
"Should download urls be shortened?",
" - Links are less secure as they could be brute forced"
})
public boolean SHORTEN_URLS = false;
@Comment({
"The web interface for clipboards",
" - All schematics are anonymous and private",
" - Downloads can be deleted by the user",
" - Supports clipboard uploads, downloads and saves",
})
public String URL = "https://empcraft.com/fawe/";
@Comment({
"The web interface for assets",
" - All schematics are organized and public",
" - Assets can be searched, selected and downloaded",
})
public String ASSETS = "https://empcraft.com/assetpack/";
}
public static class EXTENT {
@Comment({
"Don't bug console when these plugins slow down WorldEdit operations",
" - You'll see a message in console if you need to change this option"
})
public List<String> ALLOWED_PLUGINS = new ArrayList<>();
@Comment("Should debug messages be sent when third party extents are used?")
public boolean DEBUG = true;
}
@Comment("Generic tick limiter (not necessarily WorldEdit related, but useful to stop abuse)")
public static class TICK_LIMITER {
@Comment("Enable the limiter")
public boolean ENABLED = true;
@Comment("The interval in ticks")
public int INTERVAL = 20;
@Comment("Max falling blocks per interval (per chunk)")
public int FALLING = 64;
@Comment("Max physics per interval (excluding redstone)")
public int PHYSICS_MS = 10;
@Comment("Max item spawns per interval (per chunk)")
public int ITEMS = 256;
@Comment({
"Whether fireworks can load chunks",
" - Fireworks usually travel vertically so do not load any chunks",
" - Horizontal fireworks can be hacked in to crash a server"
})
public boolean FIREWORKS_LOAD_CHUNKS = false;
}
public static class CLIPBOARD {
@Comment({
"Store the clipboard on disk instead of memory",
" - Will be slightly slower",
" - Uses 2 bytes per block",
})
public boolean USE_DISK = true;
@Comment({
"Compress the clipboard to reduce the size:",
" - TODO: Buffered random access with compression is not implemented on disk yet",
" - 0 = No compression",
" - 1 = Fast compression",
" - 2-17 = Slower compression"
})
public int COMPRESSION_LEVEL = 1;
@Comment("Number of days to keep history on disk before deleting it")
public int DELETE_AFTER_DAYS = 1;
}
public static class LIGHTING {
@Comment({
"If packet sending should be delayed until relight is finished",
})
public boolean DELAY_PACKET_SENDING = true;
public boolean ASYNC = true;
@Comment({
"The relighting mode to use:",
" - 0 = None (Do no relighting)",
" - 1 = Optimal (Relight changed light sources and changed blocks)",
" - 2 = All (Slowly relight every blocks)",
})
public int MODE = 1;
@Comment({"If existing lighting should be removed before relighting"})
public boolean REMOVE_FIRST = false;
}
public void reload(File file) {
load(file);
save(file);
}
public FaweLimit getLimit(FawePlayer player) {
FaweLimit limit;
if (player.hasPermission("fawe.limit.*") || player.hasPermission("fawe.bypass")) {
limit = FaweLimit.MAX.copy();
} else {
limit = new FaweLimit();
}
ArrayList<String> keys = new ArrayList<>(LIMITS.getSections());
if (keys.remove("default")) keys.add("default");
boolean limitFound = false;
for (String key : keys) {
if ((player != null && player.hasPermission("fawe.limit." + key)) || (!limitFound && key.equals("default"))) {
limitFound = true;
LIMITS newLimit = LIMITS.get(key);
limit.MAX_ACTIONS = Math.max(limit.MAX_ACTIONS, newLimit.MAX_ACTIONS != -1 ? newLimit.MAX_ACTIONS : Integer.MAX_VALUE);
limit.MAX_CHANGES = Math.max(limit.MAX_CHANGES, newLimit.MAX_CHANGES != -1 ? newLimit.MAX_CHANGES : Integer.MAX_VALUE);
limit.MAX_BLOCKSTATES = Math.max(limit.MAX_BLOCKSTATES, newLimit.MAX_BLOCKSTATES != -1 ? newLimit.MAX_BLOCKSTATES : Integer.MAX_VALUE);
limit.MAX_CHECKS = Math.max(limit.MAX_CHECKS, newLimit.MAX_CHECKS != -1 ? newLimit.MAX_CHECKS : Integer.MAX_VALUE);
limit.MAX_ENTITIES = Math.max(limit.MAX_ENTITIES, newLimit.MAX_ENTITIES != -1 ? newLimit.MAX_ENTITIES : Integer.MAX_VALUE);
limit.MAX_FAILS = Math.max(limit.MAX_FAILS, newLimit.MAX_FAILS != -1 ? newLimit.MAX_FAILS : Integer.MAX_VALUE);
limit.MAX_ITERATIONS = Math.max(limit.MAX_ITERATIONS, newLimit.MAX_ITERATIONS != -1 ? newLimit.MAX_ITERATIONS : Integer.MAX_VALUE);
limit.MAX_HISTORY = Math.max(limit.MAX_HISTORY, newLimit.MAX_HISTORY_MB != -1 ? newLimit.MAX_HISTORY_MB : Integer.MAX_VALUE);
limit.MAX_EXPRESSION_MS = Math.max(limit.MAX_EXPRESSION_MS, newLimit.MAX_EXPRESSION_MS != -1 ? newLimit.MAX_EXPRESSION_MS : Integer.MAX_VALUE);
limit.INVENTORY_MODE = Math.min(limit.INVENTORY_MODE, newLimit.INVENTORY_MODE);
limit.SPEED_REDUCTION = Math.min(limit.SPEED_REDUCTION, newLimit.SPEED_REDUCTION);
limit.FAST_PLACEMENT |= newLimit.FAST_PLACEMENT;
limit.CONFIRM_LARGE &= newLimit.CONFIRM_LARGE;
if (limit.STRIP_NBT == null) limit.STRIP_NBT = newLimit.STRIP_NBT.isEmpty() ? Collections.emptySet() : new HashSet<>(newLimit.STRIP_NBT);
else if (limit.STRIP_NBT.isEmpty() || newLimit.STRIP_NBT.isEmpty()) {
limit.STRIP_NBT = Collections.emptySet();
} else {
limit.STRIP_NBT = new HashSet<>(limit.STRIP_NBT);
limit.STRIP_NBT.retainAll(newLimit.STRIP_NBT);
if (limit.STRIP_NBT.isEmpty()) limit.STRIP_NBT = Collections.emptySet();
}
}
}
return limit;
}
}

View File

@ -0,0 +1,85 @@
package com.boydti.fawe.configuration;
import java.util.Map;
/**
* Represents a source of configurable options and settings
*/
public interface Configuration extends ConfigurationSection {
/**
* Sets the default value of the given path as provided.
* <p>
* If no source {@link com.boydti.fawe.configuration.Configuration} was provided as a default
* collection, then a new {@link com.boydti.fawe.configuration.MemoryConfiguration} will be created to
* hold the new default value.
* <p>
* If value is null, the value will be removed from the default
* Configuration source.
*
* @param path Path of the value to set.
* @param value Value to set the default to.
* @throws IllegalArgumentException Thrown if path is null.
*/
@Override
void addDefault(final String path, final Object value);
/**
* Sets the default values of the given paths as provided.
* <p>
* If no source {@link com.boydti.fawe.configuration.Configuration} was provided as a default
* collection, then a new {@link com.boydti.fawe.configuration.MemoryConfiguration} will be created to
* hold the new default values.
*
* @param defaults A map of Path->Values to add to defaults.
* @throws IllegalArgumentException Thrown if defaults is null.
*/
void addDefaults(final Map<String, Object> defaults);
/**
* Sets the default values of the given paths as provided.
* <p>
* If no source {@link com.boydti.fawe.configuration.Configuration} was provided as a default
* collection, then a new {@link com.boydti.fawe.configuration.MemoryConfiguration} will be created to
* hold the new default value.
* <p>
* This method will not hold a reference to the specified Configuration,
* nor will it automatically update if that Configuration ever changes. If
* you require this, you should set the default source with {@link
* #setDefaults(com.boydti.fawe.configuration.Configuration)}.
*
* @param defaults A configuration holding a list of defaults to copy.
* @throws IllegalArgumentException Thrown if defaults is null or this.
*/
void addDefaults(final com.boydti.fawe.configuration.Configuration defaults);
/**
* Sets the source of all default values for this {@link com.boydti.fawe.configuration.Configuration}.
* <p>
* If a previous source was set, or previous default values were defined,
* then they will not be copied to the new source.
*
* @param defaults New source of default values for this configuration.
* @throws IllegalArgumentException Thrown if defaults is null or this.
*/
void setDefaults(final com.boydti.fawe.configuration.Configuration defaults);
/**
* Gets the source {@link com.boydti.fawe.configuration.Configuration} for this configuration.
* <p>
* If no configuration source was set, but default values were added, then
* a {@link com.boydti.fawe.configuration.MemoryConfiguration} will be returned. If no source was set
* and no defaults were set, then this method will return null.
*
* @return Configuration source for default values, or null if none exist.
*/
com.boydti.fawe.configuration.Configuration getDefaults();
/**
* Gets the {@link com.boydti.fawe.configuration.ConfigurationOptions} for this {@link com.boydti.fawe.configuration.Configuration}.
* <p>
* All setters through this method are chainable.
*
* @return Options for this configuration
*/
ConfigurationOptions options();
}

View File

@ -0,0 +1,90 @@
package com.boydti.fawe.configuration;
/**
* Various settings for controlling the input and output of a {@link
* com.boydti.fawe.configuration.Configuration}
*/
public class ConfigurationOptions {
private char pathSeparator = '.';
private boolean copyDefaults = false;
private final Configuration configuration;
protected ConfigurationOptions(final Configuration configuration) {
this.configuration = configuration;
}
/**
* Returns the {@link com.boydti.fawe.configuration.Configuration} that this object is responsible for.
*
* @return Parent configuration
*/
public Configuration configuration() {
return configuration;
}
/**
* Gets the char that will be used to separate {@link
* com.boydti.fawe.configuration.ConfigurationSection}s
* <p>
* This value does not affect how the {@link com.boydti.fawe.configuration.Configuration} is stored,
* only in how you access the data. The default value is '.'.
*
* @return Path separator
*/
public char pathSeparator() {
return pathSeparator;
}
/**
* Sets the char that will be used to separate {@link
* com.boydti.fawe.configuration.ConfigurationSection}s
* <p>
* This value does not affect how the {@link com.boydti.fawe.configuration.Configuration} is stored,
* only in how you access the data. The default value is '.'.
*
* @param value Path separator
* @return This object, for chaining
*/
public com.boydti.fawe.configuration.ConfigurationOptions pathSeparator(final char value) {
pathSeparator = value;
return this;
}
/**
* Checks if the {@link com.boydti.fawe.configuration.Configuration} should copy values from its default
* {@link com.boydti.fawe.configuration.Configuration} directly.
* <p>
* If this is true, all values in the default Configuration will be
* directly copied, making it impossible to distinguish between values
* that were set and values that are provided by default. As a result,
* {@link com.boydti.fawe.configuration.ConfigurationSection#contains(String)} will always
* return the same value as {@link
* com.boydti.fawe.configuration.ConfigurationSection#isSet(String)}. The default value is
* false.
*
* @return Whether or not defaults are directly copied
*/
public boolean copyDefaults() {
return copyDefaults;
}
/**
* Sets if the {@link com.boydti.fawe.configuration.Configuration} should copy values from its default
* {@link com.boydti.fawe.configuration.Configuration} directly.
* <p>
* If this is true, all values in the default Configuration will be
* directly copied, making it impossible to distinguish between values
* that were set and values that are provided by default. As a result,
* {@link com.boydti.fawe.configuration.ConfigurationSection#contains(String)} will always
* return the same value as {@link
* com.boydti.fawe.configuration.ConfigurationSection#isSet(String)}. The default value is
* false.
*
* @param value Whether or not defaults are directly copied
* @return This object, for chaining
*/
public com.boydti.fawe.configuration.ConfigurationOptions copyDefaults(final boolean value) {
copyDefaults = value;
return this;
}
}

View File

@ -0,0 +1,645 @@
package com.boydti.fawe.configuration;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents a section of a {@link com.boydti.fawe.configuration.Configuration}
*/
public interface ConfigurationSection {
/**
* Gets a set containing all keys in this section.
* <p>
* If deep is set to true, then this will contain all the keys within any
* child {@link com.boydti.fawe.configuration.ConfigurationSection}s (and their children, etc). These
* will be in a valid path notation for you to use.
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether or not to get a deep list, as opposed to a shallow
* list.
* @return Set of keys contained within this ConfigurationSection.
*/
Set<String> getKeys(boolean deep);
/**
* Gets a Map containing all keys and their values for this section.
* <p>
* If deep is set to true, then this will contain all the keys and values
* within any child {@link com.boydti.fawe.configuration.ConfigurationSection}s (and their children,
* etc). These keys will be in a valid path notation for you to use.
* <p>
* If deep is set to false, then this will contain only the keys and
* values of any direct children, and not their own children.
*
* @param deep Whether or not to get a deep list, as opposed to a shallow
* list.
* @return Map of keys and values of this section.
*/
Map<String, Object> getValues(boolean deep);
/**
* Checks if this {@link com.boydti.fawe.configuration.ConfigurationSection} contains the given path.
* <p>
* If the value for the requested path does not exist but a default value
* has been specified, this will return true.
*
* @param path Path to check for existence.
* @return True if this section contains the requested path, either via
* default or being set.
* @throws IllegalArgumentException Thrown when path is null.
*/
boolean contains(String path);
/**
* Checks if this {@link com.boydti.fawe.configuration.ConfigurationSection} has a value set for the
* given path.
* <p>
* If the value for the requested path does not exist but a default value
* has been specified, this will still return false.
*
* @param path Path to check for existence.
* @return True if this section contains the requested path, regardless of
* having a default.
* @throws IllegalArgumentException Thrown when path is null.
*/
boolean isSet(String path);
/**
* Gets the path of this {@link com.boydti.fawe.configuration.ConfigurationSection} from its root {@link
* com.boydti.fawe.configuration.Configuration}
* <p>
* For any {@link com.boydti.fawe.configuration.Configuration} themselves, this will return an empty
* string.
* <p>
* If the section is no longer contained within its root for any reason,
* such as being replaced with a different value, this may return null.
* <p>
* To retrieve the single name of this section, that is, the final part of
* the path returned by this method, you may use {@link #getName()}.
*
* @return Path of this section relative to its root
*/
String getCurrentPath();
/**
* Gets the name of this individual {@link com.boydti.fawe.configuration.ConfigurationSection}, in the
* path.
* <p>
* This will always be the final part of {@link #getCurrentPath()}, unless
* the section is orphaned.
*
* @return Name of this section
*/
String getName();
/**
* Gets the root {@link com.boydti.fawe.configuration.Configuration} that contains this {@link
* com.boydti.fawe.configuration.ConfigurationSection}
* <p>
* For any {@link com.boydti.fawe.configuration.Configuration} themselves, this will return its own
* object.
* <p>
* If the section is no longer contained within its root for any reason,
* such as being replaced with a different value, this may return null.
*
* @return Root configuration containing this section.
*/
Configuration getRoot();
/**
* Gets the parent {@link com.boydti.fawe.configuration.ConfigurationSection} that directly contains
* this {@link com.boydti.fawe.configuration.ConfigurationSection}.
* <p>
* For any {@link com.boydti.fawe.configuration.Configuration} themselves, this will return null.
* <p>
* If the section is no longer contained within its parent for any reason,
* such as being replaced with a different value, this may return null.
*
* @return Parent section containing this section.
*/
com.boydti.fawe.configuration.ConfigurationSection getParent();
/**
* Gets the requested Object by path.
* <p>
* If the Object does not exist but a default value has been specified,
* this will return the default value. If the Object does not exist and no
* default value was specified, this will return null.
*
* @param path Path of the Object to get.
* @return Requested Object.
*/
Object get(String path);
/**
* Gets the requested Object by path, returning a default value if not
* found.
* <p>
* If the Object does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link com.boydti.fawe.configuration.Configuration}.
*
* @param path Path of the Object to get.
* @param def The default value to return if the path is not found.
* @return Requested Object.
*/
Object get(String path, Object def);
/**
* Sets the specified path to the given value.
* <p>
* If value is null, the entry will be removed. Any existing entry will be
* replaced, regardless of what the new value is.
* <p>
* Some implementations may have limitations on what you may store. See
* their individual javadoc for details. No implementations should allow
* you to store {@link com.boydti.fawe.configuration.Configuration}s or {@link com.boydti.fawe.configuration.ConfigurationSection}s,
* please use {@link #createSection(String)} for that.
*
* @param path Path of the object to set.
* @param value New value to set the path to.
*/
void set(String path, Object value);
/**
* Creates an empty {@link com.boydti.fawe.configuration.ConfigurationSection} at the specified path.
* <p>
* Any value that was previously set at this path will be overwritten. If
* the previous value was itself a {@link com.boydti.fawe.configuration.ConfigurationSection}, it will
* be orphaned.
*
* @param path Path to create the section at.
* @return Newly created section
*/
com.boydti.fawe.configuration.ConfigurationSection createSection(String path);
/**
* Creates a {@link com.boydti.fawe.configuration.ConfigurationSection} at the specified path, with
* specified values.
* <p>
* Any value that was previously set at this path will be overwritten. If
* the previous value was itself a {@link com.boydti.fawe.configuration.ConfigurationSection}, it will
* be orphaned.
*
* @param path Path to create the section at.
* @param map The values to used.
* @return Newly created section
*/
com.boydti.fawe.configuration.ConfigurationSection createSection(String path, Map<?, ?> map);
// Primitives
/**
* Gets the requested String by path.
* <p>
* If the String does not exist but a default value has been specified,
* this will return the default value. If the String does not exist and no
* default value was specified, this will return null.
*
* @param path Path of the String to get.
* @return Requested String.
*/
String getString(String path);
/**
* Gets the requested String by path, returning a default value if not
* found.
* <p>
* If the String does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link com.boydti.fawe.configuration.Configuration}.
*
* @param path Path of the String to get.
* @param def The default value to return if the path is not found or is
* not a String.
* @return Requested String.
*/
String getString(String path, String def);
/**
* Checks if the specified path is a String.
* <p>
* <p> If the path exists but is not a String, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a String and return appropriately.</p>
*
* @param path Path of the String to check.
* @return Whether or not the specified path is a String.
*/
boolean isString(String path);
/**
* Gets the requested int by path.
* <p>
* <p>If the int does not exist but a default value has been specified, this
* will return the default value. If the int does not exist and no default
* value was specified, this will return 0.</p>
*
* @param path Path of the int to get.
* @return Requested int.
*/
int getInt(String path);
/**
* Gets the requested int by path, returning a default value if not found.
* <p>
* <p>If the int does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link com.boydti.fawe.configuration.Configuration}.</p>
*
* @param path Path of the int to get.
* @param def The default value to return if the path is not found or is
* not an int.
* @return Requested int.
*/
int getInt(String path, int def);
/**
* Checks if the specified path is an int.
* <p>
* <p>If the path exists but is not a int, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a int and return appropriately.</p>
*
* @param path Path of the int to check.
* @return Whether or not the specified path is an int.
*/
boolean isInt(String path);
/**
* Gets the requested boolean by path.
* <p>
* If the boolean does not exist but a default value has been specified,
* this will return the default value. If the boolean does not exist and
* no default value was specified, this will return false.
*
* @param path Path of the boolean to get.
* @return Requested boolean.
*/
boolean getBoolean(String path);
/**
* Gets the requested boolean by path, returning a default value if not
* found.
* <p>
* If the boolean does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link com.boydti.fawe.configuration.Configuration}.
*
* @param path Path of the boolean to get.
* @param def The default value to return if the path is not found or is
* not a boolean.
* @return Requested boolean.
*/
boolean getBoolean(String path, boolean def);
/**
* Checks if the specified path is a boolean.
* <p>
* If the path exists but is not a boolean, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a boolean and return appropriately.
*
* @param path Path of the boolean to check.
* @return Whether or not the specified path is a boolean.
*/
boolean isBoolean(String path);
/**
* Gets the requested double by path.
* <p>
* If the double does not exist but a default value has been specified,
* this will return the default value. If the double does not exist and no
* default value was specified, this will return 0.
*
* @param path Path of the double to get.
* @return Requested double.
*/
double getDouble(String path);
/**
* Gets the requested double by path, returning a default value if not
* found.
* <p>
* If the double does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link com.boydti.fawe.configuration.Configuration}.
*
* @param path Path of the double to get.
* @param def The default value to return if the path is not found or is
* not a double.
* @return Requested double.
*/
double getDouble(String path, double def);
/**
* Checks if the specified path is a double.
* <p>
* If the path exists but is not a double, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a double and return appropriately.
*
* @param path Path of the double to check.
* @return Whether or not the specified path is a double.
*/
boolean isDouble(String path);
/**
* Gets the requested long by path.
* <p>
* If the long does not exist but a default value has been specified, this
* will return the default value. If the long does not exist and no
* default value was specified, this will return 0.
*
* @param path Path of the long to get.
* @return Requested long.
*/
long getLong(String path);
/**
* Gets the requested long by path, returning a default value if not
* found.
* <p>
* If the long does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link com.boydti.fawe.configuration.Configuration}.
*
* @param path Path of the long to get.
* @param def The default value to return if the path is not found or is
* not a long.
* @return Requested long.
*/
long getLong(String path, long def);
/**
* Checks if the specified path is a long.
* <p>
* If the path exists but is not a long, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a long and return appropriately.
*
* @param path Path of the long to check.
* @return Whether or not the specified path is a long.
*/
boolean isLong(String path);
// Java
/**
* Gets the requested List by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return null.
*
* @param path Path of the List to get.
* @return Requested List.
*/
List<?> getList(String path);
/**
* Gets the requested List by path, returning a default value if not
* found.
* <p>
* If the List does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link com.boydti.fawe.configuration.Configuration}.
*
* @param path Path of the List to get.
* @param def The default value to return if the path is not found or is
* not a List.
* @return Requested List.
*/
List<?> getList(String path, List<?> def);
/**
* Checks if the specified path is a List.
* <p>
* If the path exists but is not a List, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a List and return appropriately.
*
* @param path Path of the List to check.
* @return Whether or not the specified path is a List.
*/
boolean isList(String path);
/**
* Gets the requested List of String by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a String if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of String.
*/
List<String> getStringList(String path);
/**
* Gets the requested List of Integer by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Integer if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Integer.
*/
List<Integer> getIntegerList(String path);
/**
* Gets the requested List of Boolean by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Boolean if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Boolean.
*/
List<Boolean> getBooleanList(String path);
/**
* Gets the requested List of Double by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Double if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Double.
*/
List<Double> getDoubleList(String path);
/**
* Gets the requested List of Float by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Float if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Float.
*/
List<Float> getFloatList(String path);
/**
* Gets the requested List of Long by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Long if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Long.
*/
List<Long> getLongList(String path);
/**
* Gets the requested List of Byte by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Byte if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Byte.
*/
List<Byte> getByteList(String path);
/**
* Gets the requested List of Character by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Character if
* possible, but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Character.
*/
List<Character> getCharacterList(String path);
/**
* Gets the requested List of Short by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Short if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Short.
*/
List<Short> getShortList(String path);
/**
* Gets the requested List of Maps by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Map if possible, but
* may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Maps.
*/
List<Map<?, ?>> getMapList(String path);
/**
* Gets the requested ConfigurationSection by path.
* <p>
* If the ConfigurationSection does not exist but a default value has been
* specified, this will return the default value. If the
* ConfigurationSection does not exist and no default value was specified,
* this will return null.
*
* @param path Path of the ConfigurationSection to get.
* @return Requested ConfigurationSection.
*/
com.boydti.fawe.configuration.ConfigurationSection getConfigurationSection(String path);
/**
* Checks if the specified path is a ConfigurationSection.
* <p>
* If the path exists but is not a ConfigurationSection, this will return
* false. If the path does not exist, this will return false. If the path
* does not exist but a default value has been specified, this will check
* if that default value is a ConfigurationSection and return
* appropriately.
*
* @param path Path of the ConfigurationSection to check.
* @return Whether or not the specified path is a ConfigurationSection.
*/
boolean isConfigurationSection(String path);
/**
* Gets the equivalent {@link com.boydti.fawe.configuration.ConfigurationSection} from the default
* {@link com.boydti.fawe.configuration.Configuration} defined in {@link #getRoot()}.
* <p>
* If the root contains no defaults, or the defaults doesn't contain a
* value for this path, or the value at this path is not a {@link
* com.boydti.fawe.configuration.ConfigurationSection} then this will return null.
*
* @return Equivalent section in root configuration
*/
com.boydti.fawe.configuration.ConfigurationSection getDefaultSection();
/**
* Sets the default value in the root at the given path as provided.
* <p>
* If no source {@link com.boydti.fawe.configuration.Configuration} was provided as a default
* collection, then a new {@link com.boydti.fawe.configuration.MemoryConfiguration} will be created to
* hold the new default value.
* <p>
* If value is null, the value will be removed from the default
* Configuration source.
* <p>
* If the value as returned by {@link #getDefaultSection()} is null, then
* this will create a new section at the path, replacing anything that may
* have existed there previously.
*
* @param path Path of the value to set.
* @param value Value to set the default to.
* @throws IllegalArgumentException Thrown if path is null.
*/
void addDefault(String path, Object value);
}

View File

@ -0,0 +1,46 @@
package com.boydti.fawe.configuration;
/**
* Exception thrown when attempting to load an invalid {@link com.boydti.fawe.configuration.Configuration}
*/
@SuppressWarnings("serial")
public class InvalidConfigurationException extends Exception {
/**
* Creates a new instance of InvalidConfigurationException without a
* message or cause.
*/
public InvalidConfigurationException() {
}
/**
* Constructs an instance of InvalidConfigurationException with the
* specified message.
*
* @param msg The details of the exception.
*/
public InvalidConfigurationException(final String msg) {
super(msg);
}
/**
* Constructs an instance of InvalidConfigurationException with the
* specified cause.
*
* @param cause The cause of the exception.
*/
public InvalidConfigurationException(final Throwable cause) {
super(cause);
}
/**
* Constructs an instance of InvalidConfigurationException with the
* specified message and cause.
*
* @param cause The cause of the exception.
* @param msg The details of the exception.
*/
public InvalidConfigurationException(final String msg, final Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,90 @@
package com.boydti.fawe.configuration;
import java.util.Map;
/**
* This is a {@link com.boydti.fawe.configuration.Configuration} implementation that does not save or load
* from any source, and stores all values in memory only.
* This is useful for temporary Configurations for providing defaults.
*/
public class MemoryConfiguration extends MemorySection implements Configuration {
protected Configuration defaults;
protected MemoryConfigurationOptions options;
/**
* Creates an empty {@link com.boydti.fawe.configuration.MemoryConfiguration} with no default values.
*/
public MemoryConfiguration() {
}
/**
* Creates an empty {@link com.boydti.fawe.configuration.MemoryConfiguration} using the specified {@link
* com.boydti.fawe.configuration.Configuration} as a source for all default values.
*
* @param defaults Default value provider
* @throws IllegalArgumentException Thrown if defaults is null
*/
public MemoryConfiguration(final Configuration defaults) {
this.defaults = defaults;
}
@Override
public void addDefault(final String path, final Object value) {
if (path == null) {
throw new NullPointerException("Path may not be null");
}
if (defaults == null) {
defaults = new com.boydti.fawe.configuration.MemoryConfiguration();
}
defaults.set(path, value);
}
@Override
public void addDefaults(final Map<String, Object> defaults) {
if (defaults == null) {
throw new NullPointerException("Defaults may not be null");
}
for (final Map.Entry<String, Object> entry : defaults.entrySet()) {
addDefault(entry.getKey(), entry.getValue());
}
}
@Override
public void addDefaults(final Configuration defaults) {
if (defaults == null) {
throw new NullPointerException("Defaults may not be null");
}
addDefaults(defaults.getValues(true));
}
@Override
public void setDefaults(final Configuration defaults) {
if (defaults == null) {
throw new NullPointerException("Defaults may not be null");
}
this.defaults = defaults;
}
@Override
public Configuration getDefaults() {
return defaults;
}
@Override
public ConfigurationSection getParent() {
return null;
}
@Override
public MemoryConfigurationOptions options() {
if (options == null) {
options = new MemoryConfigurationOptions(this);
}
return options;
}
}

View File

@ -0,0 +1,28 @@
package com.boydti.fawe.configuration;
/**
* Various settings for controlling the input and output of a {@link
* com.boydti.fawe.configuration.MemoryConfiguration}
*/
public class MemoryConfigurationOptions extends ConfigurationOptions {
protected MemoryConfigurationOptions(final MemoryConfiguration configuration) {
super(configuration);
}
@Override
public MemoryConfiguration configuration() {
return (MemoryConfiguration) super.configuration();
}
@Override
public com.boydti.fawe.configuration.MemoryConfigurationOptions copyDefaults(final boolean value) {
super.copyDefaults(value);
return this;
}
@Override
public com.boydti.fawe.configuration.MemoryConfigurationOptions pathSeparator(final char value) {
super.pathSeparator(value);
return this;
}
}

View File

@ -0,0 +1,846 @@
package com.boydti.fawe.configuration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A type of {@link com.boydti.fawe.configuration.ConfigurationSection} that is stored in memory.
*/
public class MemorySection implements com.boydti.fawe.configuration.ConfigurationSection {
protected final Map<String, Object> map = new LinkedHashMap<>();
private final Configuration root;
private final com.boydti.fawe.configuration.ConfigurationSection parent;
private final String path;
private final String fullPath;
/**
* Creates an empty MemorySection for use as a root {@link com.boydti.fawe.configuration.Configuration}
* section.
* <p>
* Note that calling this without being yourself a {@link com.boydti.fawe.configuration.Configuration}
* will throw an exception!
*
* @throws IllegalStateException Thrown if this is not a {@link
* com.boydti.fawe.configuration.Configuration} root.
*/
protected MemorySection() {
if (!(this instanceof Configuration)) {
throw new IllegalStateException("Cannot construct a root MemorySection when not a Configuration");
}
this.path = "";
this.fullPath = "";
this.parent = null;
this.root = (Configuration) this;
}
/**
* Creates an empty MemorySection with the specified parent and path.
*
* @param parent Parent section that contains this own section.
* @param path Path that you may access this section from via the root
* {@link com.boydti.fawe.configuration.Configuration}.
* @throws IllegalArgumentException Thrown is parent or path is null, or
* if parent contains no root Configuration.
*/
protected MemorySection(com.boydti.fawe.configuration.ConfigurationSection parent, String path) {
if (parent == null) {
throw new NullPointerException("Parent may not be null");
}
if (path == null) {
throw new NullPointerException("Path may not be null");
}
this.path = path;
this.parent = parent;
this.root = parent.getRoot();
if (this.root == null) {
throw new NullPointerException("Path may not be orphaned");
}
this.fullPath = createPath(parent, path);
}
public static double toDouble(Object obj, double def) {
if (obj instanceof Number) {
return ((Number) obj).doubleValue();
}
if (obj instanceof String) {
try {
return Double.parseDouble((String) obj);
} catch (NumberFormatException ignored) {
}
} else if (obj instanceof List) {
List<?> val = (List<?>) obj;
if (!val.isEmpty()) {
return toDouble(val.get(0), def);
}
}
return def;
}
public static int toInt(Object obj, int def) {
if (obj instanceof Number) {
return ((Number) obj).intValue();
}
if (obj instanceof String) {
try {
return Integer.parseInt((String) obj);
} catch (NumberFormatException ignored) {
}
} else if (obj instanceof List) {
List<?> val = (List<?>) obj;
if (!val.isEmpty()) {
return toInt(val.get(0), def);
}
}
return def;
}
public static long toLong(Object obj, long def) {
if (obj instanceof Number) {
return ((Number) obj).longValue();
}
if (obj instanceof String) {
try {
return Long.parseLong((String) obj);
} catch (NumberFormatException ignored) {
}
} else if (obj instanceof List) {
List<?> val = (List<?>) obj;
if (!val.isEmpty()) {
return toLong(val.get(0), def);
}
}
return def;
}
/**
* Creates a full path to the given {@link com.boydti.fawe.configuration.ConfigurationSection} from its
* root {@link com.boydti.fawe.configuration.Configuration}.
* <p>
* You may use this method for any given {@link com.boydti.fawe.configuration.ConfigurationSection}, not
* only {@link com.boydti.fawe.configuration.MemorySection}.
*
* @param section Section to create a path for.
* @param key Name of the specified section.
* @return Full path of the section from its root.
*/
public static String createPath(com.boydti.fawe.configuration.ConfigurationSection section, String key) {
return createPath(section, key, (section == null) ? null : section.getRoot());
}
/**
* Creates a relative path to the given {@link com.boydti.fawe.configuration.ConfigurationSection} from
* the given relative section.
* <p>
* You may use this method for any given {@link com.boydti.fawe.configuration.ConfigurationSection}, not
* only {@link com.boydti.fawe.configuration.MemorySection}.
*
* @param section Section to create a path for.
* @param key Name of the specified section.
* @param relativeTo Section to create the path relative to.
* @return Full path of the section from its root.
*/
public static String createPath(com.boydti.fawe.configuration.ConfigurationSection section, String key, com.boydti.fawe.configuration.ConfigurationSection relativeTo) {
if (section == null) {
throw new NullPointerException("Cannot create path without a section");
}
Configuration root = section.getRoot();
if (root == null) {
throw new IllegalStateException("Cannot create path without a root");
}
char separator = root.options().pathSeparator();
StringBuilder builder = new StringBuilder();
for (com.boydti.fawe.configuration.ConfigurationSection parent = section; (parent != null) && (parent != relativeTo); parent = parent.getParent()) {
if (builder.length() > 0) {
builder.insert(0, separator);
}
builder.insert(0, parent.getName());
}
if ((key != null) && !key.isEmpty()) {
if (builder.length() > 0) {
builder.append(separator);
}
builder.append(key);
}
return builder.toString();
}
@Override
public Set<String> getKeys(boolean deep) {
Set<String> result = new LinkedHashSet<>();
Configuration root = getRoot();
if ((root != null) && root.options().copyDefaults()) {
com.boydti.fawe.configuration.ConfigurationSection defaults = getDefaultSection();
if (defaults != null) {
result.addAll(defaults.getKeys(deep));
}
}
mapChildrenKeys(result, this, deep);
return result;
}
@Override
public Map<String, Object> getValues(boolean deep) {
Map<String, Object> result = new LinkedHashMap<>();
Configuration root = getRoot();
if ((root != null) && root.options().copyDefaults()) {
com.boydti.fawe.configuration.ConfigurationSection defaults = getDefaultSection();
if (defaults != null) {
result.putAll(defaults.getValues(deep));
}
}
mapChildrenValues(result, this, deep);
return result;
}
@Override
public boolean contains(String path) {
return get(path) != null;
}
@Override
public boolean isSet(String path) {
Configuration root = getRoot();
if (root == null) {
return false;
}
if (root.options().copyDefaults()) {
return contains(path);
}
return get(path, null) != null;
}
@Override
public String getCurrentPath() {
return this.fullPath;
}
@Override
public String getName() {
return this.path;
}
@Override
public Configuration getRoot() {
return this.root;
}
@Override
public com.boydti.fawe.configuration.ConfigurationSection getParent() {
return this.parent;
}
@Override
public void addDefault(String path, Object value) {
if (path == null) {
throw new NullPointerException("Path cannot be null");
}
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot add default without root");
}
if (root == this) {
throw new UnsupportedOperationException("Unsupported addDefault(String, Object) implementation");
}
root.addDefault(createPath(this, path), value);
}
@Override
public com.boydti.fawe.configuration.ConfigurationSection getDefaultSection() {
Configuration root = getRoot();
Configuration defaults = root == null ? null : root.getDefaults();
if (defaults != null) {
if (defaults.isConfigurationSection(getCurrentPath())) {
return defaults.getConfigurationSection(getCurrentPath());
}
}
return null;
}
@Override
public void set(String path, Object value) {
if (path == null) {
throw new NullPointerException("Cannot set to an empty path");
}
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot use section without a root");
}
char separator = root.options().pathSeparator();
// i1 is the leading (higher) index
// i2 is the trailing (lower) index
int i1 = -1, i2;
com.boydti.fawe.configuration.ConfigurationSection section = this;
while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) {
String node = path.substring(i2, i1);
com.boydti.fawe.configuration.ConfigurationSection subSection = section.getConfigurationSection(node);
if (subSection == null) {
section = section.createSection(node);
} else {
section = subSection;
}
}
String key = path.substring(i2);
if (section == this) {
if (value == null) {
this.map.remove(key);
} else {
this.map.put(key, value);
}
} else {
section.set(key, value);
}
}
@Override
public Object get(String path) {
return get(path, getDefault(path));
}
@Override
public Object get(String path, Object def) {
if (path == null) {
throw new NullPointerException("Path cannot be null");
}
if (path.isEmpty()) {
return this;
}
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot access section without a root");
}
char separator = root.options().pathSeparator();
// i1 is the leading (higher) index
// i2 is the trailing (lower) index
int i1 = -1;
int i2;
com.boydti.fawe.configuration.ConfigurationSection section = this;
while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) {
section = section.getConfigurationSection(path.substring(i2, i1));
if (section == null) {
return def;
}
}
String key = path.substring(i2);
if (section == this) {
Object result = this.map.get(key);
if (result == null) {
return def;
} else {
return result;
}
}
return section.get(key, def);
}
@Override
public com.boydti.fawe.configuration.ConfigurationSection createSection(String path) {
if (path == null) {
throw new NullPointerException("Cannot create section at empty path");
}
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot create section without a root");
}
char separator = root.options().pathSeparator();
// i1 is the leading (higher) index
// i2 is the trailing (lower) index
int i1 = -1, i2;
com.boydti.fawe.configuration.ConfigurationSection section = this;
while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) {
String node = path.substring(i2, i1);
com.boydti.fawe.configuration.ConfigurationSection subSection = section.getConfigurationSection(node);
if (subSection == null) {
section = section.createSection(node);
} else {
section = subSection;
}
}
String key = path.substring(i2);
if (section == this) {
com.boydti.fawe.configuration.ConfigurationSection result = new com.boydti.fawe.configuration.MemorySection(this, key);
this.map.put(key, result);
return result;
}
return section.createSection(key);
}
@Override
public com.boydti.fawe.configuration.ConfigurationSection createSection(String path, Map<?, ?> map) {
com.boydti.fawe.configuration.ConfigurationSection section = createSection(path);
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
section.createSection(entry.getKey().toString(), (Map<?, ?>) entry.getValue());
} else {
section.set(entry.getKey().toString(), entry.getValue());
}
}
return section;
}
// Primitives
@Override
public String getString(String path) {
Object def = getDefault(path);
return getString(path, def != null ? def.toString() : null);
}
@Override
public String getString(String path, String def) {
Object val = get(path, def);
if (val != null) {
return val.toString();
} else {
return def;
}
}
@Override
public boolean isString(String path) {
Object val = get(path);
return val instanceof String;
}
@Override
public int getInt(String path) {
Object def = getDefault(path);
return getInt(path, toInt(def, 0));
}
@Override
public int getInt(String path, int def) {
Object val = get(path, def);
return toInt(val, def);
}
@Override
public boolean isInt(String path) {
Object val = get(path);
return val instanceof Integer;
}
@Override
public boolean getBoolean(String path) {
Object def = getDefault(path);
if (def instanceof Boolean) {
return getBoolean(path, (Boolean) def);
} else {
return getBoolean(path, false);
}
}
@Override
public boolean getBoolean(String path, boolean def) {
Object val = get(path, def);
if (val instanceof Boolean) {
return (Boolean) val;
} else {
return def;
}
}
@Override
public boolean isBoolean(String path) {
Object val = get(path);
return val instanceof Boolean;
}
@Override
public double getDouble(String path) {
Object def = getDefault(path);
return getDouble(path, toDouble(def, 0));
}
@Override
public double getDouble(String path, double def) {
Object val = get(path, def);
return toDouble(val, def);
}
@Override
public boolean isDouble(String path) {
Object val = get(path);
return val instanceof Double;
}
@Override
public long getLong(String path) {
Object def = getDefault(path);
return getLong(path, toLong(def, 0));
}
@Override
public long getLong(String path, long def) {
Object val = get(path, def);
return toLong(val, def);
}
@Override
public boolean isLong(String path) {
Object val = get(path);
return val instanceof Long;
}
// Java
@Override
public List<?> getList(String path) {
Object def = getDefault(path);
return getList(path, def instanceof List ? (List<?>) def : null);
}
@Override
public List<?> getList(String path, List<?> def) {
Object val = get(path, def);
return (List<?>) ((val instanceof List) ? val : def);
}
@Override
public boolean isList(String path) {
Object val = get(path);
return val instanceof List;
}
@Override
public List<String> getStringList(String path) {
final List<?> list = getList(path);
if (list == null) {
return new ArrayList<>(0);
}
final List<String> result = new ArrayList<>();
for (final Object object : list) {
if ((object instanceof String) || (isPrimitiveWrapper(object))) {
result.add(String.valueOf(object));
}
}
return result;
}
@Override
public List<Integer> getIntegerList(String path) {
List<?> list = getList(path);
List<Integer> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Integer) {
result.add((Integer) object);
} else if (object instanceof String) {
try {
result.add(Integer.valueOf((String) object));
} catch (NumberFormatException ignored) {
}
} else if (object instanceof Character) {
result.add((int) (Character) object);
} else if (object instanceof Number) {
result.add(((Number) object).intValue());
}
}
return result;
}
@Override
public List<Boolean> getBooleanList(String path) {
List<?> list = getList(path);
List<Boolean> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Boolean) {
result.add((Boolean) object);
} else if (object instanceof String) {
if (Boolean.TRUE.toString().equals(object)) {
result.add(true);
} else if (Boolean.FALSE.toString().equals(object)) {
result.add(false);
}
}
}
return result;
}
@Override
public List<Double> getDoubleList(String path) {
List<?> list = getList(path);
List<Double> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Double) {
result.add((Double) object);
} else if (object instanceof String) {
try {
result.add(Double.valueOf((String) object));
} catch (NumberFormatException ignored) {
}
} else if (object instanceof Character) {
result.add((double) (Character) object);
} else if (object instanceof Number) {
result.add(((Number) object).doubleValue());
}
}
return result;
}
@Override
public List<Float> getFloatList(String path) {
List<?> list = getList(path);
List<Float> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Float) {
result.add((Float) object);
} else if (object instanceof String) {
try {
result.add(Float.valueOf((String) object));
} catch (NumberFormatException ignored) {
}
} else if (object instanceof Character) {
result.add((float) (Character) object);
} else if (object instanceof Number) {
result.add(((Number) object).floatValue());
}
}
return result;
}
@Override
public List<Long> getLongList(String path) {
List<?> list = getList(path);
List<Long> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Long) {
result.add((Long) object);
} else if (object instanceof String) {
try {
result.add(Long.valueOf((String) object));
} catch (NumberFormatException ignored) {
}
} else if (object instanceof Character) {
result.add((long) (Character) object);
} else if (object instanceof Number) {
result.add(((Number) object).longValue());
}
}
return result;
}
@Override
public List<Byte> getByteList(String path) {
List<?> list = getList(path);
List<Byte> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Byte) {
result.add((Byte) object);
} else if (object instanceof String) {
try {
result.add(Byte.valueOf((String) object));
} catch (NumberFormatException ignored) {
}
} else if (object instanceof Character) {
result.add((byte) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).byteValue());
}
}
return result;
}
@Override
public List<Character> getCharacterList(String path) {
List<?> list = getList(path);
List<Character> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Character) {
result.add((Character) object);
} else if (object instanceof String) {
String str = (String) object;
if (str.length() == 1) {
result.add(str.charAt(0));
}
} else if (object instanceof Number) {
result.add((char) ((Number) object).intValue());
}
}
return result;
}
@Override
public List<Short> getShortList(String path) {
List<?> list = getList(path);
List<Short> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Short) {
result.add((Short) object);
} else if (object instanceof String) {
try {
result.add(Short.valueOf((String) object));
} catch (NumberFormatException ignored) {
}
} else if (object instanceof Character) {
result.add((short) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).shortValue());
}
}
return result;
}
@Override
public List<Map<?, ?>> getMapList(String path) {
List<?> list = getList(path);
List<Map<?, ?>> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Map) {
result.add((Map<?, ?>) object);
}
}
return result;
}
@Override
public com.boydti.fawe.configuration.ConfigurationSection getConfigurationSection(String path) {
Object val = get(path, null);
if (val != null) {
return (val instanceof com.boydti.fawe.configuration.ConfigurationSection) ? (com.boydti.fawe.configuration.ConfigurationSection) val : null;
}
val = get(path, getDefault(path));
return (val instanceof com.boydti.fawe.configuration.ConfigurationSection) ? createSection(path) : null;
}
@Override
public boolean isConfigurationSection(String path) {
Object val = get(path);
return val instanceof com.boydti.fawe.configuration.ConfigurationSection;
}
protected boolean isPrimitiveWrapper(Object input) {
return (input instanceof Integer)
|| (input instanceof Boolean)
|| (input instanceof Character)
|| (input instanceof Byte)
|| (input instanceof Short)
|| (input instanceof Double)
|| (input instanceof Long)
|| (input instanceof Float);
}
protected Object getDefault(String path) {
if (path == null) {
throw new NullPointerException("Path may not be null");
}
Configuration root = getRoot();
Configuration defaults = root == null ? null : root.getDefaults();
return (defaults == null) ? null : defaults.get(createPath(this, path));
}
protected void mapChildrenKeys(Set<String> output, com.boydti.fawe.configuration.ConfigurationSection section, boolean deep) {
if (section instanceof com.boydti.fawe.configuration.MemorySection) {
com.boydti.fawe.configuration.MemorySection sec = (com.boydti.fawe.configuration.MemorySection) section;
for (Map.Entry<String, Object> entry : sec.map.entrySet()) {
output.add(createPath(section, entry.getKey(), this));
if (deep && (entry.getValue() instanceof com.boydti.fawe.configuration.ConfigurationSection)) {
com.boydti.fawe.configuration.ConfigurationSection subsection = (com.boydti.fawe.configuration.ConfigurationSection) entry.getValue();
mapChildrenKeys(output, subsection, deep);
}
}
} else {
Set<String> keys = section.getKeys(deep);
for (String key : keys) {
output.add(createPath(section, key, this));
}
}
}
protected void mapChildrenValues(Map<String, Object> output, com.boydti.fawe.configuration.ConfigurationSection section, boolean deep) {
if (section instanceof com.boydti.fawe.configuration.MemorySection) {
com.boydti.fawe.configuration.MemorySection sec = (com.boydti.fawe.configuration.MemorySection) section;
for (Map.Entry<String, Object> entry : sec.map.entrySet()) {
output.put(createPath(section, entry.getKey(), this), entry.getValue());
if (entry.getValue() instanceof com.boydti.fawe.configuration.ConfigurationSection) {
if (deep) {
mapChildrenValues(output, (ConfigurationSection) entry.getValue(), deep);
}
}
}
} else {
Map<String, Object> values = section.getValues(deep);
for (Map.Entry<String, Object> entry : values.entrySet()) {
output.put(createPath(section, entry.getKey(), this), entry.getValue());
}
}
}
@Override
public String toString() {
Configuration root = getRoot();
return getClass().getSimpleName() + "[path='" + getCurrentPath() + "', root='" + (root == null ? null : root.getClass().getSimpleName()) +
"']";
}
}

View File

@ -0,0 +1,147 @@
/**
* Copyright (c) 2008, http://www.snakeyaml.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.yaml.snakeyaml;
import java.util.HashMap;
import java.util.Map;
import org.yaml.snakeyaml.nodes.Tag;
/**
* Provides additional runtime information necessary to create a custom Java
* instance.
*/
public final class TypeDescription {
private final Class<? extends Object> type;
private Tag tag;
private Map<String, Class<? extends Object>> listProperties;
private Map<String, Class<? extends Object>> keyProperties;
private Map<String, Class<? extends Object>> valueProperties;
public TypeDescription(Class<? extends Object> clazz, Tag tag) {
this.type = clazz;
this.tag = tag;
listProperties = new HashMap<String, Class<? extends Object>>();
keyProperties = new HashMap<String, Class<? extends Object>>();
valueProperties = new HashMap<String, Class<? extends Object>>();
}
public TypeDescription(Class<? extends Object> clazz, String tag) {
this(clazz, new Tag(tag));
}
public TypeDescription(Class<? extends Object> clazz) {
this(clazz, (Tag) null);
}
/**
* Get tag which shall be used to load or dump the type (class).
*
* @return tag to be used. It may be a tag for Language-Independent Types
* (http://www.yaml.org/type/)
*/
public Tag getTag() {
return tag;
}
/**
* Set tag to be used to load or dump the type (class).
*
* @param tag
* local or global tag
*/
public void setTag(Tag tag) {
this.tag = tag;
}
public void setTag(String tag) {
setTag(new Tag(tag));
}
/**
* Get represented type (class)
*
* @return type (class) to be described.
*/
public Class<? extends Object> getType() {
return type;
}
/**
* Specify that the property is a type-safe <code>List</code>.
*
* @param property
* name of the JavaBean property
* @param type
* class of List values
*/
public void putListPropertyType(String property, Class<? extends Object> type) {
listProperties.put(property, type);
}
/**
* Get class of List values for provided JavaBean property.
*
* @param property
* property name
* @return class of List values
*/
public Class<? extends Object> getListPropertyType(String property) {
return listProperties.get(property);
}
/**
* Specify that the property is a type-safe <code>Map</code>.
*
* @param property
* property name of this JavaBean
* @param key
* class of keys in Map
* @param value
* class of values in Map
*/
public void putMapPropertyType(String property, Class<? extends Object> key,
Class<? extends Object> value) {
keyProperties.put(property, key);
valueProperties.put(property, value);
}
/**
* Get keys type info for this JavaBean
*
* @param property
* property name of this JavaBean
* @return class of keys in the Map
*/
public Class<? extends Object> getMapKeyType(String property) {
return keyProperties.get(property);
}
/**
* Get values type info for this JavaBean
*
* @param property
* property name of this JavaBean
* @return class of values in the Map
*/
public Class<? extends Object> getMapValueType(String property) {
return valueProperties.get(property);
}
@Override
public String toString() {
return "TypeDescription for " + getType() + " (tag='" + getTag() + "')";
}
}

View File

@ -0,0 +1,660 @@
/**
* Copyright (c) 2008, http://www.snakeyaml.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.yaml.snakeyaml;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.composer.Composer;
import org.yaml.snakeyaml.constructor.BaseConstructor;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.emitter.Emitable;
import org.yaml.snakeyaml.emitter.Emitter;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.parser.Parser;
import org.yaml.snakeyaml.parser.ParserImpl;
import org.yaml.snakeyaml.reader.StreamReader;
import org.yaml.snakeyaml.reader.UnicodeReader;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.resolver.Resolver;
import org.yaml.snakeyaml.serializer.Serializer;
/**
* Public YAML interface. Each Thread must have its own instance.
*/
public class Yaml {
protected final Resolver resolver;
private String name;
protected BaseConstructor constructor;
protected Representer representer;
protected DumperOptions dumperOptions;
/**
* Create Yaml instance. It is safe to create a few instances and use them
* in different Threads.
*/
public Yaml() {
this(new Constructor(), new Representer(), new DumperOptions(), new Resolver());
}
/**
* Create Yaml instance.
*
* @param dumperOptions
* DumperOptions to configure outgoing objects
*/
public Yaml(DumperOptions dumperOptions) {
this(new Constructor(), new Representer(), dumperOptions);
}
/**
* Create Yaml instance. It is safe to create a few instances and use them
* in different Threads.
*
* @param representer
* Representer to emit outgoing objects
*/
public Yaml(Representer representer) {
this(new Constructor(), representer);
}
/**
* Create Yaml instance. It is safe to create a few instances and use them
* in different Threads.
*
* @param constructor
* BaseConstructor to construct incoming documents
*/
public Yaml(BaseConstructor constructor) {
this(constructor, new Representer());
}
/**
* Create Yaml instance. It is safe to create a few instances and use them
* in different Threads.
*
* @param constructor
* BaseConstructor to construct incoming documents
* @param representer
* Representer to emit outgoing objects
*/
public Yaml(BaseConstructor constructor, Representer representer) {
this(constructor, representer, new DumperOptions());
}
/**
* Create Yaml instance. It is safe to create a few instances and use them
* in different Threads.
*
* @param representer
* Representer to emit outgoing objects
* @param dumperOptions
* DumperOptions to configure outgoing objects
*/
public Yaml(Representer representer, DumperOptions dumperOptions) {
this(new Constructor(), representer, dumperOptions, new Resolver());
}
/**
* Create Yaml instance. It is safe to create a few instances and use them
* in different Threads.
*
* @param constructor
* BaseConstructor to construct incoming documents
* @param representer
* Representer to emit outgoing objects
* @param dumperOptions
* DumperOptions to configure outgoing objects
*/
public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions) {
this(constructor, representer, dumperOptions, new Resolver());
}
/**
* Create Yaml instance. It is safe to create a few instances and use them
* in different Threads.
*
* @param constructor
* BaseConstructor to construct incoming documents
* @param representer
* Representer to emit outgoing objects
* @param dumperOptions
* DumperOptions to configure outgoing objects
* @param resolver
* Resolver to detect implicit type
*/
public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions,
Resolver resolver) {
if (!constructor.isExplicitPropertyUtils()) {
constructor.setPropertyUtils(representer.getPropertyUtils());
} else if (!representer.isExplicitPropertyUtils()) {
representer.setPropertyUtils(constructor.getPropertyUtils());
}
this.constructor = constructor;
representer.setDefaultFlowStyle(dumperOptions.getDefaultFlowStyle());
representer.setDefaultScalarStyle(dumperOptions.getDefaultScalarStyle());
representer.getPropertyUtils().setAllowReadOnlyProperties(
dumperOptions.isAllowReadOnlyProperties());
representer.setTimeZone(dumperOptions.getTimeZone());
this.representer = representer;
this.dumperOptions = dumperOptions;
this.resolver = resolver;
this.name = "Yaml:" + System.identityHashCode(this);
}
/**
* Serialize a Java object into a YAML String.
*
* @param data
* Java object to be Serialized to YAML
* @return YAML String
*/
public String dump(Object data) {
List<Object> list = new ArrayList<Object>(1);
list.add(data);
return dumpAll(list.iterator());
}
/**
* Produce the corresponding representation tree for a given Object.
*
* @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing
* Overview</a>
* @param data
* instance to build the representation tree for
* @return representation tree
*/
public Node represent(Object data) {
return representer.represent(data);
}
/**
* Serialize a sequence of Java objects into a YAML String.
*
* @param data
* Iterator with Objects
* @return YAML String with all the objects in proper sequence
*/
public String dumpAll(Iterator<? extends Object> data) {
StringWriter buffer = new StringWriter();
dumpAll(data, buffer, null);
return buffer.toString();
}
/**
* Serialize a Java object into a YAML stream.
*
* @param data
* Java object to be serialized to YAML
* @param output
* stream to write to
*/
public void dump(Object data, Writer output) {
List<Object> list = new ArrayList<Object>(1);
list.add(data);
dumpAll(list.iterator(), output, null);
}
/**
* Serialize a sequence of Java objects into a YAML stream.
*
* @param data
* Iterator with Objects
* @param output
* stream to write to
*/
public void dumpAll(Iterator<? extends Object> data, Writer output) {
dumpAll(data, output, null);
}
private void dumpAll(Iterator<? extends Object> data, Writer output, Tag rootTag) {
Serializer serializer = new Serializer(new Emitter(output, dumperOptions), resolver,
dumperOptions, rootTag);
try {
serializer.open();
while (data.hasNext()) {
Node node = representer.represent(data.next());
serializer.serialize(node);
}
serializer.close();
} catch (IOException e) {
throw new YAMLException(e);
}
}
/**
* <p>
* Serialize a Java object into a YAML string. Override the default root tag
* with <code>rootTag</code>.
* </p>
*
* <p>
* This method is similar to <code>Yaml.dump(data)</code> except that the
* root tag for the whole document is replaced with the given tag. This has
* two main uses.
* </p>
*
* <p>
* First, if the root tag is replaced with a standard YAML tag, such as
* <code>Tag.MAP</code>, then the object will be dumped as a map. The root
* tag will appear as <code>!!map</code>, or blank (implicit !!map).
* </p>
*
* <p>
* Second, if the root tag is replaced by a different custom tag, then the
* document appears to be a different type when loaded. For example, if an
* instance of MyClass is dumped with the tag !!YourClass, then it will be
* handled as an instance of YourClass when loaded.
* </p>
*
* @param data
* Java object to be serialized to YAML
* @param rootTag
* the tag for the whole YAML document. The tag should be Tag.MAP
* for a JavaBean to make the tag disappear (to use implicit tag
* !!map). If <code>null</code> is provided then the standard tag
* with the full class name is used.
* @param flowStyle
* flow style for the whole document. See Chapter 10. Collection
* Styles http://yaml.org/spec/1.1/#id930798. If
* <code>null</code> is provided then the flow style from
* DumperOptions is used.
*
* @return YAML String
*/
public String dumpAs(Object data, Tag rootTag, FlowStyle flowStyle) {
FlowStyle oldStyle = representer.getDefaultFlowStyle();
if (flowStyle != null) {
representer.setDefaultFlowStyle(flowStyle);
}
List<Object> list = new ArrayList<Object>(1);
list.add(data);
StringWriter buffer = new StringWriter();
dumpAll(list.iterator(), buffer, rootTag);
representer.setDefaultFlowStyle(oldStyle);
return buffer.toString();
}
/**
* <p>
* Serialize a Java object into a YAML string. Override the default root tag
* with <code>Tag.MAP</code>.
* </p>
* <p>
* This method is similar to <code>Yaml.dump(data)</code> except that the
* root tag for the whole document is replaced with <code>Tag.MAP</code> tag
* (implicit !!map).
* </p>
* <p>
* Block Mapping is used as the collection style. See 10.2.2. Block Mappings
* (http://yaml.org/spec/1.1/#id934537)
* </p>
*
* @param data
* Java object to be serialized to YAML
* @return YAML String
*/
public String dumpAsMap(Object data) {
return dumpAs(data, Tag.MAP, FlowStyle.BLOCK);
}
/**
* Serialize the representation tree into Events.
*
* @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
* @param data
* representation tree
* @return Event list
*/
public List<Event> serialize(Node data) {
SilentEmitter emitter = new SilentEmitter();
Serializer serializer = new Serializer(emitter, resolver, dumperOptions, null);
try {
serializer.open();
serializer.serialize(data);
serializer.close();
} catch (IOException e) {
throw new YAMLException(e);
}
return emitter.getEvents();
}
private static class SilentEmitter implements Emitable {
private List<Event> events = new ArrayList<Event>(100);
public List<Event> getEvents() {
return events;
}
public void emit(Event event) throws IOException {
events.add(event);
}
}
/**
* Parse the only YAML document in a String and produce the corresponding
* Java object. (Because the encoding in known BOM is not respected.)
*
* @param yaml
* YAML data to load from (BOM must not be present)
* @return parsed object
*/
public Object load(String yaml) {
return loadFromReader(new StreamReader(yaml), Object.class);
}
/**
* Parse the only YAML document in a stream and produce the corresponding
* Java object.
*
* @param io
* data to load from (BOM is respected and removed)
* @return parsed object
*/
public Object load(InputStream io) {
return loadFromReader(new StreamReader(new UnicodeReader(io)), Object.class);
}
/**
* Parse the only YAML document in a stream and produce the corresponding
* Java object.
*
* @param io
* data to load from (BOM must not be present)
* @return parsed object
*/
public Object load(Reader io) {
return loadFromReader(new StreamReader(io), Object.class);
}
/**
* Parse the only YAML document in a stream and produce the corresponding
* Java object.
*
* @param <T>
* Class is defined by the second argument
* @param io
* data to load from (BOM must not be present)
* @param type
* Class of the object to be created
* @return parsed object
*/
@SuppressWarnings("unchecked")
public <T> T loadAs(Reader io, Class<T> type) {
return (T) loadFromReader(new StreamReader(io), type);
}
/**
* Parse the only YAML document in a String and produce the corresponding
* Java object. (Because the encoding in known BOM is not respected.)
*
* @param <T>
* Class is defined by the second argument
* @param yaml
* YAML data to load from (BOM must not be present)
* @param type
* Class of the object to be created
* @return parsed object
*/
@SuppressWarnings("unchecked")
public <T> T loadAs(String yaml, Class<T> type) {
return (T) loadFromReader(new StreamReader(yaml), type);
}
/**
* Parse the only YAML document in a stream and produce the corresponding
* Java object.
*
* @param <T>
* Class is defined by the second argument
* @param input
* data to load from (BOM is respected and removed)
* @param type
* Class of the object to be created
* @return parsed object
*/
@SuppressWarnings("unchecked")
public <T> T loadAs(InputStream input, Class<T> type) {
return (T) loadFromReader(new StreamReader(new UnicodeReader(input)), type);
}
private Object loadFromReader(StreamReader sreader, Class<?> type) {
Composer composer = new Composer(new ParserImpl(sreader), resolver);
constructor.setComposer(composer);
return constructor.getSingleData(type);
}
/**
* Parse all YAML documents in a String and produce corresponding Java
* objects. The documents are parsed only when the iterator is invoked.
*
* @param yaml
* YAML data to load from (BOM must not be present)
* @return an iterator over the parsed Java objects in this String in proper
* sequence
*/
public Iterable<Object> loadAll(Reader yaml) {
Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
constructor.setComposer(composer);
Iterator<Object> result = new Iterator<Object>() {
public boolean hasNext() {
return constructor.checkData();
}
public Object next() {
return constructor.getData();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
return new YamlIterable(result);
}
private static class YamlIterable implements Iterable<Object> {
private Iterator<Object> iterator;
public YamlIterable(Iterator<Object> iterator) {
this.iterator = iterator;
}
public Iterator<Object> iterator() {
return iterator;
}
}
/**
* Parse all YAML documents in a String and produce corresponding Java
* objects. (Because the encoding in known BOM is not respected.) The
* documents are parsed only when the iterator is invoked.
*
* @param yaml
* YAML data to load from (BOM must not be present)
* @return an iterator over the parsed Java objects in this String in proper
* sequence
*/
public Iterable<Object> loadAll(String yaml) {
return loadAll(new StringReader(yaml));
}
/**
* Parse all YAML documents in a stream and produce corresponding Java
* objects. The documents are parsed only when the iterator is invoked.
*
* @param yaml
* YAML data to load from (BOM is respected and ignored)
* @return an iterator over the parsed Java objects in this stream in proper
* sequence
*/
public Iterable<Object> loadAll(InputStream yaml) {
return loadAll(new UnicodeReader(yaml));
}
/**
* Parse the first YAML document in a stream and produce the corresponding
* representation tree. (This is the opposite of the represent() method)
*
* @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing
* Overview</a>
* @param yaml
* YAML document
* @return parsed root Node for the specified YAML document
*/
public Node compose(Reader yaml) {
Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
constructor.setComposer(composer);
return composer.getSingleNode();
}
/**
* Parse all YAML documents in a stream and produce corresponding
* representation trees.
*
* @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
* @param yaml
* stream of YAML documents
* @return parsed root Nodes for all the specified YAML documents
*/
public Iterable<Node> composeAll(Reader yaml) {
final Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
constructor.setComposer(composer);
Iterator<Node> result = new Iterator<Node>() {
public boolean hasNext() {
return composer.checkNode();
}
public Node next() {
return composer.getNode();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
return new NodeIterable(result);
}
private static class NodeIterable implements Iterable<Node> {
private Iterator<Node> iterator;
public NodeIterable(Iterator<Node> iterator) {
this.iterator = iterator;
}
public Iterator<Node> iterator() {
return iterator;
}
}
/**
* Add an implicit scalar detector. If an implicit scalar value matches the
* given regexp, the corresponding tag is assigned to the scalar.
*
* @param tag
* tag to assign to the node
* @param regexp
* regular expression to match against
* @param first
* a sequence of possible initial characters or null (which means
* any).
*/
public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
resolver.addImplicitResolver(tag, regexp, first);
}
@Override
public String toString() {
return name;
}
/**
* Get a meaningful name. It simplifies debugging in a multi-threaded
* environment. If nothing is set explicitly the address of the instance is
* returned.
*
* @return human readable name
*/
public String getName() {
return name;
}
/**
* Set a meaningful name to be shown in toString()
*
* @param name
* human readable name
*/
public void setName(String name) {
this.name = name;
}
/**
* Parse a YAML stream and produce parsing events.
*
* @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
* @param yaml
* YAML document(s)
* @return parsed events
*/
public Iterable<Event> parse(Reader yaml) {
final Parser parser = new ParserImpl(new StreamReader(yaml));
Iterator<Event> result = new Iterator<Event>() {
public boolean hasNext() {
return parser.peekEvent() != null;
}
public Event next() {
return parser.getEvent();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
return new EventIterable(result);
}
private static class EventIterable implements Iterable<Event> {
private Iterator<Event> iterator;
public EventIterable(Iterator<Event> iterator) {
this.iterator = iterator;
}
public Iterator<Event> iterator() {
return iterator;
}
}
public void setBeanAccess(BeanAccess beanAccess) {
constructor.getPropertyUtils().setBeanAccess(beanAccess);
representer.getPropertyUtils().setBeanAccess(beanAccess);
}
}

View File

@ -0,0 +1,218 @@
package com.boydti.fawe.configuration.file;
import com.boydti.fawe.configuration.Configuration;
import com.boydti.fawe.configuration.InvalidConfigurationException;
import com.boydti.fawe.configuration.MemoryConfiguration;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
/**
* This is a base class for all File based implementations of {@link
* com.boydti.fawe.configuration.Configuration}
*/
public abstract class FileConfiguration extends MemoryConfiguration {
/**
* Creates an empty {@link com.boydti.fawe.configuration.file.FileConfiguration} with no default values.
*/
public FileConfiguration() {
}
/**
* Creates an empty {@link com.boydti.fawe.configuration.file.FileConfiguration} using the specified {@link
* com.boydti.fawe.configuration.Configuration} as a source for all default values.
*
* @param defaults Default value provider
*/
public FileConfiguration(Configuration defaults) {
super(defaults);
}
/**
* Saves this {@link com.boydti.fawe.configuration.file.FileConfiguration} to the specified location.
* <p>
* If the file does not exist, it will be created. If already exists, it
* will be overwritten. If it cannot be overwritten or created, an
* exception will be thrown.
* <p>
* This method will save using the system default encoding, or possibly
* using UTF8.
*
* @param file File to save to.
* @throws java.io.IOException Thrown when the given file cannot be written to for
* any reason.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void save(File file) throws IOException {
if (file == null) {
throw new NullPointerException("File cannot be null");
}
file.getParentFile().mkdirs();
String data = saveToString();
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
writer.write(data);
}
}
/**
* Saves this {@link com.boydti.fawe.configuration.file.FileConfiguration} to the specified location.
* <p>
* If the file does not exist, it will be created. If already exists, it
* will be overwritten. If it cannot be overwritten or created, an
* exception will be thrown.
* <p>
* This method will save using the system default encoding, or possibly
* using UTF8.
*
* @param file File to save to.
* @throws java.io.IOException Thrown when the given file cannot be written to for
* any reason.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void save(String file) throws IOException {
if (file == null) {
throw new NullPointerException("File cannot be null");
}
save(new File(file));
}
/**
* Saves this {@link com.boydti.fawe.configuration.file.FileConfiguration} to a string, and returns it.
*
* @return String containing this configuration.
*/
public abstract String saveToString();
/**
* Loads this {@link com.boydti.fawe.configuration.file.FileConfiguration} from the specified location.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given file.
* <p>
* If the file cannot be loaded for any reason, an exception will be
* thrown.
* <p>
*
* @param file File to load from.
* @throws java.io.FileNotFoundException Thrown when the given file cannot be
* opened.
* @throws java.io.IOException Thrown when the given file cannot be read.
* @throws com.boydti.fawe.configuration.InvalidConfigurationException Thrown when the given file is not
* a valid Configuration.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void load(File file) throws IOException, InvalidConfigurationException {
if (file == null) {
throw new NullPointerException("File cannot be null");
}
FileInputStream stream = new FileInputStream(file);
load(new InputStreamReader(stream, StandardCharsets.UTF_8));
}
/**
* Loads this {@link com.boydti.fawe.configuration.file.FileConfiguration} from the specified reader.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given stream.
*
* @param reader the reader to load from
* @throws java.io.IOException thrown when underlying reader throws an IOException
* @throws com.boydti.fawe.configuration.InvalidConfigurationException thrown when the reader does not
* represent a valid Configuration
* @throws IllegalArgumentException thrown when reader is null
*/
public void load(Reader reader) throws IOException, InvalidConfigurationException {
StringBuilder builder = new StringBuilder();
try (BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader)) {
String line;
while ((line = input.readLine()) != null) {
builder.append(line);
builder.append('\n');
}
}
loadFromString(builder.toString());
}
/**
* Loads this {@link com.boydti.fawe.configuration.file.FileConfiguration} from the specified location.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given file.
* <p>
* If the file cannot be loaded for any reason, an exception will be
* thrown.
*
* @param file File to load from.
* @throws java.io.FileNotFoundException Thrown when the given file cannot be
* opened.
* @throws java.io.IOException Thrown when the given file cannot be read.
* @throws com.boydti.fawe.configuration.InvalidConfigurationException Thrown when the given file is not
* a valid Configuration.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void load(String file) throws IOException, InvalidConfigurationException {
if (file == null) {
throw new NullPointerException("File cannot be null");
}
load(new File(file));
}
/**
* Loads this {@link com.boydti.fawe.configuration.file.FileConfiguration} from the specified string, as
* opposed to from file.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given string.
* <p>
* If the string is invalid in any way, an exception will be thrown.
*
* @param contents Contents of a Configuration to load.
* @throws com.boydti.fawe.configuration.InvalidConfigurationException Thrown if the specified string is
* invalid.
* @throws IllegalArgumentException Thrown if contents is null.
*/
public abstract void loadFromString(String contents) throws InvalidConfigurationException;
/**
* Compiles the header for this {@link com.boydti.fawe.configuration.file.FileConfiguration} and returns the
* result.
* <p>
* This will use the header from {@link #options()} -> {@link
* com.boydti.fawe.configuration.file.FileConfigurationOptions#header()}, respecting the rules of {@link
* com.boydti.fawe.configuration.file.FileConfigurationOptions#copyHeader()} if set.
*
* @return Compiled header
*/
protected abstract String buildHeader();
@Override
public FileConfigurationOptions options() {
if (this.options == null) {
this.options = new FileConfigurationOptions(this);
}
return (FileConfigurationOptions) this.options;
}
}

View File

@ -0,0 +1,119 @@
package com.boydti.fawe.configuration.file;
import com.boydti.fawe.configuration.MemoryConfiguration;
import com.boydti.fawe.configuration.MemoryConfigurationOptions;
/**
* Various settings for controlling the input and output of a {@link
* com.boydti.fawe.configuration.file.FileConfiguration}
*/
public class FileConfigurationOptions extends MemoryConfigurationOptions {
private String header = null;
private boolean copyHeader = true;
protected FileConfigurationOptions(final MemoryConfiguration configuration) {
super(configuration);
}
@Override
public com.boydti.fawe.configuration.file.FileConfiguration configuration() {
return (com.boydti.fawe.configuration.file.FileConfiguration) super.configuration();
}
@Override
public com.boydti.fawe.configuration.file.FileConfigurationOptions copyDefaults(final boolean value) {
super.copyDefaults(value);
return this;
}
@Override
public com.boydti.fawe.configuration.file.FileConfigurationOptions pathSeparator(final char value) {
super.pathSeparator(value);
return this;
}
/**
* Gets the header that will be applied to the top of the saved output.
* <p>
* This header will be commented out and applied directly at the top of
* the generated output of the {@link com.boydti.fawe.configuration.file.FileConfiguration}. It is not
* required to include a newline at the end of the header as it will
* automatically be applied, but you may include one if you wish for extra
* spacing.
* <p>
* Null is a valid value which will indicate that no header is to be
* applied. The default value is null.
*
* @return Header
*/
public String header() {
return header;
}
/**
* Sets the header that will be applied to the top of the saved output.
* <p>
* This header will be commented out and applied directly at the top of
* the generated output of the {@link com.boydti.fawe.configuration.file.FileConfiguration}. It is not
* required to include a newline at the end of the header as it will
* automatically be applied, but you may include one if you wish for extra
* spacing.
* <p>
* Null is a valid value which will indicate that no header is to be
* applied.
*
* @param value New header
* @return This object, for chaining
*/
public com.boydti.fawe.configuration.file.FileConfigurationOptions header(final String value) {
header = value;
return this;
}
/**
* Gets whether or not the header should be copied from a default source.
* <p>
* If this is true, if a default {@link com.boydti.fawe.configuration.file.FileConfiguration} is passed to
* {@link
* com.boydti.fawe.configuration.file.FileConfiguration#setDefaults(com.boydti.fawe.configuration.Configuration)}
* then upon saving it will use the header from that config, instead of
* the one provided here.
* <p>
* If no default is set on the configuration, or the default is not of
* type FileConfiguration, or that config has no header ({@link #header()}
* returns null) then the header specified in this configuration will be
* used.
* <p>
* Defaults to true.
*
* @return Whether or not to copy the header
*/
public boolean copyHeader() {
return copyHeader;
}
/**
* Sets whether or not the header should be copied from a default source.
* <p>
* If this is true, if a default {@link com.boydti.fawe.configuration.file.FileConfiguration} is passed to
* {@link
* com.boydti.fawe.configuration.file.FileConfiguration#setDefaults(com.boydti.fawe.configuration.Configuration)}
* then upon saving it will use the header from that config, instead of
* the one provided here.
* <p>
* If no default is set on the configuration, or the default is not of
* type FileConfiguration, or that config has no header ({@link #header()}
* returns null) then the header specified in this configuration will be
* used.
* <p>
* Defaults to true.
*
* @param value Whether or not to copy the header
* @return This object, for chaining
*/
public com.boydti.fawe.configuration.file.FileConfigurationOptions copyHeader(final boolean value) {
copyHeader = value;
return this;
}
}

View File

@ -0,0 +1,229 @@
package com.boydti.fawe.configuration.file;
import com.boydti.fawe.configuration.Configuration;
import com.boydti.fawe.configuration.ConfigurationSection;
import com.boydti.fawe.configuration.InvalidConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Map;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.representer.Representer;
/**
* An implementation of {@link com.boydti.fawe.configuration.Configuration} which saves all files in Yaml.
* Note that this implementation is not synchronized.
*/
public class YamlConfiguration extends FileConfiguration {
protected static final String COMMENT_PREFIX = "# ";
protected static final String BLANK_CONFIG = "{}\n";
private final DumperOptions yamlOptions = new DumperOptions();
private final Representer yamlRepresenter = new YamlRepresenter();
private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions);
/**
* Creates a new {@link com.boydti.fawe.configuration.file.YamlConfiguration}, loading from the given file.
* <p>
* Any errors loading the Configuration will be logged and then ignored.
* If the specified input is not a valid config, a blank config will be
* returned.
* <p>
* The encoding used may follow the system dependent default.
*
* @param file Input file
* @return Resulting configuration
* @throws IllegalArgumentException Thrown if file is null
*/
public static com.boydti.fawe.configuration.file.YamlConfiguration loadConfiguration(final File file) {
if (file == null) {
throw new NullPointerException("File cannot be null");
}
final com.boydti.fawe.configuration.file.YamlConfiguration config = new com.boydti.fawe.configuration.file.YamlConfiguration();
try {
config.load(file);
} catch (InvalidConfigurationException | IOException ex) {
try {
file.getAbsolutePath();
File dest = new File(file.getAbsolutePath() + "_broken");
int i = 0;
while (dest.exists()) {
dest = new File(file.getAbsolutePath() + "_broken_" + i++);
}
Files.copy(file.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
System.out.println("&dCould not read: &7" + file);
System.out.println("&dRenamed to: &7" + dest.getName());
System.out.println("&c============ Full stacktrace ============");
ex.printStackTrace();
System.out.println("&c=========================================");
} catch (final IOException e) {
e.printStackTrace();
}
}
return config;
}
/**
* Creates a new {@link com.boydti.fawe.configuration.file.YamlConfiguration}, loading from the given reader.
* <p>
* Any errors loading the Configuration will be logged and then ignored.
* If the specified input is not a valid config, a blank config will be
* returned.
*
* @param reader input
* @return resulting configuration
* @throws IllegalArgumentException Thrown if stream is null
*/
public static com.boydti.fawe.configuration.file.YamlConfiguration loadConfiguration(final Reader reader) {
if (reader == null) {
throw new NullPointerException("Reader cannot be null");
}
final com.boydti.fawe.configuration.file.YamlConfiguration config = new com.boydti.fawe.configuration.file.YamlConfiguration();
try {
config.load(reader);
} catch (final IOException | InvalidConfigurationException ex) {
System.out.println("Cannot load configuration from stream");
ex.printStackTrace();
}
return config;
}
@Override
public String saveToString() {
yamlOptions.setIndent(options().indent());
yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
final String header = buildHeader();
String dump = yaml.dump(getValues(false));
if (dump.equals(BLANK_CONFIG)) {
dump = "";
}
return header + dump;
}
@Override
public void loadFromString(final String contents) throws InvalidConfigurationException {
if (contents == null) {
throw new NullPointerException("Contents cannot be null");
}
Map<?, ?> input;
try {
input = (Map<?, ?>) yaml.load(contents);
} catch (final YAMLException e) {
throw new InvalidConfigurationException(e);
} catch (final ClassCastException e) {
throw new InvalidConfigurationException("Top level is not a Map.");
}
final String header = parseHeader(contents);
if (!header.isEmpty()) {
options().header(header);
}
if (input != null) {
convertMapsToSections(input, this);
}
}
protected void convertMapsToSections(final Map<?, ?> input, final ConfigurationSection section) {
for (final Map.Entry<?, ?> entry : input.entrySet()) {
final String key = entry.getKey().toString();
final Object value = entry.getValue();
if (value instanceof Map) {
convertMapsToSections((Map<?, ?>) value, section.createSection(key));
} else {
section.set(key, value);
}
}
}
protected String parseHeader(final String input) {
final String[] lines = input.split("\r?\n", -1);
final StringBuilder result = new StringBuilder();
boolean readingHeader = true;
boolean foundHeader = false;
for (int i = 0; (i < lines.length) && readingHeader; i++) {
final String line = lines[i];
if (line.startsWith(COMMENT_PREFIX)) {
if (i > 0) {
result.append("\n");
}
if (line.length() > COMMENT_PREFIX.length()) {
result.append(line.substring(COMMENT_PREFIX.length()));
}
foundHeader = true;
} else if (foundHeader && line.isEmpty()) {
result.append("\n");
} else if (foundHeader) {
readingHeader = false;
}
}
return result.toString();
}
@Override
protected String buildHeader() {
final String header = options().header();
if (options().copyHeader()) {
final Configuration def = getDefaults();
if (def != null && def instanceof FileConfiguration) {
final FileConfiguration filedefaults = (FileConfiguration) def;
final String defaultsHeader = filedefaults.buildHeader();
if ((defaultsHeader != null) && !defaultsHeader.isEmpty()) {
return defaultsHeader;
}
}
}
if (header == null) {
return "";
}
final StringBuilder builder = new StringBuilder();
final String[] lines = header.split("\r?\n", -1);
boolean startedHeader = false;
for (int i = lines.length - 1; i >= 0; i--) {
builder.insert(0, "\n");
if (startedHeader || !lines[i].isEmpty()) {
builder.insert(0, lines[i]);
builder.insert(0, COMMENT_PREFIX);
startedHeader = true;
}
}
return builder.toString();
}
@Override
public YamlConfigurationOptions options() {
if (options == null) {
options = new YamlConfigurationOptions(this);
}
return (YamlConfigurationOptions) options;
}
}

View File

@ -0,0 +1,73 @@
package com.boydti.fawe.configuration.file;
/**
* Various settings for controlling the input and output of a {@link
* com.boydti.fawe.configuration.file.YamlConfiguration}
*/
public class YamlConfigurationOptions extends FileConfigurationOptions {
private int indent = 2;
protected YamlConfigurationOptions(final YamlConfiguration configuration) {
super(configuration);
}
@Override
public YamlConfiguration configuration() {
return (YamlConfiguration) super.configuration();
}
@Override
public com.boydti.fawe.configuration.file.YamlConfigurationOptions copyDefaults(final boolean value) {
super.copyDefaults(value);
return this;
}
@Override
public com.boydti.fawe.configuration.file.YamlConfigurationOptions pathSeparator(final char value) {
super.pathSeparator(value);
return this;
}
@Override
public com.boydti.fawe.configuration.file.YamlConfigurationOptions header(final String value) {
super.header(value);
return this;
}
@Override
public com.boydti.fawe.configuration.file.YamlConfigurationOptions copyHeader(final boolean value) {
super.copyHeader(value);
return this;
}
/**
* Gets how much spaces should be used to indent each line.
* <p>
* The minimum value this may be is 2, and the maximum is 9.
*
* @return How much to indent by
*/
public int indent() {
return indent;
}
/**
* Sets how much spaces should be used to indent each line.
* <p>
* The minimum value this may be is 2, and the maximum is 9.
*
* @param value New indent
* @return This object, for chaining
*/
public com.boydti.fawe.configuration.file.YamlConfigurationOptions indent(final int value) {
if (value < 2) {
throw new IllegalArgumentException("Indent must be at least 2 characters");
}
if (value > 9) {
throw new IllegalArgumentException("Indent cannot be greater than 9 characters");
}
indent = value;
return this;
}
}

View File

@ -0,0 +1,47 @@
package com.boydti.fawe.configuration.file;
import com.boydti.fawe.configuration.serialization.ConfigurationSerialization;
import java.util.LinkedHashMap;
import java.util.Map;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
public class YamlConstructor extends SafeConstructor {
public YamlConstructor() {
yamlConstructors.put(Tag.MAP, new ConstructCustomObject());
}
private class ConstructCustomObject extends ConstructYamlMap {
@Override
public Object construct(final Node node) {
if (node.isTwoStepsConstruction()) {
throw new YAMLException("Unexpected referential mapping structure. Node: " + node);
}
final Map<?, ?> raw = (Map<?, ?>) super.construct(node);
if (raw.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
final Map<String, Object> typed = new LinkedHashMap<>(raw.size());
for (final Map.Entry<?, ?> entry : raw.entrySet()) {
typed.put(entry.getKey().toString(), entry.getValue());
}
try {
return ConfigurationSerialization.deserializeObject(typed);
} catch (final IllegalArgumentException ex) {
throw new YAMLException("Could not deserialize object", ex);
}
}
return raw;
}
@Override
public void construct2ndStep(final Node node, final Object object) {
throw new YAMLException("Unexpected referential mapping structure. Node: " + node);
}
}
}

View File

@ -0,0 +1,38 @@
package com.boydti.fawe.configuration.file;
import com.boydti.fawe.configuration.ConfigurationSection;
import com.boydti.fawe.configuration.serialization.ConfigurationSerializable;
import com.boydti.fawe.configuration.serialization.ConfigurationSerialization;
import java.util.LinkedHashMap;
import java.util.Map;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.representer.Representer;
public class YamlRepresenter extends Representer {
public YamlRepresenter() {
this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection());
this.multiRepresenters.put(ConfigurationSerializable.class, new RepresentConfigurationSerializable());
}
private class RepresentConfigurationSection extends RepresentMap {
@Override
public Node representData(Object data) {
return super.representData(((ConfigurationSection) data).getValues(false));
}
}
private class RepresentConfigurationSerializable extends RepresentMap {
@Override
public Node representData(Object data) {
ConfigurationSerializable serializable = (ConfigurationSerializable) data;
Map<String, Object> values = new LinkedHashMap<>();
values.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass()));
values.putAll(serializable.serialize());
return super.representData(values);
}
}
}

View File

@ -0,0 +1,35 @@
package com.boydti.fawe.configuration.serialization;
import java.util.Map;
/**
* Represents an object that may be serialized.
* <p>
* These objects MUST implement one of the following, in addition to the
* methods as defined by this interface:
* <ul>
* <li>A static method "deserialize" that accepts a single {@link java.util.Map}&lt;
* {@link String}, {@link Object}> and returns the class.</li>
* <li>A static method "valueOf" that accepts a single {@link java.util.Map}&lt;{@link
* String}, {@link Object}> and returns the class.</li>
* <li>A constructor that accepts a single {@link java.util.Map}&lt;{@link String},
* {@link Object}>.</li>
* </ul>
* In addition to implementing this interface, you must register the class
* with {@link com.boydti.fawe.configuration.serialization.ConfigurationSerialization#registerClass(Class)}.
*
* @see com.boydti.fawe.configuration.serialization.DelegateDeserialization
* @see com.boydti.fawe.configuration.serialization.SerializableAs
*/
public interface ConfigurationSerializable {
/**
* Creates a Map representation of this class.
* <p>
* This class must provide a method to restore this class, as defined in
* the {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable} interface javadoc.
*
* @return Map containing the current state of this class
*/
Map<String, Object> serialize();
}

View File

@ -0,0 +1,262 @@
package com.boydti.fawe.configuration.serialization;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility class for storing and retrieving classes for {@link com.boydti.fawe.configuration.Configuration}.
*/
public class ConfigurationSerialization {
public static final String SERIALIZED_TYPE_KEY = "==";
private static final Map<String, Class<? extends ConfigurationSerializable>> aliases =
new HashMap<String, Class<? extends ConfigurationSerializable>>();
private final Class<? extends ConfigurationSerializable> clazz;
protected ConfigurationSerialization(Class<? extends ConfigurationSerializable> clazz) {
this.clazz = clazz;
}
/**
* Attempts to deserialize the given arguments into a new instance of the
* given class.
* <p>
* <p>The class must implement {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable}, including
* the extra methods as specified in the javadoc of
* ConfigurationSerializable.</p>
* <p>
* <p>If a new instance could not be made, an example being the class not
* fully implementing the interface, null will be returned.</p>
*
* @param args Arguments for deserialization
* @param clazz Class to deserialize into
* @return New instance of the specified class
*/
public static ConfigurationSerializable deserializeObject(Map<String, ?> args, Class<? extends ConfigurationSerializable> clazz) {
return new com.boydti.fawe.configuration.serialization.ConfigurationSerialization(clazz).deserialize(args);
}
/**
* Attempts to deserialize the given arguments into a new instance of the
* <p>
* given class.
* <p>
* The class must implement {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable}, including
* the extra methods as specified in the javadoc of
* ConfigurationSerializable.</p>
* <p>
* <p>
* If a new instance could not be made, an example being the class not
* fully implementing the interface, null will be returned.</p>
*
* @param args Arguments for deserialization
* @return New instance of the specified class
*/
public static ConfigurationSerializable deserializeObject(Map<String, ?> args) {
Class<? extends ConfigurationSerializable> clazz = null;
if (args.containsKey(SERIALIZED_TYPE_KEY)) {
try {
String alias = (String) args.get(SERIALIZED_TYPE_KEY);
if (alias == null) {
throw new IllegalArgumentException("Cannot have null alias");
}
clazz = getClassByAlias(alias);
if (clazz == null) {
throw new IllegalArgumentException("Specified class does not exist ('" + alias + "')");
}
} catch (ClassCastException ex) {
ex.fillInStackTrace();
throw ex;
}
} else {
throw new IllegalArgumentException("Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')");
}
return new com.boydti.fawe.configuration.serialization.ConfigurationSerialization(clazz).deserialize(args);
}
/**
* Registers the given {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable} class by its
* alias.
*
* @param clazz Class to register
*/
public static void registerClass(Class<? extends ConfigurationSerializable> clazz) {
com.boydti.fawe.configuration.serialization.DelegateDeserialization delegate = clazz.getAnnotation(com.boydti.fawe.configuration.serialization.DelegateDeserialization.class);
if (delegate == null) {
registerClass(clazz, getAlias(clazz));
registerClass(clazz, clazz.getName());
}
}
/**
* Registers the given alias to the specified {@link
* com.boydti.fawe.configuration.serialization.ConfigurationSerializable} class.
*
* @param clazz Class to register
* @param alias Alias to register as
* @see com.boydti.fawe.configuration.serialization.SerializableAs
*/
public static void registerClass(Class<? extends ConfigurationSerializable> clazz, String alias) {
aliases.put(alias, clazz);
}
/**
* Unregisters the specified alias to a {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable}
*
* @param alias Alias to unregister
*/
public static void unregisterClass(String alias) {
aliases.remove(alias);
}
/**
* Unregisters any aliases for the specified {@link
* com.boydti.fawe.configuration.serialization.ConfigurationSerializable} class.
*
* @param clazz Class to unregister
*/
public static void unregisterClass(Class<? extends ConfigurationSerializable> clazz) {
while (aliases.values().remove(clazz)) {
}
}
/**
* Attempts to get a registered {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable} class by
* its alias.
*
* @param alias Alias of the serializable
* @return Registered class, or null if not found
*/
public static Class<? extends ConfigurationSerializable> getClassByAlias(String alias) {
return aliases.get(alias);
}
/**
* Gets the correct alias for the given {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable}
* class.
*
* @param clazz Class to get alias for
* @return Alias to use for the class
*/
public static String getAlias(Class<? extends ConfigurationSerializable> clazz) {
com.boydti.fawe.configuration.serialization.DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class);
if (delegate != null) {
if ((delegate.value() == null) || (delegate.value() == clazz)) {
delegate = null;
} else {
return getAlias(delegate.value());
}
}
SerializableAs alias = clazz.getAnnotation(SerializableAs.class);
if (alias != null) {
return alias.value();
}
return clazz.getName();
}
protected Method getMethod(String name, boolean isStatic) {
try {
Method method = this.clazz.getDeclaredMethod(name, Map.class);
if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) {
return null;
}
if (Modifier.isStatic(method.getModifiers()) != isStatic) {
return null;
}
return method;
} catch (NoSuchMethodException ex) {
return null;
} catch (SecurityException ex) {
return null;
}
}
protected Constructor<? extends ConfigurationSerializable> getConstructor() {
try {
return this.clazz.getConstructor(Map.class);
} catch (NoSuchMethodException ex) {
return null;
} catch (SecurityException ex) {
return null;
}
}
protected ConfigurationSerializable deserializeViaMethod(Method method, Map<String, ?> args) {
try {
ConfigurationSerializable result = (ConfigurationSerializable) method.invoke(null, args);
if (result == null) {
Logger.getLogger(com.boydti.fawe.configuration.serialization.ConfigurationSerialization.class.getName()).log(Level.SEVERE,
"Could not call method '" + method.toString() + "' of " + this.clazz + " for deserialization: method returned null");
} else {
return result;
}
} catch (Throwable ex) {
Logger.getLogger(com.boydti.fawe.configuration.serialization.ConfigurationSerialization.class.getName())
.log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + this.clazz
+ " for deserialization",
ex instanceof InvocationTargetException ? ex.getCause() : ex);
}
return null;
}
protected ConfigurationSerializable deserializeViaCtor(Constructor<? extends ConfigurationSerializable> ctor, Map<String, ?> args) {
try {
return ctor.newInstance(args);
} catch (Throwable ex) {
Logger.getLogger(com.boydti.fawe.configuration.serialization.ConfigurationSerialization.class.getName())
.log(Level.SEVERE, "Could not call constructor '" + ctor.toString() + "' of " + this.clazz
+ " for deserialization",
ex instanceof InvocationTargetException ? ex.getCause() : ex);
}
return null;
}
public ConfigurationSerializable deserialize(Map<String, ?> args) {
if (args == null) {
throw new NullPointerException("Args must not be null");
}
ConfigurationSerializable result = null;
Method method = getMethod("deserialize", true);
if (method != null) {
result = deserializeViaMethod(method, args);
}
if (result == null) {
method = getMethod("valueOf", true);
if (method != null) {
result = deserializeViaMethod(method, args);
}
}
if (result == null) {
Constructor<? extends ConfigurationSerializable> constructor = getConstructor();
if (constructor != null) {
result = deserializeViaCtor(constructor, args);
}
}
return result;
}
}

View File

@ -0,0 +1,22 @@
package com.boydti.fawe.configuration.serialization;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Applies to a {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable} that will delegate all
* deserialization to another {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DelegateDeserialization {
/**
* Which class should be used as a delegate for this classes
* deserialization
*
* @return Delegate class
*/
Class<? extends ConfigurationSerializable> value();
}

View File

@ -0,0 +1,34 @@
package com.boydti.fawe.configuration.serialization;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Represents an "alias" that a {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable} may be
* stored as.
* If this is not present on a {@link com.boydti.fawe.configuration.serialization.ConfigurationSerializable} class, it
* will use the fully qualified name of the class.
* <p>
* This value will be stored in the configuration so that the configuration
* deserialization can determine what type it is.
* <p>
* Using this annotation on any other class than a {@link
* com.boydti.fawe.configuration.serialization.ConfigurationSerializable} will have no effect.
*
* @see com.boydti.fawe.configuration.serialization.ConfigurationSerialization#registerClass(Class, String)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SerializableAs {
/**
* This is the name your class will be stored and retrieved as.
* <p>
* This name MUST be unique. We recommend using names such as
* "MyPluginThing" instead of "Thing".
*
* @return Name to serialize the class as.
*/
String value();
}

View File

@ -0,0 +1,47 @@
package com.boydti.fawe.database;
import com.boydti.fawe.Fawe;
import com.sk89q.worldedit.world.World;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DBHandler {
public final static DBHandler IMP = new DBHandler();
private Map<String, RollbackDatabase> databases = new ConcurrentHashMap<>(8, 0.9f, 1);
public RollbackDatabase getDatabase(World world) {
String worldName = Fawe.imp().getWorldName(world);
RollbackDatabase database = databases.get(worldName);
if (database != null) {
return database;
}
try {
database = new RollbackDatabase(world);
databases.put(worldName, database);
return database;
} catch (Throwable e) {
Fawe.debug("============ NO JDBC DRIVER! ============");
Fawe.debug("TODO: Bundle driver with FAWE (or disable database)");
Fawe.debug("=========================================");
e.printStackTrace();
Fawe.debug("=========================================");
return null;
}
}
public RollbackDatabase getDatabase(String world) {
RollbackDatabase database = databases.get(world);
if (database != null) {
return database;
}
try {
database = new RollbackDatabase(world);
databases.put(world, database);
return database;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,343 @@
package com.boydti.fawe.database;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.logging.rollback.RollbackOptimizedHistory;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.object.task.AsyncNotifyQueue;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
public class RollbackDatabase extends AsyncNotifyQueue {
private final String prefix;
private final File dbLocation;
private final String worldName;
private final World world;
private Connection connection;
private String INSERT_EDIT;
private String CREATE_TABLE;
// private String GET_EDITS_POINT;
private String GET_EDITS;
private String GET_EDITS_USER;
private String GET_EDITS_ASC;
private String GET_EDITS_USER_ASC;
private String DELETE_EDITS_USER;
private String DELETE_EDIT_USER;
private String PURGE;
private ConcurrentLinkedQueue<RollbackOptimizedHistory> historyChanges = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<Runnable> tasks = new ConcurrentLinkedQueue<>();
public RollbackDatabase(String world) throws SQLException, ClassNotFoundException {
this(FaweAPI.getWorld(world));
}
public RollbackDatabase(final World world) throws SQLException, ClassNotFoundException {
this.prefix = "";
this.worldName = Fawe.imp().getWorldName(world);
this.world = world;
this.dbLocation = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + Fawe.imp().getWorldName(world) + 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(?,?,?,?,?,?,?,?,?)";
PURGE = "DELETE FROM `" + prefix + "edits` WHERE `time`<?";
// GET_EDITS_POINT = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=?";
GET_EDITS = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? ORDER BY `time` DESC, `id` DESC";
GET_EDITS_USER = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? AND `player`=? ORDER BY `time` DESC, `id` DESC";
GET_EDITS_ASC = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? ORDER BY `time` ASC, `id` ASC";
GET_EDITS_USER_ASC = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? AND `player`=? ORDER BY `time` ASC, `id` ASC";
DELETE_EDITS_USER = "DELETE FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? AND `player`=?";
DELETE_EDIT_USER = "DELETE FROM `" + prefix + "edits` WHERE `player`=? AND `id`=?";
init();
purge((int) TimeUnit.DAYS.toMillis(Settings.IMP.HISTORY.DELETE_AFTER_DAYS));
}
@Override
public boolean hasQueued() {
return connection != null && (!historyChanges.isEmpty() || !tasks.isEmpty());
}
@Override
public void operate() {
synchronized (this) {
if (connection == null) {
return;
}
while (sendBatch()) {
// Still processing
}
}
}
public void init() {
try (PreparedStatement stmt = connection.prepareStatement(CREATE_TABLE)) {
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void delete(final UUID uuid, final int id) {
addTask(new Runnable() {
@Override
public void run() {
try (PreparedStatement stmt = connection.prepareStatement(DELETE_EDIT_USER)) {
byte[] uuidBytes = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array();
stmt.setBytes(1, uuidBytes);
stmt.setInt(2, id);
} catch (SQLException e) {
e.printStackTrace();
}
}
});
}
public void purge(int diff) {
long now = System.currentTimeMillis() / 1000;
final int then = (int) (now - diff);
addTask(new Runnable() {
@Override
public void run() {
try (PreparedStatement stmt = connection.prepareStatement(PURGE)) {
stmt.setInt(1, then);
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
});
}
public void getPotentialEdits(final UUID uuid, final long minTime, final Vector pos1, final Vector pos2, final RunnableVal<DiskStorageHistory> onEach, final Runnable whenDone, final boolean delete, final boolean ascending) {
final World world = FaweAPI.getWorld(this.worldName);
addTask(new Runnable() {
@Override
public void run() {
String stmtStr = ascending ? (uuid == null ? GET_EDITS_ASC : GET_EDITS_USER_ASC) : (uuid == null ? GET_EDITS : GET_EDITS_USER);
try (PreparedStatement stmt = connection.prepareStatement(stmtStr)) {
stmt.setInt(1, pos1.getBlockX());
stmt.setInt(2, pos2.getBlockX());
stmt.setByte(3, (byte) (pos1.getBlockY() - 128));
stmt.setByte(4, (byte) (pos2.getBlockY() - 128));
stmt.setInt(5, pos1.getBlockZ());
stmt.setInt(6, pos2.getBlockZ());
stmt.setInt(7, (int) (minTime / 1000));
if (uuid != null) {
byte[] uuidBytes = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array();
stmt.setBytes(8, uuidBytes);
}
ResultSet result = stmt.executeQuery();
if (!result.next()) {
TaskManager.IMP.taskNow(whenDone, false);
return;
}
do {
byte[] uuidBytes = result.getBytes(1);
int index = result.getInt(2);
ByteBuffer bb = ByteBuffer.wrap(uuidBytes);
long high = bb.getLong();
long low = bb.getLong();
DiskStorageHistory history = new DiskStorageHistory(world, new UUID(high, low), index);
if (history.getBDFile().exists()) {
onEach.run(history);
}
} while (result.next());
TaskManager.IMP.taskNow(whenDone, false);
} catch (SQLException e) {
e.printStackTrace();
}
if (delete && uuid != null) {
try (PreparedStatement stmt = connection.prepareStatement(DELETE_EDITS_USER)) {
stmt.setInt(1, pos1.getBlockX());
stmt.setInt(2, pos2.getBlockX());
stmt.setByte(3, (byte) (pos1.getBlockY() - 128));
stmt.setByte(4, (byte) (pos2.getBlockY() - 128));
stmt.setInt(5, pos1.getBlockZ());
stmt.setInt(6, pos2.getBlockZ());
stmt.setInt(7, (int) (minTime / 1000));
byte[] uuidBytes = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array();
stmt.setBytes(8, uuidBytes);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
});
}
public void logEdit(RollbackOptimizedHistory history) {
queue(() -> historyChanges.add(history));
}
public void addTask(Runnable run) {
queue(() -> tasks.add(run));
}
private void runTasks() {
Runnable task;
while ((task = tasks.poll()) != null) {
try {
task.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private boolean sendBatch() {
try {
runTasks();
commit();
if (connection.getAutoCommit()) {
connection.setAutoCommit(false);
}
int size = Math.min(1048572, historyChanges.size());
if (size == 0) {
return false;
}
RollbackOptimizedHistory[] copy = new RollbackOptimizedHistory[size];
for (int i = 0; i < size; i++) {
copy[i] = historyChanges.poll();
}
try (PreparedStatement stmt = connection.prepareStatement(INSERT_EDIT)) {
for (RollbackOptimizedHistory change : copy) {
// `player`,`id`,`x1`,`y1`,`z1`,`x2`,`y2`,`z2`,`time`
UUID uuid = change.getUUID();
byte[] uuidBytes = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array();
stmt.setBytes(1, uuidBytes);
stmt.setInt(2, change.getIndex());
stmt.setInt(3, change.getMinX());
stmt.setByte(4, (byte) (change.getMinY() - 128));
stmt.setInt(5, change.getMinZ());
stmt.setInt(6, change.getMaxX());
stmt.setByte(7, (byte) (change.getMaxY() - 128));
stmt.setInt(8, change.getMaxZ());
stmt.setInt(9, (int) (change.getTime() / 1000));
stmt.executeUpdate();
stmt.clearParameters();
}
} catch (Exception e) {
e.printStackTrace();
}
commit();
return true;
} catch (final Exception e) {
e.printStackTrace();
}
return false;
}
public void commit() {
try {
if (connection == null) {
return;
}
if (!connection.getAutoCommit()) {
connection.commit();
connection.setAutoCommit(true);
}
} catch (final SQLException e) {
e.printStackTrace();
}
}
public Connection openConnection() throws SQLException, ClassNotFoundException {
if (checkConnection()) {
return connection;
}
if (!Fawe.imp().getDirectory().exists()) {
Fawe.imp().getDirectory().mkdirs();
}
if (!(dbLocation.exists())) {
try {
dbLocation.getParentFile().mkdirs();
dbLocation.createNewFile();
} catch (final IOException e) {
e.printStackTrace();
Fawe.debug("&cUnable to create database!");
}
}
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:" + dbLocation);
return connection;
}
public Connection forceConnection() throws SQLException, ClassNotFoundException {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:" + dbLocation);
return connection;
}
/**
* Gets the connection with the database
*
* @return Connection with the database, null if none
*/
public Connection getConnection() {
if (connection == null) {
try {
forceConnection();
} catch (final ClassNotFoundException e) {
e.printStackTrace();
} catch (final SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/**
* Closes the connection with the database
*
* @return true if successful
* @throws java.sql.SQLException if the connection cannot be closed
*/
public boolean closeConnection() throws SQLException {
if (connection == null) {
return false;
}
synchronized (this) {
if (connection == null) {
return false;
}
connection.close();
connection = null;
return true;
}
}
/**
* Checks if a connection is open with the database
*
* @return true if the connection is open
* @throws java.sql.SQLException if the connection cannot be checked
*/
public boolean checkConnection() {
try {
return (connection != null) && !connection.isClosed();
} catch (final SQLException e) {
return false;
}
}
}

View File

@ -0,0 +1,195 @@
package com.boydti.fawe.example;
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 it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class DefaultFaweQueueMap implements IFaweQueueMap {
private final MappedFaweQueue parent;
public DefaultFaweQueueMap(MappedFaweQueue parent) {
this.parent = parent;
}
public final Long2ObjectOpenHashMap<FaweChunk> blocks = new Long2ObjectOpenHashMap<FaweChunk>() {
@Override
public FaweChunk put(Long key, FaweChunk value) {
return put((long) key, value);
}
@Override
public FaweChunk put(long key, FaweChunk value) {
if (parent.getProgressTask() != null) {
try {
parent.getProgressTask().run(FaweQueue.ProgressType.QUEUE, size() + 1);
} catch (Throwable e) {
e.printStackTrace();
}
}
synchronized (this) {
return super.put(key, value);
}
}
};
@Override
public Collection<FaweChunk> getFaweCunks() {
synchronized (blocks) {
return new HashSet<>(blocks.values());
}
}
@Override
public void forEachChunk(RunnableVal<FaweChunk> onEach) {
synchronized (blocks) {
for (Map.Entry<Long, FaweChunk> entry : blocks.entrySet()) {
onEach.run(entry.getValue());
}
}
}
@Override
public FaweChunk getFaweChunk(int cx, int cz) {
if (cx == lastX && cz == lastZ) {
return lastWrappedChunk;
}
long pair = MathMan.pairInt(cx, cz);
FaweChunk chunk = this.blocks.get(pair);
if (chunk == null) {
chunk = this.getNewFaweChunk(cx, cz);
FaweChunk previous = this.blocks.put(pair, chunk);
if (previous != null) {
blocks.put(pair, previous);
return previous;
}
this.blocks.put(pair, chunk);
}
return chunk;
}
@Override
public FaweChunk getCachedFaweChunk(int cx, int cz) {
if (cx == lastX && cz == lastZ) {
return lastWrappedChunk;
}
long pair = MathMan.pairInt(cx, cz);
FaweChunk chunk = this.blocks.get(pair);
lastWrappedChunk = chunk;
return chunk;
}
@Override
public void add(FaweChunk chunk) {
long pair = MathMan.pairInt(chunk.getX(), chunk.getZ());
FaweChunk previous = this.blocks.put(pair, chunk);
if (previous != null) {
blocks.put(pair, previous);
}
}
@Override
public void clear() {
blocks.clear();
}
@Override
public int size() {
return blocks.size();
}
private FaweChunk getNewFaweChunk(int cx, int cz) {
return parent.getFaweChunk(cx, cz);
}
private volatile FaweChunk lastWrappedChunk;
private int lastX = Integer.MIN_VALUE;
private int lastZ = Integer.MIN_VALUE;
@Override
public boolean next(int amount, long time) {
synchronized (blocks) {
try {
boolean skip = parent.getStage() == SetQueue.QueueStage.INACTIVE;
int added = 0;
Iterator<Map.Entry<Long, FaweChunk>> iter = blocks.entrySet().iterator();
if (amount == 1) {
long start = System.currentTimeMillis();
do {
if (iter.hasNext()) {
FaweChunk chunk = iter.next().getValue();
if (skip && chunk == lastWrappedChunk) {
continue;
}
iter.remove();
parent.start(chunk);
chunk.call();
parent.end(chunk);
} else {
break;
}
} while (System.currentTimeMillis() - start < time);
} else {
ExecutorCompletionService service = SetQueue.IMP.getCompleterService();
ForkJoinPool pool = SetQueue.IMP.getForkJoinPool();
boolean result = true;
// amount = 8;
for (int i = 0; i < amount && (result = iter.hasNext()); i++) {
Map.Entry<Long, FaweChunk> item = iter.next();
FaweChunk chunk = item.getValue();
if (skip && chunk == lastWrappedChunk) {
i--;
continue;
}
iter.remove();
parent.start(chunk);
service.submit(chunk);
added++;
}
// 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);
service.submit(chunk);
Future future = service.poll(50, TimeUnit.MILLISECONDS);
if (future != null) {
FaweChunk fc = (FaweChunk) future.get();
parent.end(fc);
}
}
}
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
Future future;
while ((future = service.poll()) != null) {
FaweChunk fc = (FaweChunk) future.get();
parent.end(fc);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return !blocks.isEmpty();
}
}
}

View File

@ -0,0 +1,24 @@
package com.boydti.fawe.example;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.RunnableVal;
import java.util.Collection;
public interface IFaweQueueMap {
Collection<FaweChunk> getFaweCunks();
void forEachChunk(RunnableVal<FaweChunk> onEach);
FaweChunk getFaweChunk(int cx, int cz);
FaweChunk getCachedFaweChunk(int cx, int cz);
void add(FaweChunk chunk);
void clear();
int size();
boolean next(int size, long time);
}

View File

@ -0,0 +1,233 @@
package com.boydti.fawe.example;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.util.MathMan;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.util.*;
public abstract class IntFaweChunk<T, V extends FaweQueue> extends FaweChunk<T> {
public final int[][] ids;
public final short[] count;
public final short[] air;
public final byte[] heightMap;
public byte[] biomes;
public HashMap<Short, CompoundTag> tiles;
public HashSet<CompoundTag> entities;
public HashSet<UUID> entityRemoves;
public T chunk;
public IntFaweChunk(FaweQueue parent, int x, int z, int[][] ids, short[] count, short[] air, byte[] heightMap) {
super(parent, x, z);
this.ids = ids;
this.count = count;
this.air = air;
this.heightMap = heightMap;
}
/**
* A FaweSections object represents a chunk and the blocks that you wish to change in it.
*
* @param parent
* @param x
* @param z
*/
public IntFaweChunk(FaweQueue parent, int x, int z) {
super(parent, x, z);
this.ids = new int[HEIGHT >> 4][];
this.count = new short[HEIGHT >> 4];
this.air = new short[HEIGHT >> 4];
this.heightMap = new byte[256];
}
@Override
public V getParent() {
return (V) super.getParent();
}
@Override
public T getChunk() {
if (this.chunk == null) {
this.chunk = getNewChunk();
}
return this.chunk;
}
public abstract T getNewChunk();
@Override
public void setLoc(final FaweQueue parent, int x, int z) {
super.setLoc(parent, x, z);
this.chunk = null;
}
/**
* Get the number of block changes in a specified section
*
* @param i
* @return
*/
public int getCount(final int i) {
return this.count[i];
}
public int getAir(final int i) {
return this.air[i];
}
public void setCount(final int i, final short value) {
this.count[i] = value;
}
public int getTotalCount() {
int total = 0;
for (int i = 0; i < count.length; i++) {
total += Math.min(4096, this.count[i]);
}
return total;
}
public int getTotalAir() {
int total = 0;
for (int i = 0; i < air.length; i++) {
total += Math.min(4096, this.air[i]);
}
return total;
}
@Override
public int getBitMask() {
int bitMask = 0;
for (int section = 0; section < ids.length; section++) {
if (ids[section] != null) {
bitMask += 1 << section;
}
}
return bitMask;
}
/**
* Get the raw data for a section
*
* @param i
* @return
*/
@Override
public int[] getIdArray(final int i) {
return this.ids[i];
}
@Override
public int[][] getCombinedIdArrays() {
return this.ids;
}
@Override
public byte[] getBiomeArray() {
return this.biomes;
}
@Override
public int getBlockCombinedId(int x, int y, int z) {
short i = FaweCache.CACHE_I[y][z][x];
int[] array = getIdArray(i);
if (array == null) {
return 0;
}
return array[FaweCache.CACHE_J[y][z][x]];
}
@Override
public void setTile(int x, int y, int z, CompoundTag tile) {
if (tiles == null) {
tiles = new HashMap<>();
}
short pair = MathMan.tripleBlockCoord(x, y, z);
tiles.put(pair, tile);
}
@Override
public CompoundTag getTile(int x, int y, int z) {
if (tiles == null) {
return null;
}
short pair = MathMan.tripleBlockCoord(x, y, z);
return tiles.get(pair);
}
@Override
public Map<Short, CompoundTag> getTiles() {
return tiles == null ? new HashMap<Short, CompoundTag>() : tiles;
}
@Override
public Set<CompoundTag> getEntities() {
return entities == null ? Collections.emptySet() : entities;
}
@Override
public void setEntity(CompoundTag tag) {
if (entities == null) {
entities = new HashSet<>();
}
entities.add(tag);
}
@Override
public void removeEntity(UUID uuid) {
if (entityRemoves == null) {
entityRemoves = new HashSet<>();
}
entityRemoves.add(uuid);
}
@Override
public HashSet<UUID> getEntityRemoves() {
return entityRemoves == null ? new HashSet<UUID>() : entityRemoves;
}
@Override
public void setBlock(int x, int y, int z, int combinedId) {
final int i = FaweCache.CACHE_I[y][z][x];
final int j = FaweCache.CACHE_J[y][z][x];
int[] vs = this.ids[i];
if (vs == null) {
vs = this.ids[i] = new int[4096];
}
vs[j] = combinedId;
this.count[i]++;
switch (BlockTypes.getFromStateId(combinedId)) {
case AIR:
case CAVE_AIR:
case VOID_AIR:
this.air[i]++;
return;
default:
heightMap[z << 4 | x] = (byte) y;
return;
}
}
@Deprecated
public void setBitMask(int ignore) {
// Remove
}
@Override
public void setBiome(final int x, final int z, byte biome) {
if (this.biomes == null) {
this.biomes = new byte[256];
}
if (biome == 0) biome = -1;
biomes[((z & 15) << 4) + (x & 15)] = biome;
}
@Override
public abstract IntFaweChunk<T, V> copy(boolean shallow);
}

View File

@ -0,0 +1,811 @@
package com.boydti.fawe.example;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.IntegerPair;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.object.extent.LightingExtent;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.blocks.BlockMaterial;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BaseBiome;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.registry.BundledBlockData;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public abstract class MappedFaweQueue<WORLD, CHUNK, CHUNKSECTIONS, SECTION> implements LightingExtent, FaweQueue {
private WORLD impWorld;
private IFaweQueueMap map;
public int lastSectionX = Integer.MIN_VALUE;
public int lastSectionZ = Integer.MIN_VALUE;
public int lastSectionY = Integer.MIN_VALUE;
public CHUNK lastChunk;
public CHUNKSECTIONS lastChunkSections;
public SECTION lastSection;
private World weWorld;
private String world;
private ConcurrentLinkedDeque<EditSession> sessions;
private long modified = System.currentTimeMillis();
private RunnableVal2<FaweChunk, FaweChunk> changeTask;
private RunnableVal2<ProgressType, Integer> progressTask;
private SetQueue.QueueStage stage;
private Settings settings = Settings.IMP;
public ConcurrentLinkedDeque<Runnable> tasks = new ConcurrentLinkedDeque<>();
private CHUNK cachedLoadChunk;
public final RunnableVal<IntegerPair> loadChunk = new RunnableVal<IntegerPair>() {
{
this.value = new IntegerPair(0, 0);
}
@Override
public void run(IntegerPair coord) {
cachedLoadChunk = loadChunk(getWorld(), coord.x, coord.z, true);
}
};
public MappedFaweQueue(final World world) {
this(world, null);
}
public MappedFaweQueue(final String world) {
this.world = world;
map = getSettings().PREVENT_CRASHES ? new WeakFaweQueueMap(this) : new DefaultFaweQueueMap(this);
}
public MappedFaweQueue(final String world, IFaweQueueMap map) {
this.world = world;
if (map == null) {
map = getSettings().PREVENT_CRASHES ? new WeakFaweQueueMap(this) : new DefaultFaweQueueMap(this);
}
this.map = map;
}
public MappedFaweQueue(final World world, IFaweQueueMap map) {
this.weWorld = world;
if (world != null) this.world = Fawe.imp().getWorldName(world);
if (map == null) {
map = getSettings().PREVENT_CRASHES ? new WeakFaweQueueMap(this) : new DefaultFaweQueueMap(this);
}
this.map = map;
}
@Override
public int getMaxY() {
return weWorld == null ? 255 : weWorld.getMaxY();
}
public IFaweQueueMap getFaweQueueMap() {
return map;
}
@Override
public Collection<FaweChunk> getFaweChunks() {
return map.getFaweCunks();
}
@Override
public void optimize() {
final ForkJoinPool pool = TaskManager.IMP.getPublicForkJoinPool();
map.forEachChunk(new RunnableVal<FaweChunk>() {
@Override
public void run(final FaweChunk chunk) {
pool.submit(new Runnable() {
@Override
public void run() {
chunk.optimize();
}
});
}
});
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
public abstract WORLD getImpWorld();
public abstract boolean regenerateChunk(WORLD world, int x, int z, BaseBiome biome, Long seed);
@Override
public abstract FaweChunk getFaweChunk(int x, int z);
public abstract CHUNK loadChunk(WORLD world, int x, int z, boolean generate);
public abstract CHUNKSECTIONS getSections(CHUNK chunk);
public abstract CHUNKSECTIONS getCachedSections(WORLD world, int cx, int cz);
public abstract CHUNK getCachedChunk(WORLD world, int cx, int cz);
public WORLD getWorld() {
if (impWorld != null) {
return impWorld;
}
return impWorld = getImpWorld();
}
@Override
public boolean regenerateChunk(int x, int z, BaseBiome biome, Long seed) {
return regenerateChunk(getWorld(), x, z, biome, seed);
}
@Override
public void addNotifyTask(int x, int z, Runnable runnable) {
FaweChunk chunk = map.getFaweChunk(x, z);
chunk.addNotifyTask(runnable);
}
@Override
public boolean setBlock(int x, int y, int z, int id) {
int cx = x >> 4;
int cz = z >> 4;
FaweChunk chunk = map.getFaweChunk(cx, cz);
chunk.setBlock(x & 15, y, z & 15, id);
return true;
}
@Override
public void setTile(int x, int y, int z, CompoundTag tag) {
if ((y >= FaweChunk.HEIGHT) || (y < 0)) {
return;
}
int cx = x >> 4;
int cz = z >> 4;
FaweChunk chunk = map.getFaweChunk(cx, cz);
chunk.setTile(x & 15, y, z & 15, tag);
}
@Override
public void setEntity(int x, int y, int z, CompoundTag tag) {
if ((y >= FaweChunk.HEIGHT) || (y < 0)) {
return;
}
int cx = x >> 4;
int cz = z >> 4;
FaweChunk chunk = map.getFaweChunk(cx, cz);
chunk.setEntity(tag);
}
@Override
public void removeEntity(int x, int y, int z, UUID uuid) {
if ((y >= FaweChunk.HEIGHT) || (y < 0)) {
return;
}
int cx = x >> 4;
int cz = z >> 4;
FaweChunk chunk = map.getFaweChunk(cx, cz);
chunk.removeEntity(uuid);
}
@Override
public boolean setBiome(int x, int z, BaseBiome biome) {
int cx = x >> 4;
int cz = z >> 4;
FaweChunk chunk = map.getFaweChunk(cx, cz);
chunk.setBiome(x & 15, z & 15, biome);
return true;
}
@Override
public boolean next(int amount, long time) {
return map.next(amount, time);
}
public void start(FaweChunk chunk) {
chunk.start();
}
public void end(FaweChunk chunk) {
if (getProgressTask() != null) {
getProgressTask().run(ProgressType.DISPATCH, size() + 1);
}
chunk.end();
}
@Override
public void runTasks() {
synchronized (this) {
this.notifyAll();
}
if (getProgressTask() != null) {
try {
getProgressTask().run(ProgressType.DONE, 1);
} catch (Throwable e) {
e.printStackTrace();
}
}
while (!tasks.isEmpty()) {
Runnable task = tasks.poll();
if (task != null) {
try {
task.run();
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
}
if (getProgressTask() != null) {
try {
getProgressTask().run(ProgressType.DONE, 1);
} catch (Throwable e) {
e.printStackTrace();
}
}
ArrayDeque<Runnable> tmp = new ArrayDeque<>(tasks);
tasks.clear();
for (Runnable run : tmp) {
try {
run.run();
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
}
public Settings getSettings() {
return settings;
}
public void setSettings(Settings settings) {
this.settings = settings == null ? Settings.IMP : settings;
}
public void setWorld(String world) {
this.world = world;
this.weWorld = null;
}
public World getWEWorld() {
return weWorld != null ? weWorld : (weWorld = FaweAPI.getWorld(world));
}
public String getWorldName() {
return world;
}
@Override
public Collection<EditSession> getEditSessions() {
Collection<EditSession> tmp = sessions;
if (tmp == null) tmp = new HashSet<>();
return tmp;
}
@Override
public void addEditSession(EditSession session) {
ConcurrentLinkedDeque<EditSession> tmp = sessions;
if (tmp == null) tmp = new ConcurrentLinkedDeque<>();
tmp.add(session);
this.sessions = tmp;
}
@Override
public boolean supports(Capability capability) {
switch (capability) {
case CHANGE_TASKS: return true;
}
return false;
}
public void setSessions(ConcurrentLinkedDeque<EditSession> sessions) {
this.sessions = sessions;
}
public long getModified() {
return modified;
}
public void setModified(long modified) {
this.modified = modified;
}
public RunnableVal2<ProgressType, Integer> getProgressTask() {
return progressTask;
}
public void setProgressTask(RunnableVal2<ProgressType, Integer> progressTask) {
this.progressTask = progressTask;
}
public void setChangeTask(RunnableVal2<FaweChunk, FaweChunk> changeTask) {
this.changeTask = changeTask;
}
public RunnableVal2<FaweChunk, FaweChunk> getChangeTask() {
return changeTask;
}
public SetQueue.QueueStage getStage() {
return stage;
}
public void setStage(SetQueue.QueueStage stage) {
this.stage = stage;
}
public void addNotifyTask(Runnable runnable) {
this.tasks.add(runnable);
}
public void addTask(Runnable whenFree) {
tasks.add(whenFree);
}
@Override
public int size() {
int size = map.size();
if (size == 0 && getStage() == SetQueue.QueueStage.NONE) {
runTasks();
}
return size;
}
@Override
public void clear() {
lastSectionX = Integer.MIN_VALUE;
lastSectionZ = Integer.MIN_VALUE;
lastSectionY = -1;
lastChunk = null;
lastChunkSections = null;
map.clear();
runTasks();
}
@Override
public void setChunk(FaweChunk chunk) {
map.add(chunk);
}
public SECTION getCachedSection(CHUNKSECTIONS chunk, int cy) {
return (SECTION) lastChunkSections;
}
public abstract int getCombinedId4Data(SECTION section, int x, int y, int z);
public int getLocalCombinedId4Data(CHUNK chunk, int x, int y, int z) {
CHUNKSECTIONS sections = getSections(lastChunk);
SECTION section = getCachedSection(sections, y >> 4);
if (section == null) {
return 0;
}
return getCombinedId4Data(lastSection, x, y, z);
}
public abstract int getBiome(CHUNK chunk, int x, int z);
public abstract CompoundTag getTileEntity(CHUNK chunk, int x, int y, int z);
public CHUNK ensureChunkLoaded(int cx, int cz) throws FaweException.FaweChunkLoadException {
CHUNK chunk = getCachedChunk(getWorld(), cx, cz);
if (chunk != null) {
return chunk;
}
boolean sync = Thread.currentThread() == Fawe.get().getMainThread();
if (sync) {
return loadChunk(getWorld(), cx, cz, true);
} else if (getSettings().HISTORY.CHUNK_WAIT_MS > 0) {
cachedLoadChunk = null;
loadChunk.value.x = cx;
loadChunk.value.z = cz;
TaskManager.IMP.syncWhenFree(loadChunk, getSettings().HISTORY.CHUNK_WAIT_MS);
return cachedLoadChunk;
} else {
return null;
}
}
public boolean queueChunkLoad(final int cx, final int cz) {
CHUNK chunk = getCachedChunk(getWorld(), cx, cz);
if (chunk == null) {
SetQueue.IMP.addTask(new Runnable() {
@Override
public void run() {
loadChunk(getWorld(), cx, cz, true);
}
});
return true;
}
return false;
}
public boolean queueChunkLoad(final int cx, final int cz, RunnableVal<CHUNK> operation) {
operation.value = getCachedChunk(getWorld(), cx, cz);
if (operation.value == null) {
SetQueue.IMP.addTask(new Runnable() {
@Override
public void run() {
operation.value = loadChunk(getWorld(), cx, cz, true);
if (operation.value != null) TaskManager.IMP.async(operation);
}
});
return true;
} else {
TaskManager.IMP.async(operation);
}
return false;
}
@Override
public boolean hasBlock(int x, int y, int z) throws FaweException.FaweChunkLoadException {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return false;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return false;
}
}
if (lastSection == null) {
return false;
}
return hasBlock(lastSection, x, y, z);
}
public boolean hasBlock(SECTION section, int x, int y, int z) {
return getCombinedId4Data(lastSection, x, y, z) != 0;
}
public int getOpacity(SECTION section, int x, int y, int z) {
int combined = getCombinedId4Data(section, x, y, z);
if (combined == 0) {
return 0;
}
return Math.min(15, BlockTypes.getFromStateId(combined).getMaterial().getLightOpacity());
}
public int getBrightness(SECTION section, int x, int y, int z) {
int combined = getCombinedId4Data(section, x, y, z);
if (combined == 0) {
return 0;
}
return Math.min(15, BlockTypes.getFromStateId(combined).getMaterial().getLightValue());
}
public int getOpacityBrightnessPair(SECTION section, int x, int y, int z) {
return MathMan.pair16(Math.min(15, getOpacity(section, x, y, z)), getBrightness(section, x, y, z));
}
public abstract int getSkyLight(SECTION sections, int x, int y, int z);
public abstract int getEmmittedLight(SECTION sections, int x, int y, int z);
public int getLight(SECTION sections, int x, int y, int z) {
if (!hasSky()) {
return getEmmittedLight(sections, x, y, z);
}
return Math.max(getSkyLight(sections, x, y, z), getEmmittedLight(sections, x, y, z));
}
@Override
public int getLight(int x, int y, int z) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
return 0;
}
return getLight(lastSection, x, y, z);
}
@Override
public int getSkyLight(int x, int y, int z) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
if (lastChunkSections == null) {
return 0;
}
int max = FaweChunk.HEIGHT >> 4;
do {
if (++cy >= max) {
return 15;
}
lastSection = getCachedSection(lastChunkSections, cy);
} while (lastSection == null);
}
if (lastSection == null) {
return getSkyLight(x, y + 16, z);
}
return getSkyLight(lastSection, x, y, z);
}
@Override
public int getBlockLight(int x, int y, int z) {
return getEmmittedLight(x, y, z);
}
@Override
public int getEmmittedLight(int x, int y, int z) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
return 0;
}
return getEmmittedLight(lastSection, x, y, z);
}
@Override
public int getOpacity(int x, int y, int z) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
return 0;
}
return getOpacity(lastSection, x, y, z);
}
@Override
public int getOpacityBrightnessPair(int x, int y, int z) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
return 0;
}
return getOpacityBrightnessPair(lastSection, x, y, z);
}
@Override
public int getBrightness(int x, int y, int z) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
return 0;
}
return getBrightness(lastSection, x, y, z);
}
@Override
public int getCachedCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException {
int cx = x >> 4;
int cz = z >> 4;
FaweChunk fc = map.getCachedFaweChunk(cx, cz);
if (fc != null) {
int combined = fc.getBlockCombinedId(x & 15, y, z & 15);
if (combined != 0) {
return combined;
}
}
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
return 0;
}
return getCombinedId4Data(lastSection, x, y, z);
}
@Override
public int getCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return 0;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return 0;
}
}
if (lastSection == null) {
return 0;
}
return getCombinedId4Data(lastSection, x, y, z);
}
@Override
public int getBiomeId(int x, int z) throws FaweException.FaweChunkLoadException {
int cx = x >> 4;
int cz = z >> 4;
lastSectionY = -1;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
} else {
lastChunkSections = null;
return 0;
}
} else if (lastChunk == null) {
return 0;
}
return getBiome(lastChunk, x, z) & 0xFF;
}
@Override
public CompoundTag getTileEntity(int x, int y, int z) throws FaweException.FaweChunkLoadException {
int cx = x >> 4;
int cz = z >> 4;
lastSectionY = -1;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
} else {
lastChunkSections = null;
return null;
}
} else if (lastChunk == null) {
return null;
}
return getTileEntity(lastChunk, x, y, z);
}
}

View File

@ -0,0 +1,230 @@
package com.boydti.fawe.example;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public abstract class NMSMappedFaweQueue<WORLD, CHUNK, CHUNKSECTION, SECTION> extends MappedFaweQueue<WORLD, CHUNK, CHUNKSECTION, SECTION> {
private final int maxY;
public NMSMappedFaweQueue(World world) {
super(world);
this.maxY = world.getMaxY();
}
public NMSMappedFaweQueue(String world) {
super(world);
this.maxY = 256;
}
public NMSMappedFaweQueue(String world, IFaweQueueMap map) {
super(world, map);
this.maxY = 256;
}
public NMSMappedFaweQueue(World world, IFaweQueueMap map) {
super(world, map);
this.maxY = world.getMaxY();
}
@Override
public void runTasks() {
super.runTasks();
if (!getRelighter().isEmpty()) {
TaskManager.IMP.async(new Runnable() {
@Override
public void run() {
if (getSettings().IMP.LIGHTING.REMOVE_FIRST) {
getRelighter().removeAndRelight(hasSky());
} else {
getRelighter().fixLightingSafe(hasSky());
}
}
});
}
}
private final Relighter relighter = getSettings().IMP.LIGHTING.MODE > 0 ? new NMSRelighter(this) : NullRelighter.INSTANCE;
@Override
public Relighter getRelighter() {
return relighter;
}
@Override
public void end(FaweChunk chunk) {
super.end(chunk);
if (getSettings().IMP.LIGHTING.MODE == 0) {
sendChunk(chunk);
return;
}
if (!getSettings().IMP.LIGHTING.DELAY_PACKET_SENDING) {
sendChunk(chunk);
}
if (getSettings().IMP.LIGHTING.MODE == 2) {
getRelighter().addChunk(chunk.getX(), chunk.getZ(), null, chunk.getBitMask());
return;
}
IntFaweChunk cfc = (IntFaweChunk) chunk;
boolean relight = false;
byte[] fix = new byte[(maxY + 1) >> 4];
boolean sky = hasSky();
if (sky) {
for (int i = cfc.ids.length - 1; i >= 0; i--) {
int air = cfc.getAir(i);
int solid = cfc.getCount(i);
if (air == 4096) {
fix[i] = Relighter.SkipReason.AIR;
} else if (air == 0 && solid == 4096) {
fix[i] = Relighter.SkipReason.SOLID;
} else if (solid == 0 && relight == false) {
fix[i] = Relighter.SkipReason.AIR;
} else {
fix[i] = Relighter.SkipReason.NONE;
relight = true;
}
}
}
if (relight) {
getRelighter().addChunk(chunk.getX(), chunk.getZ(), fix, chunk.getBitMask());
} else if (getSettings().IMP.LIGHTING.DELAY_PACKET_SENDING) {
sendChunk(chunk);
}
}
@Override
public void sendChunk(final FaweChunk fc) {
try {
refreshChunk(fc);
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
public abstract void setHeightMap(FaweChunk chunk, byte[] heightMap);
public abstract void setFullbright(CHUNKSECTION sections);
public boolean removeLighting(CHUNKSECTION sections, RelightMode mode, boolean hasSky) {
boolean result = false;
for (int i = 0; i < 16; i++) {
SECTION section = getCachedSection(sections, i);
if (section != null) {
result |= removeSectionLighting(section, i, hasSky);
}
}
return result;
}
public abstract boolean removeSectionLighting(SECTION sections, int layer, boolean hasSky);
public boolean isSurrounded(final char[][] sections, final int x, final int y, final int z) {
return this.isSolid(this.getId(sections, x, y + 1, z))
&& this.isSolid(this.getId(sections, x + 1, y - 1, z))
&& this.isSolid(this.getId(sections, x - 1, y, z))
&& this.isSolid(this.getId(sections, x, y, z + 1))
&& this.isSolid(this.getId(sections, x, y, z - 1));
}
public boolean isSolid(final int id) {
return !BlockTypes.get(id).getMaterial().isTranslucent();
}
public int getId(final char[][] sections, final int x, final int y, final int z) {
if ((x < 0) || (x > 15) || (z < 0) || (z > 15)) {
return 1;
}
if ((y < 0) || (y > maxY)) {
return 1;
}
final int i = FaweCache.CACHE_I[y][z][x];
final char[] section = sections[i];
if (section == null) {
return 0;
}
final int j = FaweCache.CACHE_J[y][z][x];
return section[j] >> 4;
}
public void saveChunk(CHUNK chunk) {
}
public abstract void relight(int x, int y, int z);
public abstract void relightBlock(int x, int y, int z);
public abstract void relightSky(int x, int y, int z);
public void setSkyLight(int x, int y, int z, int value) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return;
}
}
if (lastSection == null) {
return;
}
setSkyLight(lastSection, x, y, z, value);
}
public void setBlockLight(int x, int y, int z, int value) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 4;
if (cx != lastSectionX || cz != lastSectionZ) {
lastSectionX = cx;
lastSectionZ = cz;
lastChunk = ensureChunkLoaded(cx, cz);
if (lastChunk != null) {
lastChunkSections = getSections(lastChunk);
lastSection = getCachedSection(lastChunkSections, cy);
} else {
lastChunkSections = null;
return;
}
} else if (cy != lastSectionY) {
if (lastChunkSections != null) {
lastSection = getCachedSection(lastChunkSections, cy);
} else {
return;
}
}
if (lastSection == null) {
return;
}
setBlockLight(lastSection, x, y, z, value);
}
public abstract void setSkyLight(SECTION section, int x, int y, int z, int value);
public abstract void setBlockLight(SECTION section, int x, int y, int z, int value);
public abstract void refreshChunk(FaweChunk fs);
public abstract IntFaweChunk getPrevious(IntFaweChunk fs, CHUNKSECTION sections, Map<?, ?> tiles, Collection<?>[] entities, Set<UUID> createdEntities, boolean all) throws Exception;
}

View File

@ -0,0 +1,597 @@
package com.boydti.fawe.example;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.IntegerTrio;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.collection.BlockVectorSet;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.TaskManager;
import com.google.common.io.LineReader;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.io.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class NMSRelighter implements Relighter {
private final NMSMappedFaweQueue queue;
private final Map<Long, RelightSkyEntry> skyToRelight;
private final Object present = new Object();
private final Map<Long, Integer> chunksToSend;
private final ConcurrentLinkedQueue<RelightSkyEntry> queuedSkyToRelight = new ConcurrentLinkedQueue<>();
private final Map<Long, long[][][] /* z y x */ > lightQueue;
private final AtomicBoolean lightLock = new AtomicBoolean(false);
private final ConcurrentHashMap<Long, long[][][]> concurrentLightQueue;
private final int maxY;
private volatile boolean relighting = false;
public final IntegerTrio mutableBlockPos = new IntegerTrio();
private static final int DISPATCH_SIZE = 64;
private boolean removeFirst;
public NMSRelighter(NMSMappedFaweQueue queue) {
this.queue = queue;
this.skyToRelight = new Long2ObjectOpenHashMap<>();
this.lightQueue = new Long2ObjectOpenHashMap<>();
this.chunksToSend = new Long2ObjectOpenHashMap<>();
this.concurrentLightQueue = new ConcurrentHashMap<>();
this.maxY = queue.getMaxY();
}
@Override
public boolean isEmpty() {
return skyToRelight.isEmpty() && lightQueue.isEmpty() && queuedSkyToRelight.isEmpty() && concurrentLightQueue.isEmpty();
}
@Override
public synchronized void removeAndRelight(boolean sky) {
removeFirst = true;
fixLightingSafe(sky);
removeFirst = false;
}
private void set(int x, int y, int z, long[][][] map) {
long[][] m1 = map[z];
if (m1 == null) {
m1 = map[z] = new long[16][];
}
long[] m2 = m1[x];
if (m2 == null) {
m2 = m1[x] = new long[4];
}
long value = m2[y >> 6] |= 1l << y;
}
public void addLightUpdate(int x, int y, int z) {
long index = MathMan.pairInt(x >> 4, z >> 4);
if (lightLock.compareAndSet(false, true)) {
synchronized (lightQueue) {
try {
long[][][] currentMap = lightQueue.get(index);
if (currentMap == null) {
currentMap = new long[16][][];
this.lightQueue.put(index, currentMap);
}
set(x & 15, y, z & 15, currentMap);
} finally {
lightLock.set(false);
}
}
} else {
long[][][] currentMap = concurrentLightQueue.get(index);
if (currentMap == null) {
currentMap = new long[16][][];
this.concurrentLightQueue.put(index, currentMap);
}
set(x & 15, y, z & 15, currentMap);
}
}
public synchronized void clear() {
queuedSkyToRelight.clear();
skyToRelight.clear();
chunksToSend.clear();
lightQueue.clear();
concurrentLightQueue.clear();
}
public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) {
RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask);
queuedSkyToRelight.add(toPut);
return true;
}
private synchronized Map<Long, RelightSkyEntry> getSkyMap() {
RelightSkyEntry entry;
while ((entry = queuedSkyToRelight.poll()) != null) {
long pair = MathMan.pairInt(entry.x, entry.z);
RelightSkyEntry existing = skyToRelight.put(pair, entry);
if (existing != null) {
entry.bitmask |= existing.bitmask;
if (entry.fix != null) {
for (int i = 0; i < entry.fix.length; i++) {
entry.fix[i] &= existing.fix[i];
}
}
}
}
return skyToRelight;
}
public synchronized void removeLighting() {
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = getSkyMap().entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> entry = iter.next();
RelightSkyEntry chunk = entry.getValue();
long pair = entry.getKey();
Integer existing = chunksToSend.get(pair);
chunksToSend.put(pair, chunk.bitmask | (existing != null ? existing : 0));
queue.ensureChunkLoaded(chunk.x, chunk.z);
Object sections = queue.getCachedSections(queue.getWorld(), chunk.x, chunk.z);
queue.removeLighting(sections, FaweQueue.RelightMode.ALL, queue.hasSky());
iter.remove();
}
}
public void updateBlockLight(Map<Long, long[][][]> map) {
int size = map.size();
if (size == 0) {
return;
}
Queue<IntegerTrio> lightPropagationQueue = new ArrayDeque<>();
Queue<Object[]> lightRemovalQueue = new ArrayDeque<>();
Map<IntegerTrio, Object> visited = new HashMap<>();
Map<IntegerTrio, Object> removalVisited = new HashMap<>();
Iterator<Map.Entry<Long, long[][][]>> iter = map.entrySet().iterator();
while (iter.hasNext() && size-- > 0) {
Map.Entry<Long, long[][][]> entry = iter.next();
long index = entry.getKey();
long[][][] blocks = entry.getValue();
int chunkX = MathMan.unpairIntX(index);
int chunkZ = MathMan.unpairIntY(index);
int bx = chunkX << 4;
int bz = chunkZ << 4;
for (int lz = 0; lz < blocks.length; lz++) {
long[][] m1 = blocks[lz];
if (m1 == null) continue;
for (int lx = 0; lx < m1.length; lx++) {
long[] m2 = m1[lx];
if (m2 == null) continue;
for (int i = 0; i < m2.length; i++) {
int yStart = i << 6;
long value = m2[i];
if (value != 0) {
for (int j = 0; j < 64; j++) {
if (((value >> j) & 1) == 1) {
int x = lx + bx;
int y = yStart + j;
int z = lz + bz;
int oldLevel = queue.getEmmittedLight(x, y, z);
int newLevel = queue.getBrightness(x, y, z);
if (oldLevel != newLevel) {
queue.setBlockLight(x, y, z, newLevel);
IntegerTrio node = new IntegerTrio(x, y, z);
if (newLevel < oldLevel) {
removalVisited.put(node, present);
lightRemovalQueue.add(new Object[]{node, oldLevel});
} else {
visited.put(node, present);
lightPropagationQueue.add(node);
}
}
}
}
}
}
}
}
iter.remove();
}
while (!lightRemovalQueue.isEmpty()) {
Object[] val = lightRemovalQueue.poll();
IntegerTrio node = (IntegerTrio) val[0];
int lightLevel = (int) val[1];
this.computeRemoveBlockLight(node.x - 1, node.y, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited);
this.computeRemoveBlockLight(node.x + 1, node.y, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited);
if (node.y > 0) {
this.computeRemoveBlockLight(node.x, node.y - 1, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited);
}
if (node.y < 255) {
this.computeRemoveBlockLight(node.x, node.y + 1, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited);
}
this.computeRemoveBlockLight(node.x, node.y, node.z - 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited);
this.computeRemoveBlockLight(node.x, node.y, node.z + 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited);
}
while (!lightPropagationQueue.isEmpty()) {
IntegerTrio node = lightPropagationQueue.poll();
int lightLevel = queue.getEmmittedLight(node.x, node.y, node.z);
if (lightLevel > 1) {
this.computeSpreadBlockLight(node.x - 1, node.y, node.z, lightLevel, lightPropagationQueue, visited);
this.computeSpreadBlockLight(node.x + 1, node.y, node.z, lightLevel, lightPropagationQueue, visited);
if (node.y > 0) {
this.computeSpreadBlockLight(node.x, node.y - 1, node.z, lightLevel, lightPropagationQueue, visited);
}
if (node.y < 255) {
this.computeSpreadBlockLight(node.x, node.y + 1, node.z, lightLevel, lightPropagationQueue, visited);
}
this.computeSpreadBlockLight(node.x, node.y, node.z - 1, lightLevel, lightPropagationQueue, visited);
this.computeSpreadBlockLight(node.x, node.y, node.z + 1, lightLevel, lightPropagationQueue, visited);
}
}
}
private void computeRemoveBlockLight(int x, int y, int z, int currentLight, Queue<Object[]> queue, Queue<IntegerTrio> spreadQueue, Map<IntegerTrio, Object> visited,
Map<IntegerTrio, Object> spreadVisited) {
int current = this.queue.getEmmittedLight(x, y, z);
if (current != 0 && current < currentLight) {
this.queue.setBlockLight(x, y, z, 0);
if (current > 1) {
if (!visited.containsKey(mutableBlockPos)) {
IntegerTrio index = new IntegerTrio(x, y, z);
visited.put(index, present);
queue.add(new Object[]{index, current});
}
}
} else if (current >= currentLight) {
mutableBlockPos.set(x, y, z);
if (!spreadVisited.containsKey(mutableBlockPos)) {
IntegerTrio index = new IntegerTrio(x, y, z);
spreadVisited.put(index, present);
spreadQueue.add(index);
}
}
}
private void computeSpreadBlockLight(int x, int y, int z, int currentLight, Queue<IntegerTrio> queue, Map<IntegerTrio, Object> visited) {
currentLight = currentLight - Math.max(1, this.queue.getOpacity(x, y, z));
if (currentLight > 0) {
int current = this.queue.getEmmittedLight(x, y, z);
if (current < currentLight) {
this.queue.setBlockLight(x, y, z, currentLight);
mutableBlockPos.set(x, y, z);
if (!visited.containsKey(mutableBlockPos)) {
visited.put(new IntegerTrio(x, y, z), present);
if (currentLight > 1) {
queue.add(new IntegerTrio(x, y, z));
}
}
}
}
}
public void fixLightingSafe(boolean sky) {
if (isEmpty()) return;
try {
if (sky) {
fixSkyLighting();
} else {
synchronized (this) {
Map<Long, RelightSkyEntry> map = getSkyMap();
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> entry = iter.next();
chunksToSend.put(entry.getKey(), entry.getValue().bitmask);
iter.remove();
}
}
}
fixBlockLighting();
sendChunks();
} catch (Throwable e) {
e.printStackTrace();
}
}
public void fixBlockLighting() {
synchronized (lightQueue) {
while (!lightLock.compareAndSet(false, true));
try {
updateBlockLight(this.lightQueue);
} finally {
lightLock.set(false);
}
}
}
public synchronized void sendChunks() {
RunnableVal<Object> runnable = new RunnableVal<Object>() {
@Override
public void run(Object value) {
Iterator<Map.Entry<Long, Integer>> iter = chunksToSend.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, Integer> entry = iter.next();
long pair = entry.getKey();
int bitMask = entry.getValue();
int x = MathMan.unpairIntX(pair);
int z = MathMan.unpairIntY(pair);
queue.sendChunk(x, z, bitMask);
iter.remove();
}
}
};
if (Settings.IMP.LIGHTING.ASYNC) {
runnable.run();
} else {
TaskManager.IMP.sync(runnable);
}
}
private boolean isTransparent(int x, int y, int z) {
return queue.getOpacity(x, y, z) < 15;
}
public synchronized void fixSkyLighting() {
// Order chunks
Map<Long, RelightSkyEntry> map = getSkyMap();
ArrayList<RelightSkyEntry> chunksList = new ArrayList<>(map.size());
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> entry = iter.next();
chunksToSend.put(entry.getKey(), entry.getValue().bitmask);
chunksList.add(entry.getValue());
iter.remove();
}
Collections.sort(chunksList);
int size = chunksList.size();
if (size > DISPATCH_SIZE) {
int amount = (size + DISPATCH_SIZE - 1) / DISPATCH_SIZE;
for (int i = 0; i < amount; i++) {
int start = i * DISPATCH_SIZE;
int end = Math.min(size, start + DISPATCH_SIZE);
List<RelightSkyEntry> sub = chunksList.subList(start, end);
fixSkyLighting(sub);
}
} else {
fixSkyLighting(chunksList);
}
}
public void fill(byte[] mask, int chunkX, int y, int chunkZ, byte reason) {
if (y >= FaweChunk.HEIGHT) {
Arrays.fill(mask, (byte) 15);
return;
}
switch (reason) {
case SkipReason.SOLID: {
Arrays.fill(mask, (byte) 0);
return;
}
case SkipReason.AIR: {
int bx = chunkX << 4;
int bz = chunkZ << 4;
int index = 0;
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
mask[index++] = (byte) queue.getSkyLight(bx + x, y, bz + z);
}
}
}
}
}
private void fixSkyLighting(List<RelightSkyEntry> sorted) {
RelightSkyEntry[] chunks = sorted.toArray(new RelightSkyEntry[sorted.size()]);
boolean remove = this.removeFirst;
BlockVectorSet chunkSet = null;
if (remove) {
chunkSet = new BlockVectorSet();
BlockVectorSet tmpSet = new BlockVectorSet();
for (RelightSkyEntry chunk : chunks) {
tmpSet.add(chunk.x, 0, chunk.z);
}
for (RelightSkyEntry chunk : chunks) {
int x = chunk.x;
int z = chunk.z;
if (tmpSet.contains(x + 1, 0, z) && tmpSet.contains(x - 1, 0, z) && tmpSet.contains(x, 0, z + 1) && tmpSet.contains(x, 0, z - 1)) {
chunkSet.add(x, 0, z);
}
}
}
byte[] cacheX = FaweCache.CACHE_X[0];
byte[] cacheZ = FaweCache.CACHE_Z[0];
for (int y = FaweChunk.HEIGHT - 1; y > 0; y--) {
for (RelightSkyEntry chunk : chunks) { // Propogate skylight
int layer = y >> 4;
byte[] mask = chunk.mask;
if (chunk.fix[layer] != SkipReason.NONE) {
if ((y & 15) == 0 && layer != 0 && chunk.fix[layer - 1] == SkipReason.NONE) {
fill(mask, chunk.x, y, chunk.z, chunk.fix[layer]);
}
continue;
}
int bx = chunk.x << 4;
int bz = chunk.z << 4;
Object chunkObj = queue.ensureChunkLoaded(chunk.x, chunk.z);
Object sections = queue.getCachedSections(queue.getWorld(), chunk.x, chunk.z);
if (sections == null) continue;
Object section = queue.getCachedSection(sections, layer);
if (section == null) continue;
chunk.smooth = false;
if (remove && (y & 15) == 15 && chunkSet.contains(chunk.x, 0, chunk.z)) {
queue.removeSectionLighting(section, y >> 4, true);
}
for (int j = 0; j <= maxY; j++) {
int x = cacheX[j];
int z = cacheZ[j];
byte value = mask[j];
byte pair = (byte) queue.getOpacityBrightnessPair(section, x, y, z);
int opacity = MathMan.unpair16x(pair);
int brightness = MathMan.unpair16y(pair);
if (brightness > 1 && (brightness != 15 || opacity != 15)) {
addLightUpdate(bx + x, y, bz + z);
}
switch (value) {
case 0:
if (opacity > 1) {
queue.setSkyLight(section, x, y, z, 0);
continue;
}
break;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
if (opacity >= value) {
mask[j] = 0;
queue.setSkyLight(section, x, y, z, 0);
continue;
}
if (opacity <= 1) {
mask[j] = --value;
} else {
mask[j] = value = (byte) Math.max(0, value - opacity);
}
break;
case 15:
if (opacity > 1) {
value -= opacity;
mask[j] = value;
}
queue.setSkyLight(section, x, y, z, value);
continue;
}
chunk.smooth = true;
queue.setSkyLight(section, x, y, z, value);
}
queue.saveChunk(chunkObj);
}
for (RelightSkyEntry chunk : chunks) { // Smooth forwards
if (chunk.smooth) {
smoothSkyLight(chunk, y, true);
}
}
for (int i = chunks.length - 1; i >= 0; i--) { // Smooth backwards
RelightSkyEntry chunk = chunks[i];
if (chunk.smooth) {
smoothSkyLight(chunk, y, false);
}
}
}
}
public void smoothSkyLight(RelightSkyEntry chunk, int y, boolean direction) {
byte[] mask = chunk.mask;
int bx = chunk.x << 4;
int bz = chunk.z << 4;
queue.ensureChunkLoaded(chunk.x, chunk.z);
Object sections = queue.getCachedSections(queue.getWorld(), chunk.x, chunk.z);
if (sections == null) return;
Object section = queue.getCachedSection(sections, y >> 4);
if (section == null) return;
if (direction) {
for (int j = 0; j < 256; j++) {
int x = j & 15;
int z = j >> 4;
if (mask[j] >= 14 || (mask[j] == 0 && queue.getOpacity(section, x, y, z) > 1)) {
continue;
}
byte value = mask[j];
if ((value = (byte) Math.max(queue.getSkyLight(bx + x - 1, y, bz + z) - 1, value)) >= 14) ;
else if ((value = (byte) Math.max(queue.getSkyLight(bx + x, y, bz + z - 1) - 1, value)) >= 14) ;
if (value > mask[j]) queue.setSkyLight(section, x, y, z, mask[j] = value);
}
} else {
for (int j = 255; j >= 0; j--) {
int x = j & 15;
int z = j >> 4;
if (mask[j] >= 14 || (mask[j] == 0 && queue.getOpacity(section, x, y, z) > 1)) {
continue;
}
byte value = mask[j];
if ((value = (byte) Math.max(queue.getSkyLight(bx + x + 1, y, bz + z) - 1, value)) >= 14) ;
else if ((value = (byte) Math.max(queue.getSkyLight(bx + x, y, bz + z + 1) - 1, value)) >= 14) ;
if (value > mask[j]) queue.setSkyLight(section, x, y, z, mask[j] = value);
}
}
}
public boolean isUnlit(byte[] array) {
for (byte val : array) {
if (val != 0) {
return false;
}
}
return true;
}
private class RelightSkyEntry implements Comparable {
public final int x;
public final int z;
public final byte[] mask;
public final byte[] fix;
public int bitmask;
public boolean smooth;
public RelightSkyEntry(int x, int z, byte[] fix, int bitmask) {
this.x = x;
this.z = z;
byte[] array = new byte[256];
Arrays.fill(array, (byte) 15);
this.mask = array;
this.bitmask = bitmask;
if (fix == null) {
this.fix = new byte[(maxY + 1) >> 4];
Arrays.fill(this.fix, SkipReason.NONE);
} else {
this.fix = fix;
}
}
@Override
public String toString() {
return x + "," + z;
}
@Override
public int compareTo(Object o) {
RelightSkyEntry other = (RelightSkyEntry) o;
if (other.x < x) {
return 1;
}
if (other.x > x) {
return -1;
}
if (other.z < z) {
return 1;
}
if (other.z > z) {
return -1;
}
return 0;
}
}
}

View File

@ -0,0 +1,116 @@
package com.boydti.fawe.example;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class NullFaweChunk extends FaweChunk<Void> {
public static final NullFaweChunk INSTANCE = new NullFaweChunk(null, 0, 0);
/**
* A FaweSections object represents a chunk and the blocks that you wish to change in it.
*
* @param parent
* @param x
* @param z
*/
public NullFaweChunk(FaweQueue parent, int x, int z) {
super(parent, x, z);
}
@Override
public int[][] getCombinedIdArrays() {
return new int[16][];
}
@Override
public int[] getIdArray(int layer) {
return null;
}
@Override
public byte[] getBiomeArray() {
return new byte[256];
}
@Override
public int getBitMask() {
return 0;
}
@Override
public int getBlockCombinedId(int x, int y, int z) {
return 0;
}
@Override
public Void getChunk() {
return null;
}
@Override
public void setTile(int x, int y, int z, CompoundTag tile) {
}
@Override
public void setEntity(CompoundTag entity) {
}
@Override
public void removeEntity(UUID uuid) {
}
@Override
public void setBlock(int x, int y, int z, int combinedId) {
}
@Override
public Set<CompoundTag> getEntities() {
return new HashSet<>();
}
@Override
public Set<UUID> getEntityRemoves() {
return new HashSet<>();
}
@Override
public Map<Short, CompoundTag> getTiles() {
return new HashMap<>();
}
@Override
public CompoundTag getTile(int x, int y, int z) {
return null;
}
@Override
public void setBiome(int x, int z, BaseBiome biome) {
}
@Override
public void setBiome(int x, int z, byte biome) {
}
@Override
public FaweChunk<Void> copy(boolean shallow) {
return this;
}
@Override
public FaweChunk call() {
return null;
}
}

View File

@ -0,0 +1,34 @@
package com.boydti.fawe.example;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.util.MainUtil;
public class NullQueueIntFaweChunk extends IntFaweChunk {
public NullQueueIntFaweChunk(int cx, int cz) {
super(null, cx, cz);
}
public NullQueueIntFaweChunk(int x, int z, int[][] ids, short[] count, short[] air, byte[] heightMap) {
super(null, x, z, ids, count, air, heightMap);
}
@Override
public Object getNewChunk() {
return null;
}
@Override
public IntFaweChunk copy(boolean shallow) {
if (shallow) {
return new NullQueueIntFaweChunk(getX(), getZ(), ids, count, air, heightMap);
} else {
return new NullQueueIntFaweChunk(getX(), getZ(), (int[][]) MainUtil.copyNd(ids), count.clone(), air.clone(), heightMap.clone());
}
}
@Override
public FaweChunk call() {
return null;
}
}

View File

@ -0,0 +1,49 @@
package com.boydti.fawe.example;
public class NullRelighter implements Relighter {
public static NullRelighter INSTANCE = new NullRelighter();
private NullRelighter() {
}
@Override
public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) {
return false;
}
@Override
public void addLightUpdate(int x, int y, int z) {
}
@Override
public void fixLightingSafe(boolean sky) {
}
@Override
public void clear() {
}
@Override
public void removeLighting() {
}
@Override
public void fixBlockLighting() {
}
@Override
public void fixSkyLighting() {
}
@Override
public boolean isEmpty() {
return true;
}
}

View File

@ -0,0 +1,30 @@
package com.boydti.fawe.example;
public interface Relighter {
boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask);
void addLightUpdate(int x, int y, int z);
void fixLightingSafe(boolean sky);
default void removeAndRelight(boolean sky) {
removeLighting();
fixLightingSafe(sky);
}
void clear();
void removeLighting();
void fixBlockLighting();
void fixSkyLighting();
boolean isEmpty();
public static class SkipReason {
public static final byte NONE = 0;
public static final byte AIR = 1;
public static final byte SOLID = 2;
}
}

View File

@ -0,0 +1,40 @@
package com.boydti.fawe.example;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.util.MainUtil;
public class SimpleIntFaweChunk extends IntFaweChunk {
public SimpleIntFaweChunk(FaweQueue parent, int x, int z) {
super(parent, x, z);
}
public SimpleIntFaweChunk(FaweQueue parent, int x, int z, int[][] ids, short[] count, short[] air, byte[] heightMap) {
super(parent, x, z, ids, count, air, heightMap);
}
@Override
public Object getNewChunk() {
return this;
}
@Override
public IntFaweChunk copy(boolean shallow) {
SimpleIntFaweChunk copy;
if (shallow) {
copy = new SimpleIntFaweChunk(getParent(), getX(), getZ(), ids, count, air, heightMap);
copy.biomes = biomes;
} else {
copy = new SimpleIntFaweChunk(getParent(), getX(), getZ(), (int[][]) MainUtil.copyNd(ids), count.clone(), air.clone(), heightMap.clone());
copy.biomes = biomes != null ? biomes.clone() : null;
}
return copy;
}
@Override
public FaweChunk call() {
getParent().setChunk(this);
return this;
}
}

View File

@ -0,0 +1,242 @@
package com.boydti.fawe.example;
import com.boydti.fawe.Fawe;
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 it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class WeakFaweQueueMap implements IFaweQueueMap {
private final MappedFaweQueue parent;
public WeakFaweQueueMap(MappedFaweQueue parent) {
this.parent = parent;
}
public final Long2ObjectOpenHashMap<Reference<FaweChunk>> blocks = new Long2ObjectOpenHashMap<Reference<FaweChunk>>() {
@Override
public Reference<FaweChunk> put(Long key, Reference<FaweChunk> value) {
return put((long) key, value);
}
@Override
public Reference<FaweChunk> put(long key, Reference<FaweChunk> value) {
if (parent.getProgressTask() != null) {
try {
parent.getProgressTask().run(FaweQueue.ProgressType.QUEUE, size());
} catch (Throwable e) {
e.printStackTrace();
}
}
synchronized (this) {
return super.put(key, value);
}
}
};
@Override
public Collection<FaweChunk> getFaweCunks() {
HashSet<FaweChunk> set = new HashSet<>();
synchronized (blocks) {
Iterator<Map.Entry<Long, Reference<FaweChunk>>> iter = blocks.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, Reference<FaweChunk>> entry = iter.next();
FaweChunk value = entry.getValue().get();
if (value != null) {
set.add(value);
} else {
Fawe.debug("Skipped modifying chunk due to low memory (1)");
iter.remove();
}
}
return set;
}
}
@Override
public void forEachChunk(RunnableVal<FaweChunk> onEach) {
synchronized (blocks) {
Iterator<Map.Entry<Long, Reference<FaweChunk>>> iter = blocks.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, Reference<FaweChunk>> entry = iter.next();
FaweChunk value = entry.getValue().get();
if (value != null) {
onEach.run(value);
} else {
Fawe.debug("Skipped modifying chunk due to low memory (2)");
iter.remove();
}
}
}
}
@Override
public FaweChunk getFaweChunk(int cx, int cz) {
if (cx == lastX && cz == lastZ) {
return lastWrappedChunk;
}
long pair = MathMan.pairInt(cx, cz);
Reference<FaweChunk> chunkReference = this.blocks.get(pair);
FaweChunk chunk;
if (chunkReference == null || (chunk = chunkReference.get()) == null) {
chunk = this.getNewFaweChunk(cx, cz);
Reference<FaweChunk> previous = this.blocks.put(pair, new SoftReference(chunk));
if (previous != null) {
FaweChunk tmp = previous.get();
if (tmp != null) {
chunk = tmp;
this.blocks.put(pair, previous);
}
}
}
return chunk;
}
@Override
public FaweChunk getCachedFaweChunk(int cx, int cz) {
if (cx == lastX && cz == lastZ) {
return lastWrappedChunk;
}
long pair = MathMan.pairInt(cx, cz);
Reference<FaweChunk> reference = this.blocks.get(pair);
if (reference != null) {
return reference.get();
} else {
return null;
}
}
@Override
public void add(FaweChunk chunk) {
long pair = MathMan.pairInt(chunk.getX(), chunk.getZ());
Reference<FaweChunk> previous = this.blocks.put(pair, new SoftReference<FaweChunk>(chunk));
if (previous != null) {
FaweChunk previousChunk = previous.get();
if (previousChunk != null) {
blocks.put(pair, previous);
}
}
}
@Override
public void clear() {
blocks.clear();
}
@Override
public int size() {
return blocks.size();
}
private FaweChunk getNewFaweChunk(int cx, int cz) {
return parent.getFaweChunk(cx, cz);
}
private FaweChunk lastWrappedChunk;
private int lastX = Integer.MIN_VALUE;
private int lastZ = Integer.MIN_VALUE;
@Override
public boolean next(int amount, long time) {
synchronized (blocks) {
try {
boolean skip = parent.getStage() == SetQueue.QueueStage.INACTIVE;
int added = 0;
Iterator<Map.Entry<Long, Reference<FaweChunk>>> iter = blocks.entrySet().iterator();
if (amount == 1) {
long start = System.currentTimeMillis();
do {
if (iter.hasNext()) {
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);
chunk.call();
parent.end(chunk);
} else {
Fawe.debug("Skipped modifying chunk due to low memory (3)");
}
} else {
break;
}
} while (System.currentTimeMillis() - start < time);
return !blocks.isEmpty();
}
ExecutorCompletionService service = SetQueue.IMP.getCompleterService();
ForkJoinPool pool = SetQueue.IMP.getForkJoinPool();
boolean result = true;
// amount = 8;
for (int i = 0; i < amount && (result = iter.hasNext()); ) {
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);
service.submit(chunk);
added++;
i++;
} else {
Fawe.debug("Skipped modifying chunk due to low memory (4)");
}
}
// if result, then submitted = amount
if (result) {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < time && result) {
if (result = iter.hasNext()) {
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);
service.submit(chunk);
Future future = service.poll(50, TimeUnit.MILLISECONDS);
if (future != null) {
FaweChunk fc = (FaweChunk) future.get();
parent.end(fc);
}
}
}
}
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
Future future;
while ((future = service.poll()) != null) {
FaweChunk fc = (FaweChunk) future.get();
parent.end(fc);
}
} catch (Throwable e) {
e.printStackTrace();
}
return !blocks.isEmpty();
}
}
}

View File

@ -0,0 +1,44 @@
package com.boydti.fawe.installer;
import com.boydti.fawe.Fawe;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.prefs.Preferences;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.stage.DirectoryChooser;
public abstract class BrowseButton extends InteractiveButton {
private final String id;
public BrowseButton(String id) {
super("Browse");
this.id = id;
}
public abstract void onSelect(File folder);
@Override
public void actionPerformed(ActionEvent e) {
Preferences prefs = Preferences.userRoot().node(Fawe.class.getName());
String lastUsed = prefs.get("LAST_USED_FOLDER", null);
final File lastFile = lastUsed == null ? null : new File(lastUsed).getParentFile();
browse(lastFile);
}
public void browse(File from) {
DirectoryChooser folderChooser = new DirectoryChooser();
folderChooser.setInitialDirectory(from);
new JFXPanel(); // Init JFX Platform
Platform.runLater(() -> {
File file = folderChooser.showDialog(null);
if (file != null && file.exists()) {
File parent = file.getParentFile();
if (parent == null) parent = file;
Preferences.userRoot().node(Fawe.class.getName()).put("LAST_USED_FOLDER" + id, parent.getPath());
onSelect(file);
}
});
}
}

View File

@ -0,0 +1,16 @@
package com.boydti.fawe.installer;
import java.awt.Color;
import java.awt.event.ActionEvent;
public class CloseButton extends InteractiveButton {
public CloseButton() {
super("X");
setColor(new Color(0x66, 0x33, 0x33));
}
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}

View File

@ -0,0 +1,30 @@
package com.boydti.fawe.installer;
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class ImagePanel extends JPanel {
private BufferedImage image;
public ImagePanel(BufferedImage image) {
this.image = image;
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.6f));
g.drawImage(image, 0, 0, getWidth(), getWidth(), this); // see javadoc for more info on the parameters
}
}

View File

@ -0,0 +1,358 @@
package com.boydti.fawe.installer;
import com.boydti.fawe.FaweVersion;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.StringMan;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
public class InstallerFrame extends JFrame {
private final InvisiblePanel loggerPanel;
private Color LIGHT_GRAY = new Color(0x66, 0x66, 0x66);
private Color GRAY = new Color(0x44, 0x44, 0x46);
private Color DARK_GRAY = new Color(0x33, 0x33, 0x36);
private Color DARKER_GRAY = new Color(0x26, 0x26, 0x28);
private Color INVISIBLE = new Color(0, 0, 0, 0);
private Color OFF_WHITE = new Color(200, 200, 200);
private JTextArea loggerTextArea;
private BrowseButton browse;
public InstallerFrame() throws Exception {
final MovablePanel movable = new MovablePanel(this);
Container content = this.getContentPane();
content.add(movable);
this.setSize(480, 320);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setUndecorated(true);
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
int x = (int) ((dimension.getWidth() - this.getWidth()) / 2);
int y = (int) ((dimension.getHeight() - this.getHeight()) / 2);
this.setLocation(x, y);
this.setVisible(true);
this.setOpacity(0);
movable.setBackground(DARK_GRAY);
movable.setLayout(new BorderLayout());
fadeIn();
JPanel topBar = new InvisiblePanel(new BorderLayout());
{
JPanel topBarLeft = new InvisiblePanel();
JPanel topBarRight = new InvisiblePanel();
JLabel title = new JLabel("FastAsyncWorldEdit Installer");
title.setHorizontalAlignment(SwingConstants.CENTER);
title.setAlignmentX(Component.RIGHT_ALIGNMENT);
title.setForeground(LIGHT_GRAY);
MinimizeButton minimize = new MinimizeButton(this);
CloseButton exit = new CloseButton();
topBarLeft.add(title);
topBarRight.add(minimize);
topBarRight.add(exit);
topBar.add(topBarLeft, BorderLayout.CENTER);
topBar.add(topBarRight, BorderLayout.EAST);
}
final JPanel mainContent = new InvisiblePanel(new BorderLayout());
{
final JPanel browseContent = new InvisiblePanel(new BorderLayout());
File dir = MainUtil.getWorkingDirectory("minecraft");
JLabel folder = new JLabel("Folder: ");
folder.setForeground(OFF_WHITE);
final InteractiveButton text = new InteractiveButton(dir.getPath(), DARKER_GRAY) {
@Override
public void actionPerformed(ActionEvent e) {
browse.actionPerformed(e);
}
};
text.setForeground(OFF_WHITE);
text.setBackground(DARKER_GRAY);
text.setOpaque(true);
text.setBorder(new EmptyBorder(4, 4, 4, 4));
browse = new BrowseButton("") {
@Override
public void onSelect(File folder) {
text.setText(folder.getPath());
movable.repaint();
}
};
InteractiveButton install = new InteractiveButton(">> Create Profile <<", DARKER_GRAY) {
@Override
public void actionPerformed(ActionEvent e) {
try {
install(text.getText());
} catch (Exception e1) {
e1.printStackTrace();
}
}
};
browseContent.add(folder, BorderLayout.WEST);
browseContent.add(text, BorderLayout.CENTER);
browseContent.add(browse, BorderLayout.EAST);
final JPanel installContent = new InvisiblePanel(new FlowLayout());
install.setPreferredSize(new Dimension(416, 32));
installContent.add(install);
installContent.setBorder(new EmptyBorder(10, 0, 10, 0));
this.loggerPanel = new InvisiblePanel(new BorderLayout());
this.loggerPanel.setBackground(Color.GREEN);
loggerPanel.setPreferredSize(new Dimension(416, 160));
loggerTextArea = new JTextArea(12, 52);
loggerTextArea.setBackground(GRAY);
loggerTextArea.setForeground(DARKER_GRAY);
loggerTextArea.setFont(new Font(loggerTextArea.getFont().getName(), Font.PLAIN, 9));
JScrollPane scroll = new JScrollPane(loggerTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setBackground(DARK_GRAY);
scroll.setBorder(new EmptyBorder(0, 0, 0, 0));
loggerPanel.add(scroll);
loggerPanel.setVisible(false);
mainContent.setBorder(new EmptyBorder(6, 32, 6, 32));
mainContent.add(browseContent, BorderLayout.NORTH);
mainContent.add(installContent, BorderLayout.CENTER);
mainContent.add(loggerPanel, BorderLayout.SOUTH);
}
JPanel bottomBar = new InvisiblePanel();
{
try {
InputStream stream = getClass().getResourceAsStream("/fawe.properties");
java.util.Scanner scanner = new java.util.Scanner(stream).useDelimiter("\\A");
String versionString = scanner.next().trim();
scanner.close();
FaweVersion version = new FaweVersion(versionString);
String date = new Date(100 + version.year, version.month, version.day).toGMTString();
String build = "https://ci.athion.net/job/FastAsyncWorldEdit/" + version.build;
String commit = "https://github.com/boy0001/FastAsyncWorldedit/commit/" + Integer.toHexString(version.hash);
String footerMessage = "FAWE v" + version.major + "." + version.minor + "." + version.patch + " by Empire92 (c) 2017 (GPL v3.0)";
URL licenseUrl = new URL("https://github.com/boy0001/FastAsyncWorldedit/blob/master/LICENSE");
URLButton licenseButton = new URLButton(licenseUrl, footerMessage);
bottomBar.add(licenseButton);
} catch (Throwable ignore) {
ignore.printStackTrace();
}
URL chat = new URL("http://webchat.esper.net/?nick=&channels=IntellectualCrafters&fg_color=000&fg_sec_color=000&bg_color=FFF");
URLButton chatButton = new URLButton(chat, "Chat");
bottomBar.add(chatButton);
URL wiki = new URL("https://github.com/boy0001/FastAsyncWorldedit/wiki");
URLButton wikiButton = new URLButton(wiki, "Wiki");
bottomBar.add(wikiButton);
URL issue = new URL("https://github.com/boy0001/FastAsyncWorldedit/issues/new");
URLButton issueButton = new URLButton(issue, "Report Issue");
bottomBar.add(issueButton);
}
// We want to add these a bit later
movable.add(topBar, BorderLayout.NORTH);
this.setVisible(true);
this.repaint();
movable.add(mainContent, BorderLayout.CENTER);
this.setVisible(true);
this.repaint();
movable.add(bottomBar, BorderLayout.SOUTH);
this.setVisible(true);
this.repaint();
}
private boolean newLine = false;
public void prompt(String message) {
JOptionPane.showMessageDialog(null, message);
}
public void debug(String m) {
System.out.println(m);
}
public void install(String name) throws Exception {
if (!loggerPanel.isVisible()) {
loggerPanel.setVisible(true);
this.repaint();
System.setOut(new TextAreaOutputStream(loggerTextArea));
}
if (name == null || name.isEmpty()) {
prompt("No folder selection");
return;
}
final File dirMc = new File(name);
if (!dirMc.exists()) {
prompt("Folder does not exist");
return;
}
if (!dirMc.isDirectory()) {
prompt("You must select a folder, not a file");
return;
}
Thread installThread = new Thread(new Runnable() {
@Override
public void run() {
List<String> supported = Arrays.asList("v1710", "v189", "v194", "v110", "v111");
String supportedString = null;
for (String version : supported) {
try {
Class.forName("com.boydti.fawe.forge." + version + ".ForgeChunk_All");
supportedString = version;
break;
} catch (ClassNotFoundException ignore) {
}
}
if (supportedString == null) {
prompt("This version of FAWE cannot be installed this way.");
return;
}
debug("Selected version " + supportedString);
URL forgeUrl;
URL worldEditUrl;
URL worldEditCuiUrl;
try {
switch (supportedString) {
case "v111":
forgeUrl = new URL("https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.11.2-13.20.0.2201/forge-1.11.2-13.20.0.2201-installer.jar");
worldEditUrl = new URL("http://builds.enginehub.org/job/worldedit/9593/download/worldedit-forge-mc1.11-6.1.6-SNAPSHOT-dist.jar");
worldEditCuiUrl = new URL("https://addons-origin.cursecdn.com/files/2361/241/worldeditcuife-v1.0.6-mf-1.11.2-13.20.0.2201.jar");
break;
case "v110":
forgeUrl = new URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/1.10.2-12.18.3.2185/forge-1.10.2-12.18.3.2185-installer.jar");
worldEditUrl = new URL("http://builds.enginehub.org/job/worldedit/9395/download/worldedit-forge-mc1.10.2-6.1.4-SNAPSHOT-dist.jar");
worldEditCuiUrl = new URL("https://addons-origin.cursecdn.com/files/2361/239/WorldEditCuiFe-v1.0.6-mf-1.10.2-12.18.2.2125.jar");
break;
case "v194":
forgeUrl = new URL("https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.9.4-12.17.0.2051/forge-1.9.4-12.17.0.2051-installer.jar");
worldEditUrl = new URL("http://builds.enginehub.org/job/worldedit/9171/download/worldedit-forge-mc1.9.4-6.1.3-SNAPSHOT-dist.jar");
worldEditCuiUrl = new URL("https://addons-origin.cursecdn.com/files/2361/236/WorldEditCuiFe-v1.0.6-mf-1.9.4-12.17.0.1976.jar");
break;
case "v189":
forgeUrl = new URL("https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.8.9-11.15.1.1902-1.8.9/forge-1.8.9-11.15.1.1902-1.8.9-installer.jar");
worldEditUrl = new URL("http://builds.enginehub.org/job/worldedit/8755/download/worldedit-forge-mc1.8.9-6.1.1-dist.jar");
worldEditCuiUrl = new URL("https://addons-origin.cursecdn.com/files/2361/235/WorldEditCuiFe-v1.0.6-mf-1.8.9-11.15.1.1855.jar");
break;
case "v1710":
forgeUrl = new URL("https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.7.10-10.13.4.1614-1.7.10/forge-1.7.10-10.13.4.1614-1.7.10-installer.jar");
worldEditUrl = new URL("http://builds.enginehub.org/job/worldedit/9194/download/worldedit-forge-mc1.7.10-6.1.2-SNAPSHOT-dist.jar");
worldEditCuiUrl = new URL("https://addons-origin.cursecdn.com/files/2361/234/WorldEditCuiFe-v1.0.6-mf-1.7.10-10.13.4.1566.jar");
break;
default:
return;
}
} catch (MalformedURLException e) {
e.printStackTrace();
return;
}
try { // install forge
debug("Downloading forge installer from:\n - https://files.minecraftforge.net/");
URLClassLoader loader = new URLClassLoader(new URL[]{forgeUrl});
debug("Connected");
Class<?> forgeInstallClass = loader.loadClass("net.minecraftforge.installer.ClientInstall");
debug("Found ClientInstall class");
Object forgeInstallInstance = forgeInstallClass.newInstance();
debug(forgeInstallInstance + " | " + forgeInstallClass + " | " + StringMan.getString(forgeInstallClass.getMethods()));
debug("Created instance " + forgeInstallInstance);
Method methodRun = forgeInstallClass.getDeclaredMethods()[0];//("run", File.class, Predicate.class);
Object alwaysTrue = loader.loadClass("com.google.common.base.Predicates").getDeclaredMethod("alwaysTrue").invoke(null);
methodRun.invoke(forgeInstallInstance, dirMc, alwaysTrue);
debug("Forge profile created, now installing WorldEdit");
} catch (Throwable e) {
e.printStackTrace();
prompt("[ERROR] Forge install failed, download from:\nhttps://files.minecraftforge.net/");
}
File mods = new File(dirMc, "mods");
if (!mods.exists()) {
debug("Creating mods directory");
mods.mkdirs();
} else {
for (File file : mods.listFiles()) {
String name = file.getName().toLowerCase();
if ((name.contains("worldedit") || name.contains("fawe"))) {
debug("Delete existing: " + file.getName());
file.delete();
}
}
}
try { // install worldedit
debug("Downloading WE-CUI from:\n - https://minecraft.curseforge.com/projects/worldeditcui-forge-edition");
try (ReadableByteChannel rbc = Channels.newChannel(worldEditCuiUrl.openStream())) {
try (FileOutputStream fos = new FileOutputStream(new File(mods, "WorldEditCUI.jar"))) {
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
}
debug("Successfully downloaded WorldEdit-CUI");
} catch (Throwable e) {
prompt("[ERROR] WorldEdit install failed, download from:\nhttp://builds.enginehub.org/job/worldedit");
}
try { // install worldedit
debug("Downloading WorldEdit from:\n - http://builds.enginehub.org/job/worldedit");
try (ReadableByteChannel rbc = Channels.newChannel(worldEditUrl.openStream())) {
try (FileOutputStream fos = new FileOutputStream(new File(mods, "WorldEdit.jar"))) {
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
}
debug("Successfully downloaded WorldEdit");
} catch (Throwable e) {
prompt("[ERROR] WorldEdit install failed, download from:\nhttp://builds.enginehub.org/job/worldedit");
}
try { // install FAWE
debug("Copying FastAsyncWorldEdit to mods directory");
File file = new File(InstallerFrame.class.getProtectionDomain().getCodeSource().getLocation().getPath());
debug(" - " + file.getPath());
MainUtil.copyFile(file, new File(mods, "FastAsyncWorldEdit.jar"));
debug("Installation complete!");
} catch (Throwable e) {
prompt("[ERROR] Copy installer failed, please copy this installer jar manually");
}
prompt("Installation complete!\nLaunch the game using the forge profile.");
}
});
installThread.start();
}
public void fadeIn() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (float i = 0; i <= 1; i += 0.001) {
InstallerFrame.this.setOpacity(i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
public static void main(String[] args) throws Exception {
InstallerFrame window = new InstallerFrame();
}
}

View File

@ -0,0 +1,68 @@
package com.boydti.fawe.installer;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JButton;
public class InteractiveButton extends JButton implements ActionListener, MouseListener {
private Color background;
public InteractiveButton(String text) {
this(text, new Color(0, 0, 0, 0));
}
public InteractiveButton(String text, Color background) {
setText(text);
setBorderPainted(false);
setVisible(true);
setForeground(new Color(200, 200, 200));
addActionListener(this);
addMouseListener(this);
setFocusable(false);
if (background.getAlpha() != 0) {
this.background = background;
} else {
this.background = new Color(0x38, 0x38, 0x39);
}
setBackground(this.background);
}
public void setColor(Color background) {
setBackground(background);
this.background = background;
}
@Override
public void actionPerformed(ActionEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
setBackground(new Color(0x44, 0x44, 0x44));
}
@Override
public void mouseExited(MouseEvent e) {
setBackground(this.background);
repaint();
}
@Override
public void mousePressed(MouseEvent e) {
setBackground(new Color(0x77, 0x77, 0x77));
}
@Override
public void mouseReleased(MouseEvent e) {
setBackground(new Color(0x33, 0x33, 0x36));
}
}

View File

@ -0,0 +1,17 @@
package com.boydti.fawe.installer;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.LayoutManager;
import javax.swing.JPanel;
public class InvisiblePanel extends JPanel {
public InvisiblePanel(LayoutManager layout) {
super(layout);
setBackground(new Color(0, 0, 0, 0));
}
public InvisiblePanel() {
this(new FlowLayout());
}
}

View File

@ -0,0 +1,60 @@
package com.boydti.fawe.installer;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import javax.swing.JFileChooser;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import sun.swing.FilePane;
public class JSystemFileChooser extends JFileChooser {
public void updateUI(){
LookAndFeel old = UIManager.getLookAndFeel();
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Throwable ex) {
old = null;
}
super.updateUI();
if(old != null){
FilePane filePane = findFilePane(this);
filePane.setViewType(FilePane.VIEWTYPE_DETAILS);
filePane.setViewType(FilePane.VIEWTYPE_LIST);
Color background = UIManager.getColor("Label.background");
setBackground(background);
setOpaque(true);
try {
UIManager.setLookAndFeel(old);
}
catch (UnsupportedLookAndFeelException ignored) {} // shouldn't get here
}
}
private static FilePane findFilePane(Container parent){
for(Component comp: parent.getComponents()){
if(FilePane.class.isInstance(comp)){
return (FilePane)comp;
}
if(comp instanceof Container){
Container cont = (Container)comp;
if(cont.getComponentCount() > 0){
FilePane found = findFilePane(cont);
if (found != null) {
return found;
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,19 @@
package com.boydti.fawe.installer;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;
public class MinimizeButton extends InteractiveButton {
private final JFrame window;
public MinimizeButton(JFrame window) {
super("-");
this.window = window;
}
@Override
public void actionPerformed(ActionEvent e) {
window.setState(Frame.ICONIFIED);
}
}

View File

@ -0,0 +1,43 @@
package com.boydti.fawe.installer;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MovablePanel extends JPanel {
private Point initialClick;
private JFrame parent;
public MovablePanel(final JFrame parent) {
this.parent = parent;
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
initialClick = e.getPoint();
getComponentAt(initialClick);
}
});
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
// get location of Window
int thisX = parent.getLocation().x;
int thisY = parent.getLocation().y;
// Determine how much the mouse moved since the initial click
int xMoved = (thisX + e.getX()) - (thisX + initialClick.x);
int yMoved = (thisY + e.getY()) - (thisY + initialClick.y);
// Move window to this position
int X = thisX + xMoved;
int Y = thisY + yMoved;
parent.setLocation(X, Y);
}
});
}
}

View File

@ -0,0 +1,58 @@
package com.boydti.fawe.installer;
import com.boydti.fawe.config.BBC;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JTextArea;
public class TextAreaOutputStream extends PrintStream {
public TextAreaOutputStream(final JTextArea textArea) {
super(new OutputStream() {
StringBuffer buffer = new StringBuffer();
ExecutorService executor = Executors.newSingleThreadExecutor();
AtomicBoolean updated = new AtomicBoolean();
AtomicBoolean waiting = new AtomicBoolean();
boolean lineColor = false;
@Override
public void write(int b) throws IOException {
buffer.append((char) b);
if (b == '\n') {
updated.set(true);
if (waiting.compareAndSet(false, true)) {
executor.submit(new Runnable() {
@Override
public void run() {
try {
updated.set(false);
int len = buffer.length();
textArea.append(BBC.stripColor(buffer.substring(0, len)));
buffer.delete(0, len);
textArea.setVisible(true);
textArea.repaint();
} finally {
waiting.set(false);
if (updated.get() && waiting.compareAndSet(false, true)) {
executor.submit(this);
}
}
}
});
}
} else {
updated.lazySet(true);
}
}
@Override
protected void finalize() throws Throwable {
executor.shutdownNow();
}
});
}
}

View File

@ -0,0 +1,40 @@
package com.boydti.fawe.installer;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
public class URLButton extends InteractiveButton {
private final URL url;
public URLButton(URL url, String text) {
super("<HTML>" + text + "</HTML>");
this.url = url;
setFont(new Font(getFont().getName(), Font.PLAIN, 9));
setForeground(new Color(0x77, 0x77, 0x77));
}
@Override
public void actionPerformed(ActionEvent event) {
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().browse(url.toURI());
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return;
}
Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
Clipboard systemClipboard = defaultToolkit.getSystemClipboard();
systemClipboard.setContents(new StringSelection(url.toString()), null);
}
}

View File

@ -0,0 +1,314 @@
package com.boydti.fawe.jnbt;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.clipboard.CPUOptimizedClipboard;
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
import com.boydti.fawe.object.clipboard.FaweClipboard;
import com.boydti.fawe.object.clipboard.MemoryOptimizedClipboard;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.regions.CuboidRegion;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;
public class CorruptSchematicStreamer {
private final InputStream stream;
private final UUID uuid;
private FaweClipboard fc;
final AtomicInteger volume = new AtomicInteger();
final AtomicInteger width = new AtomicInteger();
final AtomicInteger height = new AtomicInteger();
final AtomicInteger length = new AtomicInteger();
final AtomicInteger offsetX = new AtomicInteger();
final AtomicInteger offsetY = new AtomicInteger();
final AtomicInteger offsetZ = new AtomicInteger();
final AtomicInteger originX = new AtomicInteger();
final AtomicInteger originY = new AtomicInteger();
final AtomicInteger originZ = new AtomicInteger();
public CorruptSchematicStreamer(InputStream rootStream, UUID uuid) {
this.stream = rootStream;
this.uuid = uuid;
}
public void match(String matchTag, CorruptReader reader) {
try {
stream.reset();
stream.mark(Integer.MAX_VALUE);
DataInputStream dataInput = new DataInputStream(new BufferedInputStream(new GZIPInputStream(stream)));
byte[] match = matchTag.getBytes();
int[] matchValue = new int[match.length];
int matchIndex = 0;
int read;
while ((read = dataInput.read()) != -1) {
int expected = match[matchIndex];
if (expected == -1) {
if (++matchIndex == match.length) {
break;
}
} else if (read == expected) {
if (++matchIndex == match.length) {
reader.run(dataInput);
break;
}
} else {
if (matchIndex == 2)
matchIndex = 0;
}
}
Fawe.debug(" - Recover " + matchTag + " = success");
} catch (Throwable e) {
Fawe.debug(" - Recover " + matchTag + " = partial failure");
e.printStackTrace();
}
}
public FaweClipboard setupClipboard() {
if (fc != null) {
return fc;
}
Vector dimensions = guessDimensions(volume.get(), width.get(), height.get(), length.get());
if (width.get() == 0 || height.get() == 0 || length.get() == 0) {
Fawe.debug("No dimensions found! Estimating based on factors:" + dimensions);
}
if (Settings.IMP.CLIPBOARD.USE_DISK) {
fc = new DiskOptimizedClipboard(dimensions.getBlockX(), dimensions.getBlockY(), dimensions.getBlockZ(), uuid);
} else if (Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL == 0) {
fc = new CPUOptimizedClipboard(dimensions.getBlockX(), dimensions.getBlockY(), dimensions.getBlockZ());
} else {
fc = new MemoryOptimizedClipboard(dimensions.getBlockX(), dimensions.getBlockY(), dimensions.getBlockZ());
}
return fc;
}
public Clipboard recover() {
// TODO FIXME
throw new UnsupportedOperationException("TODO FIXME");
// try {
// if (stream == null || !stream.markSupported()) {
// throw new IllegalArgumentException("Can only recover from a marked and resettable stream!");
// }
// match("Width", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// width.set(in.readShort());
// }
// });
// match("Height", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// height.set(in.readShort());
// }
// });
// match("Length", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// length.set(in.readShort());
// }
// });
// match("WEOffsetX", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// offsetX.set(in.readInt());
// }
// });
// match("WEOffsetY", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// offsetY.set(in.readInt());
// }
// });
// match("WEOffsetZ", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// offsetZ.set(in.readInt());
// }
// });
// match("WEOriginX", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// originX.set(in.readInt());
// }
// });
// match("WEOriginY", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// originY.set(in.readInt());
// }
// });
// match("WEOriginZ", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// originZ.set(in.readInt());
// }
// });
// match("Blocks", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// int length = in.readInt();
// volume.set(length);
// setupClipboard();
// for (int i = 0; i < length; i++) {
// fc.setId(i, in.read());
// }
// }
// });
// match("Data", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// int length = in.readInt();
// volume.set(length);
// setupClipboard();
// for (int i = 0; i < length; i++) {
// fc.setData(i, in.read());
// }
// }
// });
// match("AddBlocks", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// int length = in.readInt();
// int expected = volume.get();
// if (expected == 0) {
// expected = length * 2;
// volume.set(expected);
// }
// setupClipboard();
// if (expected == length * 2) {
// for (int i = 0; i < length; i++) {
// int value = in.read();
// int first = value & 0x0F;
// int second = (value & 0xF0) >> 4;
// int gIndex = i << 1;
// if (first != 0) fc.setAdd(gIndex, first);
// if (second != 0) fc.setAdd(gIndex + 1, second);
// }
// } else {
// for (int i = 0; i < length; i++) {
// int value = in.read();
// if (value != 0) fc.setAdd(i, value);
// }
// }
// }
// });
// match("Biomes", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// int length = in.readInt();
// for (int i = 0; i < length; i++) {
// fc.setBiome(i, in.read());
// }
// }
// });
// Vector dimensions = guessDimensions(volume.get(), width.get(), height.get(), length.get());
// Vector min = new Vector(originX.get(), originY.get(), originZ.get());
// Vector offset = new Vector(offsetX.get(), offsetY.get(), offsetZ.get());
// Vector origin = min.subtract(offset);
// CuboidRegion region = new CuboidRegion(min, min.add(dimensions.getBlockX(), dimensions.getBlockY(), dimensions.getBlockZ()).subtract(Vector.ONE));
// fc.setOrigin(offset);
// final BlockArrayClipboard clipboard = new BlockArrayClipboard(region, fc);
// match("TileEntities", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// int childType = in.readByte();
// int length = in.readInt();
// NBTInputStream nis = new NBTInputStream(in);
// for (int i = 0; i < length; ++i) {
// CompoundTag tag = (CompoundTag) nis.readTagPayload(childType, 1);
// int x = tag.getInt("x");
// int y = tag.getInt("y");
// int z = tag.getInt("z");
// fc.setTile(x, y, z, tag);
// }
// }
// });
// match("Entities", new CorruptSchematicStreamer.CorruptReader() {
// @Override
// public void run(DataInputStream in) throws IOException {
// int childType = in.readByte();
// int length = in.readInt();
// NBTInputStream nis = new NBTInputStream(in);
// for (int i = 0; i < length; ++i) {
// CompoundTag tag = (CompoundTag) nis.readTagPayload(childType, 1);
// int x = tag.getInt("x");
// int y = tag.getInt("y");
// int z = tag.getInt("z");
// String id = tag.getString("id");
// if (id.isEmpty()) {
// return;
// }
// ListTag positionTag = tag.getListTag("Pos");
// ListTag directionTag = tag.getListTag("Rotation");
// BaseEntity state = new BaseEntity(id, tag);
// fc.createEntity(clipboard, positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2), (float) directionTag.asDouble(0), (float) directionTag.asDouble(1), state);
// }
// }
// });
// return clipboard;
// } catch (Throwable e) {
// if (fc != null) fc.close();
// throw e;
// }
}
private Vector guessDimensions(int volume, int width, int height, int length) {
if (volume == 0) {
return new Vector(width, height, length);
}
if (volume == width * height * length) {
return new Vector(width, height, length);
}
if (width == 0 && height != 0 && length != 0 && volume % (height * length) == 0 && height * length <= volume) {
return new Vector(volume / (height * length), height, length);
}
if (height == 0 && width != 0 && length != 0 && volume % (width * length) == 0 && width * length <= volume) {
return new Vector(width, volume / (width * length), length);
}
if (length == 0 && height != 0 && width != 0 && volume % (height * width) == 0 && height * width <= volume) {
return new Vector(width, height, volume / (width * height));
}
List<Integer> factors = new ArrayList<>();
for (int i = (int) Math.sqrt(volume); i > 0; i--) {
if (volume % i == 0) {
factors.add(i);
factors.add(volume / i);
}
}
int min = Integer.MAX_VALUE;
Vector dimensions = new Vector();
for (int x = 0; x < factors.size(); x++) {
int xValue = factors.get(x);
for (int y = 0; y < factors.size(); y++) {
int yValue = factors.get(y);
long area = xValue * yValue;
if (volume % area == 0) {
int z = (int) (volume / area);
int max = Math.max(Math.max(xValue, yValue), z);
if (max < min) {
min = max;
dimensions = new Vector(xValue, z, yValue);
}
}
}
}
return dimensions;
}
public interface CorruptReader {
void run(DataInputStream in) throws IOException;
}
}

View File

@ -0,0 +1,429 @@
package com.boydti.fawe.jnbt;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.IntArrayTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongTag;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;
import java.util.regex.Pattern;
public class JSON2NBT {
private static final Pattern INT_ARRAY_MATCHER = Pattern.compile("\\[[-+\\d|,\\s]+\\]");
private JSON2NBT() {
}
public static CompoundTag getTagFromJson(String jsonString) throws NBTException {
jsonString = jsonString.trim();
if (!jsonString.startsWith("{")) {
throw new NBTException("Invalid tag encountered, expected \'{\' as first char.");
} else if (topTagsCount(jsonString) != 1) {
throw new NBTException("Encountered multiple top tags, only one expected");
} else {
return (CompoundTag) nameValueToNBT("tag", jsonString).parse();
}
}
public static int topTagsCount(String str) throws NBTException {
int i = 0;
boolean flag = false;
Stack stack = new Stack();
for (int j = 0; j < str.length(); ++j) {
char c0 = str.charAt(j);
if (c0 == 34) {
if (isCharEscaped(str, j)) {
if (!flag) {
throw new NBTException("Illegal use of \\\": " + str);
}
} else {
flag = !flag;
}
} else if (!flag) {
if (c0 != 123 && c0 != 91) {
if (c0 == 125 && (stack.isEmpty() || ((Character) stack.pop()).charValue() != 123)) {
throw new NBTException("Unbalanced curly brackets {}: " + str);
}
if (c0 == 93 && (stack.isEmpty() || ((Character) stack.pop()).charValue() != 91)) {
throw new NBTException("Unbalanced square brackets []: " + str);
}
} else {
if (stack.isEmpty()) {
++i;
}
stack.push(Character.valueOf(c0));
}
}
}
if (flag) {
throw new NBTException("Unbalanced quotation: " + str);
} else if (!stack.isEmpty()) {
throw new NBTException("Unbalanced brackets: " + str);
} else {
if (i == 0 && !str.isEmpty()) {
i = 1;
}
return i;
}
}
private static JSON2NBT.Any joinStrToNBT(String... args) throws NBTException {
return nameValueToNBT(args[0], args[1]);
}
private static JSON2NBT.Any nameValueToNBT(String key, String value) throws NBTException {
value = value.trim();
String s;
boolean c0;
char c01;
if (value.startsWith("{")) {
value = value.substring(1, value.length() - 1);
JSON2NBT.Compound JSON2NBT$list1;
for (JSON2NBT$list1 = new JSON2NBT.Compound(key); value.length() > 0; value = value.substring(s.length() + 1)) {
s = nextNameValuePair(value, true);
if (s.length() > 0) {
c0 = false;
JSON2NBT$list1.tagList.add(getTagFromNameValue(s, false));
}
if (value.length() < s.length() + 1) {
break;
}
c01 = value.charAt(s.length());
if (c01 != 44 && c01 != 123 && c01 != 125 && c01 != 91 && c01 != 93) {
throw new NBTException("Unexpected token \'" + c01 + "\' at: " + value.substring(s.length()));
}
}
return JSON2NBT$list1;
} else if (value.startsWith("[") && !INT_ARRAY_MATCHER.matcher(value).matches()) {
value = value.substring(1, value.length() - 1);
JSON2NBT.List JSON2NBT$list;
for (JSON2NBT$list = new JSON2NBT.List(key); value.length() > 0; value = value.substring(s.length() + 1)) {
s = nextNameValuePair(value, false);
if (s.length() > 0) {
c0 = true;
JSON2NBT$list.tagList.add(getTagFromNameValue(s, true));
}
if (value.length() < s.length() + 1) {
break;
}
c01 = value.charAt(s.length());
if (c01 != 44 && c01 != 123 && c01 != 125 && c01 != 91 && c01 != 93) {
throw new NBTException("Unexpected token \'" + c01 + "\' at: " + value.substring(s.length()));
}
}
return JSON2NBT$list;
} else {
return new JSON2NBT.Primitive(key, value);
}
}
private static JSON2NBT.Any getTagFromNameValue(String str, boolean isArray) throws NBTException {
String s = locateName(str, isArray);
String s1 = locateValue(str, isArray);
return joinStrToNBT(new String[]{s, s1});
}
private static String nextNameValuePair(String str, boolean isCompound) throws NBTException {
int i = getNextCharIndex(str, ':');
int j = getNextCharIndex(str, ',');
if (isCompound) {
if (i == -1) {
throw new NBTException("Unable to locate name/value separator for string: " + str);
}
if (j != -1 && j < i) {
throw new NBTException("Name error at: " + str);
}
} else if (i == -1 || i > j) {
i = -1;
}
return locateValueAt(str, i);
}
private static String locateValueAt(String str, int index) throws NBTException {
Stack stack = new Stack();
int i = index + 1;
boolean flag = false;
boolean flag1 = false;
boolean flag2 = false;
for (int j = 0; i < str.length(); ++i) {
char c0 = str.charAt(i);
if (c0 == 34) {
if (isCharEscaped(str, i)) {
if (!flag) {
throw new NBTException("Illegal use of \\\": " + str);
}
} else {
flag = !flag;
if (flag && !flag2) {
flag1 = true;
}
if (!flag) {
j = i;
}
}
} else if (!flag) {
if (c0 != 123 && c0 != 91) {
if (c0 == 125 && (stack.isEmpty() || ((Character) stack.pop()).charValue() != 123)) {
throw new NBTException("Unbalanced curly brackets {}: " + str);
}
if (c0 == 93 && (stack.isEmpty() || ((Character) stack.pop()).charValue() != 91)) {
throw new NBTException("Unbalanced square brackets []: " + str);
}
if (c0 == 44 && stack.isEmpty()) {
return str.substring(0, i);
}
} else {
stack.push(Character.valueOf(c0));
}
}
if (!Character.isWhitespace(c0)) {
if (!flag && flag1 && j != i) {
return str.substring(0, j + 1);
}
flag2 = true;
}
}
return str.substring(0, i);
}
private static String locateName(String str, boolean isArray) throws NBTException {
if (isArray) {
str = str.trim();
if (str.startsWith("{") || str.startsWith("[")) {
return "";
}
}
int i = getNextCharIndex(str, ':');
if (i == -1) {
if (isArray) {
return "";
} else {
throw new NBTException("Unable to locate name/value separator for string: " + str);
}
} else {
return str.substring(0, i).trim();
}
}
private static String locateValue(String str, boolean isArray) throws NBTException {
if (isArray) {
str = str.trim();
if (str.startsWith("{") || str.startsWith("[")) {
return str;
}
}
int i = getNextCharIndex(str, ':');
if (i == -1) {
if (isArray) {
return str;
} else {
throw new NBTException("Unable to locate name/value separator for string: " + str);
}
} else {
return str.substring(i + 1).trim();
}
}
private static int getNextCharIndex(String str, char targetChar) {
int i = 0;
for (boolean flag = true; i < str.length(); ++i) {
char c0 = str.charAt(i);
if (c0 == 34) {
if (!isCharEscaped(str, i)) {
flag = !flag;
}
} else if (flag) {
if (c0 == targetChar) {
return i;
}
if (c0 == 123 || c0 == 91) {
return -1;
}
}
}
return -1;
}
private static boolean isCharEscaped(String str, int index) {
return index > 0 && str.charAt(index - 1) == 92 && !isCharEscaped(str, index - 1);
}
private static class Primitive extends JSON2NBT.Any {
private static final Pattern DOUBLE = Pattern.compile("[-+]?[0-9]*\\.?[0-9]+[d|D]");
private static final Pattern FLOAT = Pattern.compile("[-+]?[0-9]*\\.?[0-9]+[f|F]");
private static final Pattern BYTE = Pattern.compile("[-+]?[0-9]+[b|B]");
private static final Pattern LONG = Pattern.compile("[-+]?[0-9]+[l|L]");
private static final Pattern SHORT = Pattern.compile("[-+]?[0-9]+[s|S]");
private static final Pattern INTEGER = Pattern.compile("[-+]?[0-9]+");
private static final Pattern DOUBLE_UNTYPED = Pattern.compile("[-+]?[0-9]*\\.?[0-9]+");
private static final Splitter SPLITTER = Splitter.on(',').omitEmptyStrings();
protected String jsonValue;
public Primitive(String jsonIn, String valueIn) {
this.json = jsonIn;
this.jsonValue = valueIn;
}
public Tag parse() throws NBTException {
try {
if (DOUBLE.matcher(this.jsonValue).matches()) {
return new DoubleTag(Double.parseDouble(this.jsonValue.substring(0, this.jsonValue.length() - 1)));
}
if (FLOAT.matcher(this.jsonValue).matches()) {
return new FloatTag(Float.parseFloat(this.jsonValue.substring(0, this.jsonValue.length() - 1)));
}
if (BYTE.matcher(this.jsonValue).matches()) {
return new ByteTag(Byte.parseByte(this.jsonValue.substring(0, this.jsonValue.length() - 1)));
}
if (LONG.matcher(this.jsonValue).matches()) {
return new LongTag(Long.parseLong(this.jsonValue.substring(0, this.jsonValue.length() - 1)));
}
if (SHORT.matcher(this.jsonValue).matches()) {
return new ShortTag(Short.parseShort(this.jsonValue.substring(0, this.jsonValue.length() - 1)));
}
if (INTEGER.matcher(this.jsonValue).matches()) {
return new IntTag(Integer.parseInt(this.jsonValue));
}
if (DOUBLE_UNTYPED.matcher(this.jsonValue).matches()) {
return new DoubleTag(Double.parseDouble(this.jsonValue));
}
if ("true".equalsIgnoreCase(this.jsonValue) || "false".equalsIgnoreCase(this.jsonValue)) {
return new ByteTag((byte) (Boolean.parseBoolean(this.jsonValue) ? 1 : 0));
}
} catch (NumberFormatException var6) {
this.jsonValue = this.jsonValue.replaceAll("\\\\\"", "\"");
return new StringTag(this.jsonValue);
}
if (this.jsonValue.startsWith("[") && this.jsonValue.endsWith("]")) {
String var7 = this.jsonValue.substring(1, this.jsonValue.length() - 1);
String[] var8 = (String[]) ((String[]) Iterables.toArray(SPLITTER.split(var7), String.class));
try {
int[] var5 = new int[var8.length];
for (int j = 0; j < var8.length; ++j) {
var5[j] = Integer.parseInt(var8[j].trim());
}
return new IntArrayTag(var5);
} catch (NumberFormatException var51) {
return new StringTag(this.jsonValue);
}
} else {
if (this.jsonValue.startsWith("\"") && this.jsonValue.endsWith("\"")) {
this.jsonValue = this.jsonValue.substring(1, this.jsonValue.length() - 1);
}
this.jsonValue = this.jsonValue.replaceAll("\\\\\"", "\"");
StringBuilder stringbuilder = new StringBuilder();
for (int i = 0; i < this.jsonValue.length(); ++i) {
if (i < this.jsonValue.length() - 1 && this.jsonValue.charAt(i) == 92 && this.jsonValue.charAt(i + 1) == 92) {
stringbuilder.append('\\');
++i;
} else {
stringbuilder.append(this.jsonValue.charAt(i));
}
}
return new StringTag(stringbuilder.toString());
}
}
}
private static class List extends JSON2NBT.Any {
protected java.util.List<JSON2NBT.Any> tagList = Lists.newArrayList();
public List(String json) {
this.json = json;
}
public Tag parse() throws NBTException {
ArrayList<Tag> list = new ArrayList<>();
Iterator var2 = this.tagList.iterator();
while (var2.hasNext()) {
JSON2NBT.Any JSON2NBT$any = (JSON2NBT.Any) var2.next();
list.add(JSON2NBT$any.parse());
}
Class<? extends Tag> tagType = list.isEmpty() ? CompoundTag.class : list.get(0).getClass();
return new ListTag(tagType, list);
}
}
private static class Compound extends JSON2NBT.Any {
protected java.util.List<JSON2NBT.Any> tagList = Lists.newArrayList();
public Compound(String jsonIn) {
this.json = jsonIn;
}
public Tag parse() throws NBTException {
HashMap<String, Tag> map = new HashMap<String, Tag>();
Iterator var2 = this.tagList.iterator();
while (var2.hasNext()) {
JSON2NBT.Any JSON2NBT$any = (JSON2NBT.Any) var2.next();
map.put(JSON2NBT$any.json, JSON2NBT$any.parse());
}
return new CompoundTag(map);
}
}
private abstract static class Any {
protected String json;
Any() {
}
public abstract Tag parse() throws NBTException;
}
}

View File

@ -0,0 +1,17 @@
package com.boydti.fawe.jnbt;
public class NBTException extends RuntimeException {
public NBTException(String message) {
super(message);
}
/**
* Faster exception throwing if you don't fill the stacktrace
*
* @return
*/
@Override
public Throwable fillInStackTrace() {
return this;
}
}

View File

@ -0,0 +1,92 @@
package com.boydti.fawe.jnbt;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.exception.FaweException;
import com.sk89q.jnbt.NBTInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
public class NBTStreamer {
private final NBTInputStream is;
private final HashMap<String, BiConsumer> readers;
public NBTStreamer(NBTInputStream stream) {
this.is = stream;
readers = new HashMap<>();
}
/**
* Reads the entire stream and runs the applicable readers
*
* @throws IOException
*/
public void readFully() throws IOException {
is.readNamedTagLazy(node -> readers.get(node));
is.close();
}
/**
* Reads the stream until all readers have been used<br>
* - Use readFully if you expect a reader to appear more than once
* - Can exit early without having reading the entire file
*
* @throws IOException
*/
public void readQuick() throws IOException {
try {
is.readNamedTagLazy(node -> {
if (readers.isEmpty()) {
throw new FaweException(BBC.WORLDEDIT_CANCEL_REASON_MANUAL);
}
return readers.remove(node);
});
} catch (FaweException ignore) {}
is.close();
}
public <T, V> void addReader(String node, BiConsumer<T, V> run) {
if (run instanceof NBTStreamReader) {
((NBTStreamReader) run).init(node);
}
readers.put(node, run);
}
public <T, V> void addReader(BiConsumer<T, V> run, String... nodes) {
for (String node : nodes) {
addReader(node, run);
}
}
public static abstract class NBTStreamReader<T, V> implements BiConsumer<T, V> {
private String node;
public void init(String node) {
this.node = node;
}
public String getNode() {
return node;
}
}
public static abstract class ByteReader implements BiConsumer<Integer, Integer> {
@Override
public void accept(Integer index, Integer value) {
run(index, value);
}
public abstract void run(int index, int byteValue);
}
public static abstract class LongReader implements BiConsumer<Integer, Long> {
@Override
public void accept(Integer index, Long value) {
run(index, value);
}
public abstract void run(int index, long byteValue);
}
}

View File

@ -0,0 +1,484 @@
package com.boydti.fawe.jnbt;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweInputStream;
import com.boydti.fawe.object.FaweOutputStream;
import com.boydti.fawe.object.clipboard.CPUOptimizedClipboard;
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
import com.boydti.fawe.object.clipboard.FaweClipboard;
import com.boydti.fawe.object.clipboard.MemoryOptimizedClipboard;
import com.boydti.fawe.object.io.FastByteArrayOutputStream;
import com.boydti.fawe.object.io.FastByteArraysInputStream;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BlockMaterial;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.registry.state.PropertyKey;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.world.block.*;
import com.sk89q.worldedit.world.entity.EntityTypes;
import com.sk89q.worldedit.world.registry.LegacyMapper;
import net.jpountz.lz4.LZ4BlockInputStream;
import net.jpountz.lz4.LZ4BlockOutputStream;
import java.io.IOException;
import java.util.UUID;
import java.util.function.BiConsumer;
// TODO FIXME
public class SchematicStreamer extends NBTStreamer {
private final UUID uuid;
private FastByteArrayOutputStream idOut = new FastByteArrayOutputStream();
private FastByteArrayOutputStream dataOut = new FastByteArrayOutputStream();
private FastByteArrayOutputStream addOut;
private FaweOutputStream ids;
private FaweOutputStream datas;
private FaweOutputStream adds;
public SchematicStreamer(NBTInputStream stream, UUID uuid) {
super(stream);
this.uuid = uuid;
clipboard = new BlockArrayClipboard(new CuboidRegion(new Vector(0, 0, 0), new Vector(0, 0, 0)), fc);
}
public void addBlockReaders() throws IOException {
NBTStreamReader idInit = new NBTStreamReader<Integer, Integer>() {
@Override
public void accept(Integer length, Integer type) {
setupClipboard(length);
ids = new FaweOutputStream(new LZ4BlockOutputStream(idOut));
}
};
NBTStreamReader dataInit = new NBTStreamReader<Integer, Integer>() {
@Override
public void accept(Integer length, Integer type) {
setupClipboard(length);
datas = new FaweOutputStream(new LZ4BlockOutputStream(dataOut));
}
};
NBTStreamReader addInit = new NBTStreamReader<Integer, Integer>() {
@Override
public void accept(Integer length, Integer type) {
setupClipboard(length*2);
addOut = new FastByteArrayOutputStream();
adds = new FaweOutputStream(new LZ4BlockOutputStream(addOut));
}
};
addReader("Schematic.Blocks.?", idInit);
addReader("Schematic.Data.?", dataInit);
addReader("Schematic.AddBlocks.?", addInit);
addReader("Schematic.Blocks.#", new ByteReader() {
@Override
public void run(int index, int value) {
try {
ids.write(value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
addReader("Schematic.Data.#", new ByteReader() {
@Override
public void run(int index, int value) {
try {
datas.write(value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
addReader("Schematic.AddBlocks.#", new ByteReader() {
@Override
public void run(int index, int value) {
if (value != 0) {
int first = value & 0x0F;
int second = (value & 0xF0) >> 4;
int gIndex = index << 1;
try {
if (first != 0) adds.write(first);
if (second != 0) adds.write(second);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
});
ByteReader biomeReader = new ByteReader() {
@Override
public void run(int index, int value) {
fc.setBiome(index, value);
}
};
NBTStreamReader<Integer, Integer> initializer23 = new NBTStreamReader<Integer, Integer>() {
@Override
public void accept(Integer value1, Integer value2) {
if (fc == null) setupClipboard(length * width * height);
}
};
addReader("Schematic.AWEBiomes.?", initializer23);
addReader("Schematic.Biomes.?", initializer23);
addReader("Schematic.AWEBiomes.#", biomeReader); // AWE stores as an int[]
addReader("Schematic.Biomes.#", biomeReader); // FAWE stores as a byte[] (4x smaller)
// Tiles
addReader("Schematic.TileEntities.#", new BiConsumer<Integer, CompoundTag>() {
@Override
public void accept(Integer index, CompoundTag value) {
if (fc == null) {
setupClipboard(0);
}
int x = value.getInt("x");
int y = value.getInt("y");
int z = value.getInt("z");
fc.setTile(x, y, z, value);
}
});
// Entities
addReader("Schematic.Entities.#", new BiConsumer<Integer, CompoundTag>() {
@Override
public void accept(Integer index, CompoundTag compound) {
if (fc == null) {
setupClipboard(0);
}
String id = convertEntityId(compound.getString("id"));
if (id.isEmpty()) {
return;
}
ListTag positionTag = compound.getListTag("Pos");
ListTag directionTag = compound.getListTag("Rotation");
BaseEntity state = new BaseEntity(EntityTypes.get(id), compound);
fc.createEntity(clipboard, positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2), (float) directionTag.asDouble(0), (float) directionTag.asDouble(1), state);
}
});
}
private String convertEntityId(String id) {
switch(id) {
case "xp_orb":
return "experience_orb";
case "xp_bottle":
return "experience_bottle";
case "eye_of_ender_signal":
return "eye_of_ender";
case "ender_crystal":
return "end_crystal";
case "fireworks_rocket":
return "firework_rocket";
case "commandblock_minecart":
return "command_block_minecart";
case "snowman":
return "snow_golem";
case "villager_golem":
return "iron_golem";
case "evocation_fangs":
return "evoker_fangs";
case "evocation_illager":
return "evoker";
case "vindication_illager":
return "vindicator";
case "illusion_illager":
return "illusioner";
}
return id;
}
@Override
public void readFully() throws IOException {
super.readFully();
if (ids != null) ids.close();
if (datas != null) datas.close();
if (adds != null) adds.close();
FaweInputStream idIn = new FaweInputStream(new LZ4BlockInputStream(new FastByteArraysInputStream(idOut.toByteArrays())));
FaweInputStream dataIn = new FaweInputStream(new LZ4BlockInputStream(new FastByteArraysInputStream(dataOut.toByteArrays())));
LegacyMapper remap = LegacyMapper.getInstance();
Vector dimensions = fc.getDimensions();
int length = dimensions.getBlockX() * dimensions.getBlockY() * dimensions.getBlockZ();
if (adds == null) {
for (int i = 0; i < length; i++) {
fc.setBlock(i, remap.getBlockFromLegacyCombinedId(((idIn.read() & 0xFF) << 4) + (dataIn.read() & 0xF)));
}
} else {
FaweInputStream addIn = new FaweInputStream(new LZ4BlockInputStream(new FastByteArraysInputStream(dataOut.toByteArrays())));
for (int i = 0; i < length; i++) {
fc.setBlock(i, remap.getBlockFromLegacyCombinedId(((addIn.read() & 0xFF) << 8) + ((idIn.read() & 0xFF) << 4) + (dataIn.read() & 0xF)));
}
addIn.close();
}
idIn.close();
dataIn.close();
}
private void fixStates() {
fc.forEach(new FaweClipboard.BlockReader() {
@Override
public void run(int x, int y, int z, BlockState block) {
BlockTypes type = block.getBlockType();
switch (type) {
case ACACIA_STAIRS:
case BIRCH_STAIRS:
case BRICK_STAIRS:
case COBBLESTONE_STAIRS:
case DARK_OAK_STAIRS:
case DARK_PRISMARINE_STAIRS:
case JUNGLE_STAIRS:
case NETHER_BRICK_STAIRS:
case OAK_STAIRS:
case PRISMARINE_BRICK_STAIRS:
case PRISMARINE_STAIRS:
case PURPUR_STAIRS:
case QUARTZ_STAIRS:
case RED_SANDSTONE_STAIRS:
case SANDSTONE_STAIRS:
case SPRUCE_STAIRS:
case STONE_BRICK_STAIRS:
Object half = block.getState(PropertyKey.HALF);
Direction facing = block.getState(PropertyKey.FACING);
BlockVector forward = facing.toBlockVector();
Direction left = facing.getLeft();
Direction right = facing.getRight();
BlockStateHolder forwardBlock = fc.getBlock(x + forward.getBlockX(), y + forward.getBlockY(), z + forward.getBlockZ());
BlockTypes forwardType = forwardBlock.getBlockType();
if (forwardType.hasProperty(PropertyKey.SHAPE)) {
Direction forwardFacing = (Direction) forwardBlock.getState(PropertyKey.FACING);
if (forwardFacing == left) {
BlockStateHolder rightBlock = fc.getBlock(x + right.getBlockX(), y + right.getBlockY(), z + right.getBlockZ());
BlockTypes rightType = rightBlock.getBlockType();
if (!rightType.hasProperty(PropertyKey.SHAPE) || rightBlock.getState(PropertyKey.FACING) != facing) {
fc.setBlock(x, y, z, block.with(PropertyKey.SHAPE, "inner_left"));
}
return;
} else if (forwardFacing == right) {
BlockStateHolder leftBlock = fc.getBlock(x + left.getBlockX(), y + left.getBlockY(), z + left.getBlockZ());
BlockTypes leftType = leftBlock.getBlockType();
if (!leftType.hasProperty(PropertyKey.SHAPE) || leftBlock.getState(PropertyKey.FACING) != facing) {
fc.setBlock(x, y, z, block.with(PropertyKey.SHAPE, "inner_right"));
}
return;
}
}
BlockStateHolder backwardsBlock = fc.getBlock(x - forward.getBlockX(), y - forward.getBlockY(), z - forward.getBlockZ());
BlockTypes backwardsType = backwardsBlock.getBlockType();
if (backwardsType.hasProperty(PropertyKey.SHAPE)) {
Direction backwardsFacing = (Direction) backwardsBlock.getState(PropertyKey.FACING);
if (backwardsFacing == left) {
BlockStateHolder rightBlock = fc.getBlock(x + right.getBlockX(), y + right.getBlockY(), z + right.getBlockZ());
BlockTypes rightType = rightBlock.getBlockType();
if (!rightType.hasProperty(PropertyKey.SHAPE) || rightBlock.getState(PropertyKey.FACING) != facing) {
fc.setBlock(x, y, z, block.with(PropertyKey.SHAPE, "outer_left"));
}
return;
} else if (backwardsFacing == right) {
BlockStateHolder leftBlock = fc.getBlock(x + left.getBlockX(), y + left.getBlockY(), z + left.getBlockZ());
BlockTypes leftType = leftBlock.getBlockType();
if (!leftType.hasProperty(PropertyKey.SHAPE) || leftBlock.getState(PropertyKey.FACING) != facing) {
fc.setBlock(x, y, z, block.with(PropertyKey.SHAPE, "outer_right"));
}
return;
}
}
break;
default:
int group = group(type);
if (group != -1) {
BlockStateHolder set = block;
if (set.getState(PropertyKey.NORTH) == Boolean.FALSE && merge(group, x, y, z - 1)) set = set.with(PropertyKey.NORTH, true);
if (set.getState(PropertyKey.EAST) == Boolean.FALSE && merge(group, x + 1, y, z)) set = set.with(PropertyKey.EAST, true);
if (set.getState(PropertyKey.SOUTH) == Boolean.FALSE && merge(group, x, y, z + 1)) set = set.with(PropertyKey.SOUTH, true);
if (set.getState(PropertyKey.WEST) == Boolean.FALSE && merge(group, x - 1, y, z)) set = set.with(PropertyKey.WEST, true);
if (set != block) fc.setBlock(x, y, z, set);
}
break;
}
}
}, false);
}
private BlockTypeSwitch<Boolean> fullCube = new BlockTypeSwitchBuilder<>(false).add(type -> {
BlockMaterial mat = type.getMaterial();
return (mat.isFullCube() && !mat.isFragileWhenPushed() && mat.getLightValue() == 0 && mat.isOpaque() && mat.isSolid() && !mat.isTranslucent());
}, true).build();
private boolean merge(int group, int x, int y, int z) {
BlockStateHolder block = fc.getBlock(x, y, z);
BlockTypes type = block.getBlockType();
return group(type) == group || fullCube.apply(type);
}
private int group(BlockTypes type) {
switch (type) {
case ACACIA_FENCE:
case BIRCH_FENCE:
case DARK_OAK_FENCE:
case JUNGLE_FENCE:
case OAK_FENCE:
case SPRUCE_FENCE:
return 0;
case NETHER_BRICK_FENCE:
return 1;
case COBBLESTONE_WALL:
case MOSSY_COBBLESTONE_WALL:
return 2;
case IRON_BARS:
case BLACK_STAINED_GLASS_PANE:
case BLUE_STAINED_GLASS_PANE:
case BROWN_MUSHROOM_BLOCK:
case BROWN_STAINED_GLASS_PANE:
case CYAN_STAINED_GLASS_PANE:
case GLASS_PANE:
case GRAY_STAINED_GLASS_PANE:
case GREEN_STAINED_GLASS_PANE:
case LIGHT_BLUE_STAINED_GLASS_PANE:
case LIGHT_GRAY_STAINED_GLASS_PANE:
case LIME_STAINED_GLASS_PANE:
case MAGENTA_STAINED_GLASS_PANE:
case ORANGE_STAINED_GLASS_PANE:
case PINK_STAINED_GLASS_PANE:
case PURPLE_STAINED_GLASS_PANE:
case RED_STAINED_GLASS_PANE:
case WHITE_STAINED_GLASS_PANE:
case YELLOW_STAINED_GLASS_PANE:
return 3;
default:
return -1;
}
}
public void addDimensionReaders() {
addReader("Schematic.Height", new BiConsumer<Integer, Short>() {
@Override
public void accept(Integer index, Short value) {
height = (value);
}
});
addReader("Schematic.Width", new BiConsumer<Integer, Short>() {
@Override
public void accept(Integer index, Short value) {
width = (value);
}
});
addReader("Schematic.Length", new BiConsumer<Integer, Short>() {
@Override
public void accept(Integer index, Short value) {
length = (value);
}
});
addReader("Schematic.WEOriginX", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
originX = (value);
}
});
addReader("Schematic.WEOriginY", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
originY = (value);
}
});
addReader("Schematic.WEOriginZ", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
originZ = (value);
}
});
addReader("Schematic.WEOffsetX", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
offsetX = (value);
}
});
addReader("Schematic.WEOffsetY", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
offsetY = (value);
}
});
addReader("Schematic.WEOffsetZ", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
offsetZ = (value);
}
});
}
private int height;
private int width;
private int length;
private int originX;
private int originY;
private int originZ;
private int offsetX;
private int offsetY;
private int offsetZ;
private BlockArrayClipboard clipboard;
private FaweClipboard fc;
private FaweClipboard setupClipboard(int size) {
if (fc != null) {
if (fc.getDimensions().getX() == 0) {
fc.setDimensions(new Vector(size, 1, 1));
}
return fc;
}
if (Settings.IMP.CLIPBOARD.USE_DISK) {
return fc = new DiskOptimizedClipboard(size, 1, 1, uuid);
} else if (Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL == 0) {
return fc = new CPUOptimizedClipboard(size, 1, 1);
} else {
return fc = new MemoryOptimizedClipboard(size, 1, 1);
}
}
public Vector getOrigin() {
return new Vector(originX, originY, originZ);
}
public Vector getOffset() {
return new Vector(offsetX, offsetY, offsetZ);
}
public Vector getDimensions() {
return new Vector(width, height, length);
}
public void setClipboard(FaweClipboard clipboard) {
this.fc = clipboard;
}
public Clipboard getClipboard() throws IOException {
try {
addDimensionReaders();
addBlockReaders();
readFully();
Vector min = new Vector(originX, originY, originZ);
Vector offset = new Vector(offsetX, offsetY, offsetZ);
Vector origin = min.subtract(offset);
Vector dimensions = new Vector(width, height, length);
fc.setDimensions(dimensions);
fixStates();
CuboidRegion region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE));
clipboard.init(region, fc);
clipboard.setOrigin(origin);
return clipboard;
} catch (Throwable e) {
if (fc != null) {
fc.close();
}
throw e;
}
}
}

View File

@ -0,0 +1,19 @@
package com.boydti.fawe.jnbt.anvil;
public class ChunkSimplifier {
private final HeightMapMCAGenerator gen;
public ChunkSimplifier(HeightMapMCAGenerator gen) {
this.gen = gen;
}
public void simplify(MCAChunk chunk) {
// Copy biome
// Calculate water level
// Determine floor block
// Determine overlay block
// determine main block
// Copy bedrock
// copy anomalies to blocks
}
}

View File

@ -0,0 +1,143 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.TextureUtil;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public final class HeightMapMCADrawer {
private final HeightMapMCAGenerator gen;
private final TextureUtil tu;
private final ForkJoinPool pool;
public HeightMapMCADrawer(HeightMapMCAGenerator generator, TextureUtil textureUtil) {
this.gen = generator;
this.tu = textureUtil;
this.pool = new ForkJoinPool();
}
public HeightMapMCADrawer(HeightMapMCAGenerator generator) {
this(generator, Fawe.get().getCachedTextureUtil(false, 0, 100));
}
public BufferedImage draw() {
BufferedImage img = new BufferedImage(gen.getWidth(), gen.getLength(), BufferedImage.TYPE_INT_RGB);
final int[] overlay = gen.overlay == null ? gen.floor.get() : gen.overlay.get();
final int[] floor = gen.floor.get();
final int[] main = gen.main.get();
final byte[] heights = gen.heights.get();
final byte[] biomes = gen.biomes.get();
final int waterHeight = gen.primtives.waterHeight;
final int width = gen.getWidth();
final int length = gen.getLength();
int[] raw = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
int parallelism = pool.getParallelism();
int size = (heights.length + parallelism - 1) / parallelism;
for (int i = 0; i < parallelism; i++) {
int start = i * size;
int end = Math.min(heights.length, start + size);
pool.submit((Runnable) () -> {
for (int index = start; index < end; index ++) {
int height = (heights[index] & 0xFF);
int combined;
if ((combined = overlay[index]) == 0) {
height--;
combined = floor[index];
if (BlockTypes.getFromStateId(combined).getMaterial().isAir()) {
height--;
combined = main[index];
}
}
// draw combined
int color;
switch (combined >> 4) {
case 2:
color = getAverageBiomeColor(biomes, width, index);
break;
case 78:
color = (0xDD << 16) + (0xDD << 8) + (0xDD << 0);
break;
default:
color = tu.getColor(BlockTypes.getFromStateId(combined));
break;
}
int slope = getSlope(heights, width, index, height);
if (slope != 0) {
slope = (slope << 3) + (slope << 2);
int r = MathMan.clamp(((color >> 16) & 0xFF) + slope, 0, 255);
int g = MathMan.clamp(((color >> 8) & 0xFF) + slope, 0, 255);
int b = MathMan.clamp(((color >> 0) & 0xFF) + slope, 0, 255);
color = (r << 16) + (g << 8) + (b << 0);
}
if (height + 1 < waterHeight) {
int waterId = gen.primtives.waterId;
int waterColor = 0;
BlockTypes waterType = BlockTypes.get(waterId);
switch (waterType) {
case WATER:
color = tu.averageColor((0x11 << 16) + (0x66 << 8) + (0xCC), color);
break;
case LAVA:
color = (0xCC << 16) + (0x33 << 8) + (0);
break;
default:
color = tu.getColor(waterType);
break;
}
}
raw[index] = color;
}
});
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
pool.shutdownNow();
return img;
}
private final int getAverageBiomeColor(byte[] biomes, int width, int index) {
int c0 = tu.getBiome(biomes[index] & 0xFF).grassCombined;
int c2 = getBiome(biomes, index + 1 + width, index);
int c1 = getBiome(biomes, index - 1 - width, index);
// int c3 = getBiome(biomes, index + width, index);
// int c4 = getBiome(biomes, index - width, index);
int r = ((c0 >> 16) & 0xFF) + ((c1 >> 16) & 0xFF) + ((c2 >> 16) & 0xFF);// + ((c3 >> 16) & 0xFF) + ((c4 >> 16) & 0xFF);
int g = ((c0 >> 8) & 0xFF) + ((c1 >> 8) & 0xFF) + ((c2 >> 8) & 0xFF);// + ((c3 >> 8) & 0xFF) + ((c4 >> 8) & 0xFF);
int b = ((c0) & 0xFF) + ((c1) & 0xFF) + ((c2) & 0xFF);// + ((c3) & 0xFF) + ((c4) & 0xFF);
r = r * 85 >> 8;
g = g * 85 >> 8;
b = b * 85 >> 8;
return (r << 16) + (g << 8) + (b);
}
private final int getBiome(byte[] biomes, int newIndex, int index) {
if (newIndex < 0 || newIndex >= biomes.length) newIndex = index;
int biome = biomes[newIndex] & 0xFF;
return tu.getBiome(biome).grassCombined;
}
private int getSlope(byte[] heights, int width, int index, int height) {
return (
+ getHeight(heights, index + 1, height)
// + getHeight(heights, index + width, height)
+ getHeight(heights, index + width + 1, height)
- getHeight(heights, index - 1, height)
// - getHeight(heights, index - width, height)
- getHeight(heights, index - width - 1, height)
);
}
private int getHeight(byte[] heights, int index, int height) {
if (index < 0 || index >= heights.length) return height;
return heights[index] & 0xFF;
}
}

View File

@ -0,0 +1,877 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.NBTStreamer;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.io.FastByteArrayOutputStream;
import com.boydti.fawe.object.number.MutableLong;
import com.boydti.fawe.util.ArrayUtil;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.*;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.function.BiConsumer;
public class MCAChunk extends FaweChunk<Void> {
// ids: byte[16][4096]
// data: byte[16][2048]
// skylight: byte[16][2048]
// blocklight: byte[16][2048]
// entities: Map<Short, CompoundTag>
// tiles: List<CompoundTag>
// biomes: byte[256]
// compressedSize: int
// modified: boolean
// deleted: boolean
public byte[][] ids;
public byte[][] data;
public byte[][] skyLight;
public byte[][] blockLight;
public byte[] biomes;
public Map<Short, CompoundTag> tiles = new HashMap<>();
public Map<UUID, CompoundTag> entities = new HashMap<>();
private long inhabitedTime;
private long lastUpdate;
private int[] heightMap;
private int modified;
private boolean deleted;
public MCAChunk(FaweQueue queue, int x, int z) {
super(queue, x, z);
this.ids = new byte[16][];
this.data = new byte[16][];
this.skyLight = new byte[16][];
this.blockLight = new byte[16][];
this.biomes = new byte[256];
this.tiles = new HashMap<>();
this.entities = new HashMap<>();
this.lastUpdate = System.currentTimeMillis();
this.heightMap = new int[256];
this.setModified();
}
public MCAChunk(MCAChunk parent, boolean shallow) {
super(parent.getParent(), parent.getX(), parent.getZ());
if (shallow) {
this.ids = parent.ids;
this.data = parent.data;
this.skyLight = parent.skyLight;
this.blockLight = parent.blockLight;
this.biomes = parent.biomes;
this.tiles = parent.tiles;
this.entities = parent.entities;
this.inhabitedTime = parent.inhabitedTime;
this.lastUpdate = parent.lastUpdate;
this.heightMap = parent.heightMap;
this.modified = parent.modified;
this.deleted = parent.deleted;
} else {
this.ids = (byte[][]) MainUtil.copyNd(parent.ids);
this.data = (byte[][]) MainUtil.copyNd(parent.data);
this.skyLight = (byte[][]) MainUtil.copyNd(parent.skyLight);
this.blockLight = (byte[][]) MainUtil.copyNd(parent.blockLight);
this.biomes = parent.biomes.clone();
this.tiles = new HashMap<>(parent.tiles);
this.entities = new HashMap<>(parent.entities);
this.inhabitedTime = parent.inhabitedTime;
this.lastUpdate = parent.lastUpdate;
this.heightMap = parent.heightMap.clone();
this.modified = parent.modified;
this.deleted = parent.deleted;
}
}
public void write(NBTOutputStream nbtOut) throws IOException {
nbtOut.writeNamedTagName("", NBTConstants.TYPE_COMPOUND);
nbtOut.writeLazyCompoundTag("Level", new NBTOutputStream.LazyWrite() {
@Override
public void write(NBTOutputStream out) throws IOException {
out.writeNamedTag("V", (byte) 1);
out.writeNamedTag("xPos", getX());
out.writeNamedTag("zPos", getZ());
out.writeNamedTag("LightPopulated", (byte) 0);
out.writeNamedTag("TerrainPopulated", (byte) 1);
if (entities.isEmpty()) {
out.writeNamedEmptyList("Entities");
} else {
out.writeNamedTag("Entities", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>(entities.values())));
}
if (tiles.isEmpty()) {
out.writeNamedEmptyList("TileEntities");
} else {
out.writeNamedTag("TileEntities", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>(tiles.values())));
}
out.writeNamedTag("InhabitedTime", inhabitedTime);
out.writeNamedTag("LastUpdate", lastUpdate);
if (biomes != null) {
out.writeNamedTag("Biomes", biomes);
}
out.writeNamedTag("HeightMap", heightMap);
out.writeNamedTagName("Sections", NBTConstants.TYPE_LIST);
nbtOut.getOutputStream().writeByte(NBTConstants.TYPE_COMPOUND);
int len = 0;
for (int layer = 0; layer < ids.length; layer++) {
if (ids[layer] != null) len++;
}
nbtOut.getOutputStream().writeInt(len);
for (int layer = 0; layer < ids.length; layer++) {
byte[] idLayer = ids[layer];
if (idLayer == null) {
continue;
}
out.writeNamedTag("Y", (byte) layer);
out.writeNamedTag("BlockLight", blockLight[layer]);
out.writeNamedTag("SkyLight", skyLight[layer]);
out.writeNamedTag("Blocks", idLayer);
out.writeNamedTag("Data", data[layer]);
out.writeEndTag();
}
}
});
nbtOut.writeEndTag();
}
public byte[] toBytes(byte[] buffer) throws IOException {
if (buffer == null) {
buffer = new byte[8192];
}
FastByteArrayOutputStream buffered = new FastByteArrayOutputStream(buffer);
DataOutputStream dataOut = new DataOutputStream(buffered);
try (NBTOutputStream nbtOut = new NBTOutputStream((DataOutput) dataOut)) {
write(nbtOut);
}
return buffered.toByteArray();
}
public long getInhabitedTime() {
return inhabitedTime;
}
public long getLastUpdate() {
return lastUpdate;
}
public void setInhabitedTime(long inhabitedTime) {
this.inhabitedTime = inhabitedTime;
}
public void setLastUpdate(long lastUpdate) {
this.lastUpdate = lastUpdate;
}
public void copyFrom(MCAChunk other, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, int offsetX, int offsetY, int offsetZ) {
minY = Math.max(-offsetY - minY, minY);
maxY = Math.min(255 - offsetY, maxY);
minZ = Math.max(-offsetZ - minZ, minZ);
maxZ = Math.min(15 - offsetZ, maxZ);
minX = Math.max(-offsetX - minX, minX);
maxX = Math.min(15 - offsetX, maxX);
if (minX > maxX || minZ > maxZ || minY > maxY) return;
int startLayer = minY >> 4;
int endLayer = maxY >> 4;
for (int otherY = minY, thisY = minY + offsetY; otherY <= maxY; otherY++, thisY++) {
int thisLayer = thisY >> 4;
int otherLayer = otherY >> 4;
byte[] thisIds = ids[thisLayer];
byte[] otherIds = other.ids[otherLayer];
if (otherIds == null) {
if (thisIds != null) {
int indexY = (thisY & 15) << 8;
byte[] thisData = data[thisLayer];
byte[] thisSkyLight = skyLight[thisLayer];
byte[] thisBlockLight = blockLight[thisLayer];
for (int otherZ = minZ, thisZ = minZ + offsetZ; otherZ <= maxZ; otherZ++, thisZ++) {
int startIndex = indexY + (thisZ << 4) + minX + offsetX;
int endIndex = startIndex + maxX - minX;
ArrayUtil.fill(thisIds, startIndex, endIndex + 1, (byte) 0);
int startIndexShift = startIndex >> 1;
int endIndexShift = endIndex >> 1;
if ((startIndex & 1) != 0) {
startIndexShift++;
setNibble(startIndex, thisData, (byte) 0);
setNibble(startIndex, thisSkyLight, (byte) 0);
setNibble(startIndex, thisBlockLight, (byte) 0);
}
if ((endIndex & 1) != 1) {
endIndexShift--;
setNibble(endIndex, thisData, (byte) 0);
setNibble(endIndex, thisSkyLight, (byte) 0);
setNibble(endIndex, thisBlockLight, (byte) 0);
}
ArrayUtil.fill(thisData, startIndexShift, endIndexShift + 1, (byte) 0);
ArrayUtil.fill(thisSkyLight, startIndexShift, endIndexShift + 1, (byte) 0);
ArrayUtil.fill(thisBlockLight, startIndexShift, endIndexShift + 1, (byte) 0);
}
}
continue;
} else if (thisIds == null) {
ids[thisLayer] = thisIds = new byte[4096];
data[thisLayer] = new byte[2048];
skyLight[thisLayer] = new byte[2048];
blockLight[thisLayer] = new byte[2048];
}
int indexY = (thisY & 15) << 8;
int otherIndexY = (otherY & 15) << 8;
byte[] thisData = data[thisLayer];
byte[] thisSkyLight = skyLight[thisLayer];
byte[] thisBlockLight = blockLight[thisLayer];
byte[] otherData = other.data[otherLayer];
byte[] otherSkyLight = other.skyLight[otherLayer];
byte[] otherBlockLight = other.blockLight[otherLayer];
for (int otherZ = minZ, thisZ = minZ + offsetZ; otherZ <= maxZ; otherZ++, thisZ++) {
int startIndex = indexY + (thisZ << 4) + minX + offsetX;
int endIndex = startIndex + maxX - minX;
int otherStartIndex = otherIndexY + (otherZ << 4) + minX;
int otherEndIndex = otherStartIndex + maxX - minX;
System.arraycopy(otherIds, otherStartIndex, thisIds, startIndex, endIndex - startIndex + 1);
if ((startIndex & 1) == (otherStartIndex & 1)) {
int startIndexShift = startIndex >> 1;
int endIndexShift = endIndex >> 1;
int otherStartIndexShift = otherStartIndex >> 1;
int otherEndIndexShift = otherEndIndex >> 1;
if ((startIndex & 1) != 0) {
startIndexShift++;
otherStartIndexShift++;
setNibble(startIndex, thisData, getNibble(otherStartIndex, otherData));
setNibble(startIndex, thisSkyLight, getNibble(otherStartIndex, otherSkyLight));
setNibble(startIndex, thisBlockLight, getNibble(otherStartIndex, otherBlockLight));
}
if ((endIndex & 1) != 1) {
endIndexShift--;
otherEndIndexShift--;
setNibble(endIndex, thisData, getNibble(otherEndIndex, otherData));
setNibble(endIndex, thisSkyLight, getNibble(otherEndIndex, otherSkyLight));
setNibble(endIndex, thisBlockLight, getNibble(otherEndIndex, otherBlockLight));
}
System.arraycopy(otherData, otherStartIndexShift, thisData, startIndexShift, endIndexShift - startIndexShift + 1);
System.arraycopy(otherSkyLight, otherStartIndexShift, thisSkyLight, startIndexShift, endIndexShift - startIndexShift + 1);
System.arraycopy(otherBlockLight, otherStartIndexShift, thisBlockLight, startIndexShift, endIndexShift - startIndexShift + 1);
} else {
for (int thisIndex = startIndex, otherIndex = otherStartIndex; thisIndex <= endIndex; thisIndex++, otherIndex++) {
setNibble(thisIndex, thisData, getNibble(otherIndex, otherData));
setNibble(thisIndex, thisSkyLight, getNibble(otherIndex, otherSkyLight));
setNibble(thisIndex, thisBlockLight, getNibble(otherIndex, otherBlockLight));
}
}
}
}
if (!other.tiles.isEmpty()) {
for (Map.Entry<Short, CompoundTag> entry : other.tiles.entrySet()) {
int key = entry.getKey();
int x = MathMan.untripleBlockCoordX(key);
int y = MathMan.untripleBlockCoordY(key);
int z = MathMan.untripleBlockCoordZ(key);
if (x < minX || x > maxX) continue;
if (z < minZ || z > maxZ) continue;
if (y < minY || y > maxY) continue;
x += offsetX;
y += offsetY;
z += offsetZ;
short pair = MathMan.tripleBlockCoord(x, y, z);
CompoundTag tag = entry.getValue();
Map<String, Tag> map = ReflectionUtils.getMap(tag.getValue());
map.put("x", new IntTag((x & 15) + (getX() << 4)));
map.put("y", new IntTag(y));
map.put("z", new IntTag((z & 15) + (getZ() << 4)));
tiles.put(pair, tag);
}
}
}
public void copyFrom(MCAChunk other, int minY, int maxY, int offsetY) {
minY = Math.max(-offsetY - minY, minY);
maxY = Math.min(255 - offsetY, maxY);
if (minY > maxY) return;
if ((offsetY & 15) == 0) {
int offsetLayer = offsetY >> 4;
int startLayer = minY >> 4;
int endLayer = maxY >> 4;
for (int thisLayer = startLayer + offsetLayer, otherLayer = startLayer; thisLayer <= endLayer; thisLayer++, otherLayer++) {
byte[] otherIds = other.ids[otherLayer];
byte[] currentIds = ids[thisLayer];
int by = otherLayer << 4;
int ty = by + 15;
if (by >= minY && ty <= maxY) {
if (otherIds != null) {
ids[thisLayer] = otherIds;
data[thisLayer] = other.data[otherLayer];
skyLight[thisLayer] = other.skyLight[otherLayer];
blockLight[thisLayer] = other.blockLight[otherLayer];
} else {
ids[thisLayer] = null;
}
} else {
by = Math.max(by, minY) & 15;
ty = Math.min(ty, maxY) & 15;
int indexStart = by << 8;
int indexEnd = 256 + (ty << 8);
int indexStartShift = indexStart >> 1;
int indexEndShift = indexEnd >> 1;
if (otherIds == null) {
if (currentIds != null) {
ArrayUtil.fill(currentIds, indexStart, indexEnd, (byte) 0);
ArrayUtil.fill(data[thisLayer], indexStartShift, indexEndShift, (byte) 0);
ArrayUtil.fill(skyLight[thisLayer], indexStartShift, indexEndShift, (byte) 0);
ArrayUtil.fill(blockLight[thisLayer], indexStartShift, indexEndShift, (byte) 0);
}
} else {
if (currentIds == null) {
currentIds = this.ids[thisLayer] = new byte[4096];
this.data[thisLayer] = new byte[2048];
this.skyLight[thisLayer] = new byte[2048];
this.blockLight[thisLayer] = new byte[2048];
}
System.arraycopy(other.ids[otherLayer], indexStart, currentIds, indexStart, indexEnd - indexStart);
System.arraycopy(other.data[otherLayer], indexStartShift, data[thisLayer], indexStartShift, indexEndShift - indexStartShift);
System.arraycopy(other.skyLight[otherLayer], indexStartShift, skyLight[thisLayer], indexStartShift, indexEndShift - indexStartShift);
System.arraycopy(other.blockLight[otherLayer], indexStartShift, blockLight[thisLayer], indexStartShift, indexEndShift - indexStartShift);
}
}
}
} else {
for (int otherY = minY, thisY = minY + offsetY; otherY <= maxY; otherY++, thisY++) {
int otherLayer = otherY >> 4;
int thisLayer = thisY >> 4;
byte[] thisIds = this.ids[thisLayer];
byte[] otherIds = other.ids[otherLayer];
int thisStartIndex = (thisY & 15) << 8;
int thisStartIndexShift = thisStartIndex >> 1;
if (otherIds == null) {
if (thisIds == null) {
continue;
}
ArrayUtil.fill(thisIds, thisStartIndex, thisStartIndex + 256, (byte) 0);
ArrayUtil.fill(this.data[thisLayer], thisStartIndexShift, thisStartIndexShift + 128, (byte) 0);
ArrayUtil.fill(this.skyLight[thisLayer], thisStartIndexShift, thisStartIndexShift + 128, (byte) 0);
ArrayUtil.fill(this.blockLight[thisLayer], thisStartIndexShift, thisStartIndexShift + 128, (byte) 0);
continue;
} else if (thisIds == null) {
ids[thisLayer] = thisIds = new byte[4096];
data[thisLayer] = new byte[2048];
skyLight[thisLayer] = new byte[2048];
blockLight[thisLayer] = new byte[2048];
}
int otherStartIndex = (otherY & 15) << 8;
int otherStartIndexShift = otherStartIndex >> 1;
System.arraycopy(other.ids[otherLayer], otherStartIndex, thisIds, thisStartIndex, 256);
System.arraycopy(other.data[otherLayer], otherStartIndexShift, data[thisLayer], thisStartIndexShift, 128);
System.arraycopy(other.skyLight[otherLayer], otherStartIndexShift, skyLight[thisLayer], thisStartIndexShift, 128);
System.arraycopy(other.blockLight[otherLayer], otherStartIndexShift, blockLight[thisLayer], thisStartIndexShift, 128);
}
}
// Copy nbt
int thisMinY = minY + offsetY;
int thisMaxY = maxY + offsetY;
if (!tiles.isEmpty()) {
Iterator<Map.Entry<Short, CompoundTag>> iter = tiles.entrySet().iterator();
while (iter.hasNext()) {
int y = MathMan.untripleBlockCoordY(iter.next().getKey());
if (y >= thisMinY && y <= thisMaxY) iter.remove();
}
}
if (!other.tiles.isEmpty()) {
for (Map.Entry<Short, CompoundTag> entry : other.tiles.entrySet()) {
int key = entry.getKey();
int y = MathMan.untripleBlockCoordY(key);
if (y >= minY && y <= maxY) {
tiles.put((short) (key + offsetY), entry.getValue());
}
}
}
if (!other.entities.isEmpty()) {
for (Map.Entry<UUID, CompoundTag> entry : other.entities.entrySet()) {
// TODO
}
}
}
public int getMinLayer() {
for (int layer = 0; layer < ids.length; layer++) {
if (ids[layer] != null) {
return layer;
}
}
return Integer.MAX_VALUE;
}
public int getMaxLayer() {
for (int layer = ids.length - 1; layer >= 0; layer--) {
if (ids[layer] != null) {
return layer;
}
}
return Integer.MIN_VALUE;
}
/**
* Deprecated, use the toBytes method
*
* @return
*/
@Deprecated
public CompoundTag toTag() {
if (deleted) {
return null;
}
// e.g. by precalculating the length
HashMap<String, Object> level = new HashMap<String, Object>();
level.put("Entities", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>(entities.values())));
level.put("TileEntities", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>(tiles.values())));
level.put("InhabitedTime", inhabitedTime);
level.put("LastUpdate", lastUpdate);
level.put("LightPopulated", (byte) 0);
level.put("TerrainPopulated", (byte) 1);
level.put("V", (byte) 1);
level.put("xPos", getX());
level.put("zPos", getZ());
if (biomes != null) {
level.put("Biomes", biomes);
}
level.put("HeightMap", heightMap);
ArrayList<HashMap<String, Object>> sections = new ArrayList<>();
for (int layer = 0; layer < ids.length; layer++) {
byte[] idLayer = ids[layer];
if (idLayer == null) {
continue;
}
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("Y", (byte) layer);
map.put("BlockLight", blockLight[layer]);
map.put("SkyLight", skyLight[layer]);
map.put("Blocks", idLayer);
map.put("Data", data[layer]);
sections.add(map);
}
level.put("Sections", sections);
HashMap<String, Object> root = new HashMap<>();
root.put("Level", level);
return FaweCache.asTag(root);
}
public MCAChunk(NBTInputStream nis, FaweQueue parent, int x, int z, boolean readPos) throws IOException {
super(parent, x, z);
ids = new byte[16][];
data = new byte[16][];
skyLight = new byte[16][];
blockLight = new byte[16][];
NBTStreamer streamer = new NBTStreamer(nis);
streamer.addReader(".Level.InhabitedTime", new BiConsumer<Integer, Long>() {
@Override
public void accept(Integer index, Long value) {
inhabitedTime = value;
}
});
streamer.addReader(".Level.LastUpdate", new BiConsumer<Integer, Long>() {
@Override
public void accept(Integer index, Long value) {
lastUpdate = value;
}
});
streamer.addReader(".Level.Sections.#", new BiConsumer<Integer, CompoundTag>() {
@Override
public void accept(Integer index, CompoundTag tag) {
int layer = tag.getByte("Y");
ids[layer] = tag.getByteArray("Blocks");
data[layer] = tag.getByteArray("Data");
skyLight[layer] = tag.getByteArray("SkyLight");
blockLight[layer] = tag.getByteArray("BlockLight");
}
});
streamer.addReader(".Level.TileEntities.#", new BiConsumer<Integer, CompoundTag>() {
@Override
public void accept(Integer index, CompoundTag tile) {
int x = tile.getInt("x") & 15;
int y = tile.getInt("y");
int z = tile.getInt("z") & 15;
short pair = MathMan.tripleBlockCoord(x, y, z);
tiles.put(pair, tile);
}
});
streamer.addReader(".Level.Entities.#", new BiConsumer<Integer, CompoundTag>() {
@Override
public void accept(Integer index, CompoundTag entityTag) {
if (entities == null) {
entities = new HashMap<UUID, CompoundTag>();
}
long least = entityTag.getLong("UUIDLeast");
long most = entityTag.getLong("UUIDMost");
entities.put(new UUID(most, least), entityTag);
}
});
streamer.addReader(".Level.Biomes", new BiConsumer<Integer, byte[]>() {
@Override
public void accept(Integer index, byte[] value) {
biomes = value;
}
});
streamer.addReader(".Level.HeightMap", new BiConsumer<Integer, int[]>() {
@Override
public void accept(Integer index, int[] value) {
heightMap = value;
}
});
if (readPos) {
streamer.addReader(".Level.xPos", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
MCAChunk.this.setLoc(getParent(), value, getZ());
}
});
streamer.addReader(".Level.zPos", new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer index, Integer value) {
MCAChunk.this.setLoc(getParent(), getX(), value);
}
});
}
streamer.readFully();
}
public long filterBlocks(MutableMCABackedBaseBlock mutableBlock, MCAFilter filter) {
MutableLong result = new MutableLong();
mutableBlock.setChunk(this);
int bx = getX() << 4;
int bz = getZ() << 4;
int tx = bx + 15;
int tz = bz + 15;
for (int layer = 0; layer < ids.length; layer++) {
if (doesSectionExist(layer)) {
mutableBlock.setArrays(layer);
int yStart = layer << 4;
int yEnd = yStart + 15;
for (int y = yStart, y0 = (yStart & 15); y <= yEnd; y++, y0++) {
int yIndex = ((y0) << 8);
mutableBlock.setY(y);
for (int z = bz, z0 = bz & 15; z <= tz; z++, z0++) {
int zIndex = yIndex + ((z0) << 4);
mutableBlock.setZ(z);
for (int x = bx, x0 = bx & 15; x <= tx; x++, x0++) {
int xIndex = zIndex + x0;
mutableBlock.setX(x);
mutableBlock.setIndex(xIndex);
filter.applyBlock(x, y, z, mutableBlock, result);
}
}
}
}
}
return result.get();
}
public int[] getHeightMapArray() {
return heightMap;
}
public void setDeleted(boolean deleted) {
setModified();
this.deleted = deleted;
}
public boolean isDeleted() {
return deleted;
}
public boolean isModified() {
return modified != 0;
}
public int getModified() {
return modified;
}
@Deprecated
public final void setModified() {
this.modified++;
}
@Override
public int getBitMask() {
int bitMask = 0;
for (int section = 0; section < ids.length; section++) {
if (ids[section] != null) {
bitMask += 1 << section;
}
}
return bitMask;
}
@Override
public void setTile(int x, int y, int z, CompoundTag tile) {
setModified();
short pair = MathMan.tripleBlockCoord(x, y, z);
if (tile != null) {
tiles.put(pair, tile);
} else {
tiles.remove(pair);
}
}
@Override
public void setEntity(CompoundTag entityTag) {
setModified();
long least = entityTag.getLong("UUIDLeast");
long most = entityTag.getLong("UUIDMost");
entities.put(new UUID(most, least), entityTag);
}
@Override
public void setBiome(int x, int z, byte biome) {
setModified();
biomes[x + (z << 4)] = biome;
}
@Override
public Set<CompoundTag> getEntities() {
return new HashSet<>(entities.values());
}
@Override
public Map<Short, CompoundTag> getTiles() {
return tiles == null ? new HashMap<Short, CompoundTag>() : tiles;
}
@Override
public CompoundTag getTile(int x, int y, int z) {
if (tiles == null || tiles.isEmpty()) {
return null;
}
short pair = MathMan.tripleBlockCoord(x, y, z);
return tiles.get(pair);
}
public boolean doesSectionExist(int cy) {
return ids[cy] != null;
}
@Override
public FaweChunk<Void> copy(boolean shallow) {
return new MCAChunk(this, shallow);
}
@Override
public int getBlockCombinedId(int x, int y, int z) {
// TODO FIXME
return 0;
// int layer = y >> 4;
// byte[] idLayer = ids[layer];
// if (idLayer == null) {
// return 0;
// }
// int j = FaweCache.CACHE_J[y][z & 15][x & 15];
// int id = idLayer[j] & 0xFF;
// if (FaweCache.hasData(id)) {
// byte[] dataLayer = data[layer];
// if (dataLayer != null) {
// return (id << 4) + getNibble(j, dataLayer);
// }
// }
// return id << 4;
}
@Override
public byte[] getBiomeArray() {
return this.biomes;
}
@Override
public Set<UUID> getEntityRemoves() {
return new HashSet<>();
}
public void setSkyLight(int x, int y, int z, int value) {
setModified();
int layer = y >> 4;
byte[] skyLayer = skyLight[layer];
if (skyLayer == null) {
return;
}
int index = FaweCache.CACHE_J[y][z & 15][x & 15];
setNibble(index, skyLayer, value);
}
public void setBlockLight(int x, int y, int z, int value) {
setModified();
int layer = y >> 4;
byte[] blockLayer = blockLight[layer];
if (blockLayer == null) {
return;
}
int index = FaweCache.CACHE_J[y][z & 15][x & 15];
setNibble(index, blockLayer, value);
}
public int getSkyLight(int x, int y, int z) {
int layer = y >> 4;
byte[] skyLayer = skyLight[layer];
if (skyLayer == null) {
return 0;
}
int index = FaweCache.CACHE_J[y][z & 15][x & 15];
return getNibble(index, skyLayer);
}
public int getBlockLight(int x, int y, int z) {
int layer = y >> 4;
byte[] blockLayer = blockLight[layer];
if (blockLayer == null) {
return 0;
}
int index = FaweCache.CACHE_J[y][z & 15][x & 15];
return getNibble(index, blockLayer);
}
public void setFullbright() {
setModified();
for (byte[] array : skyLight) {
if (array != null) {
Arrays.fill(array, (byte) 255);
}
}
}
public void removeLight() {
for (int i = 0; i < skyLight.length; i++) {
removeLight(i);
}
}
public void removeLight(int i) {
byte[] array1 = skyLight[i];
if (array1 == null) {
return;
}
byte[] array2 = blockLight[i];
Arrays.fill(array1, (byte) 0);
Arrays.fill(array2, (byte) 0);
}
public int getNibble(int index, byte[] array) {
int indexShift = index >> 1;
if ((index & 1) == 0) {
return array[indexShift] & 15;
} else {
return array[indexShift] >> 4 & 15;
}
}
public void setNibble(int index, byte[] array, int value) {
int indexShift = index >> 1;
byte existing = array[indexShift];
int valueShift = value << 4;
if (existing == value + valueShift) {
return;
}
if ((index & 1) == 0) {
array[indexShift] = (byte) (existing & 240 | value);
} else {
array[indexShift] = (byte) (existing & 15 | valueShift);
}
}
public void setIdUnsafe(byte[] idsLayer, int index, byte id) {
idsLayer[index] = id;
}
public void setBlockUnsafe(byte[] idsLayer, byte[] dataLayer, int index, byte id, int data) {
idsLayer[index] = id;
setNibble(index, dataLayer, data);
}
@Override
public void setBlock(int x, int y, int z, int combinedId) {
// TODO FIXME
// setModified();
// int layer = y >> 4;
// byte[] idsLayer = ids[layer];
// if (idsLayer == null) {
// idsLayer = this.ids[layer] = new byte[4096];
// this.data[layer] = new byte[2048];
// this.skyLight[layer] = new byte[2048];
// this.blockLight[layer] = new byte[2048];
// }
// int j = FaweCache.CACHE_J[y][z & 15][x & 15];
// idsLayer[j] = (byte) id;
// byte[] dataLayer = this.data[layer];
// setNibble(j, dataLayer, data);
}
@Override
public void setBiome(byte biome) {
Arrays.fill(biomes, biome);
}
@Override
public void removeEntity(UUID uuid) {
setModified();
entities.remove(uuid);
}
private final boolean idsEqual(byte[] a, byte[] b) {
// Assumes both are null, or none are (idsEqual - 2d array)
// Assumes length is 4096
if (a == b) return true;
for (char i = 0; i < 4096; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
private final boolean idsEqual(byte[][] a, byte[][] b, boolean matchNullToAir) {
// Assumes length is 16
for (byte i = 0; i < 16; i++) {
if ((a[i] == null) != (b[i] == null)) {
if (matchNullToAir) {
if (b[i] != null) {
for (byte c : b[i]) {
if (c != 0) return false;
}
} else if (a[i] != null) {
for (byte c : a[i]) {
if (c != 0) return false;
}
}
}
return false;
}
}
// Check the chunks close to the ground first
for (byte i = 4; i < 8; i++) {
if (!idsEqual(a[i], b[i])) return false;
}
for (byte i = 3; i >= 0; i--) {
if (!idsEqual(a[i], b[i])) return false;
}
for (byte i = 8; i < 16; i++) {
if (!idsEqual(a[i], b[i])) return false;
}
return true;
}
/**
* Check if the ids match the ids in the other chunk
* @param other
* @param matchNullToAir
* @return
*/
public boolean idsEqual(MCAChunk other, boolean matchNullToAir) {
return idsEqual(other.ids, this.ids, matchNullToAir);
}
@Override
public Void getChunk() {
throw new UnsupportedOperationException("Not applicable for this");
}
@Override
public FaweChunk call() {
throw new UnsupportedOperationException("Not supported");
}
}

View File

@ -0,0 +1,28 @@
package com.boydti.fawe.jnbt.anvil;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.regions.CuboidRegion;
public class MCAClipboard {
private final MCAQueue queue;
private final CuboidRegion region;
private final Vector origin;
public MCAClipboard(MCAQueue queue, CuboidRegion region, Vector origin) {
this.queue = queue;
this.region = region;
this.origin = origin;
}
public MCAQueue getQueue() {
return queue;
}
public CuboidRegion getRegion() {
return region;
}
public Vector getOrigin() {
return origin;
}
}

View File

@ -0,0 +1,678 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.jnbt.NBTStreamer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.RunnableVal4;
import com.boydti.fawe.object.collection.IterableThreadLocal;
import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.object.io.BufferedRandomAccessFile;
import com.boydti.fawe.object.io.FastByteArrayInputStream;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.sk89q.jnbt.NBTInputStream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* Chunk format: http://minecraft.gamepedia.com/Chunk_format#Entity_format
* e.g.: `.Level.Entities.#` (Starts with a . as the root tag is unnamed)
*/
public class MCAFile {
private static Field fieldBuf2;
private static Field fieldBuf3;
static {
try {
fieldBuf2 = InflaterInputStream.class.getDeclaredField("buf");
fieldBuf2.setAccessible(true);
fieldBuf3 = NBTInputStream.class.getDeclaredField("buf");
fieldBuf3.setAccessible(true);
} catch (Throwable e) {
e.printStackTrace();
}
}
private final FaweQueue queue;
private final File file;
private RandomAccessFile raf;
private byte[] locations;
private boolean deleted;
private final int X, Z;
private final Int2ObjectOpenHashMap<MCAChunk> chunks = new Int2ObjectOpenHashMap<>();
final ThreadLocal<byte[]> byteStore1 = new ThreadLocal<byte[]>() {
@Override
protected byte[] initialValue() {
return new byte[4096];
}
};
final ThreadLocal<byte[]> byteStore2 = new ThreadLocal<byte[]>() {
@Override
protected byte[] initialValue() {
return new byte[4096];
}
};
final ThreadLocal<byte[]> byteStore3 = new ThreadLocal<byte[]>() {
@Override
protected byte[] initialValue() {
return new byte[1024];
}
};
public MCAFile(FaweQueue parent, File file) {
this.queue = parent;
this.file = file;
if (!file.exists()) {
throw new FaweException.FaweChunkLoadException();
}
String[] split = file.getName().split("\\.");
X = Integer.parseInt(split[1]);
Z = Integer.parseInt(split[2]);
}
public MCAFile(FaweQueue parent, int mcrX, int mcrZ) throws Exception {
this(parent, mcrX, mcrZ, new File(parent.getSaveFolder(), "r." + mcrX + "." + mcrZ + ".mca"));
}
public MCAFile(FaweQueue parent, int mcrX, int mcrZ, File file) {
this.queue = parent;
this.file = file;
X = mcrX;
Z = mcrZ;
}
public void clear() {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
synchronized (chunks) {
chunks.clear();
}
locations = null;
IterableThreadLocal.clean(byteStore1);
IterableThreadLocal.clean(byteStore2);
IterableThreadLocal.clean(byteStore3);
}
@Override
protected void finalize() throws Throwable {
IterableThreadLocal.clean(byteStore1);
IterableThreadLocal.clean(byteStore2);
IterableThreadLocal.clean(byteStore3);
super.finalize();
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public boolean isDeleted() {
return deleted;
}
public FaweQueue getParent() {
return queue;
}
/**
* Loads the location header from disk
*/
public void init() {
try {
if (raf == null) {
this.locations = new byte[4096];
if (file != null) {
this.raf = new RandomAccessFile(file, "rw");
if (raf.length() < 8192) {
raf.setLength(8192);
} else {
raf.seek(0);
raf.readFully(locations);
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
public int getX() {
return X;
}
public int getZ() {
return Z;
}
public RandomAccessFile getRandomAccessFile() {
return raf;
}
public File getFile() {
return file;
}
public MCAChunk getCachedChunk(int cx, int cz) {
int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31));
synchronized (chunks) {
return chunks.get(pair);
}
}
public void setChunk(MCAChunk chunk) {
int cx = chunk.getX();
int cz = chunk.getZ();
int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31));
synchronized (chunks) {
chunks.put(pair, chunk);
}
}
public MCAChunk getChunk(int cx, int cz) throws IOException {
MCAChunk cached = getCachedChunk(cx, cz);
if (cached != null) {
return cached;
} else {
return readChunk(cx, cz);
}
}
public MCAChunk readChunk(int cx, int cz) throws IOException {
int i = ((cx & 31) << 2) + ((cz & 31) << 7);
int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i + 2] & 0xFF))) << 12;
int size = (locations[i + 3] & 0xFF) << 12;
if (offset == 0) {
return null;
}
NBTInputStream nis = getChunkIS(offset);
MCAChunk chunk = new MCAChunk(nis, queue, cx, cz, false);
nis.close();
int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31));
synchronized (chunks) {
chunks.put(pair, chunk);
}
return chunk;
}
/**
* CX, CZ, OFFSET, SIZE
*
* @param onEach
* @throws IOException
*/
public void forEachSortedChunk(RunnableVal4<Integer, Integer, Integer, Integer> onEach) throws IOException {
char[] offsets = new char[(int) (raf.length() / 4096) - 2];
Arrays.fill(offsets, Character.MAX_VALUE);
char i = 0;
for (int z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++, i += 4) {
int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i + 2] & 0xFF))) - 2;
int size = locations[i + 3] & 0xFF;
if (size != 0) {
if (offset < offsets.length) {
offsets[offset] = i;
} else {
Fawe.debug("Ignoring invalid offset " + offset);
}
}
}
}
for (i = 0; i < offsets.length; i++) {
int index = offsets[i];
if (index != Character.MAX_VALUE) {
int offset = i + 2;
int size = locations[index + 3] & 0xFF;
int index2 = index >> 2;
int x = (index2) & 31;
int z = (index2) >> 5;
onEach.run(x, z, offset << 12, size << 12);
}
}
}
/**
* @param onEach cx, cz, offset, size
*/
public void forEachChunk(RunnableVal4<Integer, Integer, Integer, Integer> onEach) {
int i = 0;
for (int z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++, i += 4) {
int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i + 2] & 0xFF)));
int size = locations[i + 3] & 0xFF;
if (size != 0) {
onEach.run(x, z, offset << 12, size << 12);
}
}
}
}
public void forEachChunk(RunnableVal<MCAChunk> onEach) {
int i = 0;
for (int z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++, i += 4) {
int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i + 2] & 0xFF)));
int size = locations[i + 3] & 0xFF;
if (size != 0) {
try {
onEach.run(getChunk(x, z));
} catch (Throwable ignore) {
}
}
}
}
}
public int getOffset(int cx, int cz) {
int i = ((cx & 31) << 2) + ((cz & 31) << 7);
int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i + 2] & 0xFF)));
return offset << 12;
}
public int getSize(int cx, int cz) {
int i = ((cx & 31) << 2) + ((cz & 31) << 7);
return (locations[i + 3] & 0xFF) << 12;
}
public List<Integer> getChunks() {
final List<Integer> values;
synchronized (chunks) {
values = new ArrayList<>(chunks.size());
}
for (int i = 0; i < locations.length; i += 4) {
int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i + 2] & 0xFF)));
values.add(offset);
}
return values;
}
public byte[] getChunkCompressedBytes(int offset) throws IOException {
if (offset == 0) {
return null;
}
synchronized (raf) {
raf.seek(offset);
int size = raf.readInt();
int compression = raf.read();
byte[] data = new byte[size];
raf.readFully(data);
return data;
}
}
private NBTInputStream getChunkIS(int offset) throws IOException {
try {
byte[] data = getChunkCompressedBytes(offset);
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
InflaterInputStream iis = new InflaterInputStream(bais, new Inflater(), 1);
fieldBuf2.set(iis, byteStore2.get());
BufferedInputStream bis = new BufferedInputStream(iis);
NBTInputStream nis = new NBTInputStream(bis);
fieldBuf3.set(nis, byteStore3.get());
return nis;
} catch (IllegalAccessException unlikely) {
unlikely.printStackTrace();
return null;
}
}
public void streamChunk(int cx, int cz, RunnableVal<NBTStreamer> addReaders) throws IOException {
streamChunk(getOffset(cx, cz), addReaders);
}
public void streamChunk(int offset, RunnableVal<NBTStreamer> withStream) throws IOException {
byte[] data = getChunkCompressedBytes(offset);
streamChunk(data, withStream);
}
public void streamChunk(byte[] data, RunnableVal<NBTStreamer> withStream) throws IOException {
if (data != null) {
try {
FastByteArrayInputStream nbtIn = new FastByteArrayInputStream(data);
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
InflaterInputStream iis = new InflaterInputStream(bais, new Inflater(), 1);
fieldBuf2.set(iis, byteStore2.get());
BufferedInputStream bis = new BufferedInputStream(iis);
NBTInputStream nis = new NBTInputStream(bis);
fieldBuf3.set(nis, byteStore3.get());
NBTStreamer streamer = new NBTStreamer(nis);
withStream.run(streamer);
streamer.readQuick();
} catch (IllegalAccessException unlikely) {
unlikely.printStackTrace();
}
}
}
/**
* @param onEach chunk
*/
public void forEachCachedChunk(RunnableVal<MCAChunk> onEach) {
synchronized (chunks) {
for (Map.Entry<Integer, MCAChunk> entry : chunks.entrySet()) {
onEach.run(entry.getValue());
}
}
}
public List<MCAChunk> getCachedChunks() {
synchronized (chunks) {
return new ArrayList<>(chunks.values());
}
}
public void uncache(int cx, int cz) {
int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31));
synchronized (chunks) {
chunks.remove(pair);
}
}
private byte[] toBytes(MCAChunk chunk) throws Exception {
if (chunk.isDeleted()) {
return null;
}
byte[] uncompressed = chunk.toBytes(byteStore3.get());
byte[] compressed = MainUtil.compress(uncompressed, byteStore2.get(), null);
return compressed;
}
private byte[] getChunkBytes(int cx, int cz) throws Exception {
MCAChunk mca = getCachedChunk(cx, cz);
if (mca == null) {
int offset = getOffset(cx, cz);
if (offset == 0) {
return null;
}
return getChunkCompressedBytes(offset);
}
return toBytes(mca);
}
private void writeSafe(RandomAccessFile raf, int offset, byte[] data) throws IOException {
int len = data.length + 5;
raf.seek(offset);
if (raf.length() - offset < len) {
raf.setLength(((offset + len + 4095) / 4096) * 4096);
}
// Length of remaining data
raf.writeInt(data.length + 1);
// Compression type
raf.write(2);
raf.write(data);
}
private void writeHeader(RandomAccessFile raf, int cx, int cz, int offsetMedium, int sizeByte, boolean writeTime) throws IOException {
int i = ((cx & 31) << 2) + ((cz & 31) << 7);
locations[i] = (byte) (offsetMedium >> 16);
locations[i + 1] = (byte) (offsetMedium >> 8);
locations[i + 2] = (byte) (offsetMedium);
locations[i + 3] = (byte) sizeByte;
raf.seek(i);
raf.write((offsetMedium >> 16));
raf.write((offsetMedium >> 8));
raf.write((offsetMedium >> 0));
raf.write(sizeByte);
raf.seek(i + 4096);
if (offsetMedium == 0 && sizeByte == 0) {
raf.writeInt(0);
} else {
raf.writeInt((int) (System.currentTimeMillis() / 1000L));
}
}
public void close(ForkJoinPool pool) {
if (raf == null) return;
synchronized (raf) {
if (raf != null) {
flush(pool);
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
raf = null;
locations = null;
}
}
}
public boolean isModified() {
if (isDeleted()) {
return true;
}
synchronized (chunks) {
for (Int2ObjectMap.Entry<MCAChunk> entry : chunks.int2ObjectEntrySet()) {
MCAChunk chunk = entry.getValue();
if (chunk.isModified() || chunk.isDeleted()) {
return true;
}
}
}
return false;
}
/**
* Write the chunk to the file
* @param pool
*/
public void flush(ForkJoinPool pool) {
synchronized (raf) {
// If the file is marked as deleted, nothing is written
if (isDeleted()) {
clear();
file.delete();
return;
}
boolean wait; // If the flush method needs to wait for the pool
if (pool == null) {
wait = true;
pool = new ForkJoinPool();
} else wait = false;
// Chunks that need to be relocated
Int2ObjectOpenHashMap<byte[]> relocate = new Int2ObjectOpenHashMap<>();
// The position of each chunk
final Int2ObjectOpenHashMap<Integer> offsetMap = new Int2ObjectOpenHashMap<>(); // Offset -> <byte cx, byte cz, short size>
// The data of each modified chunk
final Int2ObjectOpenHashMap<byte[]> compressedMap = new Int2ObjectOpenHashMap<>();
// The data of each chunk that needs to be moved
final Int2ObjectOpenHashMap<byte[]> append = new Int2ObjectOpenHashMap<>();
boolean modified = false;
// Get the current time for the chunk timestamp
long now = System.currentTimeMillis();
// Load the chunks into the append or compressed map
for (MCAChunk chunk : getCachedChunks()) {
if (chunk.isModified() || chunk.isDeleted()) {
modified = true;
chunk.setLastUpdate(now);
if (!chunk.isDeleted()) {
pool.submit(new Runnable() {
@Override
public void run() {
try {
byte[] compressed = toBytes(chunk);
int pair = MathMan.pair((short) (chunk.getX() & 31), (short) (chunk.getZ() & 31));
Int2ObjectOpenHashMap map;
if (getOffset(chunk.getX(), chunk.getZ()) == 0) {
map = append;
} else {
map = compressedMap;
}
synchronized (map) {
map.put(pair, compressed);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
});
}
}
}
// If any changes were detected
if (modified) {
file.setLastModified(now);
// Load the offset data into the offset map
forEachChunk(new RunnableVal4<Integer, Integer, Integer, Integer>() {
@Override
public void run(Integer cx, Integer cz, Integer offset, Integer size) {
short pair1 = MathMan.pairByte((byte) (cx & 31), (byte) (cz & 31));
short pair2 = (short) (size >> 12);
offsetMap.put((int) offset, (Integer) MathMan.pair(pair1, pair2));
}
});
// Wait for previous tasks
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
int start = 8192;
int written = start;
int end = 8192;
int nextOffset = 8192;
try {
for (int count = 0; count < offsetMap.size(); count++) {
// Get the previous position of the next chunk
Integer loc = offsetMap.get(nextOffset);
while (loc == null) {
nextOffset += 4096;
loc = offsetMap.get(nextOffset);
}
int offset = nextOffset;
// Get the x/z from the paired location
short cxz = MathMan.unpairX(loc);
int cx = MathMan.unpairShortX(cxz);
int cz = MathMan.unpairShortY(cxz);
// Get the size from the pair
int size = MathMan.unpairY(loc) << 12;
nextOffset += size;
end = Math.min(start + size, end);
int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31));
byte[] newBytes = relocate.get(pair);
// newBytes is null if the chunk isn't modified or marked for moving
if (newBytes == null) {
MCAChunk cached = getCachedChunk(cx, cz);
// If the previous offset marks the current write position (start) then we only write the header
if (offset == start) {
if (cached == null || !cached.isModified()) {
writeHeader(raf, cx, cz, start >> 12, size >> 12, true);
start += size;
written = start + size;
continue;
} else {
newBytes = compressedMap.get(pair);
}
} else {
// The chunk needs to be moved, fetch the data if necessary
newBytes = compressedMap.get(pair);
if (newBytes == null) {
if (cached == null || !cached.isDeleted()) {
newBytes = getChunkCompressedBytes(getOffset(cx, cz));
}
}
}
}
if (newBytes == null) {
writeHeader(raf, cx, cz, 0, 0, false);
continue;
}
// The length to be written (compressed data + 5 byte chunk header)
int len = newBytes.length + 5;
int oldSize = (size + 4095) >> 12;
int newSize = (len + 4095) >> 12;
int nextOffset2 = end;
// If the current write position (start) + length of data to write (len) are longer than the position of the next chunk, we need to move the next chunks
while (start + len > end) {
Integer nextLoc = offsetMap.get(nextOffset2);
if (nextLoc != null) {
short nextCXZ = MathMan.unpairX(nextLoc);
int nextCX = MathMan.unpairShortX(nextCXZ);
int nextCZ = MathMan.unpairShortY(nextCXZ);
MCAChunk cached = getCachedChunk(nextCX, nextCZ);
if (cached == null || !cached.isModified()) {
byte[] nextBytes = getChunkCompressedBytes(nextOffset2);
relocate.put(MathMan.pair((short) (nextCX & 31), (short) (nextCZ & 31)), nextBytes);
}
int nextSize = MathMan.unpairY(nextLoc) << 12;
end += nextSize;
nextOffset2 += nextSize;
} else {
end += 4096;
nextOffset2 += 4096;
}
}
// Write the chunk + chunk header
writeSafe(raf, start, newBytes);
// Write the location data (beginning of file)
writeHeader(raf, cx, cz, start >> 12, newSize, true);
written = start + newBytes.length + 5;
start += newSize << 12;
}
// Write all the chunks which need to be appended
if (!append.isEmpty()) {
for (Int2ObjectMap.Entry<byte[]> entry : append.int2ObjectEntrySet()) {
int pair = entry.getIntKey();
short cx = MathMan.unpairX(pair);
short cz = MathMan.unpairY(pair);
byte[] bytes = entry.getValue();
int len = bytes.length + 5;
int newSize = (len + 4095) >> 12;
writeSafe(raf, start, bytes);
writeHeader(raf, cx, cz, start >> 12, newSize, true);
written = start + bytes.length + 5;
start += newSize << 12;
}
}
// Round the file length, since the vanilla server doesn't like it for some reason
raf.setLength(4096 * ((written + 4095) / 4096));
if (raf instanceof BufferedRandomAccessFile) {
((BufferedRandomAccessFile) raf).flush();
}
raf.close();
} catch (Throwable e) {
e.printStackTrace();
}
if (wait) {
pool.shutdown();
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
}
}
IterableThreadLocal.clean(byteStore1);
IterableThreadLocal.clean(byteStore2);
IterableThreadLocal.clean(byteStore3);
}
}

View File

@ -0,0 +1,108 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.object.collection.IterableThreadLocal;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.ForkJoinPool;
/**
* MCAQueue.filterWorld(MCAFilter)<br>
* - Read and modify the world
*/
public class MCAFilter<T> extends IterableThreadLocal<T> {
public void withPool(ForkJoinPool pool, MCAQueue queue) {
return;
}
/**
* Check whether this .mca file should be read
* @param path
* @param attr
* @return
*/
public boolean appliesFile(Path path, BasicFileAttributes attr) {
return true;
}
/**
* Check whether a .mca file should be read
*
* @param mcaX
* @param mcaZ
* @return
*/
public boolean appliesFile(int mcaX, int mcaZ) {
return true;
}
/**
* Do something with the MCAFile<br>
* - Return null if you don't want to filter chunks<br>
* - Return the same file if you do want to filter chunks<br>
*
* @param file
* @return file or null
*/
public MCAFile applyFile(MCAFile file) {
return file;
}
/**
* Check whether a chunk should be read
*
* @param cx
* @param cz
* @return
*/
public boolean appliesChunk(int cx, int cz) {
return true;
}
/**
* Do something with the MCAChunk<br>
* - Return null if you don't want to filter blocks<br>
* - Return the chunk if you do want to filter blocks<br>
*
* @param chunk
* @return
*/
public MCAChunk applyChunk(MCAChunk chunk, T cache) {
return chunk;
}
/**
* Make changes to the block here<br>
* - e.g. block.setId(...)<br>
* - Note: Performance is critical here<br>
*
* @param x
* @param y
* @param z
* @param block
*/
public void applyBlock(int x, int y, int z, BaseBlock block, T cache) {
}
/**
* Do something with the MCAChunk after block filtering<br>
*
* @param chunk
* @param cache
* @return
*/
public void finishChunk(MCAChunk chunk, T cache) {
}
/**
* Do something with the MCAFile after block filtering<br>
*
* @param file
* @param cache
* @return
*/
public void finishFile(MCAFile file, T cache) {
}
}

View File

@ -0,0 +1,23 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.object.number.MutableLong;
public class MCAFilterCounter extends MCAFilter<MutableLong> {
@Override
public void finishChunk(MCAChunk chunk, MutableLong cache) {
cache.add(chunk.getModified());
}
@Override
public MutableLong init() {
return new MutableLong();
}
public long getTotal() {
long total = 0;
for (MutableLong value : getAll()) {
total += value.get();
}
return total;
}
}

View File

@ -0,0 +1,846 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.example.IntFaweChunk;
import com.boydti.fawe.example.NMSMappedFaweQueue;
import com.boydti.fawe.example.NullFaweChunk;
import com.boydti.fawe.jnbt.anvil.filters.DelegateMCAFilter;
import com.boydti.fawe.jnbt.anvil.history.IAnvilHistory;
import com.boydti.fawe.jnbt.anvil.history.NullAnvilHistory;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.RunnableVal4;
import com.boydti.fawe.object.collection.IterableThreadLocal;
import com.boydti.fawe.util.MainUtil;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk, FaweChunk> {
private FaweQueue parent;
private NMSMappedFaweQueue parentNMS;
private final boolean hasSky;
private final File saveFolder;
private final ThreadLocal<MutableMCABackedBaseBlock> blockStore = new ThreadLocal<MutableMCABackedBaseBlock>() {
@Override
protected MutableMCABackedBaseBlock initialValue() {
return new MutableMCABackedBaseBlock();
}
};
@Override
protected void finalize() throws Throwable {
IterableThreadLocal.clean(blockStore);
super.finalize();
}
public MCAQueue(FaweQueue parent) {
super(parent.getWorldName(), new MCAQueueMap());
this.parent = parent;
if (parent instanceof NMSMappedFaweQueue) {
parentNMS = (NMSMappedFaweQueue) parent;
}
((MCAQueueMap) getFaweQueueMap()).setParentQueue(parent);
hasSky = parent.hasSky();
saveFolder = parent.getSaveFolder();
}
public MCAQueue(String world, File saveFolder, boolean hasSky) {
super(world, new MCAQueueMap());
((MCAQueueMap) getFaweQueueMap()).setParentQueue(this);
this.saveFolder = saveFolder;
this.hasSky = hasSky;
}
@Override
public FaweChunk loadChunk(FaweQueue faweQueue, int x, int z, boolean generate) {
return getFaweChunk(x, z);
}
@Override
public FaweChunk getSections(FaweChunk faweChunk) {
return faweChunk;
}
@Override
public FaweChunk getCachedChunk(FaweQueue faweQueue, int cx, int cz) {
return getFaweChunk(cx, cz);
}
@Override
public int getBiome(FaweChunk faweChunk, int x, int z) {
if (faweChunk instanceof MCAChunk) {
return ((MCAChunk) faweChunk).getBiomeArray()[((z & 0xF) << 4 | x & 0xF)];
} else if (parent != null) {
return parent.getBiomeId(x, z);
} else {
return 0;
}
}
/**
*
* @param newChunk
* @param bx
* @param tx
* @param bz
* @param tz
* @param oX
* @param oZ
* @return true if the newChunk has been changed
*/
public boolean copyTo(MCAChunk newChunk, int bx, int tx, int bz, int tz, int oX, int oZ) {
int obx = bx - oX;
int obz = bz - oZ;
int otx = tx - oX;
int otz = tz - oZ;
int otherBCX = (obx) >> 4;
int otherBCZ = (obz) >> 4;
int otherTCX = (otx) >> 4;
int otherTCZ = (otz) >> 4;
int cx = newChunk.getX();
int cz = newChunk.getZ();
int cbx = (cx << 4) - oX;
int cbz = (cz << 4) - oZ;
boolean changed = false;
for (int otherCZ = otherBCZ; otherCZ <= otherTCZ; otherCZ++) {
for (int otherCX = otherBCX; otherCX <= otherTCX; otherCX++) {
FaweChunk chunk;
synchronized (this) {
chunk = this.getFaweChunk(otherCX, otherCZ);
}
if (!(chunk instanceof NullFaweChunk)) {
changed = true;
MCAChunk other = (MCAChunk) chunk;
int ocbx = otherCX << 4;
int ocbz = otherCZ << 4;
int octx = ocbx + 15;
int octz = ocbz + 15;
int offsetY = 0;
int minX = obx > ocbx ? (obx - ocbx) & 15 : 0;
int maxX = otx < octx ? (otx - ocbx) : 15;
int minZ = obz > ocbz ? (obz - ocbz) & 15 : 0;
int maxZ = otz < octz ? (otz - ocbz) : 15;
int offsetX = ocbx - cbx;
int offsetZ = ocbz - cbz;
newChunk.copyFrom(other, minX, maxX, 0, 255, minZ, maxZ, offsetX, offsetY, offsetZ);
}
}
}
return changed;
}
@Override
public boolean setMCA(int mcaX, int mcaZ, RegionWrapper region, Runnable whileLocked, boolean save, boolean unload) {
if (parent != null) return parent.setMCA(mcaX, mcaZ, region, whileLocked, save, unload);
return super.setMCA(mcaX, mcaZ, region, whileLocked, save, unload);
}
public void pasteRegion(MCAQueue from, final RegionWrapper regionFrom, Vector offset) throws IOException {
pasteRegion(from, regionFrom, offset, new NullAnvilHistory());
}
public void pasteRegion(MCAQueue from, final RegionWrapper regionFrom, Vector offset, IAnvilHistory history) throws IOException {
int oX = offset.getBlockX();
int oZ = offset.getBlockZ();
int oY = offset.getBlockY();
int oCX = oX >> 4;
int oCZ = oZ >> 4;
RegionWrapper regionTo = new RegionWrapper(regionFrom.minX + oX, regionFrom.maxX + oX, regionFrom.minZ + oZ, regionFrom.maxZ + oZ);
File folder = getSaveFolder();
int bMcaX = (regionTo.minX >> 9);
int bMcaZ = (regionTo.minZ >> 9);
int tMcaX = (regionTo.maxX >> 9);
int tMcaZ = (regionTo.maxZ >> 9);
filterCopy(new MCAFilter() {
@Override
public MCAFile applyFile(MCAFile mcaFile) {
try {
int mcaX = mcaFile.getX();
int mcaZ = mcaFile.getZ();
int bcx = Math.max(mcaX << 5, regionTo.minX >> 4);
int bcz = Math.max(mcaZ << 5, regionTo.minZ >> 4);
int tcx = Math.min((mcaX << 5) + 31, regionTo.maxX >> 4);
int tcz = Math.min((mcaZ << 5) + 31, regionTo.maxZ >> 4);
mcaFile.init();
final long heapSize = Runtime.getRuntime().totalMemory();
final long heapMaxSize = Runtime.getRuntime().maxMemory();
int free = (int) (((heapMaxSize - heapSize) + Runtime.getRuntime().freeMemory()) / (1024 * 1024));
// int obcx = bcx - oCX;
// int obcz = bcz - oCX;
// int otcx = tcx - oCX;
// int otcz = tcz - oCX;
for (int cz = bcz; cz <= tcz; cz++) {
for (int cx = bcx; cx <= tcx; cx++) {
int bx = cx << 4;
int bz = cz << 4;
int tx = bx + 15;
int tz = bz + 15;
if (oX == 0 && oZ == 0) {
if (bx >= regionTo.minX && tx <= regionTo.maxX && bz >= regionTo.minZ && tz <= regionTo.maxZ) {
FaweChunk chunk = from.getFaweChunk(cx - oCX, cz - oCZ);
if (!(chunk instanceof NullFaweChunk)) {
// if (regionTo.minY == 0 && regionTo.maxY == 255) {
// System.out.println("Vertical");
// MCAChunk mcaChunk = (MCAChunk) chunk;
// mcaChunk.setLoc(null, cx, cz);
// mcaChunk.setModified();
// mcaFile.setChunk(mcaChunk);
// } else
{
MCAChunk newChunk = mcaFile.getChunk(cx, cz);
if (newChunk == null) {
newChunk = new MCAChunk(MCAQueue.this, cx, cz);
mcaFile.setChunk(newChunk);
} else {
newChunk.setModified();
}
newChunk.copyFrom((MCAChunk) chunk, regionFrom.minY, regionFrom.maxY, oY);
}
}
continue;
}
}
bx = Math.max(regionTo.minX, bx);
bz = Math.max(regionTo.minZ, bz);
tx = Math.min(regionTo.maxX, tx);
tz = Math.min(regionTo.maxZ, tz);
int obx = bx - oX;
int obz = bz - oZ;
int otx = tx - oX;
int otz = tz - oZ;
int otherBCX = (obx) >> 4;
int otherBCZ = (obz) >> 4;
int otherTCX = (otx) >> 4;
int otherTCZ = (otz) >> 4;
MCAChunk newChunk = mcaFile.getChunk(cx, cz);
boolean created;
if (newChunk == null) {
newChunk = new MCAChunk(MCAQueue.this, cx, cz);
created = true;
} else {
created = false;
newChunk.setModified();
}
boolean modified = false;
int cbx = (cx << 4) - oX;
int cbz = (cz << 4) - oZ;
for (int otherCZ = otherBCZ; otherCZ <= otherTCZ; otherCZ++) {
for (int otherCX = otherBCX; otherCX <= otherTCX; otherCX++) {
FaweChunk chunk = from.getFaweChunk(otherCX, otherCZ);
if (!(chunk instanceof NullFaweChunk)) {
MCAChunk other = (MCAChunk) chunk;
int ocbx = otherCX << 4;
int ocbz = otherCZ << 4;
int octx = ocbx + 15;
int octz = ocbz + 15;
int minY = regionFrom.minY;
int maxY = regionFrom.maxY;
int offsetY = oY;
int minX = obx > ocbx ? (obx - ocbx) & 15 : 0;
int maxX = otx < octx ? (otx - ocbx) : 15;
int minZ = obz > ocbz ? (obz - ocbz) & 15 : 0;
int maxZ = otz < octz ? (otz - ocbz) : 15;
int offsetX = ocbx - cbx;
int offsetZ = ocbz - cbz;
newChunk.copyFrom(other, minX, maxX, minY, maxY, minZ, maxZ, offsetX, offsetY, offsetZ);
newChunk.setModified();
modified = true;
}
}
}
if (created && modified) {
mcaFile.setChunk(newChunk);
}
}
}
from.clear();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}, regionTo, history);
from.clear();
}
private void performCopy(MCAFile original, MCAFile copy, RegionWrapper region, IAnvilHistory task, ForkJoinPool pool) {
original.clear();
File originalFile = original.getFile();
File copyFile = copy.getFile();
if (copy.isModified()) {
if (copy.isDeleted()) {
if (task.addFileChange(originalFile)) return;
setMCA(original.getX(), original.getZ(), region, () -> task.addFileChange(originalFile), true, true);
return;
} else if (copyFile.exists()) {
// If the task is the normal delete task, we can do a normal file move
copy.close(pool);
if (task.getClass() == NullAnvilHistory.class) {
try {
Files.move(copyFile.toPath(), originalFile.toPath(), StandardCopyOption.ATOMIC_MOVE);
return;
} catch (IOException ignore) {}
}
setMCA(original.getX(), original.getZ(), region, () -> {
task.addFileChange(originalFile);
if (!copyFile.renameTo(originalFile)) {
Fawe.debug("Failed to copy (2)");
}
}, true, true);
}
}
copy.clear();
copyFile.delete();
}
public <G, T extends MCAFilter<G>> T filterCopy(final T filter, RegionWrapper region) {
return filterCopy(filter, region, new NullAnvilHistory());
}
public <G, T extends MCAFilter<G>> T filterCopy(final T filter, RegionWrapper region, IAnvilHistory task) {
DelegateMCAFilter<G> delegate = new DelegateMCAFilter<G>(filter) {
MCAFile original;
MCAFile copy;
ForkJoinPool pool;
@Override
public void withPool(ForkJoinPool pool, MCAQueue queue) {
this.pool = pool;
}
@Override
public MCAFile applyFile(MCAFile original) {
this.original = original;
this.original.clear();
File file = original.getFile();
file.setWritable(true);
File copyDest = new File(file.getParentFile(), file.getName() + "-copy");
setMCA(original.getX(), original.getZ(), region, () -> {
try {
Files.copy(file.toPath(), copyDest.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}, true, false);
this.copy = new MCAFile(original.getParent(), copyDest);
MCAFile result = filter.applyFile(copy);
if (result == null) {
performCopy(original, copy, region, task, pool);
}
if (result == null || !copy.getFile().equals(result.getFile())) {
copy.clear();
if (copyDest.exists() && !copyDest.delete()) copyDest.deleteOnExit();
}
return result;
}
@Override
public void finishFile(MCAFile newRegion, G cache) {
performCopy(original, newRegion, region, task, pool);
}
};
if (region == RegionWrapper.GLOBAL()) {
this.filterWorld(delegate);
} else {
this.filterRegion(delegate, region);
}
return filter;
}
public <G, T extends MCAFilter<G>> T filterRegion(final T filter, final RegionWrapper region) {
DelegateMCAFilter<G> delegate = new DelegateMCAFilter<G>(filter) {
@Override
public boolean appliesFile(Path path, BasicFileAttributes attr) {
String name = path.toString();
int[] coords = MainUtil.regionNameToCoords(name);
final int mcaX = coords[0];
final int mcaZ = coords[1];
return region.isInMCA(mcaX, mcaZ) && filter.appliesFile(path, attr);
}
@Override
public boolean appliesFile(int mcaX, int mcaZ) {
return region.isInMCA(mcaX, mcaZ) && filter.appliesFile(mcaX, mcaZ);
}
@Override
public boolean appliesChunk(int cx, int cz) {
return region.isInChunk(cx, cz) && filter.appliesChunk(cx, cz);
}
@Override
public G get() {
return filter.get();
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, G value) {
chunk = filter.applyChunk(chunk, value);
if (chunk != null) {
final MutableMCABackedBaseBlock mutableBlock = blockStore.get();
mutableBlock.setChunk(chunk);
int bx = chunk.getX() << 4;
int bz = chunk.getZ() << 4;
int tx = bx + 15;
int tz = bz + 15;
bx = Math.max(bx, region.minX);
bz = Math.max(bz, region.minZ);
tx = Math.min(tx, region.maxX);
tz = Math.min(tz, region.maxZ);
int minLayer = region.minY >> 4;
int maxLayer = region.maxY >> 4;
for (int layer = minLayer; layer <= maxLayer; layer++) {
if (chunk.doesSectionExist(layer)) {
mutableBlock.setArrays(layer);
int yStart = layer << 4;
int yEnd = yStart + 15;
yStart = Math.max(yStart, region.minY);
yEnd = Math.min(yEnd, region.maxY);
for (int y = yStart, y0 = (yStart & 15); y <= yEnd; y++, y0++) {
int yIndex = ((y0) << 8);
mutableBlock.setY(y);
for (int z = bz, z0 = bz & 15; z <= tz; z++, z0++) {
int zIndex = yIndex + ((z0) << 4);
mutableBlock.setZ(z);
for (int x = bx, x0 = bx & 15; x <= tx; x++, x0++) {
int xIndex = zIndex + x0;
mutableBlock.setX(x);
mutableBlock.setIndex(xIndex);
filter.applyBlock(x, y, z, mutableBlock, value);
}
}
}
}
}
}
return null;
}
};
final int minMCAX = region.minX >> 9;
final int minMCAZ = region.minZ >> 9;
final int maxMCAX = region.maxX >> 9;
final int maxMCAZ = region.maxZ >> 9;
long mcaArea = (maxMCAX - minMCAX + 1l) * (maxMCAZ - minMCAZ + 1l);
if (mcaArea < 128) {
this.filterWorld(delegate, new RunnableVal2<Path, RunnableVal2<Path, BasicFileAttributes>>() {
@Override
public void run(Path root, RunnableVal2<Path, BasicFileAttributes> funx) {
for (int x = minMCAX; x <= maxMCAX; x++) {
for (int z = minMCAZ; z <= maxMCAZ; z++) {
Path newPath = root.resolve(Paths.get("r." + x + "." + z + ".mca"));
if (Files.exists(newPath)) {
try {
BasicFileAttributes attrs = Files.readAttributes(newPath, BasicFileAttributes.class);
funx.run(newPath, attrs);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
});
} else {
this.filterWorld(delegate);
}
return filter;
}
public <G, T extends MCAFilter<G>> T createRegion(final T filter, final RegionWrapper region) {
int botMcaX = region.minX >> 9;
int botMcaZ = region.minZ >> 9;
int topMcaX = region.maxX >> 9;
int topMcaZ = region.maxZ >> 9;
for (int mcaX = botMcaX >> 9; mcaX <= topMcaX; mcaX++) {
for (int mcaZ = botMcaZ >> 9; mcaZ <= topMcaZ; mcaZ++) {
}
}
return filter;
}
private <G, T extends MCAFilter<G>> RunnableVal2<Path, BasicFileAttributes> filterFunction(final T filter, ForkJoinPool pool) {
return new RunnableVal2<Path, BasicFileAttributes>() {
@Override
public void run(Path path, BasicFileAttributes attr) {
try {
String name = path.getFileName().toString();
if (!name.endsWith(".mca") && !name.endsWith(".mcapm")) {
return;
}
if (!filter.appliesFile(path, attr)) {
return;
}
String[] split = name.split("\\.");
final int mcaX = Integer.parseInt(split[1]);
final int mcaZ = Integer.parseInt(split[2]);
if (filter.appliesFile(mcaX, mcaZ)) {
File file = path.toFile();
final MCAFile original = new MCAFile(MCAQueue.this, file);
final MCAFile finalFile = filter.applyFile(original);
if (finalFile != null && !finalFile.isDeleted()) {
finalFile.init();
// May not do anything, but seems to lead to smaller lag spikes
final int cbx = mcaX << 5;
final int cbz = mcaZ << 5;
finalFile.forEachSortedChunk(new RunnableVal4<Integer, Integer, Integer, Integer>() {
@Override
public void run(final Integer rcx, final Integer rcz, Integer offset, Integer size) {
pool.submit(new Runnable() {
@Override
public void run() {
try {
int cx = cbx + rcx;
int cz = cbz + rcz;
if (filter.appliesChunk(cx, cz)) {
MCAChunk chunk = finalFile.getChunk(cx, cz);
try {
final G value = filter.get();
chunk = filter.applyChunk(chunk, value);
if (chunk != null) {
final MutableMCABackedBaseBlock mutableBlock = blockStore.get();
mutableBlock.setChunk(chunk);
int bx = cx << 4;
int bz = cz << 4;
for (int layer = 0; layer < 16; layer++) {
if (chunk.doesSectionExist(layer)) {
mutableBlock.setArrays(layer);
int yStart = layer << 4;
int index = 0;
for (int y = yStart; y < yStart + 16; y++) {
mutableBlock.setY(y);
for (int z = bz; z < bz + 16; z++) {
mutableBlock.setZ(z);
for (int x = bx; x < bx + 16; x++, index++) {
mutableBlock.setX(x);
mutableBlock.setIndex(index);
filter.applyBlock(x, y, z, mutableBlock, value);
}
}
}
}
}
filter.finishChunk(chunk, value);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
});
}
});
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
filter.finishFile(finalFile, filter.get());
} else {
if (original.isDeleted()) {
try {
original.close(pool);
file.delete();
} catch (Throwable ignore) {
ignore.printStackTrace();
}
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
original.close(pool);
if (original.isDeleted()) {
file.delete();
}
}
} catch (Throwable ignore) {
ignore.printStackTrace();
}
}
};
}
private <G, T extends MCAFilter<G>> T filterWorld(final T filter, RunnableVal2<Path, RunnableVal2<Path, BasicFileAttributes>> traverser) {
File folder = getSaveFolder();
final ForkJoinPool pool = new ForkJoinPool();
filter.withPool(pool, this);
RunnableVal2<Path, BasicFileAttributes> task = filterFunction(filter, pool);
traverser.run(folder.toPath(), task);
pool.shutdown();
try {
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
e.printStackTrace();
}
return filter;
}
public <G, T extends MCAFilter<G>> T filterWorld(final T filter) {
return filterWorld(filter, new RunnableVal2<Path, RunnableVal2<Path, BasicFileAttributes>>() {
@Override
public void run(Path value1, RunnableVal2<Path, BasicFileAttributes> value2) {
MainUtil.traverse(value1, value2);
}
});
}
public <G, T extends MCAFilter<G>> T filterWorld(final T filter, Comparator<File> comparator) {
return filterWorld(filter, new RunnableVal2<Path, RunnableVal2<Path, BasicFileAttributes>>() {
@Override
public void run(Path value1, RunnableVal2<Path, BasicFileAttributes> value2) {
MainUtil.forEachFile(value1, value2, comparator);
}
});
}
@Override
public boolean supports(Capability capability) {
switch (capability) {
case CHANGE_TASKS: return false;
}
return super.supports(capability);
}
@Override
public void relight(int x, int y, int z) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public void relightBlock(int x, int y, int z) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public void relightSky(int x, int y, int z) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public boolean regenerateChunk(FaweQueue faweQueue, int x, int z, BaseBiome biome, Long seed) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public IntFaweChunk getPrevious(IntFaweChunk fs, FaweChunk sections, Map<?, ?> tiles, Collection<?>[] entities, Set<UUID> createdEntities, boolean all) throws Exception {
throw new UnsupportedOperationException("Not supported");
}
@Override
public FaweQueue getImpWorld() {
return parent;
}
@Override
public void setHeightMap(FaweChunk chunk, byte[] heightMap) {
MCAChunk mca = (MCAChunk) chunk;
if (mca != null) {
int[] otherMap = mca.getHeightMapArray();
for (int i = 0; i < heightMap.length; i++) {
int newHeight = heightMap[i] & 0xFF;
int currentHeight = otherMap[i];
if (newHeight > currentHeight) {
otherMap[i] = newHeight;
}
}
}
}
@Override
public void setFullbright(FaweChunk sections) {
if (sections.getClass() == MCAChunk.class) {
((MCAChunk) sections).setFullbright();
} else if (parentNMS != null) {
int cx = sections.getX();
int cz = sections.getZ();
parentNMS.ensureChunkLoaded(cx, cz);
Object parentSections = parentNMS.getCachedSections(parentNMS.getWorld(), cx, cz);
if (parentSections != null) {
parentNMS.setFullbright(sections);
}
}
}
@Override
public boolean removeSectionLighting(FaweChunk sections, int layer, boolean hasSky) {
if (sections.getClass() == MCAChunk.class) {
((MCAChunk) sections).removeLight(layer);
} else if (parentNMS != null) {
int cx = sections.getX();
int cz = sections.getZ();
parentNMS.ensureChunkLoaded(cx, cz);
Object parentSections = parentNMS.getCachedSections(parentNMS.getWorld(), cx, cz);
if (parentSections != null) {
parentNMS.removeSectionLighting(sections, layer, hasSky);
}
}
return true;
}
@Override
public boolean removeLighting(FaweChunk sections, RelightMode mode, boolean hasSky) {
if (mode != RelightMode.NONE) {
if (sections.getClass() == MCAChunk.class) {
((MCAChunk) sections).removeLight();
} else if (parentNMS != null) {
int cx = sections.getX();
int cz = sections.getZ();
parentNMS.ensureChunkLoaded(cx, cz);
Object parentSections = parentNMS.getCachedSections(parentNMS.getWorld(), cx, cz);
if (parentSections != null) {
parentNMS.removeLighting(sections, mode, hasSky);
}
}
return true;
}
return false;
}
@Override
public void setSkyLight(FaweChunk sections, int x, int y, int z, int value) {
if (sections.getClass() == MCAChunk.class) {
((MCAChunk) sections).setSkyLight(x, y, z, value);
} else if (parentNMS != null) {
parentNMS.setSkyLight(x, y, z, value);
}
}
@Override
public void setBlockLight(FaweChunk sections, int x, int y, int z, int value) {
if (sections.getClass() == MCAChunk.class) {
((MCAChunk) sections).setBlockLight(x, y, z, value);
} else if (parentNMS != null) {
parentNMS.setBlockLight(x, y, z, value);
}
}
@Override
public void refreshChunk(FaweChunk fs) {
if (fs.getClass() != MCAChunk.class) {
parentNMS.sendChunk(fs);
}
}
@Override
public void sendChunk(int x, int z, int bitMask) {
if (parentNMS != null) {
parentNMS.sendChunk(x, z, bitMask);
}
}
@Override
public CompoundTag getTileEntity(FaweChunk sections, int x, int y, int z) {
if (sections.getClass() == MCAChunk.class) {
return sections.getTile(x, y, z);
} else {
return parentNMS.getTileEntity(x, y, z);
}
}
@Override
public FaweChunk getFaweChunk(int cx, int cz) {
return getFaweQueueMap().getFaweChunk(cx, cz);
}
@Override
public File getSaveFolder() {
return saveFolder;
}
@Override
public boolean hasSky() {
return hasSky;
}
@Override
public MCAChunk getCachedSections(FaweQueue faweQueue, int cx, int cz) {
return (MCAChunk) getFaweQueueMap().getFaweChunk(cx, cz);
}
@Override
public FaweChunk getCachedSection(FaweChunk sections, int cy) {
if (sections.getClass() == MCAChunk.class) {
if (((MCAChunk) sections).doesSectionExist(cy)) {
return sections;
}
return null;
} else if (parentNMS != null) {
return sections;
}
return null;
}
@Override
public int getCombinedId4Data(FaweChunk sections, int x, int y, int z) {
if (sections.getClass() == MCAChunk.class) {
return sections.getBlockCombinedId(x, y, z);
} else {
return parentNMS.getCombinedId4Data(x, y, z);
}
}
@Override
public int getSkyLight(FaweChunk sections, int x, int y, int z) {
if (sections.getClass() == MCAChunk.class) {
return ((MCAChunk) sections).getSkyLight(x, y, z);
} else {
return parentNMS.getSkyLight(x, y, z);
}
}
@Override
public int getEmmittedLight(FaweChunk sections, int x, int y, int z) {
if (sections.getClass() == MCAChunk.class) {
return ((MCAChunk) sections).getBlockLight(x, y, z);
} else {
return parentNMS.getEmmittedLight(x, y, z);
}
}
@Override
public void startSet(boolean parallel) {
if (parent != null) {
parent.startSet(parallel);
}
}
@Override
public void endSet(boolean parallel) {
if (parent != null) {
parent.endSet(parallel);
}
}
@Override
public void sendBlockUpdate(FaweChunk chunk, FawePlayer... players) {
if (parent != null) {
parentNMS.sendBlockUpdate(chunk, players);
}
}
}

View File

@ -0,0 +1,222 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.example.IFaweQueueMap;
import com.boydti.fawe.example.MappedFaweQueue;
import com.boydti.fawe.example.NullFaweChunk;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.SetQueue;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MCAQueueMap implements IFaweQueueMap {
private FaweQueue queue;
private Map<Long, MCAFile> mcaFileMap = new ConcurrentHashMap<>(8, 0.9f, 1);
private NullFaweChunk nullChunk;
private boolean isHybridQueue;
public void setParentQueue(FaweQueue queue) {
this.queue = queue;
this.nullChunk = new NullFaweChunk(queue, 0, 0);
this.isHybridQueue = queue != null && !(queue instanceof MCAQueue) && (!(queue instanceof MappedFaweQueue) || ((MappedFaweQueue) queue).getFaweQueueMap() != this);
}
private MCAFile lastFile;
private int lastFileX = Integer.MIN_VALUE;
private int lastFileZ = Integer.MIN_VALUE;
private FaweChunk lastChunk;
private int lastX = Integer.MIN_VALUE;
private int lastZ = Integer.MIN_VALUE;
public synchronized MCAFile getMCAFile(int cx, int cz, boolean create) {
int mcaX = cx >> 5;
int mcaZ = cz >> 5;
if (mcaX == lastFileX && mcaZ == lastFileZ) {
return lastFile;
}
long pair = MathMan.pairInt(lastFileX = mcaX, lastFileZ = mcaZ);
MCAFile tmp;
lastFile = tmp = mcaFileMap.get(pair);
if (lastFile == null) {
try {
queue.setMCA(lastFileX, lastFileZ, RegionWrapper.GLOBAL(), null, true, false);
File save = queue.getSaveFolder();
File file;
if (save != null) {
file = new File(queue.getSaveFolder(), "r." + lastFileX + "." + lastFileZ + ".mca");
if (create) {
File parent = file.getParentFile();
if (!parent.exists()) parent.mkdirs();
if (!file.exists()) file.createNewFile();
}
} else {
file = null;
}
lastFile = tmp = new MCAFile(queue, mcaX, mcaZ, file);
} catch (FaweException.FaweChunkLoadException ignore) {
lastFile = null;
return null;
} catch (Exception e) {
e.printStackTrace();
lastFile = null;
return null;
}
mcaFileMap.put(pair, tmp);
}
return tmp;
}
@Override
public Collection<FaweChunk> getFaweCunks() {
final List<FaweChunk> chunks = new ArrayList<>();
for (Map.Entry<Long, MCAFile> entry : mcaFileMap.entrySet()) {
MCAFile file = entry.getValue();
if (file != null) {
chunks.addAll(file.getCachedChunks());
}
}
return chunks;
}
@Override
public void forEachChunk(RunnableVal<FaweChunk> onEach) {
for (FaweChunk chunk : getFaweCunks()) {
onEach.run(chunk);
}
}
public FaweChunk getFaweChunk(int cx, int cz, boolean create) {
if (cx == lastX && cz == lastZ) {
if (nullChunk == lastChunk) {
nullChunk.setLoc(queue, lastX, lastZ);
}
return lastChunk;
}
lastX = cx;
lastZ = cz;
if (isHybridQueue) {
MappedFaweQueue mfq = ((MappedFaweQueue) queue);
lastChunk = mfq.getFaweQueueMap().getCachedFaweChunk(cx, cz);
if (lastChunk != null) {
return lastChunk;
}
}
try {
MCAFile mcaFile = getMCAFile(cx, cz, create);
if (mcaFile != null) {
mcaFile.init();
lastChunk = mcaFile.getChunk(cx, cz);
if (lastChunk != null) {
return lastChunk;
} else if (create) {
MCAChunk chunk = new MCAChunk(queue, cx, cz);
mcaFile.setChunk(chunk);
lastChunk = chunk;
return chunk;
}
}
} catch (Throwable ignore) {
ignore.printStackTrace();
}
if (isHybridQueue) { // Use parent queue for in use chunks
lastChunk = ((MappedFaweQueue) queue).getFaweQueueMap().getFaweChunk(cx, cz);
return lastChunk;
}
nullChunk.setLoc(queue, lastX, lastZ);
return lastChunk = nullChunk;
}
@Override
public FaweChunk getFaweChunk(int cx, int cz) {
return getFaweChunk(cx, cz, !isHybridQueue);
}
@Override
public FaweChunk getCachedFaweChunk(int cx, int cz) {
int mcaX = cx >> 5;
int mcaZ = cz >> 5;
long pair = MathMan.pairInt(mcaX, mcaZ);
MCAFile file = mcaFileMap.get(pair);
if (file != null) {
return file.getCachedChunk(cx, cz);
}
return null;
}
@Override
public void add(FaweChunk chunk) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public void clear() {
for (Map.Entry<Long, MCAFile> entry : mcaFileMap.entrySet()) {
entry.getValue().clear();
}
mcaFileMap.clear();
lastChunk = null;
lastFile = null;
lastFileX = Integer.MIN_VALUE;
lastFileZ = Integer.MIN_VALUE;
lastX = Integer.MIN_VALUE;
lastZ = Integer.MIN_VALUE;
if (isHybridQueue) {
queue.clear();
}
}
@Override
public int size() {
int size = mcaFileMap.size();
if (isHybridQueue) {
size += queue.size();
}
return size;
}
@Override
public boolean next(int size, long time) {
lastX = Integer.MIN_VALUE;
lastZ = Integer.MIN_VALUE;
lastFileX = Integer.MIN_VALUE;
lastFileZ = Integer.MIN_VALUE;
if (!mcaFileMap.isEmpty()) {
Iterator<Map.Entry<Long, MCAFile>> iter = mcaFileMap.entrySet().iterator();
boolean result;
long start = System.currentTimeMillis();
do {
if (result = iter.hasNext()) {
MCAFile file = iter.next().getValue();
iter.remove();
queue.setMCA(file.getX(), file.getZ(), RegionWrapper.GLOBAL(), new Runnable() {
@Override
public void run() {
file.close(SetQueue.IMP.getForkJoinPool());
}
}, true, true);
} else {
break;
}
} while (System.currentTimeMillis() - start < time);
return result;
}
if (isHybridQueue) {
boolean value = queue.next();
return value;
}
return false;
}
}

View File

@ -0,0 +1,117 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.object.extent.FastWorldEditExtent;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.SimpleWorld;
import com.sk89q.worldedit.world.biome.BaseBiome;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
public class MCAWorld implements SimpleWorld {
private final String name;
private final MCAQueue queue;
private final FastWorldEditExtent extent;
public MCAWorld(String name, File saveFolder, boolean hasSky) {
this.name = name;
this.queue = new MCAQueue(name, saveFolder, hasSky);
this.extent = new FastWorldEditExtent(this, queue);
}
public MCAQueue getQueue() {
return queue;
}
@Override
public String getName() {
return name;
}
@Override
public boolean setBlock(Vector position, BlockStateHolder block) throws WorldEditException {
return extent.setBlock(position, block);
}
@Override
public int getBlockLightLevel(Vector position) {
return queue.getEmmittedLight(position.getBlockX(), position.getBlockY(), position.getBlockZ());
}
@Override
public boolean clearContainerBlockContents(Vector position) {
BlockStateHolder block = extent.getLazyBlock(position);
if (block.hasNbtData()) {
Map<String, Tag> nbt = ReflectionUtils.getMap(block.getNbtData().getValue());
if (nbt.containsKey("Items")) {
nbt.put("Items", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>()));
try {
extent.setBlock(position, block);
} catch (WorldEditException e) {
e.printStackTrace();
}
}
}
return true;
}
@Override
public void dropItem(Vector position, BaseItemStack item) {
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
return false;
}
@Override
public List<? extends Entity> getEntities(Region region) {
return new ArrayList<>();
}
@Override
public List<? extends Entity> getEntities() {
return new ArrayList<>();
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
return extent.createEntity(location, entity);
}
@Override
public BlockState getBlock(Vector position) {
return extent.getLazyBlock(position);
}
@Override
public BaseBiome getBiome(Vector2D position) {
return extent.getBiome(position);
}
@Override
public boolean setBiome(Vector2D position, BaseBiome biome) {
return extent.setBiome(position, biome);
}
}

View File

@ -0,0 +1,221 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.object.collection.IterableThreadLocal;
import com.boydti.fawe.object.io.BufferedRandomAccessFile;
import com.boydti.fawe.util.MainUtil;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.zip.Deflater;
public abstract class MCAWriter {
private File folder;
private final int length;
private final int width;
private final int area;
private int OX, OZ;
public MCAWriter(int width, int length, File regionFolder) {
this.folder = regionFolder;
this.width = width;
this.length = length;
this.area = width * length;
}
public final File getFolder() {
return folder;
}
public void setFolder(File folder) {
this.folder = folder;
}
public final int getWidth() {
return width;
}
public final int getLength() {
return length;
}
/**
* Set the MCA file offset (each mca file is 512 blocks)
* - A negative value will shift the map negative
* - This only applies to generation, not block get/set
*
* @param mcaOX
* @param mcaOZ
*/
public void setMCAOffset(int mcaOX, int mcaOZ) {
OX = mcaOX << 9;
OZ = mcaOZ << 9;
}
public int getOffsetX() {
return OX;
}
public int getOffsetZ() {
return OZ;
}
public final int getArea() {
return area;
}
public abstract boolean shouldWrite(int chunkX, int chunkZ);
public abstract MCAChunk write(MCAChunk input, int startX, int endX, int startZ, int endZ);
public void generate() throws IOException {
if (!folder.exists()) {
folder.mkdirs();
}
final ForkJoinPool pool = new ForkJoinPool();
int bcx = 0;
int bcz = 0;
int tcx = (width - 1) >> 4;
int tcz = (length - 1) >> 4;
final ThreadLocal<MCAChunk> chunkStore = new ThreadLocal<MCAChunk>() {
@Override
protected MCAChunk initialValue() {
MCAChunk chunk = new MCAChunk(null, 0, 0);
chunk.biomes = new byte[256];
return chunk;
}
};
final ThreadLocal<byte[]> byteStore1 = new ThreadLocal<byte[]>() {
@Override
protected byte[] initialValue() {
return new byte[500000];
}
};
final ThreadLocal<byte[]> byteStore2 = new ThreadLocal<byte[]>() {
@Override
protected byte[] initialValue() {
return new byte[500000];
}
};
final ThreadLocal<Deflater> deflateStore = new ThreadLocal<Deflater>() {
@Override
protected Deflater initialValue() {
Deflater deflater = new Deflater(Deflater.BEST_SPEED, false);
return deflater;
}
};
byte[] fileBuf = new byte[1 << 16];
int mcaXMin = 0;
int mcaZMin = 0;
int mcaXMax = mcaXMin + ((width - 1) >> 9);
int mcaZMax = mcaZMin + ((length - 1) >> 9);
for (int mcaZ = mcaXMin; mcaZ <= mcaZMax; mcaZ++) {
for (int mcaX = mcaXMin; mcaX <= mcaXMax; mcaX++) {
final int fmcaX = mcaX;
final int fmcaZ = mcaZ;
File file = new File(folder, "r." + (mcaX + (getOffsetX() >> 9)) + "." + (mcaZ + (getOffsetZ() >> 9)) + ".mca");
if (!file.exists()) {
file.createNewFile();
}
final BufferedRandomAccessFile raf = new BufferedRandomAccessFile(file, "rw", fileBuf);
final byte[] header = new byte[4096];
final byte[][] compressed = new byte[1024][];
int bx = mcaX << 9;
int bz = mcaZ << 9;
int scx = bx >> 4;
int ecx = Math.min(scx + 31, tcx);
int scz = bz >> 4;
int ecz = Math.min(scz + 31, tcz);
for (int cz = scz; cz <= ecz; cz++) {
final int csz = cz << 4;
final int cez = Math.min(csz + 15, length - 1);
for (int cx = scx; cx <= ecx; cx++) {
final int csx = cx << 4;
final int cex = Math.min(csx + 15, width - 1);
final int fcx = cx;
final int fcz = cz;
if (shouldWrite(cx, cz)) {
pool.submit(new Runnable() {
@Override
public void run() {
try {
MCAChunk chunk = chunkStore.get();
chunk.setLoc(null, fcx, fcz);
chunk = write(chunk, csx, cex, csz, cez);
if (chunk != null) {
// Generation offset
chunk.setLoc(null, fcx + (getOffsetX() >> 4), fcz + (getOffsetZ() >> 4));
// Compress
byte[] bytes = chunk.toBytes(byteStore1.get());
byte[] compressedBytes = MainUtil.compress(bytes, byteStore2.get(), deflateStore.get());
int blocks = (compressed.length + 4095) >> 12;
compressed[((fcx & 31)) + ((fcz & 31) << 5)] = compressedBytes.clone();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
});
}
}
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
pool.submit(new Runnable() {
@Override
public void run() {
try {
int totalLength = 8192;
for (int i = 0; i < compressed.length; i++) {
byte[] compressedBytes = compressed[i];
if (compressedBytes != null) {
int blocks = ((4095 + compressedBytes.length + 5) / 4096) * 4096;
totalLength += blocks;
}
}
raf.setLength(totalLength);
int offset = 8192;
for (int i = 0; i < compressed.length; i++) {
byte[] compressedBytes = compressed[i];
if (compressedBytes != null) {
// Set header
int index = i << 2;
int offsetMedium = offset >> 12;
int blocks = ((4095 + compressedBytes.length + 5) / 4096);
header[index] = (byte) (offsetMedium >> 16);
header[index + 1] = (byte) ((offsetMedium >> 8));
header[index + 2] = (byte) ((offsetMedium >> 0));
header[index + 3] = (byte) (blocks);
// Write bytes
int cx = (fmcaX << 5) + (i & 31);
int cz = (fmcaZ << 5) + (i >> 5);
raf.seek(offset);
raf.writeInt(compressedBytes.length + 1);
raf.write(2);
raf.write(compressedBytes);
offset += blocks * 4096;
}
}
raf.seek(0);
raf.write(header);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
pool.shutdown();
IterableThreadLocal.clean(byteStore1);
IterableThreadLocal.clean(byteStore2);
IterableThreadLocal.clean(deflateStore);
}
}

View File

@ -0,0 +1,104 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.FaweCache;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import javax.annotation.Nullable;
/**
* I'm aware this isn't OOP, but object creation is expensive
*/
public class MutableMCABackedBaseBlock extends BaseBlock {
private MCAChunk chunk;
private byte[] data;
private byte[] ids;
private int index;
private int x;
private int y;
private int z;
public MutableMCABackedBaseBlock() {
super(0, null);
}
public void setChunk(MCAChunk chunk) {
this.chunk = chunk;
}
public void setArrays(int layer) {
ids = chunk.ids[layer];
data = chunk.data[layer];
}
public MCAChunk getChunk() {
return chunk;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setZ(int z) {
this.z = z;
}
public void setIndex(int index) {
this.index = index;
}
// TODO FIXME update to latest
// @Override
// public int getId() {
// return Byte.toUnsignedInt(ids[index]);
// }
//
// @Override
// public int getData() {
// if (!FaweCache.hasData(ids[index] & 0xFF)) {
// return 0;
// } else {
// int indexShift = index >> 1;
// if ((index & 1) == 0) {
// return data[indexShift] & 15;
// } else {
// return (data[indexShift] >> 4) & 15;
// }
// }
// }
@Nullable
@Override
public CompoundTag getNbtData() {
return chunk.getTile(x, y, z);
}
// @Override
// public void setId(int id) {
// ids[index] = (byte) id;
// chunk.setModified();
// }
//
// @Override
// public void setData(int value) {
// int indexShift = index >> 1;
// if ((index & 1) == 0) {
// data[indexShift] = (byte) (data[indexShift] & 240 | value & 15);
// } else {
// data[indexShift] = (byte) (data[indexShift] & 15 | (value & 15) << 4);
// }
// chunk.setModified();
// }
@Override
public void setNbtData(@Nullable CompoundTag nbtData) {
chunk.setTile(x, y, z, nbtData);
chunk.setModified();
}
}

View File

@ -0,0 +1,53 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.number.MutableLong;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
// TODO FIXME
public class CountFilter extends MCAFilterCounter {
// private final boolean[] allowedId = new boolean[FaweCache.getId(Character.MAX_VALUE)];
// private final boolean[] allowed = new boolean[Character.MAX_VALUE];
public CountFilter() {
}
public CountFilter addBlock(BaseBlock block) {
// addBlock(block.getId(), block.getData());
return this;
}
public CountFilter addBlock(int id, int data) {
// allowedId[id] = true;
// allowed[FaweCache.getCombined(id, data)] = true;
return this;
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, MutableLong count) {
// for (int layer = 0; layer < chunk.ids.length; layer++) {
// byte[] ids = chunk.ids[layer];
// if (ids == null) {
// continue;
// }
// byte[] datas = chunk.data[layer];
// for (int i = 0; i < ids.length; i++) {
// int id = ids[i] & 0xFF;
// if (!allowedId[id]) {
// continue;
// }
// int combined = (id) << 4;
// if (FaweCache.hasData(id)) {
// combined += chunk.getNibble(i, datas);
// }
// if (allowed[combined]) {
// count.increment();
// }
// }
// }
return null;
}
}

View File

@ -0,0 +1,44 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.number.MutableLong;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
public class CountIdFilter extends MCAFilterCounter {
private final boolean[] allowedId = new boolean[BlockTypes.size()];
public CountIdFilter() {
}
public CountIdFilter addBlock(BlockType type) {
allowedId[type.getInternalId()] = true;
return this;
}
public CountIdFilter addBlock(BlockStateHolder block) {
allowedId[block.getInternalBlockTypeId()] = true;
return this;
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, MutableLong count) {
// TODO FIXME
for (int layer = 0; layer < chunk.ids.length; layer++) {
byte[] ids = chunk.ids[layer];
if (ids != null) {
for (byte i : ids) {
if (allowedId[i & 0xFF]) {
count.increment();
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,85 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.number.MutableLong;
// TODO FIXME
public class DebugFixAir extends MCAFilterCounter {
@Override
public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) {
none:
{
some:
{
for (int layer = 0; layer < chunk.ids.length; layer++) {
byte[] idLayer = chunk.ids[layer];
if (idLayer == null) continue;
for (int i = 0; i < 4096; i++) {
if (idLayer[i] != 0) {
if (layer != 0) break some;
break none;
}
}
{ // Possibly dead code depending on the generator
chunk.ids[layer] = null;
chunk.data[layer] = null;
chunk.setModified();
}
}
cache.add(Character.MAX_VALUE);
chunk.setDeleted(true);
return null;
}
return null;
}
for (int i = 0; i < 5; i++) {
if (chunk.ids[i] == null) return null;
}
// // layer 0
// boolean modified = false;
// byte[] ids0 = chunk.ids[0];
// for (int i = 0; i < 256; i++) {
// if (ids0[i] == 0) {
// if (!modified) {
// modified = true;
// }
// for (int layer = 0; layer < 4; layer++) {
// byte[] arr = chunk.ids[layer];
// for (int y = i; y < 4096; y += 256) {
// arr[y] = BlockTypes.DIRT;
// }
// }
// ids0[i] = BlockTypes.BEDROCK;
// if (chunk.ids[4][i] == 0) chunk.ids[4][i] = BlockTypes.GRASS;
// cache.add(256);
// }
// }
// if (modified) {
// Arrays.fill(chunk.skyLight[4], (byte) 255);
// chunk.setModified();
// }
return null;
}
@Override
public void finishFile(MCAFile file, MutableLong cache) {
Fawe.debug(" - apply " + file.getFile());
boolean[] deleteFile = { true };
file.forEachCachedChunk(new RunnableVal<MCAChunk>() {
@Override
public void run(MCAChunk value) {
if (!value.isDeleted()) {
deleteFile[0] = false;
}
}
});
if (deleteFile[0]) {
file.setDeleted(true);
}
}
}

View File

@ -0,0 +1,11 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
public class DebugFixP2Roads extends MCAFilterCounter {
@Override
public MCAFile applyFile(MCAFile file) {
return super.applyFile(file);
}
}

View File

@ -0,0 +1,105 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilter;
import com.boydti.fawe.jnbt.anvil.MCAQueue;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Spliterator;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Consumer;
public class DelegateMCAFilter<T> extends MCAFilter<T> {
private final MCAFilter<T> filter;
@Override
public void withPool(ForkJoinPool pool, MCAQueue queue) {
filter.withPool(pool, queue);
}
@Override
public boolean appliesFile(Path path, BasicFileAttributes attr) {
return filter.appliesFile(path, attr);
}
@Override
public boolean appliesFile(int mcaX, int mcaZ) {
return filter.appliesFile(mcaX, mcaZ);
}
@Override
public MCAFile applyFile(MCAFile file) {
return filter.applyFile(file);
}
@Override
public boolean appliesChunk(int cx, int cz) {
return filter.appliesChunk(cx, cz);
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, T cache) {
return filter.applyChunk(chunk, cache);
}
@Override
public void applyBlock(int x, int y, int z, BaseBlock block, T cache) {
filter.applyBlock(x, y, z, block, cache);
}
@Override
public void finishChunk(MCAChunk chunk, T cache) {
filter.finishChunk(chunk, cache);
}
@Override
public void finishFile(MCAFile file, T cache) {
filter.finishFile(file, cache);
}
@Override
public T init() {
return filter.init();
}
@Override
public void clean() {
filter.clean();
}
@Override
public T get() {
return filter.get();
}
@Override
public void forEach(Consumer<? super T> action) {
filter.forEach(action);
}
@Override
public Spliterator<T> spliterator() {
return filter.spliterator();
}
@Override
public void remove() {
filter.remove();
}
@Override
public void set(T value) {
filter.set(value);
}
public DelegateMCAFilter(MCAFilter<T> filter) {
this.filter = filter;
}
public MCAFilter<T> getFilter() {
return filter;
}
}

View File

@ -0,0 +1,23 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.number.MutableLong;
import com.sk89q.worldedit.world.biome.BaseBiome;
public class DeleteBiomeFilterSimple extends MCAFilterCounter {
private final int id;
public DeleteBiomeFilterSimple(BaseBiome biome) {
this.id = biome.getId();
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) {
if ((chunk.biomes[0] & 0xFF) == id) {
chunk.setDeleted(true);
cache.add(Character.MAX_VALUE);
}
return null;
}
}

View File

@ -0,0 +1,28 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
public class DeleteOldFilter extends MCAFilterCounter {
private final long time;
public DeleteOldFilter(long time) {
this.time = time;
if (time < 1) {
throw new IllegalArgumentException("Time must be positive");
}
}
@Override
public boolean appliesFile(Path path, BasicFileAttributes attr) {
long modified = attr.lastModifiedTime().toMillis();
// long access = attr.lastAccessTime().toMillis();
long last = modified;//Math.max(modified, access);
if (last != 0 && System.currentTimeMillis() - last > this.time) {
path.toFile().delete();
get().add(512 * 512 * 256);
}
return false;
}
}

View File

@ -0,0 +1,47 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.regions.FaweMaskManager;
import com.boydti.fawe.regions.general.RegionFilter;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.io.IOException;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
public class DeleteUnclaimedFilter extends DeleteUninhabitedFilter {
private ArrayList<RegionFilter> filters = new ArrayList<>();
public DeleteUnclaimedFilter(World world, long fileDuration, long inhabitedTicks, long chunkInactivity) {
super(fileDuration, inhabitedTicks, chunkInactivity);
for (FaweMaskManager m : FaweAPI.getMaskManagers()) {
RegionFilter filter = m.getFilter(Fawe.imp().getWorldName(world));
if (filter != null) {
filters.add(filter);
}
}
}
@Override
public boolean shouldDelete(File file, BasicFileAttributes attr, int mcaX, int mcaZ) throws IOException {
boolean contains = false;
for (RegionFilter filter : filters) {
if (contains = filter.containsRegion(mcaX, mcaZ)) {
break;
}
}
return !contains && super.shouldDelete(file, attr, mcaX, mcaZ);
}
@Override
public boolean shouldDeleteChunk(MCAFile mca, int cx, int cz) {
boolean contains = false;
for (RegionFilter filter : filters) {
if (contains = filter.containsChunk(cx, cz)) {
break;
}
}
return !contains && super.shouldDeleteChunk(mca, cx, cz);
}
}

View File

@ -0,0 +1,162 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.jnbt.NBTStreamer;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.RunnableVal4;
import com.boydti.fawe.object.exception.FaweException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Date;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
/**
* Deletes unvisited MCA files and Chunks<br>
* - This a global filter and cannot be used a selection<br>
*/
public class DeleteUninhabitedFilter extends MCAFilterCounter {
private final long inhabitedTicks;
private final long fileDurationMillis;
private final long cutoffChunkAgeEpoch;
private boolean debug = false;
public DeleteUninhabitedFilter(long fileDurationMillis, long inhabitedTicks, long chunkInactivityMillis) {
this.fileDurationMillis = fileDurationMillis;
this.inhabitedTicks = inhabitedTicks;
this.cutoffChunkAgeEpoch = System.currentTimeMillis() - chunkInactivityMillis;
}
public void enableDebug() {
this.debug = true;
}
public long getInhabitedTicks() {
return inhabitedTicks;
}
public long getFileDurationMillis() {
return fileDurationMillis;
}
public long getCutoffChunkAgeEpoch() {
return cutoffChunkAgeEpoch;
}
@Override
public boolean appliesFile(Path path, BasicFileAttributes attr) {
String name = path.getFileName().toString();
String[] split = name.split("\\.");
final int mcaX = Integer.parseInt(split[1]);
final int mcaZ = Integer.parseInt(split[2]);
File file = path.toFile();
long lastModified = attr.lastModifiedTime().toMillis();
if (lastModified > cutoffChunkAgeEpoch) {
return false;
}
try {
if (shouldDelete(file, attr, mcaX, mcaZ)) {
if (debug) {
Fawe.debug("Deleting " + file + " as it was modified at " + new Date(lastModified) + " and you provided a threshold of " + new Date(cutoffChunkAgeEpoch));
}
file.delete();
get().add(512 * 512 * 256);
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
@Override
public MCAFile applyFile(MCAFile mca) {
try {
ForkJoinPool pool = new ForkJoinPool();
mca.init();
filter(mca, pool);
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
mca.close(pool);
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public boolean shouldDelete(File file, BasicFileAttributes attr, int mcaX, int mcaZ) throws IOException {
long creation = attr.creationTime().toMillis();
long modified = attr.lastModifiedTime().toMillis();
if ((modified - creation < fileDurationMillis && modified > creation) || file.length() < 12288) {
return true;
}
return false;
}
public boolean shouldDeleteChunk(MCAFile mca, int cx, int cz) {
return true;
}
public void filter(MCAFile mca, ForkJoinPool pool) throws IOException {
mca.forEachSortedChunk(new RunnableVal4<Integer, Integer, Integer, Integer>() {
@Override
public void run(Integer x, Integer z, Integer offset, Integer size) {
int bx = mca.getX() << 5;
int bz = mca.getZ() << 5;
int cx = bx + x;
int cz = bz + z;
if (shouldDeleteChunk(mca, cx, cz)) {
Runnable task = new Runnable() {
@Override
public void run() {
try {
mca.streamChunk(offset, new RunnableVal<NBTStreamer>() {
@Override
public void run(NBTStreamer value) {
addReaders(mca, x, z, value);
}
});
} catch (FaweException ignore) {
} catch (IOException e) {
e.printStackTrace();
}
}
};
pool.submit(task);
}
}
});
}
public void addReaders(MCAFile mca, int x, int z, NBTStreamer streamer) {
streamer.addReader(".Level.InhabitedTime", new BiConsumer<Integer, Long>() {
@Override
public void accept(Integer index, Long value) {
if (value <= inhabitedTicks) {
MCAChunk chunk = new MCAChunk(null, x, z);
if (debug) {
int cx = (mca.getX() << 5) + (x & 31);
int cz = (mca.getZ() << 5) + (z & 31);
Fawe.debug("Deleting chunk " + cx + "," + cz + " as it was only inhabited for " + value + " and passed all other checks");
}
chunk.setDeleted(true);
synchronized (mca) {
mca.setChunk(chunk);
}
get().add(16 * 16 * 256);
}
}
});
}
}

View File

@ -0,0 +1,74 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.number.MutableLong;
import com.boydti.fawe.util.StringMan;
import com.sk89q.worldedit.MutableBlockVector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.pattern.RandomPattern;
import java.util.ArrayList;
import java.util.List;
// TODO FIXME
public class MappedReplacePatternFilter extends MCAFilterCounter {
private Pattern[] map = new Pattern[Character.MAX_VALUE + 1];
public MappedReplacePatternFilter() {
}
public MappedReplacePatternFilter(String from, RandomPattern to, boolean useData) throws InputParseException {
// List<String> split = StringMan.split(from, ',');
// Pattern[] patterns = ((RandomPattern) to).getPatterns().toArray(new Pattern[0]);
// if (patterns.length == split.size()) {
// for (int i = 0; i < split.size(); i++) {
// Pattern pattern = patterns[i];
// String arg = split.get(i);
// ArrayList<BaseBlock> blocks = new ArrayList<BaseBlock>();
// for (String arg2 : arg.split(",")) {
// BaseBlock block = FaweCache.getBlock(arg, true, !useData);
// blocks.add(block);
// }
// for (BaseBlock block : blocks) {
// if (block.getData() != -1) {
// int combined = FaweCache.getCombined(block);
// map[combined] = pattern;
// } else {
// for (int data = 0; data < 16; data++) {
// int combined = FaweCache.getCombined(block.getId(), data);
// map[combined] = pattern;
// }
// }
// }
// }
// } else {
// throw new InputParseException("Mask:Pattern must be a 1:1 match");
// }
}
public void addReplace(BaseBlock block, Pattern pattern) {
// map[block.getCombined()] = pattern;
}
private final MutableBlockVector mutable = new MutableBlockVector(0, 0, 0);
@Override
public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong ignore) {
// int id = block.getId();
// int data = FaweCache.hasData(id) ? block.getData() : 0;
// int combined = FaweCache.getCombined(id, data);
// Pattern p = map[combined];
// if (p != null) {
// BaseBlock newBlock = p.apply(x, y, z);
// int currentId = block.getId();
// if (FaweCache.hasNBT(currentId)) {
// block.setNbtData(null);
// }
// block.setId(newBlock.getId());
// block.setData(newBlock.getData());
// }
}
}

View File

@ -0,0 +1,192 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.object.RunnableVal4;
import com.boydti.fawe.object.collection.LongHashSet;
import com.intellectualcrafters.plot.PS;
import com.intellectualcrafters.plot.generator.HybridGen;
import com.intellectualcrafters.plot.generator.HybridPlotWorld;
import com.intellectualcrafters.plot.generator.IndependentPlotGenerator;
import com.intellectualcrafters.plot.object.Location;
import com.intellectualcrafters.plot.object.Plot;
import com.intellectualcrafters.plot.object.PlotArea;
import com.intellectualcrafters.plot.util.expiry.ExpireManager;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.registry.LegacyMapper;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.concurrent.ForkJoinPool;
public class PlotTrimFilter extends DeleteUninhabitedFilter {
private final HybridPlotWorld hpw;
private final HybridGen hg;
private final MCAChunk reference;
private final LongHashSet occupiedRegions;
private final LongHashSet unoccupiedChunks;
private boolean referenceIsVoid;
public static boolean shouldSuggest(PlotArea area) {
IndependentPlotGenerator gen = area.getGenerator();
if (area instanceof HybridPlotWorld && gen instanceof HybridGen) {
HybridPlotWorld hpw = (HybridPlotWorld) area;
return hpw.PLOT_BEDROCK && !hpw.PLOT_SCHEMATIC && hpw.MAIN_BLOCK.length == 1 && hpw.TOP_BLOCK.length == 1;
}
return false;
}
public PlotTrimFilter(World world, long fileDuration, long inhabitedTicks, long chunkInactivity) {
super(fileDuration, inhabitedTicks, chunkInactivity);
Fawe.debug("Initializing Plot trim...");
String worldName = Fawe.imp().getWorldName(world);
PlotArea area = PS.get().getPlotAreaByString(worldName);
IndependentPlotGenerator gen = area.getGenerator();
if (!(area instanceof HybridPlotWorld) || !(gen instanceof HybridGen)) {
throw new UnsupportedOperationException("Trim does not support non hybrid plot worlds");
}
this.hg = (HybridGen) gen;
this.hpw = (HybridPlotWorld) area;
if (hpw.PLOT_SCHEMATIC || hpw.MAIN_BLOCK.length != 1 || hpw.TOP_BLOCK.length != 1) {
throw new UnsupportedOperationException("WIP - will implement later");
}
this.occupiedRegions = new LongHashSet();
this.unoccupiedChunks = new LongHashSet();
this.reference = calculateReference();
Fawe.debug(" - calculating claims");
this.calculateClaimedArea();
}
private MCAChunk calculateReference() {
MCAChunk reference = new MCAChunk(null, 0, 0);
if (hpw.PLOT_BEDROCK) {
reference.fillCuboid(0, 15, 0, 0, 0, 15, BlockTypes.BEDROCK.getInternalId());
} else if (hpw.MAIN_BLOCK[0].id == 0 && hpw.TOP_BLOCK[0].id == 0) {
referenceIsVoid = true;
}
reference.fillCuboid(0, 15, 1, hpw.PLOT_HEIGHT - 1, 0, 15, LegacyMapper.getInstance().getBlockFromLegacy(hpw.MAIN_BLOCK[0].id).getInternalId());
reference.fillCuboid(0, 15, hpw.PLOT_HEIGHT, hpw.PLOT_HEIGHT, 0, 15, LegacyMapper.getInstance().getBlockFromLegacy(hpw.TOP_BLOCK[0].id).getInternalId());
return reference;
}
private void calculateClaimedArea() {
ArrayList<Plot> plots = new ArrayList<>(hpw.getPlots());
if (ExpireManager.IMP != null) {
plots.removeAll(ExpireManager.IMP.getPendingExpired());
}
for (Plot plot : plots) {
Location pos1 = plot.getBottom();
Location pos2 = plot.getTop();
int ccx1 = pos1.getX() >> 9;
int ccz1 = pos1.getZ() >> 9;
int ccx2 = pos2.getX() >> 9;
int ccz2 = pos2.getZ() >> 9;
for (int x = ccx1; x <= ccx2; x++) {
for (int z = ccz1; z <= ccz2; z++) {
if (!occupiedRegions.containsKey(x, z)) {
occupiedRegions.add(x, z);
int bcx = x << 5;
int bcz = z << 5;
int tcx = bcx + 32;
int tcz = bcz + 32;
for (int cz = bcz; cz < tcz; cz++) {
for (int cx = bcx; cx < tcx; cx++) {
unoccupiedChunks.add(cx, cz);
}
}
}
}
}
int cx1 = pos1.getX() >> 4;
int cz1 = pos1.getZ() >> 4;
int cx2 = pos2.getX() >> 4;
int cz2 = pos2.getZ() >> 4;
for (int cz = cz1; cz <= cz2; cz++) {
for (int cx = cx1; cx <= cx2; cx++) {
unoccupiedChunks.remove(cx, cz);
}
}
}
}
@Override
public boolean shouldDelete(File file, BasicFileAttributes attr, int mcaX, int mcaZ) throws IOException {
Fawe.debug("Apply file: " + file);
return !occupiedRegions.containsKey(mcaX, mcaZ) || super.shouldDelete(file, attr, mcaX, mcaZ);
}
@Override
public boolean shouldDeleteChunk(MCAFile mca, int cx, int cz) {
return unoccupiedChunks.containsKey(cx, cz);
}
@Override
public void filter(MCAFile mca, ForkJoinPool pool) throws IOException {
if (reference == null) {
super.filter(mca, pool);
return;
}
Path file = mca.getFile().toPath();
BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class);
long creationDate = attr.creationTime().toMillis();
mca.forEachSortedChunk(new RunnableVal4<Integer, Integer, Integer, Integer>() {
@Override
public void run(Integer x, Integer z, Integer offset, Integer size) {
int bx = mca.getX() << 5;
int bz = mca.getZ() << 5;
int cx = bx + x;
int cz = bz + z;
if (shouldDeleteChunk(mca, cx, cz)) {
MCAChunk chunk = new MCAChunk(null, x, z);
chunk.setDeleted(true);
synchronized (mca) {
mca.setChunk(chunk);
}
get().add(16 * 16 * 256);
return;
}
Runnable task = new Runnable() {
@Override
public void run() {
try {
MCAChunk chunk = mca.getChunk(x, z);
if (chunk.getInhabitedTime() <= getInhabitedTicks()) {
chunk.setDeleted(true);
get().add(16 * 16 * 256);
return;
}
if (referenceIsVoid) {
for (int i = 0; i < chunk.ids.length; i++) {
byte[] arr = chunk.ids[i];
if (arr != null) {
for (byte b : arr) {
if (b != 0) return;
}
}
}
}
else if (!reference.idsEqual(chunk, false)) {
return;
}
chunk.setDeleted(true);
get().add(16 * 16 * 256);
} catch (IOException e) {
e.printStackTrace();
}
}
};
pool.submit(task);
}
});
}
}

View File

@ -0,0 +1,4 @@
package com.boydti.fawe.jnbt.anvil.filters;
public class RegionFilter {
}

View File

@ -0,0 +1,172 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.jnbt.anvil.MutableMCABackedBaseBlock;
import com.boydti.fawe.object.clipboard.remap.ClipboardRemapper;
import com.boydti.fawe.object.collection.BlockVectorSet;
import com.boydti.fawe.object.number.MutableLong;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// TODO FIXME
public class RemapFilter extends MCAFilterCounter {
private final ClipboardRemapper remapper;
private final ClipboardRemapper.RemapPlatform from;
private boolean skipRemap;
private List<CompoundTag> portals = Collections.synchronizedList(new ArrayList<>());
private BlockVectorSet pLocs = new BlockVectorSet();
private int dimension;
public RemapFilter(ClipboardRemapper remapper) {
this.remapper = remapper;
this.from = null;
}
public RemapFilter(ClipboardRemapper.RemapPlatform from, ClipboardRemapper.RemapPlatform to) {
this.remapper = new ClipboardRemapper(from, to);
this.from = from;
}
@Override
public MCAFile applyFile(MCAFile mca) {
File file = mca.getFile();
this.skipRemap = file.getName().endsWith(".mcapm");
return super.applyFile(mca);
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) {
if (skipRemap) return null;
return super.applyChunk(chunk, cache);
}
public List<CompoundTag> getPortals() {
return portals;
}
public void setDimension(int dimension) {
this.dimension = dimension;
}
@Override
public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong cache) {
// int id = block.getId();
// if (remapper.hasRemap(id)) {
// BaseBlock result = remapper.remap(block);
// if (result != block) {
// cache.add(1);
// block.setId(id = result.getId());
// if (id == 218) {
// CompoundTag nbt = block.getNbtData();
// if (nbt != null) {
// Map<String, Tag> map = ReflectionUtils.getMap(nbt.getValue());
// map.putIfAbsent("facing", new ByteTag((byte) block.getData()));
// }
// }
// block.setData(result.getData());
// }
// }
// if (from != null) {
// outer:
// switch (from) {
// case PC: {
// int newLight = 0;
// switch (id) {
// case 90: {
// pLocs.add(x, y, z);
// if (pLocs.contains(x, y - 1, z) || pLocs.contains(x - 1, y, z) || pLocs.contains(x, y, z - 1))
// break;
// Map<String, Tag> tag = new HashMap<>();
// tag.put("Span", new ByteTag((byte) 1));
// tag.put("TpX", new IntTag(x));
// tag.put("TpY", new IntTag(y));
// tag.put("TpZ", new IntTag(z));
// tag.put("DimId", new IntTag(dimension));
// int data = block.getData();
// tag.put("Xa", new ByteTag((byte) ((data == 2) ? 0 : 1)));
// tag.put("Za", new ByteTag((byte) ((data == 2) ? 1 : 0)));
// portals.add(new CompoundTag(tag));
// break;
// }
// case 29:
// case 33: {
// int data = block.getData();
// Map<String, Object> map = new HashMap<>();
// map.put("Progress", 1f);
// map.put("State", (byte) 2);
// map.put("LastProgress", 1f);
// map.put("NewState", (byte) 2);
// map.put("isMoveable", (byte) 0);
// map.put("id", "PistonArm");
// map.put("AttachedBlocks", new ArrayList<>());
// map.put("Sticky", (byte) (id == 29 ? 1 : 0));
// map.put("x", x);
// map.put("y", y);
// map.put("z", z);
// block.setNbtData(FaweCache.asTag(map));
// break;
// }
// case 44:
// case 182:
// case 158:
// case 53:
// case 67:
// case 108:
// case 109:
// case 114:
// case 128:
// case 134:
// case 135:
// case 136:
// case 156:
// case 163:
// case 164:
// case 180:
// case 203:
// case 198:
// MutableMCABackedBaseBlock mcaBlock = (MutableMCABackedBaseBlock) block;
// MCAChunk chunk = mcaBlock.getChunk();
// int currentLight = chunk.getSkyLight(x, y, z);
// if (currentLight >= 14) {
// break;
// }
// newLight = chunk.getSkyLight(x, (y + 1) & 0xFF, z);
// if (newLight > currentLight) break;
// if (x > 0) {
// if ((newLight = chunk.getSkyLight(x - 1, y, z)) > currentLight) break;
// }
// if (x < 16) {
// if ((newLight = chunk.getSkyLight(x + 1, y, z)) > currentLight) break;
// }
// if (z > 0) {
// if ((newLight = chunk.getSkyLight(x, y, z - 1)) > currentLight) break;
// }
// if (z < 16) {
// if ((newLight = chunk.getSkyLight(x, y, z + 1)) > currentLight) break;
// }
// default:
// break outer;
// }
// if (newLight != 0) {
// ((MutableMCABackedBaseBlock) block).getChunk().setSkyLight(x, y, z, newLight);
// }
// break;
// }
// }
// }
}
}

View File

@ -0,0 +1,50 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.number.MutableLong;
import com.boydti.fawe.util.ArrayUtil;
public class RemoveLayerFilter extends MCAFilterCounter {
private final int startLayer;
private final int endLayer;
private final int minY, maxY;
private final int id;
public RemoveLayerFilter(int minY, int maxY, int id) {
this.minY = minY;
this.maxY = maxY;
this.startLayer = minY >> 4;
this.endLayer = maxY >> 4;
this.id = id;
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) {
for (int layer = startLayer; layer <= endLayer; layer++) {
byte[] ids = chunk.ids[layer];
if (ids == null) {
return null;
}
int startY = Math.max(minY, layer << 4) & 15;
int endY = Math.min(maxY, 15 + (layer << 4)) & 15;
for (int y = startY; y <= endY; y++) {
int indexStart = y << 8;
int indexEnd = indexStart + 255;
for (int index = indexStart; index <= indexEnd; index++) {
if (ids[index] != id) {
return null;
}
}
}
for (int y = startY; y <= endY; y++) {
int indexStart = y << 8;
int indexEnd = indexStart + 255;
ArrayUtil.fill(ids, indexStart, indexEnd + 1, (byte) 0);
}
chunk.setModified();
}
return null;
}
}

View File

@ -0,0 +1,33 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.mask.FaweBlockMatcher;
import com.boydti.fawe.object.number.MutableLong;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.function.pattern.Pattern;
// TODO FIXME
public class ReplacePatternFilter extends MCAFilterCounter {
private final FaweBlockMatcher matchFrom;
private final Pattern to;
public ReplacePatternFilter(FaweBlockMatcher from, Pattern to) {
this.matchFrom = from;
this.to = to;
}
@Override
public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong ignore) {
// if (matchFrom.apply(block)) {
// BaseBlock newBlock = to.apply(x, y, z);
// int currentId = block.getId();
// if (FaweCache.hasNBT(currentId)) {
// block.setNbtData(null);
// }
// block.setId(newBlock.getId());
// block.setData(newBlock.getData());
// }
}
}

View File

@ -0,0 +1,25 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.mask.FaweBlockMatcher;
import com.boydti.fawe.object.number.MutableLong;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
public class ReplaceSimpleFilter extends MCAFilterCounter {
private final FaweBlockMatcher to;
private final FaweBlockMatcher from;
public ReplaceSimpleFilter(FaweBlockMatcher from, FaweBlockMatcher to) {
this.from = from;
this.to = to;
}
@Override
public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong count) {
if (from.apply(block)) {
to.apply(block);
count.increment();
}
}
}

View File

@ -0,0 +1,29 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.number.MutableLong;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.function.pattern.Pattern;
// TODO FIXME
public class SetPatternFilter extends MCAFilterCounter {
private final Pattern to;
public SetPatternFilter(Pattern to) {
this.to = to;
}
@Override
public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong count) {
// BaseBlock newBlock = to.apply(x, y, z);
// int currentId = block.getId();
// if (FaweCache.hasNBT(currentId)) {
// block.setNbtData(null);
// }
// block.setId(newBlock.getId());
// block.setData(newBlock.getData());
// count.increment();
}
}

View File

@ -0,0 +1,46 @@
package com.boydti.fawe.jnbt.anvil.filters;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.number.MutableLong;
public class TrimAirFilter extends MCAFilterCounter {
@Override
public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) {
for (int layer = 0; layer < chunk.ids.length; layer++) {
byte[] idLayer = chunk.ids[layer];
if (idLayer == null) continue;
for (int i = 0; i < 4096; i++) {
if (idLayer[i] != 0) {
return null;
}
}
{ // Possibly dead code depending on the generator
chunk.ids[layer] = null;
chunk.data[layer] = null;
chunk.setModified();
}
}
cache.add(Character.MAX_VALUE);
chunk.setDeleted(true);
return null;
}
@Override
public void finishFile(MCAFile file, MutableLong cache) {
boolean[] deleteFile = { true };
file.forEachCachedChunk(new RunnableVal<MCAChunk>() {
@Override
public void run(MCAChunk value) {
if (!value.isDeleted()) {
deleteFile[0] = false;
}
}
});
if (deleteFile[0]) {
file.setDeleted(true);
}
}
}

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