mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2025-06-12 12:33:54 +00:00
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:
@ -4,15 +4,21 @@ apply plugin: 'idea'
|
||||
dependencies {
|
||||
compile 'de.schlichtherle:truezip:6.8.3'
|
||||
compile 'rhino:js:1.7R2'
|
||||
compile 'org.yaml:snakeyaml:1.9'
|
||||
compile 'com.google.guava:guava:21.0'
|
||||
compile 'com.sk89q:jchronic:0.2.4a'
|
||||
compile 'com.google.code.findbugs:jsr305:1.3.9'
|
||||
compile 'com.thoughtworks.paranamer:paranamer:2.6'
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile 'com.sk89q.lib:jlibnoise:1.0.0'
|
||||
//compile 'net.sf.trove4j:trove4j:3.0.3'
|
||||
testCompile 'org.mockito:mockito-core:1.9.0-rc1'
|
||||
|
||||
// Fawe depends
|
||||
compile 'org.yaml:snakeyaml:1.19'
|
||||
compile 'net.fabiozumbi12:redprotect:1.9.6'
|
||||
compile group: "com.plotsquared", name: "plotsquared-api", version: "latest"
|
||||
// compile 'org.primesoft:BlocksHub:2.0'
|
||||
compile 'com.github.luben:zstd-jni:1.1.1'
|
||||
compile 'co.aikar:fastutil-lite:1.0'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -27,4 +33,14 @@ sourceSets {
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
from('src/main/resources') {
|
||||
include 'fawe.properties'
|
||||
expand(
|
||||
version: "${project.parent.version}",
|
||||
name: project.parent.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
build.dependsOn(shadowJar)
|
||||
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.blocks;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.Vector;
|
||||
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.extent.Extent;
|
||||
|
||||
/**
|
||||
* A implementation of a lazy block for {@link Extent#getLazyBlock(Vector)}
|
||||
* that takes the block's ID and metadata, but will defer loading of NBT
|
||||
* data until time of access.
|
||||
*
|
||||
* <p>NBT data is later loaded using a call to {@link Extent#getBlock(Vector)}
|
||||
* with a stored {@link Extent} and location.</p>
|
||||
*
|
||||
* <p>All mutators on this object will throw an
|
||||
* {@link UnsupportedOperationException}.</p>
|
||||
*/
|
||||
public class LazyBlock extends BaseBlock {
|
||||
|
||||
private final Extent extent;
|
||||
private final Vector position;
|
||||
private boolean loaded = false;
|
||||
|
||||
/**
|
||||
* Create a new lazy block.
|
||||
*
|
||||
* @param type the block type
|
||||
* @param extent the extent to later load the full block data from
|
||||
* @param position the position to later load the full block data from
|
||||
*/
|
||||
public LazyBlock(BlockType type, Extent extent, Vector position) {
|
||||
super(type);
|
||||
checkNotNull(extent);
|
||||
checkNotNull(position);
|
||||
this.extent = extent;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new lazy block.
|
||||
*
|
||||
* @param state the block state
|
||||
* @param extent the extent to later load the full block data from
|
||||
* @param position the position to later load the full block data from
|
||||
*/
|
||||
public LazyBlock(BlockState state, Extent extent, Vector position) {
|
||||
super(state);
|
||||
checkNotNull(extent);
|
||||
checkNotNull(position);
|
||||
this.extent = extent;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getNbtData() {
|
||||
if (!loaded) {
|
||||
BlockState loadedBlock = extent.getFullBlock(position);
|
||||
this.nbtData = loadedBlock.getNbtData();
|
||||
loaded = true;
|
||||
}
|
||||
return super.getNbtData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNbtData(CompoundTag nbtData) {
|
||||
throw new UnsupportedOperationException("This object is immutable");
|
||||
}
|
||||
|
||||
}
|
@ -25,8 +25,8 @@ import com.sk89q.jnbt.NBTUtils;
|
||||
import com.sk89q.jnbt.ShortTag;
|
||||
import com.sk89q.jnbt.StringTag;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -35,7 +35,7 @@ import java.util.Map;
|
||||
/**
|
||||
* A mob spawner block.
|
||||
*/
|
||||
public class MobSpawnerBlock extends BaseBlock implements TileEntityBlock {
|
||||
public class MobSpawnerBlock extends BaseBlock {
|
||||
|
||||
private String mobType;
|
||||
private short delay;
|
||||
@ -118,7 +118,7 @@ public class MobSpawnerBlock extends BaseBlock implements TileEntityBlock {
|
||||
|
||||
@Override
|
||||
public CompoundTag getNbtData() {
|
||||
Map<String, Tag> values = new HashMap<>();
|
||||
Map<String, Tag> values = new HashMap<String, Tag>();
|
||||
values.put("EntityId", new StringTag(mobType));
|
||||
values.put("Delay", new ShortTag(delay));
|
||||
values.put("SpawnCount", new ShortTag(spawnCount));
|
||||
|
@ -22,9 +22,9 @@ package com.sk89q.worldedit.blocks;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.StringTag;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.util.gson.GsonUtil;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.util.gson.GsonUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -93,7 +93,7 @@ public class SignBlock extends BaseBlock implements TileEntityBlock {
|
||||
|
||||
@Override
|
||||
public CompoundTag getNbtData() {
|
||||
Map<String, Tag> values = new HashMap<>();
|
||||
Map<String, Tag> values = new HashMap<String, Tag>();
|
||||
values.put("Text1", new StringTag(text[0]));
|
||||
values.put("Text2", new StringTag(text[1]));
|
||||
values.put("Text3", new StringTag(text[2]));
|
||||
|
@ -22,8 +22,8 @@ package com.sk89q.worldedit.blocks;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.StringTag;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -114,4 +114,4 @@ public class SkullBlock extends BaseBlock implements TileEntityBlock {
|
||||
owner = ((StringTag) t).getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
585
worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
Normal file
585
worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
Normal 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();
|
||||
}
|
||||
}
|
564
worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java
Normal file
564
worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
218
worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java
Normal file
218
worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
68
worldedit-core/src/main/java/com/boydti/fawe/IFawe.java
Normal file
68
worldedit-core/src/main/java/com/boydti/fawe/IFawe.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
// }
|
||||
}
|
@ -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
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.boydti.fawe.command;
|
||||
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.internal.command.WorldEditBinding;
|
||||
|
||||
public class FaweBinding extends WorldEditBinding {
|
||||
private final WorldEdit worldEdit;
|
||||
|
||||
public FaweBinding(WorldEdit worldEdit) {
|
||||
super(worldEdit);
|
||||
this.worldEdit = worldEdit;
|
||||
}
|
||||
|
||||
public WorldEdit getWorldEdit() {
|
||||
return worldEdit;
|
||||
}
|
||||
}
|
@ -0,0 +1,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;
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<>();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
744
worldedit-core/src/main/java/com/boydti/fawe/config/BBC.java
Normal file
744
worldedit-core/src/main/java/com/boydti/fawe/config/BBC.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
456
worldedit-core/src/main/java/com/boydti/fawe/config/Config.java
Normal file
456
worldedit-core/src/main/java/com/boydti/fawe/config/Config.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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()) +
|
||||
"']";
|
||||
}
|
||||
}
|
@ -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() + "')";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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}<
|
||||
* {@link String}, {@link Object}> and returns the class.</li>
|
||||
* <li>A static method "valueOf" that accepts a single {@link java.util.Map}<{@link
|
||||
* String}, {@link Object}> and returns the class.</li>
|
||||
* <li>A constructor that accepts a single {@link java.util.Map}<{@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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
429
worldedit-core/src/main/java/com/boydti/fawe/jnbt/JSON2NBT.java
Normal file
429
worldedit-core/src/main/java/com/boydti/fawe/jnbt/JSON2NBT.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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());
|
||||
// }
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package com.boydti.fawe.jnbt.anvil.filters;
|
||||
|
||||
public class RegionFilter {
|
||||
}
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user