mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2025-07-15 01:08:35 +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:
@ -0,0 +1,27 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ArrayUtil {
|
||||
public static final void fill(byte[] a, int fromIndex, int toIndex, byte val) {
|
||||
for (int i = fromIndex; i < toIndex; i++) a[i] = val;
|
||||
}
|
||||
|
||||
public static final void fill(char[] a, int fromIndex, int toIndex, char val) {
|
||||
for (int i = fromIndex; i < toIndex; i++) a[i] = val;
|
||||
}
|
||||
|
||||
public static <T> T[] concatAll(T[] first, T[]... rest) {
|
||||
int totalLength = first.length;
|
||||
for (T[] array : rest) {
|
||||
totalLength += array.length;
|
||||
}
|
||||
T[] result = Arrays.copyOf(first, totalLength);
|
||||
int offset = first.length;
|
||||
for (T[] array : rest) {
|
||||
System.arraycopy(array, 0, result, offset, array.length);
|
||||
offset += array.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.FaweCache;
|
||||
import com.boydti.fawe.object.brush.BrushSettings;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.StringTag;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.blocks.BaseItem;
|
||||
import com.sk89q.worldedit.command.tool.BrushTool;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public final class BrushCache {
|
||||
private static final WeakHashMap<Object, BrushTool> brushCache = new WeakHashMap<>();
|
||||
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private static final CompoundTag getNBT(BaseItem item) {
|
||||
return item.hasNbtData() ? item.getNbtData() : null;
|
||||
}
|
||||
|
||||
private static final Object getKey(BaseItem item) {
|
||||
return item.getNativeItem();
|
||||
}
|
||||
|
||||
private static final ThreadLocal<Boolean> RECURSION = new ThreadLocal<>();
|
||||
|
||||
public static final BrushTool getTool(Player player, LocalSession session, BaseItem item) {
|
||||
if (!item.hasNbtData()) return null;
|
||||
Object key = getKey(item);
|
||||
if (key == null) return null;
|
||||
BrushTool cached = brushCache.get(key);
|
||||
if (cached != null) return cached;
|
||||
|
||||
StringTag json = (StringTag) item.getNbtData().getValue().get("weBrushJson");
|
||||
if (json != null) {
|
||||
try {
|
||||
if (RECURSION.get() != null) return null;
|
||||
RECURSION.set(true);
|
||||
|
||||
BrushTool tool = BrushTool.fromString(player, session, json.getValue());
|
||||
tool.setHolder(item);
|
||||
brushCache.put(key, tool);
|
||||
return tool;
|
||||
} catch (Throwable ignore) {
|
||||
ignore.printStackTrace();
|
||||
Fawe.debug("Invalid brush for " + player + " holding " + item.getType() + ": " + json.getValue());
|
||||
if (item != null) {
|
||||
item.setNbtData(null);
|
||||
brushCache.remove(key);
|
||||
}
|
||||
} finally {
|
||||
RECURSION.remove();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BrushTool getCachedTool(BaseItem item) {
|
||||
Object key = getKey(item);
|
||||
if (key != null) return brushCache.get(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final BrushTool setTool(BaseItem item, BrushTool tool) {
|
||||
if (item.getNativeItem() == null) return null;
|
||||
|
||||
CompoundTag nbt = item.getNbtData();
|
||||
Map<String, Tag> map;
|
||||
if (nbt == null) {
|
||||
if (tool == null) {
|
||||
return tool;
|
||||
}
|
||||
nbt = new CompoundTag(map = new HashMap<>());
|
||||
} else {
|
||||
map = ReflectionUtils.getMap(nbt.getValue());
|
||||
}
|
||||
brushCache.remove(getKey(item));
|
||||
CompoundTag display = (CompoundTag) map.get("display");
|
||||
Map<String, Tag> displayMap;
|
||||
if (tool != null) {
|
||||
String json = tool.toString(gson);
|
||||
map.put("weBrushJson", new StringTag(json));
|
||||
if (display == null) {
|
||||
map.put("display", new CompoundTag(displayMap = new HashMap()));
|
||||
} else {
|
||||
displayMap = ReflectionUtils.getMap(display.getValue());
|
||||
}
|
||||
displayMap.put("Lore", FaweCache.asTag(json.split("\\r?\\n")));
|
||||
String primary = (String) tool.getPrimary().getSettings().get(BrushSettings.SettingType.BRUSH);
|
||||
String secondary = (String) tool.getSecondary().getSettings().get(BrushSettings.SettingType.BRUSH);
|
||||
if (primary == null) primary = secondary;
|
||||
if (secondary == null) secondary = primary;
|
||||
if (primary != null) {
|
||||
String name = primary == secondary ? primary.split(" ")[0] : primary.split(" ")[0] + " / " + secondary.split(" ")[0];
|
||||
displayMap.put("Name", new StringTag(name));
|
||||
}
|
||||
} else if (map.containsKey("weBrushJson")) {
|
||||
map.remove("weBrushJson");
|
||||
if (display != null) {
|
||||
displayMap = ReflectionUtils.getMap(display.getValue());
|
||||
displayMap.remove("Lore");
|
||||
displayMap.remove("Name");
|
||||
if (displayMap.isEmpty()) {
|
||||
map.remove("display");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return tool;
|
||||
}
|
||||
item.setNbtData(nbt);
|
||||
if (tool != null) {
|
||||
brushCache.put(getKey(item), tool);
|
||||
}
|
||||
return tool;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
public class CachedMathMan {
|
||||
private static final int ATAN2_BITS = 7;
|
||||
private static final int ATAN2_BITS2 = ATAN2_BITS << 1;
|
||||
private static final int ATAN2_MASK = ~(-1 << ATAN2_BITS2);
|
||||
private static final int ATAN2_COUNT = ATAN2_MASK + 1;
|
||||
private static final int ATAN2_DIM = (int) Math.sqrt(ATAN2_COUNT);
|
||||
private static final float INV_ATAN2_DIM_MINUS_1 = 1.0f / (ATAN2_DIM - 1);
|
||||
private static final float[] atan2 = new float[ATAN2_COUNT];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < ATAN2_DIM; i++) {
|
||||
for (int j = 0; j < ATAN2_DIM; j++) {
|
||||
float x0 = (float) i / ATAN2_DIM;
|
||||
float y0 = (float) j / ATAN2_DIM;
|
||||
|
||||
atan2[(j * ATAN2_DIM) + i] = (float) Math.atan2(y0, x0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float[] ANGLES = new float[65536];
|
||||
static {
|
||||
for (int i = 0; i < 65536; ++i) {
|
||||
ANGLES[i] = (float) Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D);
|
||||
}
|
||||
}
|
||||
|
||||
private static char[] SQRT = new char[65536];
|
||||
static {
|
||||
for (int i = 0; i < SQRT.length; i++) {
|
||||
SQRT[i] = (char) Math.round(Math.sqrt(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized for i elem 0,65536 (characters)
|
||||
* @param i
|
||||
* @return square root
|
||||
*/
|
||||
protected static int usqrt(int i) {
|
||||
return SQRT[i];
|
||||
}
|
||||
|
||||
protected static float sinInexact(double paramFloat) {
|
||||
return ANGLES[(int) (paramFloat * 10430.378F) & 0xFFFF];
|
||||
}
|
||||
|
||||
protected static float cosInexact(double paramFloat) {
|
||||
return ANGLES[(int) (paramFloat * 10430.378F + 16384.0F) & 0xFFFF];
|
||||
}
|
||||
|
||||
protected static final float atan2(float y, float x) {
|
||||
float add, mul;
|
||||
|
||||
if (x < 0.0f) {
|
||||
if (y < 0.0f) {
|
||||
x = -x;
|
||||
y = -y;
|
||||
|
||||
mul = 1.0f;
|
||||
} else {
|
||||
x = -x;
|
||||
mul = -1.0f;
|
||||
}
|
||||
|
||||
add = -3.141592653f;
|
||||
} else {
|
||||
if (y < 0.0f) {
|
||||
y = -y;
|
||||
mul = -1.0f;
|
||||
} else {
|
||||
mul = 1.0f;
|
||||
}
|
||||
|
||||
add = 0.0f;
|
||||
}
|
||||
|
||||
float invDiv = 1.0f / (((x < y) ? y : x) * INV_ATAN2_DIM_MINUS_1);
|
||||
|
||||
int xi = (int) (x * invDiv);
|
||||
int yi = (int) (y * invDiv);
|
||||
|
||||
return (atan2[(yi * ATAN2_DIM) + xi] + add) * mul;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.FaweCache;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class CachedTextureUtil extends DelegateTextureUtil {
|
||||
private final TextureUtil parent;
|
||||
private transient Int2ObjectOpenHashMap<BlockTypes> colorBlockMap;
|
||||
private transient Int2ObjectOpenHashMap<Integer> colorBiomeMap;
|
||||
private transient Int2ObjectOpenHashMap<BlockTypes[]> colorLayerMap;
|
||||
|
||||
public CachedTextureUtil(TextureUtil parent) throws FileNotFoundException {
|
||||
super(parent);
|
||||
this.parent = parent;
|
||||
this.colorBlockMap = new Int2ObjectOpenHashMap<>();
|
||||
this.colorLayerMap = new Int2ObjectOpenHashMap<>();
|
||||
this.colorBiomeMap = new Int2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTypes[] getNearestLayer(int color) {
|
||||
BlockTypes[] closest = colorLayerMap.get(color);
|
||||
if (closest != null) {
|
||||
return closest;
|
||||
}
|
||||
closest = parent.getNearestLayer(color);
|
||||
if (closest != null) {
|
||||
colorLayerMap.put(color, closest.clone());
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeColor getNearestBiome(int color) {
|
||||
Integer value = colorBiomeMap.get(color);
|
||||
if (value != null) {
|
||||
return getBiome(value);
|
||||
}
|
||||
BiomeColor result = parent.getNearestBiome(color);
|
||||
if (result != null) {
|
||||
colorBiomeMap.put((int) color, (Integer) result.id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTypes getNearestBlock(int color) {
|
||||
BlockTypes value = colorBlockMap.get(color);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
BlockTypes result = parent.getNearestBlock(color);
|
||||
if (result != null) {
|
||||
colorBlockMap.put((int) color, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CleanTextureUtil extends TextureUtil {
|
||||
private final int min, max;
|
||||
|
||||
public CleanTextureUtil(TextureUtil parent, int minPercent, int maxPercent) throws FileNotFoundException {
|
||||
super(parent.getFolder());
|
||||
this.min = minPercent;
|
||||
this.max = maxPercent;
|
||||
int minIndex = ((parent.distances.length - 1) * minPercent) / 100;
|
||||
int maxIndex = ((parent.distances.length - 1) * maxPercent) / 100;
|
||||
long min = parent.distances[minIndex];
|
||||
long max = parent.distances[maxIndex];
|
||||
for (; minIndex > 0 && parent.distances[minIndex - 1] == min; minIndex--) ;
|
||||
for (; maxIndex < parent.distances.length - 1 && parent.distances[maxIndex + 1] == max; maxIndex++) ;
|
||||
int num = maxIndex - minIndex + 1;
|
||||
this.validMixBiomeColors = parent.validMixBiomeColors;
|
||||
this.validMixBiomeIds = parent.validMixBiomeIds;
|
||||
this.validBiomes = parent.validBiomes;
|
||||
this.blockColors = parent.blockColors;
|
||||
this.blockDistance = parent.blockDistance;
|
||||
this.distances = Arrays.copyOfRange(parent.blockDistance, minIndex, maxIndex + 1);
|
||||
this.validColors = new int[distances.length];
|
||||
this.validBlockIds = new int[distances.length];
|
||||
for (int i = 0, j = 0; i < parent.validBlockIds.length; i++) {
|
||||
int combined = parent.validBlockIds[i];
|
||||
long distance = parent.blockDistance[combined];
|
||||
if (distance >= min && distance <= max) {
|
||||
int color = parent.validColors[i];
|
||||
this.validColors[j] = color;
|
||||
this.validBlockIds[j++] = combined;
|
||||
}
|
||||
}
|
||||
this.calculateLayerArrays();
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return max;
|
||||
}
|
||||
}
|
146
worldedit-core/src/main/java/com/boydti/fawe/util/ColorUtil.java
Normal file
146
worldedit-core/src/main/java/com/boydti/fawe/util/ColorUtil.java
Normal file
@ -0,0 +1,146 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ColorUtil {
|
||||
private static final int PARSE_COMPONENT = 0; // percent, or clamped to [0,255] => [0,1]
|
||||
private static final int PARSE_PERCENT = 1; // clamped to [0,100]% => [0,1]
|
||||
private static final int PARSE_ANGLE = 2; // clamped to [0,360]
|
||||
private static final int PARSE_ALPHA = 3; // clamped to [0f,1f]
|
||||
|
||||
private static float parseComponent(String color, int off, int end, int type) {
|
||||
color = color.substring(off, end).trim();
|
||||
if (color.endsWith("%")) {
|
||||
if (type > PARSE_PERCENT) {
|
||||
throw new IllegalArgumentException("Invalid color specification");
|
||||
}
|
||||
type = PARSE_PERCENT;
|
||||
color = color.substring(0, color.length()-1).trim();
|
||||
} else if (type == PARSE_PERCENT) {
|
||||
throw new IllegalArgumentException("Invalid color specification");
|
||||
}
|
||||
float c = ((type == PARSE_COMPONENT)
|
||||
? Integer.parseInt(color)
|
||||
: Float.parseFloat(color));
|
||||
switch (type) {
|
||||
case PARSE_ALPHA:
|
||||
return (c < 0f) ? 0f : ((c > 1f) ? 1f : c);
|
||||
case PARSE_PERCENT:
|
||||
return (c <= 0f) ? 0f : ((c >= 100f) ? 1f : (c / 100f));
|
||||
case PARSE_COMPONENT:
|
||||
return (c <= 0f) ? 0f : ((c >= 255f) ? 1f : (c / 255f));
|
||||
case PARSE_ANGLE:
|
||||
return ((c < 0f)
|
||||
? ((c % 360f) + 360f)
|
||||
: ((c > 360f)
|
||||
? (c % 360f)
|
||||
: c));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid color specification");
|
||||
}
|
||||
|
||||
private static Color parseRGBColor(String color, int roff)
|
||||
{
|
||||
try {
|
||||
int rend = color.indexOf(',', roff);
|
||||
int gend = rend < 0 ? -1 : color.indexOf(',', rend+1);
|
||||
int bend = gend < 0 ? -1 : color.indexOf(gend+1);
|
||||
float r = parseComponent(color, roff, rend, PARSE_COMPONENT);
|
||||
float g = parseComponent(color, rend+1, gend, PARSE_COMPONENT);
|
||||
float b = parseComponent(color, gend+1, bend, PARSE_COMPONENT);
|
||||
return new Color(r, g, b);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
|
||||
throw new IllegalArgumentException("Invalid color specification");
|
||||
}
|
||||
|
||||
private static Color parseHSLColor(String color, int hoff)
|
||||
{
|
||||
try {
|
||||
int hend = color.indexOf(',', hoff);
|
||||
int send = hend < 0 ? -1 : color.indexOf(',', hend+1);
|
||||
int lend = send < 0 ? -1 : color.indexOf(send+1);
|
||||
float h = parseComponent(color, hoff, hend, PARSE_ANGLE);
|
||||
float s = parseComponent(color, hend+1, send, PARSE_PERCENT);
|
||||
float l = parseComponent(color, send+1, lend, PARSE_PERCENT);
|
||||
return Color.getHSBColor(h, s, l);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
|
||||
throw new IllegalArgumentException("Invalid color specification");
|
||||
}
|
||||
|
||||
public static Color parseColor(String colorString) {
|
||||
if (colorString == null) {
|
||||
throw new NullPointerException(
|
||||
"The color components or name must be specified");
|
||||
}
|
||||
if (colorString.isEmpty()) {
|
||||
throw new IllegalArgumentException("Invalid color specification");
|
||||
}
|
||||
|
||||
String color = colorString.toLowerCase(Locale.ROOT);
|
||||
|
||||
if (color.startsWith("#")) {
|
||||
color = color.substring(1);
|
||||
} else if (color.startsWith("0x")) {
|
||||
color = color.substring(2);
|
||||
} else if (color.startsWith("rgb")) {
|
||||
if (color.startsWith("(", 3)) {
|
||||
return parseRGBColor(color, 4);
|
||||
} else if (color.startsWith("a(", 3)) {
|
||||
return parseRGBColor(color, 5);
|
||||
}
|
||||
} else if (color.startsWith("hsl")) {
|
||||
if (color.startsWith("(", 3)) {
|
||||
return parseHSLColor(color, 4);
|
||||
} else if (color.startsWith("a(", 3)) {
|
||||
return parseHSLColor(color, 5);
|
||||
}
|
||||
} else {
|
||||
Color col = null;
|
||||
try {
|
||||
Field field = java.awt.Color.class.getField(color.toLowerCase());
|
||||
col = (Color) field.get(null);
|
||||
} catch (Throwable ignore) {}
|
||||
if (col != null) {
|
||||
return col;
|
||||
}
|
||||
}
|
||||
|
||||
int len = color.length();
|
||||
|
||||
try {
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
int a;
|
||||
|
||||
if (len == 3) {
|
||||
r = Integer.parseInt(color.substring(0, 1), 16);
|
||||
g = Integer.parseInt(color.substring(1, 2), 16);
|
||||
b = Integer.parseInt(color.substring(2, 3), 16);
|
||||
return new Color(r / 15f, g / 15f, b / 15f);
|
||||
} else if (len == 4) {
|
||||
r = Integer.parseInt(color.substring(0, 1), 16);
|
||||
g = Integer.parseInt(color.substring(1, 2), 16);
|
||||
b = Integer.parseInt(color.substring(2, 3), 16);
|
||||
return new Color(r / 15f, g / 15f, b / 15f);
|
||||
} else if (len == 6) {
|
||||
r = Integer.parseInt(color.substring(0, 2), 16);
|
||||
g = Integer.parseInt(color.substring(2, 4), 16);
|
||||
b = Integer.parseInt(color.substring(4, 6), 16);
|
||||
return new Color(r, g, b);
|
||||
} else if (len == 8) {
|
||||
r = Integer.parseInt(color.substring(0, 2), 16);
|
||||
g = Integer.parseInt(color.substring(2, 4), 16);
|
||||
b = Integer.parseInt(color.substring(4, 6), 16);
|
||||
return new Color(r, g, b);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {}
|
||||
|
||||
throw new IllegalArgumentException("Invalid color specification");
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
public class DelegateTextureUtil extends TextureUtil {
|
||||
private final TextureUtil parent;
|
||||
|
||||
public DelegateTextureUtil(TextureUtil parent) throws FileNotFoundException {
|
||||
super(parent.getFolder());
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTypes getNearestBlock(int color) {
|
||||
return parent.getNearestBlock(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockType getNearestBlock(BlockType block) {
|
||||
return parent.getNearestBlock(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockType getNextNearestBlock(int color) {
|
||||
return parent.getNextNearestBlock(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTypes[] getNearestLayer(int color) {
|
||||
return parent.getNearestLayer(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockType getLighterBlock(BlockType block) {
|
||||
return parent.getLighterBlock(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockType getDarkerBlock(BlockType block) {
|
||||
return parent.getDarkerBlock(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(BlockType block) {
|
||||
return parent.getColor(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsBlockCloserThanBiome(int[] blockAndBiomeIdOutput, int color, int biomePriority) {
|
||||
return parent.getIsBlockCloserThanBiome(blockAndBiomeIdOutput, color, biomePriority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBiomeMix(int[] biomeIdsOutput, int color) {
|
||||
return parent.getBiomeMix(biomeIdsOutput, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeColor getBiome(int biome) {
|
||||
return parent.getBiome(biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeColor getNearestBiome(int color) {
|
||||
return parent.getNearestBiome(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFolder() {
|
||||
return parent.getFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int combineTransparency(int top, int bottom) {
|
||||
return parent.combineTransparency(top, bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculateLayerArrays() {
|
||||
parent.calculateLayerArrays();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadModTextures() throws IOException {
|
||||
parent.loadModTextures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int multiplyColor(int c1, int c2) {
|
||||
return parent.multiplyColor(c1, c2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockType getNearestBlock(BlockType block, boolean darker) {
|
||||
return parent.getNearestBlock(block, darker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockType getNearestBlock(int color, boolean darker) {
|
||||
return parent.getNearestBlock(color, darker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAlpha(int color) {
|
||||
return parent.hasAlpha(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long colorDistance(int c1, int c2) {
|
||||
return parent.colorDistance(c1, c2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long colorDistance(int red1, int green1, int blue1, int c2) {
|
||||
return parent.colorDistance(red1, green1, blue1, c2);
|
||||
}
|
||||
|
||||
public static int hueDistance(int red1, int green1, int blue1, int red2, int green2, int blue2) {
|
||||
return TextureUtil.hueDistance(red1, green1, blue1, red2, green2, blue2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDistance(BufferedImage image, int c1) {
|
||||
return parent.getDistance(image, c1);
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.command.AnvilCommands;
|
||||
import com.boydti.fawe.command.CFICommands;
|
||||
import com.sk89q.minecraft.util.commands.Command;
|
||||
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
||||
import com.sk89q.minecraft.util.commands.NestedCommand;
|
||||
import com.sk89q.worldedit.command.BiomeCommands;
|
||||
import com.sk89q.worldedit.command.BrushCommands;
|
||||
import com.sk89q.worldedit.command.BrushOptionsCommands;
|
||||
import com.sk89q.worldedit.command.ChunkCommands;
|
||||
import com.sk89q.worldedit.command.ClipboardCommands;
|
||||
import com.sk89q.worldedit.command.GenerationCommands;
|
||||
import com.sk89q.worldedit.command.HistoryCommands;
|
||||
import com.sk89q.worldedit.command.MaskCommands;
|
||||
import com.sk89q.worldedit.command.NavigationCommands;
|
||||
import com.sk89q.worldedit.command.OptionsCommands;
|
||||
import com.sk89q.worldedit.command.PatternCommands;
|
||||
import com.sk89q.worldedit.command.RegionCommands;
|
||||
import com.sk89q.worldedit.command.SchematicCommands;
|
||||
import com.sk89q.worldedit.command.ScriptingCommands;
|
||||
import com.sk89q.worldedit.command.SelectionCommands;
|
||||
import com.sk89q.worldedit.command.SnapshotCommands;
|
||||
import com.sk89q.worldedit.command.SnapshotUtilCommands;
|
||||
import com.sk89q.worldedit.command.SuperPickaxeCommands;
|
||||
import com.sk89q.worldedit.command.ToolCommands;
|
||||
import com.sk89q.worldedit.command.TransformCommands;
|
||||
import com.sk89q.worldedit.command.UtilityCommands;
|
||||
import com.sk89q.worldedit.command.WorldEditCommands;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public final class DocumentationPrinter {
|
||||
|
||||
private DocumentationPrinter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates documentation.
|
||||
*
|
||||
* @param args arguments
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
writePermissionsWikiTable();
|
||||
}
|
||||
|
||||
private static void writePermissionsWikiTable()
|
||||
throws IOException {
|
||||
try (FileOutputStream fos = new FileOutputStream("wiki_permissions.md")) {
|
||||
PrintStream stream = new PrintStream(fos);
|
||||
|
||||
stream.print("## Overview\n");
|
||||
stream.print("This page is generated from the source. " +
|
||||
"Click one of the edit buttons below to modify a command class. " +
|
||||
"You will need to find the parts which correspond to the documentation. " +
|
||||
"Command documentation will be consistent with what is available ingame");
|
||||
stream.println();
|
||||
stream.println();
|
||||
stream.print("To view this information ingame use `//help [category|command]`\n");
|
||||
stream.print("## Command Syntax \n");
|
||||
stream.print(" - `<arg>` - A required parameter \n");
|
||||
stream.print(" - `[arg]` - An optional parameter \n");
|
||||
stream.print(" - `<arg1|arg2>` - Multiple parameters options \n");
|
||||
stream.print(" - `<arg=value>` - Default or suggested value \n");
|
||||
stream.print(" - `-a` - A command flag e.g. `//<command> -a [flag-value]`");
|
||||
stream.println();
|
||||
stream.print("## See also\n");
|
||||
stream.print(" - [Masks](https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit---FAWE-mask-list)\n");
|
||||
stream.print(" - [Patterns](https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns)\n");
|
||||
stream.print(" - [Transforms](https://github.com/boy0001/FastAsyncWorldedit/wiki/Transforms)\n");
|
||||
stream.println();
|
||||
stream.print("## Content");
|
||||
stream.println();
|
||||
stream.print("Click on a category to go to the list of commands, or `More Info` for detailed descriptions ");
|
||||
stream.println();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
writePermissionsWikiTable(stream, builder, "/we ", WorldEditCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", UtilityCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", RegionCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", SelectionCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", HistoryCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/schematic ", SchematicCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", ClipboardCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", GenerationCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", BiomeCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/anvil ", AnvilCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/sp ", SuperPickaxeCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", NavigationCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/snapshot", SnapshotCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", SnapshotUtilCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", ScriptingCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", ChunkCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", OptionsCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/", BrushOptionsCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/tool ", ToolCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "/brush ", BrushCommands.class);
|
||||
writePermissionsWikiTable(stream, builder, "", MaskCommands.class, "/Masks");
|
||||
writePermissionsWikiTable(stream, builder, "", PatternCommands.class, "/Patterns");
|
||||
writePermissionsWikiTable(stream, builder, "", TransformCommands.class, "/Transforms");
|
||||
writePermissionsWikiTable(stream, builder, "/cfi ", CFICommands.class, "Create From Image");
|
||||
stream.println();
|
||||
stream.print("#### Uncategorized\n");
|
||||
stream.append("| Aliases | Permission | flags | Usage |\n");
|
||||
stream.append("| --- | --- | --- | --- |\n");
|
||||
stream.append("| //cancel | fawe.cancel | | Cancels your current operations |\n");
|
||||
stream.append("| /plot replaceall | plots.replaceall | | Replace all blocks in the plot world |\n");
|
||||
// stream.append("| /plot createfromimage | plots.createfromimage | | Starts world creation from a heightmap image: [More Info](https://github.com/boy0001/FastAsyncWorldedit/wiki/CreateFromImage) |\n");
|
||||
stream.print("\n---\n");
|
||||
|
||||
stream.print(builder);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writePermissionsWikiTable(PrintStream stream, StringBuilder content, String prefix, Class<?> cls) {
|
||||
writePermissionsWikiTable(stream, content, prefix, cls, getName(cls));
|
||||
}
|
||||
|
||||
public static String getName(Class cls) {
|
||||
return cls.getSimpleName().replaceAll("(\\p{Ll})(\\p{Lu})", "$1 $2");
|
||||
}
|
||||
|
||||
private static void writePermissionsWikiTable(PrintStream stream, StringBuilder content, String prefix, Class<?> cls, String name) {
|
||||
stream.print(" - [`" + name + "`](#" + name.replaceAll(" ", "-").replaceAll("/", "").toLowerCase() + "-edittop) ");
|
||||
Command cmd = cls.getAnnotation(Command.class);
|
||||
if (cmd != null) {
|
||||
stream.print(" (" + cmd.desc() + ")");
|
||||
}
|
||||
stream.println();
|
||||
writePermissionsWikiTable(content, prefix, cls, name, true);
|
||||
}
|
||||
|
||||
private static void writePermissionsWikiTable(StringBuilder stream, String prefix, Class<?> cls, String name, boolean title) {
|
||||
// //setbiome || worldedit.biome.set || //setbiome || p || Sets the biome of the player's current block or region.
|
||||
if (title) {
|
||||
String path = "https://github.com/boy0001/FastAsyncWorldedit/edit/master/core/src/main/java/" + cls.getName().replaceAll("\\.", "/") + ".java";
|
||||
stream.append("### **" + name + "** `[`[`edit`](" + path + ")`|`[`top`](#overview)`]`");
|
||||
stream.append("\n");
|
||||
Command cmd = cls.getAnnotation(Command.class);
|
||||
if (cmd != null) {
|
||||
if (!cmd.desc().isEmpty()) {
|
||||
stream.append("> (" + (cmd.desc()) + ") \n");
|
||||
}
|
||||
if (!cmd.help().isEmpty()) {
|
||||
stream.append("" + (cmd.help()) + " \n");
|
||||
}
|
||||
}
|
||||
stream.append("\n");
|
||||
stream.append("---");
|
||||
stream.append("\n");
|
||||
stream.append("\n");
|
||||
}
|
||||
for (Method method : cls.getMethods()) {
|
||||
if (!method.isAnnotationPresent(Command.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Command cmd = method.getAnnotation(Command.class);
|
||||
String[] aliases = cmd.aliases();
|
||||
String usage = prefix + aliases[0] + " " + cmd.usage();
|
||||
if (!cmd.flags().isEmpty()) {
|
||||
for (char c : cmd.flags().toCharArray()) {
|
||||
usage += " [-" + c + "]";
|
||||
}
|
||||
}
|
||||
// stream.append("#### [`" + usage + "`](" + "https://github.com/boy0001/FastAsyncWorldedit/wiki/" + aliases[0] + ")\n");
|
||||
stream.append("#### `" + usage + "`\n");
|
||||
if (method.isAnnotationPresent(CommandPermissions.class)) {
|
||||
CommandPermissions perms = method.getAnnotation(CommandPermissions.class);
|
||||
stream.append("**Perm**: `" + StringMan.join(perms.value(), "`, `") + "` \n");
|
||||
}
|
||||
String help = cmd.help() == null || cmd.help().isEmpty() ? cmd.desc() : cmd.help();
|
||||
stream.append("**Desc**: " + help.trim().replaceAll("\n", "<br />") + " \n");
|
||||
|
||||
if (method.isAnnotationPresent(NestedCommand.class)) {
|
||||
NestedCommand nested =
|
||||
method.getAnnotation(NestedCommand.class);
|
||||
|
||||
Class<?>[] nestedClasses = nested.value();
|
||||
for (Class clazz : nestedClasses) {
|
||||
writePermissionsWikiTable(stream, prefix + cmd.aliases()[0] + " ", clazz, getName(clazz), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
stream.append("\n");
|
||||
if (title) stream.append("---");
|
||||
stream.append("\n");
|
||||
stream.append("\n");
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
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.*;
|
||||
import com.boydti.fawe.object.changeset.DiskStorageHistory;
|
||||
import com.boydti.fawe.object.changeset.FaweChangeSet;
|
||||
import com.boydti.fawe.object.changeset.MemoryOptimizedHistory;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.event.extent.EditSessionEvent;
|
||||
import com.sk89q.worldedit.extent.inventory.BlockBag;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.util.eventbus.EventBus;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class EditSessionBuilder {
|
||||
private World world;
|
||||
private String worldName;
|
||||
private FaweQueue queue;
|
||||
private FawePlayer player;
|
||||
private FaweLimit limit;
|
||||
private FaweChangeSet changeSet;
|
||||
private Region[] allowedRegions;
|
||||
private Boolean autoQueue;
|
||||
private Boolean fastmode;
|
||||
private Boolean checkMemory;
|
||||
private Boolean combineStages;
|
||||
private EventBus eventBus;
|
||||
private BlockBag blockBag;
|
||||
private EditSessionEvent event;
|
||||
|
||||
/**
|
||||
* An EditSession builder<br>
|
||||
* - Unset values will revert to their default<br>
|
||||
* <br>
|
||||
* player: The player doing the edit (defaults to to null)<br>
|
||||
* limit: Block/Entity/Action limit (defaults to unlimited)<br>
|
||||
* changeSet: Stores changes (defaults to config.yml value)<br>
|
||||
* allowedRegions: Allowed editable regions (defaults to player's allowed regions, or everywhere)<br>
|
||||
* autoQueue: Changes can occur before flushQueue() (defaults true)<br>
|
||||
* fastmode: bypasses history (defaults to player fastmode or config.yml console history)<br>
|
||||
* checkMemory: If low memory checks are enabled (defaults to player's fastmode or true)<br>
|
||||
* combineStages: If history is combined with dispatching
|
||||
*
|
||||
* @param world A world must be provided for all EditSession(s)
|
||||
*/
|
||||
public EditSessionBuilder(@Nonnull World world) {
|
||||
checkNotNull(world);
|
||||
this.world = world;
|
||||
this.worldName = Fawe.imp().getWorldName(world);
|
||||
}
|
||||
|
||||
public EditSessionBuilder(@Nonnull String worldName) {
|
||||
checkNotNull(worldName);
|
||||
this.worldName = worldName;
|
||||
this.world = FaweAPI.getWorld(worldName);
|
||||
}
|
||||
|
||||
public EditSessionBuilder player(@Nullable FawePlayer player) {
|
||||
this.player = player;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder limit(@Nullable FaweLimit limit) {
|
||||
this.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder limitUnlimited() {
|
||||
return limit(FaweLimit.MAX.copy());
|
||||
}
|
||||
|
||||
public EditSessionBuilder limitUnprocessed(@Nonnull FawePlayer fp) {
|
||||
checkNotNull(fp);
|
||||
limitUnlimited();
|
||||
FaweLimit tmp = fp.getLimit();
|
||||
limit.INVENTORY_MODE = tmp.INVENTORY_MODE;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder changeSet(@Nullable FaweChangeSet changeSet) {
|
||||
this.changeSet = changeSet;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder changeSetNull() {
|
||||
return changeSet(world == null ? new NullChangeSet(worldName) : new NullChangeSet(world));
|
||||
}
|
||||
|
||||
public EditSessionBuilder world(@Nonnull World world) {
|
||||
checkNotNull(world);
|
||||
this.world = world;
|
||||
this.worldName = Fawe.imp().getWorldName(world);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param disk If it should be stored on disk
|
||||
* @param uuid The uuid to store it under (if on disk)
|
||||
* @param compression Compression level (0-9)
|
||||
* @return
|
||||
*/
|
||||
public EditSessionBuilder changeSet(boolean disk, @Nullable UUID uuid, int compression) {
|
||||
if (world == null) {
|
||||
if (disk) {
|
||||
if (Settings.IMP.HISTORY.USE_DATABASE) {
|
||||
this.changeSet = new RollbackOptimizedHistory(worldName, uuid);
|
||||
} else {
|
||||
this.changeSet = new DiskStorageHistory(worldName, uuid);
|
||||
}
|
||||
} else {
|
||||
this.changeSet = new MemoryOptimizedHistory(worldName);
|
||||
}
|
||||
} else if (disk) {
|
||||
if (Settings.IMP.HISTORY.USE_DATABASE) {
|
||||
this.changeSet = new RollbackOptimizedHistory(world, uuid);
|
||||
} else {
|
||||
this.changeSet = new DiskStorageHistory(world, uuid);
|
||||
}
|
||||
} else {
|
||||
this.changeSet = new MemoryOptimizedHistory(world);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder allowedRegions(@Nullable Region[] allowedRegions) {
|
||||
this.allowedRegions = allowedRegions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public EditSessionBuilder allowedRegions(@Nullable RegionWrapper[] allowedRegions) {
|
||||
this.allowedRegions = allowedRegions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder allowedRegions(@Nullable RegionWrapper allowedRegion) {
|
||||
this.allowedRegions = allowedRegion == null ? null : allowedRegion.toArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder allowedRegionsEverywhere() {
|
||||
return allowedRegions(new Region[]{RegionWrapper.GLOBAL()});
|
||||
}
|
||||
|
||||
public EditSessionBuilder autoQueue(@Nullable Boolean autoQueue) {
|
||||
this.autoQueue = autoQueue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder fastmode(@Nullable Boolean fastmode) {
|
||||
this.fastmode = fastmode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder checkMemory(@Nullable Boolean checkMemory) {
|
||||
this.checkMemory = checkMemory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder combineStages(@Nullable Boolean combineStages) {
|
||||
this.combineStages = combineStages;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder queue(@Nullable FaweQueue queue) {
|
||||
this.queue = queue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder blockBag(@Nullable BlockBag blockBag) {
|
||||
this.blockBag = blockBag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder eventBus(@Nullable EventBus eventBus) {
|
||||
this.eventBus = eventBus;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSessionBuilder event(@Nullable EditSessionEvent event) {
|
||||
this.event = event;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EditSession build() {
|
||||
return new EditSession(worldName, world, queue, player, limit, changeSet, allowedRegions, autoQueue, fastmode, checkMemory, combineStages, blockBag, eventBus, event);
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ExtentTraverser<T extends Extent> {
|
||||
private T root;
|
||||
private ExtentTraverser<T> parent;
|
||||
|
||||
public ExtentTraverser(T root) {
|
||||
this(root, null);
|
||||
}
|
||||
|
||||
public ExtentTraverser(T root, ExtentTraverser<T> parent) {
|
||||
this.root = root;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public boolean exists() {
|
||||
return root != null;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public boolean setNext(T next) {
|
||||
try {
|
||||
Field field = AbstractDelegateExtent.class.getDeclaredField("extent");
|
||||
ReflectionUtils.setFailsafeFieldValue(field, root, next);
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ExtentTraverser<T> last() {
|
||||
ExtentTraverser<T> last = this;
|
||||
ExtentTraverser<T> traverser = this;
|
||||
while (traverser != null && traverser.get() instanceof AbstractDelegateExtent) {
|
||||
last = traverser;
|
||||
traverser = traverser.next();
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
public boolean insert(T extent) {
|
||||
try {
|
||||
Field field = AbstractDelegateExtent.class.getDeclaredField("extent");
|
||||
field.setAccessible(true);
|
||||
field.set(extent, field.get(root));
|
||||
field.set(root, extent);
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public <U> U findAndGet(Class<U> clazz) {
|
||||
ExtentTraverser<Extent> traverser = find((Class) clazz);
|
||||
return (traverser != null) ? (U) traverser.get() : null;
|
||||
}
|
||||
|
||||
public <U extends Extent> ExtentTraverser<U> find(Class<U> clazz) {
|
||||
try {
|
||||
ExtentTraverser<T> value = this;
|
||||
while (value != null) {
|
||||
if (clazz.isAssignableFrom(value.root.getClass())) {
|
||||
return (ExtentTraverser<U>) value;
|
||||
}
|
||||
value = value.next();
|
||||
}
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public <U extends Extent> ExtentTraverser<U> find(Object object) {
|
||||
try {
|
||||
ExtentTraverser<T> value = this;
|
||||
while (value != null) {
|
||||
if (value.root == object) {
|
||||
return (ExtentTraverser<U>) value;
|
||||
}
|
||||
value = value.next();
|
||||
}
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ExtentTraverser<T> previous() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public ExtentTraverser<T> next() {
|
||||
try {
|
||||
if (root instanceof AbstractDelegateExtent) {
|
||||
Field field = AbstractDelegateExtent.class.getDeclaredField("extent");
|
||||
field.setAccessible(true);
|
||||
T value = (T) field.get(root);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return new ExtentTraverser<>(value, this);
|
||||
}
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
public class FaweTimer implements Runnable {
|
||||
|
||||
private final double[] history = new double[]{20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d, 20d};
|
||||
private int historyIndex = 0;
|
||||
private long lastPoll = System.currentTimeMillis();
|
||||
private long tickStart = System.currentTimeMillis();
|
||||
private final long tickInterval = 5;
|
||||
private final double millisPer20Interval = tickInterval * 50 * 20;
|
||||
private long tick = 0;
|
||||
private long tickMod = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tickStart = System.currentTimeMillis();
|
||||
tick++;
|
||||
if (++tickMod == tickInterval) {
|
||||
tickMod = 0;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
long timeSpent = (tickStart - lastPoll);
|
||||
if (timeSpent == 0) {
|
||||
timeSpent = 1;
|
||||
}
|
||||
double tps = millisPer20Interval / timeSpent;
|
||||
history[historyIndex++] = tps;
|
||||
if (historyIndex >= history.length) {
|
||||
historyIndex = 0;
|
||||
}
|
||||
lastPoll = tickStart;
|
||||
}
|
||||
|
||||
private long lastGetTPSTick = 0;
|
||||
private double lastGetTPSValue = 20d;
|
||||
|
||||
public double getTPS() {
|
||||
if (tick < lastGetTPSTick + tickInterval) {
|
||||
return lastGetTPSValue;
|
||||
}
|
||||
double total = 0;
|
||||
for (double tps : history) {
|
||||
total += tps;
|
||||
}
|
||||
lastGetTPSValue = total / history.length;
|
||||
lastGetTPSTick = tick;
|
||||
return lastGetTPSValue;
|
||||
}
|
||||
|
||||
public long getTick() {
|
||||
return tick;
|
||||
}
|
||||
|
||||
public long getTickMillis() {
|
||||
return System.currentTimeMillis() - tickStart;
|
||||
}
|
||||
|
||||
public long getTickStart() {
|
||||
return tickStart;
|
||||
}
|
||||
|
||||
private long skip = 0;
|
||||
private long skipTick = 0;
|
||||
|
||||
public boolean isAbove(double tps) {
|
||||
if (tps <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (skip > 0) {
|
||||
if (skipTick != tick) {
|
||||
skip--;
|
||||
skipTick = tick;
|
||||
return true; // Run once per tick
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (getTickMillis() > 100 || getTPS() < tps) {
|
||||
skip = 10;
|
||||
skipTick = tick;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runIfAbove(Runnable run, double tps) {
|
||||
if (isAbove(tps)) {
|
||||
run.run();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.FaweCache;
|
||||
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;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Set;
|
||||
|
||||
public class FilteredTextureUtil extends TextureUtil {
|
||||
private final Set<BlockType> blocks;
|
||||
|
||||
public FilteredTextureUtil(TextureUtil parent, Set<BlockType> blocks) throws FileNotFoundException {
|
||||
super(parent.getFolder());
|
||||
this.blocks = blocks;
|
||||
this.validMixBiomeColors = parent.validMixBiomeColors;
|
||||
this.validMixBiomeIds = parent.validMixBiomeIds;
|
||||
this.validBiomes = parent.validBiomes;
|
||||
this.blockColors = parent.blockColors;
|
||||
this.blockDistance = parent.blockDistance;
|
||||
this.distances = parent.distances;
|
||||
this.validColors = new int[distances.length];
|
||||
this.validBlockIds = new int[distances.length];
|
||||
int num = 0;
|
||||
for (int i = 0; i < parent.validBlockIds.length; i++) {
|
||||
BlockTypes block = BlockTypes.get(parent.validBlockIds[i]);
|
||||
if (blocks.contains(block)) num++;
|
||||
}
|
||||
this.validBlockIds = new int[num];
|
||||
this.validColors = new int[num];
|
||||
num = 0;
|
||||
for (int i = 0; i < parent.validBlockIds.length; i++) {
|
||||
BlockTypes block = BlockTypes.get(parent.validBlockIds[i]);
|
||||
if (blocks.contains(block)) {
|
||||
validBlockIds[num] = parent.validBlockIds[i];
|
||||
validColors[num++] = parent.validColors[i];
|
||||
}
|
||||
}
|
||||
this.calculateLayerArrays();
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class HastebinUtility {
|
||||
|
||||
public static final String BIN_URL = "https://hastebin.com/documents", USER_AGENT = "Mozilla/5.0";
|
||||
public static final Pattern PATTERN = Pattern.compile("\\{\"key\":\"([\\S\\s]*)\"\\}");
|
||||
|
||||
public static String upload(final String string) throws IOException {
|
||||
final URL url = new URL(BIN_URL);
|
||||
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
|
||||
outputStream.write(string.getBytes());
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
StringBuilder response;
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||
response = new StringBuilder();
|
||||
|
||||
String inputLine;
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
}
|
||||
|
||||
Matcher matcher = PATTERN.matcher(response.toString());
|
||||
if (matcher.matches()) {
|
||||
return "https://hastebin.com/" + matcher.group(1);
|
||||
} else {
|
||||
throw new RuntimeException("Couldn't read response!");
|
||||
}
|
||||
}
|
||||
|
||||
public static String upload(final File file) throws IOException {
|
||||
final StringBuilder content = new StringBuilder();
|
||||
List<String> lines = new ArrayList<>();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
String line;
|
||||
int i = 0;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
for (int i = Math.max(0, lines.size() - 1000); i < lines.size(); i++) {
|
||||
content.append(lines.get(i)).append("\n");
|
||||
}
|
||||
return upload(content.toString());
|
||||
}
|
||||
|
||||
public static String debugPaste() throws IOException {
|
||||
String settingsYML = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "config.yml"));
|
||||
String messagesYML = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "message.yml"));
|
||||
String commandsYML = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "commands.yml"));
|
||||
String latestLOG;
|
||||
try {
|
||||
latestLOG = HastebinUtility.upload(new File(Fawe.imp().getDirectory(), "../../logs/latest.log"));
|
||||
} catch (IOException ignored) {
|
||||
latestLOG = "too big :(";
|
||||
}
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(
|
||||
"# Welcome to this paste\n# It is meant to provide us at IntellectualSites with better information about your "
|
||||
+ "problem\n\n# We will start with some informational files\n");
|
||||
b.append("links.config_yml: ").append(settingsYML).append('\n');
|
||||
b.append("links.messages_yml: ").append(messagesYML).append('\n');
|
||||
b.append("links.commands_yml: ").append(commandsYML).append('\n');
|
||||
b.append("links.latest_log: ").append(latestLOG).append('\n');
|
||||
b.append("\n# Server Information\n");
|
||||
b.append("server.platform: ").append(Fawe.imp().getPlatform()).append('\n');
|
||||
b.append(Fawe.imp().getDebugInfo()).append('\n');
|
||||
b.append("\n\n# YAY! Now, let's see what we can find in your JVM\n");
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
b.append("memory.free: ").append(runtime.freeMemory()).append('\n');
|
||||
b.append("memory.max: ").append(runtime.maxMemory()).append('\n');
|
||||
b.append("java.specification.version: '").append(System.getProperty("java.specification.version")).append("'\n");
|
||||
b.append("java.vendor: '").append(System.getProperty("java.vendor")).append("'\n");
|
||||
b.append("java.version: '").append(System.getProperty("java.version")).append("'\n");
|
||||
b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n");
|
||||
b.append("os.name: '").append(System.getProperty("os.name")).append("'\n");
|
||||
b.append("os.version: '").append(System.getProperty("os.version")).append("'\n\n");
|
||||
b.append("# Okay :D Great. You are now ready to create your bug report!");
|
||||
b.append("\n# You can do so at https://github.com/boy0001/FastAsyncWorldedit/issues");
|
||||
|
||||
String link = HastebinUtility.upload(b.toString());
|
||||
return link;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
|
||||
public final class IOUtil {
|
||||
public InputStream toInputStream(URI uri) throws IOException {
|
||||
String scheme = uri.getScheme();
|
||||
switch (scheme.toLowerCase()) {
|
||||
case "file":
|
||||
return new FileInputStream(uri.getPath());
|
||||
case "http":
|
||||
case "https":
|
||||
return uri.toURL().openStream();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final int readInt(InputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
int ch2 = in.read();
|
||||
int ch3 = in.read();
|
||||
int ch4 = in.read();
|
||||
if ((ch1 | ch2 | ch3 | ch4) < 0)
|
||||
throw new EOFException();
|
||||
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
|
||||
}
|
||||
|
||||
public static final void writeInt(OutputStream out, int v) throws IOException {
|
||||
out.write((v >>> 24) & 0xFF);
|
||||
out.write((v >>> 16) & 0xFF);
|
||||
out.write((v >>> 8) & 0xFF);
|
||||
out.write((v >>> 0) & 0xFF);
|
||||
}
|
||||
|
||||
public static void writeVarInt(OutputStream out, int i) throws IOException {
|
||||
while((i & -128) != 0) {
|
||||
out.write(i & 127 | 128);
|
||||
i >>>= 7;
|
||||
}
|
||||
out.write(i);
|
||||
}
|
||||
|
||||
public static int readVarInt(InputStream in) throws IOException {
|
||||
int i = 0;
|
||||
int offset = 0;
|
||||
int b;
|
||||
while ((b = in.read()) > 127) {
|
||||
i |= (b - 128) << offset;
|
||||
offset += 7;
|
||||
}
|
||||
i |= b << offset;
|
||||
return i;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.object.io.FastByteArrayOutputStream;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Base64;
|
||||
|
||||
public class ImgurUtility {
|
||||
public static final String CLIENT_ID = "50e34b65351eb07";
|
||||
|
||||
public static URL uploadImage(File file) throws IOException {
|
||||
return uploadImage(new FileInputStream(file));
|
||||
}
|
||||
|
||||
public static URL uploadImage(InputStream is) throws IOException {
|
||||
is = new BufferedInputStream(is);
|
||||
FastByteArrayOutputStream baos = new FastByteArrayOutputStream(Short.MAX_VALUE);
|
||||
int d;
|
||||
while ((d = is.read()) != -1) {
|
||||
baos.write(d);
|
||||
}
|
||||
baos.flush();
|
||||
return uploadImage(baos.toByteArray());
|
||||
}
|
||||
|
||||
public static URL uploadImage(byte[] image) throws IOException {
|
||||
String json = getImgurContent(CLIENT_ID, image);
|
||||
Gson gson = new Gson();
|
||||
JsonObject obj = gson.fromJson(json, JsonObject.class);
|
||||
JsonObject data = obj.get("data").getAsJsonObject();
|
||||
String link = data.get("link").getAsString();
|
||||
return new URL(link);
|
||||
}
|
||||
|
||||
public static String getImgurContent(String clientID, byte[] image) throws IOException {
|
||||
String imageString = Base64.getEncoder().encodeToString(image);
|
||||
URL url = new URL("https://api.imgur.com/3/image");
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
String data = URLEncoder.encode("image", "UTF-8") + "=" + URLEncoder.encode(imageString, "UTF-8");
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Authorization", "Client-ID " + clientID);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
conn.connect();
|
||||
StringBuilder stb = new StringBuilder();
|
||||
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
|
||||
wr.write(data);
|
||||
wr.flush();
|
||||
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line;
|
||||
while ((line = rd.readLine()) != null) {
|
||||
stb.append(line).append("\n");
|
||||
}
|
||||
wr.close();
|
||||
rd.close();
|
||||
return stb.toString();
|
||||
}
|
||||
}
|
73
worldedit-core/src/main/java/com/boydti/fawe/util/Jars.java
Normal file
73
worldedit-core/src/main/java/com/boydti/fawe/util/Jars.java
Normal file
@ -0,0 +1,73 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
public enum Jars {
|
||||
WE_B_6_1_7_2("https://addons.cursecdn.com/files/2431/372/worldedit-bukkit-6.1.7.2.jar",
|
||||
"CRVJCWGJJ6UK40CTGHXQVK2/3C9BBTOS25FWI0ZHD4S=", 1726340),
|
||||
|
||||
VS_B_5_171_0("https://media.forgecdn.net/files/2488/589/VoxelSniper-5.172.0-SNAPSHOT.jar",
|
||||
"4CTEDDEKCAN/M6R0DHS925++HBSJ/TUYAAKAR4CUWC4=", 3615020),
|
||||
|
||||
MM_v1_4_0("https://github.com/InventivetalentDev/MapManager/releases/download/1.4.0-SNAPSHOT/MapManager_v1.4.0-SNAPSHOT.jar",
|
||||
"AEO5SKBUGN4YJRS8XGGNLBM2QRZPTI1KF0/1W1URTGA=", 163279),
|
||||
|
||||
PL_v3_6_0("https://github.com/InventivetalentDev/PacketListenerAPI/releases/download/3.6.0-SNAPSHOT/PacketListenerAPI_v3.6.0-SNAPSHOT.jar",
|
||||
"OYBE75VIU+NNWHRVREBLDARWA+/TBDQZ1RC562QULBA=", 166508),
|
||||
|
||||
;
|
||||
|
||||
public final String url;
|
||||
public final int filesize;
|
||||
public final String digest;
|
||||
|
||||
/**
|
||||
* @param url
|
||||
* Where this jar can be found and downloaded
|
||||
* @param digest
|
||||
* The SHA-256 hexadecimal digest
|
||||
* @param filesize
|
||||
* Size of this jar in bytes
|
||||
*/
|
||||
Jars(String url, String digest, int filesize) {
|
||||
this.url = url;
|
||||
this.digest = digest.toUpperCase();
|
||||
this.filesize = filesize;
|
||||
}
|
||||
|
||||
/** download a jar, verify hash, return byte[] containing the jar */
|
||||
public byte[] download() throws IOException {
|
||||
byte[] jarBytes = new byte[this.filesize];
|
||||
URL url = new URL(this.url);
|
||||
try (DataInputStream dis = new DataInputStream(url.openConnection().getInputStream());) {
|
||||
dis.readFully(jarBytes);
|
||||
if (dis.read() != -1) { // assert that we've read everything
|
||||
throw new IllegalStateException("downloaded jar is longer than expected");
|
||||
}
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] jarDigestBytes = md.digest(jarBytes);
|
||||
|
||||
String jarDigest = Base64.getEncoder().encodeToString(jarDigestBytes).toUpperCase();
|
||||
|
||||
if (this.digest.equals(jarDigest)) {
|
||||
Fawe.debug("++++ HASH CHECK ++++");
|
||||
Fawe.debug(this.url);
|
||||
Fawe.debug(this.digest);
|
||||
return jarBytes;
|
||||
} else {
|
||||
|
||||
Fawe.debug(jarDigest + " | " + url);
|
||||
throw new IllegalStateException("downloaded jar does not match the hash");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Shouldn't ever happen, Minecraft won't even run on such a JRE
|
||||
throw new IllegalStateException("Your JRE does not support SHA-256");
|
||||
}
|
||||
}
|
||||
}
|
1175
worldedit-core/src/main/java/com/boydti/fawe/util/MainUtil.java
Normal file
1175
worldedit-core/src/main/java/com/boydti/fawe/util/MainUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,54 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.object.mask.ResettableMask;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
public class MaskTraverser {
|
||||
private final Mask mask;
|
||||
|
||||
public MaskTraverser(Mask start) {
|
||||
this.mask = start;
|
||||
}
|
||||
|
||||
public void reset(Extent newExtent) {
|
||||
reset(mask, newExtent);
|
||||
}
|
||||
|
||||
private void reset(Mask mask, Extent newExtent) {
|
||||
if (mask == null) {
|
||||
return;
|
||||
}
|
||||
if (mask instanceof ResettableMask) {
|
||||
((ResettableMask) mask).reset();
|
||||
}
|
||||
Class<?> current = mask.getClass();
|
||||
while (current.getSuperclass() != null) {
|
||||
try {
|
||||
Field field = current.getDeclaredField("extent");
|
||||
field.setAccessible(true);
|
||||
field.set(mask, newExtent);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ignore) {
|
||||
}
|
||||
try {
|
||||
Field field = current.getDeclaredField("mask");
|
||||
field.setAccessible(true);
|
||||
Mask next = (Mask) field.get(mask);
|
||||
reset(next, newExtent);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ignore) {
|
||||
}
|
||||
try {
|
||||
Field field = current.getDeclaredField("masks");
|
||||
field.setAccessible(true);
|
||||
Collection<Mask> masks = (Collection<Mask>) field.get(mask);
|
||||
for (Mask next : masks) {
|
||||
reset(next, newExtent);
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException ignore) {
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
}
|
417
worldedit-core/src/main/java/com/boydti/fawe/util/MathMan.java
Normal file
417
worldedit-core/src/main/java/com/boydti/fawe/util/MathMan.java
Normal file
@ -0,0 +1,417 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
public class MathMan {
|
||||
|
||||
/**
|
||||
* Optimized for i elem 0,65536 (characters)
|
||||
* @param i
|
||||
* @return square root
|
||||
*/
|
||||
public static int usqrt(int i) {
|
||||
if (i < 65536) {
|
||||
return CachedMathMan.usqrt(i);
|
||||
}
|
||||
return (int) Math.round(Math.sqrt(i));
|
||||
}
|
||||
|
||||
public static float sinInexact(double paramFloat) {
|
||||
return CachedMathMan.sinInexact(paramFloat);
|
||||
}
|
||||
|
||||
public static float cosInexact(double paramFloat) {
|
||||
return CachedMathMan.cosInexact(paramFloat);
|
||||
}
|
||||
|
||||
public static int log2nlz( int bits ) {
|
||||
return Integer.SIZE - Integer.numberOfLeadingZeros(bits);
|
||||
}
|
||||
|
||||
public static int floorZero(double d0) {
|
||||
int i = (int) d0;
|
||||
return d0 < (double) i ? i - 1 : i;
|
||||
}
|
||||
|
||||
public static double max(double... values) {
|
||||
double max = Double.MIN_VALUE;
|
||||
for (double d : values) {
|
||||
if (d > max) {
|
||||
max = d;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
public static int max(int... values) {
|
||||
int max = Integer.MIN_VALUE;
|
||||
for (int d : values) {
|
||||
if (d > max) {
|
||||
max = d;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
public static int min(int... values) {
|
||||
int min = Integer.MAX_VALUE;
|
||||
for (int d : values) {
|
||||
if (d < min) {
|
||||
min = d;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
public static double min(double... values) {
|
||||
double min = Double.MAX_VALUE;
|
||||
for (double d : values) {
|
||||
if (d < min) {
|
||||
min = d;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
public static int ceilZero(float floatNumber) {
|
||||
int floor = (int) floatNumber;
|
||||
return floatNumber > (float) floor ? floor + 1 : floor;
|
||||
}
|
||||
|
||||
public static int sqr(int val) {
|
||||
return val * val;
|
||||
}
|
||||
|
||||
public static int clamp(int check, int min, int max) {
|
||||
return check > max ? max : (check < min ? min : check);
|
||||
}
|
||||
|
||||
public static float clamp(float check, float min, float max) {
|
||||
return check > max ? max : (check < min ? min : check);
|
||||
}
|
||||
|
||||
public static double hypot(final double... pars) {
|
||||
double sum = 0;
|
||||
for (final double d : pars) {
|
||||
sum += Math.pow(d, 2);
|
||||
}
|
||||
return Math.sqrt(sum);
|
||||
}
|
||||
|
||||
public static double hypot2(final double... pars) {
|
||||
double sum = 0;
|
||||
for (final double d : pars) {
|
||||
sum += Math.pow(d, 2);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static final int wrap(int value, int min, int max) {
|
||||
if (max < min) {
|
||||
return value;
|
||||
}
|
||||
if (min == max) {
|
||||
return min;
|
||||
}
|
||||
int diff = max - min + 1;
|
||||
if (value < min) {
|
||||
return max - ((min - value) % diff);
|
||||
} else if (value > max) {
|
||||
return min + ((value - min) % diff);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static final long inverseRound(double val) {
|
||||
long round = Math.round(val);
|
||||
return (long) (round + Math.signum(val - round));
|
||||
}
|
||||
|
||||
public static final int pair(short x, short y) {
|
||||
return (x << 16) | (y & 0xFFFF);
|
||||
}
|
||||
|
||||
public static final short unpairX(int hash) {
|
||||
return (short) (hash >> 16);
|
||||
}
|
||||
|
||||
public static final short unpairY(int hash) {
|
||||
return (short) (hash & 0xFFFF);
|
||||
}
|
||||
|
||||
public static final short pairByte(int x, int y) {
|
||||
return (short) ((x << 8) | (y & 0xFF));
|
||||
}
|
||||
|
||||
public static final byte unpairShortX(short pair) {
|
||||
return (byte) (pair >> 8);
|
||||
}
|
||||
|
||||
public static final byte unpairShortY(short pair) {
|
||||
return (byte) pair;
|
||||
}
|
||||
|
||||
public static final long pairInt(int x, int y) {
|
||||
return (((long) x) << 32) | (y & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static final long tripleWorldCoord(int x, int y, int z) {
|
||||
return y + (((long) x & 0x3FFFFFF) << 8) + (((long) z & 0x3FFFFFF) << 34);
|
||||
}
|
||||
|
||||
public static final long untripleWorldCoordX(long triple) {
|
||||
return (((triple >> 8) & 0x3FFFFFF) << 38) >> 38;
|
||||
}
|
||||
|
||||
public static final long untripleWorldCoordY(long triple) {
|
||||
return triple & 0xFF;
|
||||
}
|
||||
|
||||
public static final long untripleWorldCoordZ(long triple) {
|
||||
return (((triple >> 34) & 0x3FFFFFF) << 38) >> 38;
|
||||
}
|
||||
|
||||
public static final short tripleBlockCoord(int x, int y, int z) {
|
||||
return (short) ((x & 15) << 12 | (z & 15) << 8 | y);
|
||||
}
|
||||
|
||||
public static final char tripleBlockCoordChar(int x, int y, int z) {
|
||||
return (char) ((x & 15) << 12 | (z & 15) << 8 | y);
|
||||
}
|
||||
|
||||
public static final int untripleBlockCoordX(int triple) {
|
||||
return (triple >> 12) & 0xF;
|
||||
}
|
||||
|
||||
public static final int untripleBlockCoordY(int triple) {
|
||||
return (triple & 0xFF);
|
||||
}
|
||||
|
||||
public static final int untripleBlockCoordZ(int triple) {
|
||||
return (triple >> 8) & 0xF;
|
||||
}
|
||||
|
||||
public static int tripleSearchCoords(int x, int y, int z) {
|
||||
byte b1 = (byte) y;
|
||||
byte b3 = (byte) (x);
|
||||
byte b4 = (byte) (z);
|
||||
int x16 = (x >> 8) & 0x7;
|
||||
int z16 = (z >> 8) & 0x7;
|
||||
byte b2 = MathMan.pair8(x16, z16);
|
||||
return ((b1 & 0xFF)
|
||||
+ ((b2 & 0x7F) << 8)
|
||||
+ ((b3 & 0xFF) << 15)
|
||||
+ ((b4 & 0xFF) << 23))
|
||||
;
|
||||
}
|
||||
|
||||
public static int pairSearchCoords(int x, int y) {
|
||||
byte b1 = (byte) ((x & 0xF) + ((y & 0xF) << 4));
|
||||
byte b2 = (byte) ((x >> 4) & 0xFF);
|
||||
byte b3 = (byte) ((y >> 4) & 0xFF);
|
||||
int x16 = (x >> 12) & 0xF;
|
||||
int y16 = (y >> 12) & 0xF;
|
||||
byte b4 = (byte) ((x16 & 0xF) + ((y16 & 0xF) << 4));
|
||||
return ((b1 & 0xFF)
|
||||
+ ((b2 & 0xFF) << 8)
|
||||
+ ((b3 & 0xFF) << 16)
|
||||
+ ((b4 & 0xFF) << 24));
|
||||
}
|
||||
|
||||
public static int unpairSearchCoordsX(int pair) {
|
||||
int x1 = (pair >> 24) & 0x7;
|
||||
int x2 = (pair >> 8) & 0xFF;
|
||||
int x3 = (pair & 0xF);
|
||||
return x3 + (x2 << 4) + (x1 << 12);
|
||||
}
|
||||
|
||||
public static int unpairSearchCoordsY(int pair) {
|
||||
int y1 = ((pair >> 24) & 0x7F) >> 3;
|
||||
int y2 = (pair >> 16) & 0xFF;
|
||||
int y3 = (pair & 0xFF) >> 4;
|
||||
return y3 + (y2 << 4) + (y1 << 12);
|
||||
}
|
||||
|
||||
public static final long chunkXZ2Int(int x, int z) {
|
||||
return (long) x & 4294967295L | ((long) z & 4294967295L) << 32;
|
||||
}
|
||||
|
||||
public static final int unpairIntX(long pair) {
|
||||
return (int) (pair >> 32);
|
||||
}
|
||||
|
||||
public static final int unpairIntY(long pair) {
|
||||
return (int) pair;
|
||||
}
|
||||
|
||||
public static final byte pair16(int x, int y) {
|
||||
return (byte) (x + (y << 4));
|
||||
}
|
||||
|
||||
public static final byte unpair16x(byte value) {
|
||||
return (byte) (value & 0xF);
|
||||
}
|
||||
|
||||
public static final byte unpair16y(byte value) {
|
||||
return (byte) ((value >> 4) & 0xF);
|
||||
}
|
||||
|
||||
public static final byte pair8(int x, int y) {
|
||||
return (byte) (x + (y << 3));
|
||||
}
|
||||
|
||||
public static byte unpair8x(int value) {
|
||||
return (byte) (value & 0x7);
|
||||
}
|
||||
|
||||
public static byte unpair8y(int value) {
|
||||
return (byte) ((value >> 3) & 0x7F);
|
||||
}
|
||||
|
||||
public static final int lossyFastDivide(int a, int b) {
|
||||
return (a * ((1 << 16) / b)) >> 16;
|
||||
}
|
||||
|
||||
public static final int gcd(int a, int b) {
|
||||
if (b == 0) {
|
||||
return a;
|
||||
}
|
||||
return gcd(b, a % b);
|
||||
}
|
||||
|
||||
public static final int gcd(int[] a) {
|
||||
int result = a[0];
|
||||
for (int i = 1; i < a.length; i++) {
|
||||
result = gcd(result, a[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static final double getMean(int[] array) {
|
||||
double count = 0;
|
||||
for (int i : array) {
|
||||
count += i;
|
||||
}
|
||||
return count / array.length;
|
||||
}
|
||||
|
||||
public static final double getMean(double[] array) {
|
||||
double count = 0;
|
||||
for (double i : array) {
|
||||
count += i;
|
||||
}
|
||||
return count / array.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [x, y, z]
|
||||
*
|
||||
* @param yaw
|
||||
* @param pitch
|
||||
* @return
|
||||
*/
|
||||
public static final float[] getDirection(float yaw, float pitch) {
|
||||
double pitch_sin = Math.sin(pitch);
|
||||
return new float[]{(float) (pitch_sin * Math.cos(yaw)), (float) (pitch_sin * Math.sin(yaw)), (float) Math.cos(pitch)};
|
||||
}
|
||||
|
||||
public static final int roundInt(double value) {
|
||||
return (int) (value < 0 ? (value == (int) value) ? value : value - 1 : value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [ pitch, yaw ]
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @return
|
||||
*/
|
||||
public static final float[] getPitchAndYaw(float x, float y, float z) {
|
||||
float distance = sqrtApprox((z * z) + (x * x));
|
||||
return new float[]{atan2(y, distance), atan2(x, z)};
|
||||
}
|
||||
|
||||
public static final float atan2(float y, float x) {
|
||||
return CachedMathMan.atan2(y, x);
|
||||
}
|
||||
|
||||
public static final float sqrtApprox(float f) {
|
||||
return f * Float.intBitsToFloat(0x5f375a86 - (Float.floatToIntBits(f) >> 1));
|
||||
}
|
||||
|
||||
public static final double sqrtApprox(double d) {
|
||||
return Double.longBitsToDouble(((Double.doubleToLongBits(d) - (1l << 52)) >> 1) + (1l << 61));
|
||||
}
|
||||
|
||||
public static final float invSqrt(float x) {
|
||||
float xhalf = 0.5f * x;
|
||||
int i = Float.floatToIntBits(x);
|
||||
i = 0x5f3759df - (i >> 1);
|
||||
x = Float.intBitsToFloat(i);
|
||||
x = x * (1.5f - (xhalf * x * x));
|
||||
return x;
|
||||
}
|
||||
|
||||
public static final boolean isInteger(String str) {
|
||||
if (str == null) {
|
||||
return false;
|
||||
}
|
||||
int length = str.length();
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
int i = 0;
|
||||
if (str.charAt(0) == '-') {
|
||||
if (length == 1) {
|
||||
return false;
|
||||
}
|
||||
i = 1;
|
||||
}
|
||||
for (; i < length; i++) {
|
||||
char c = str.charAt(i);
|
||||
if ((c <= '/') || (c >= ':')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final double getSD(double[] array, double av) {
|
||||
double sd = 0;
|
||||
for (double element : array) {
|
||||
sd += Math.pow(Math.abs(element - av), 2);
|
||||
}
|
||||
return Math.sqrt(sd / array.length);
|
||||
}
|
||||
|
||||
public static final double getSD(int[] array, double av) {
|
||||
double sd = 0;
|
||||
for (int element : array) {
|
||||
sd += Math.pow(Math.abs(element - av), 2);
|
||||
}
|
||||
return Math.sqrt(sd / array.length);
|
||||
}
|
||||
|
||||
public static final int absByte(int value) {
|
||||
return (value ^ (value >> 8)) - (value >> 8);
|
||||
}
|
||||
|
||||
public static final int mod(int x, int y) {
|
||||
if (isPowerOfTwo(y)) {
|
||||
return x & (y - 1);
|
||||
}
|
||||
return x % y;
|
||||
}
|
||||
|
||||
public static final int unsignedmod(int x, int y) {
|
||||
if (isPowerOfTwo(y)) {
|
||||
return x & (y - 1);
|
||||
}
|
||||
return x % y;
|
||||
}
|
||||
|
||||
public static final boolean isPowerOfTwo(int x) {
|
||||
return (x & (x - 1)) == 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.config.Settings;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class MemUtil {
|
||||
|
||||
private static AtomicBoolean memory = new AtomicBoolean(false);
|
||||
|
||||
public static boolean isMemoryFree() {
|
||||
return !memory.get();
|
||||
}
|
||||
|
||||
public static boolean isMemoryLimited() {
|
||||
return memory.get();
|
||||
}
|
||||
|
||||
public static boolean isMemoryLimitedSlow() {
|
||||
if (memory.get()) {
|
||||
System.gc();
|
||||
System.gc();
|
||||
calculateMemory();
|
||||
return memory.get();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static long getUsedBytes() {
|
||||
long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
return used;
|
||||
}
|
||||
|
||||
public static long getFreeBytes() {
|
||||
return Runtime.getRuntime().maxMemory() - getUsedBytes();
|
||||
}
|
||||
|
||||
public static int calculateMemory() {
|
||||
final long heapSize = Runtime.getRuntime().totalMemory();
|
||||
final long heapMaxSize = Runtime.getRuntime().maxMemory();
|
||||
if (heapSize < heapMaxSize) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
final long heapFreeSize = Runtime.getRuntime().freeMemory();
|
||||
final int size = (int) ((heapFreeSize * 100) / heapMaxSize);
|
||||
if (size > (100 - Settings.IMP.MAX_MEMORY_PERCENT)) {
|
||||
memoryPlentifulTask();
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private static Queue<Runnable> memoryLimitedTasks = new ConcurrentLinkedQueue<>();
|
||||
private static Queue<Runnable> memoryPlentifulTasks = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public static void addMemoryLimitedTask(Runnable run) {
|
||||
if (run != null)
|
||||
memoryLimitedTasks.add(run);
|
||||
}
|
||||
|
||||
public static void addMemoryPlentifulTask(Runnable run) {
|
||||
if (run != null)
|
||||
memoryPlentifulTasks.add(run);
|
||||
}
|
||||
|
||||
public static void memoryLimitedTask() {
|
||||
System.gc();
|
||||
System.gc();
|
||||
for (Runnable task : memoryLimitedTasks) {
|
||||
task.run();
|
||||
}
|
||||
memory.set(true);
|
||||
}
|
||||
|
||||
public static void memoryPlentifulTask() {
|
||||
for (Runnable task : memoryPlentifulTasks) {
|
||||
task.run();
|
||||
}
|
||||
memory.set(false);
|
||||
}
|
||||
}
|
44
worldedit-core/src/main/java/com/boydti/fawe/util/Perm.java
Normal file
44
worldedit-core/src/main/java/com/boydti/fawe/util/Perm.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
|
||||
public enum Perm {
|
||||
/*
|
||||
* Permission related functions
|
||||
*/
|
||||
ADMIN("fawe.admin", "admin");
|
||||
|
||||
public String s;
|
||||
public String cat;
|
||||
|
||||
Perm(final String perm, final String cat) {
|
||||
this.s = perm;
|
||||
this.cat = cat;
|
||||
}
|
||||
|
||||
public boolean has(final FawePlayer<?> player) {
|
||||
return this.hasPermission(player, this);
|
||||
}
|
||||
|
||||
public boolean hasPermission(final FawePlayer<?> player, final Perm perm) {
|
||||
return hasPermission(player, perm.s);
|
||||
}
|
||||
|
||||
public static boolean hasPermission(final FawePlayer<?> player, final String perm) {
|
||||
if ((player == null) || player.hasPermission(ADMIN.s)) {
|
||||
return true;
|
||||
}
|
||||
if (player.hasPermission(perm)) {
|
||||
return true;
|
||||
}
|
||||
final String[] nodes = perm.split("\\.");
|
||||
final StringBuilder n = new StringBuilder();
|
||||
for (int i = 0; i < (nodes.length - 1); i++) {
|
||||
n.append(nodes[i] + ("."));
|
||||
if (player.hasPermission(n + "*")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.object.PseudoRandom;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class RandomTextureUtil extends CachedTextureUtil {
|
||||
|
||||
public RandomTextureUtil(TextureUtil parent) throws FileNotFoundException {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
private int index;
|
||||
private int[] biomeMixBuffer = new int[3];
|
||||
private Int2ObjectOpenHashMap<Integer> offsets = new Int2ObjectOpenHashMap<>();
|
||||
private Int2ObjectOpenHashMap<int[]> biomeMixes = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
protected int addRandomColor(int c1, int c2) {
|
||||
int red1 = (c1 >> 16) & 0xFF;
|
||||
int green1 = (c1 >> 8) & 0xFF;
|
||||
int blue1 = (c1 >> 0) & 0xFF;
|
||||
byte red2 = (byte) ((c2 >> 16));
|
||||
byte green2 = (byte) ((c2 >> 8));
|
||||
byte blue2 = (byte) ((c2 >> 0));
|
||||
int red = MathMan.clamp(red1 + random(red2), 0, 255);
|
||||
int green = MathMan.clamp(green1 + random(green2), 0, 255);
|
||||
int blue = MathMan.clamp(blue1 + random(blue2), 0, 255);
|
||||
return (red << 16) + (green << 8) + (blue << 0) + (255 << 24);
|
||||
}
|
||||
|
||||
private int random(int i) {
|
||||
if (i < 0) {
|
||||
return -PseudoRandom.random.nextInt((-i));
|
||||
} else {
|
||||
return PseudoRandom.random.nextInt(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsBlockCloserThanBiome(int[] blockAndBiomeIdOutput, int color, int biomePriority) {
|
||||
BlockType block = getNearestBlock(color);
|
||||
int[] mix = biomeMixes.getOrDefault(color, null);
|
||||
if (mix == null) {
|
||||
int average = getBiomeMix(biomeMixBuffer, color);
|
||||
mix = new int[4];
|
||||
System.arraycopy(biomeMixBuffer, 0, mix, 0, 3);
|
||||
mix[3] = average;
|
||||
biomeMixes.put(color, mix);
|
||||
}
|
||||
if (++index > 2) index = 0;
|
||||
int biomeId = mix[index];
|
||||
int biomeAvColor = mix[3];
|
||||
int blockColor = getColor(block);
|
||||
blockAndBiomeIdOutput[0] = block.getInternalId();
|
||||
blockAndBiomeIdOutput[1] = biomeId;
|
||||
if (colorDistance(biomeAvColor, color) - biomePriority > colorDistance(blockColor, color)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BiomeColor getNearestBiome(int color) {
|
||||
int[] mix = biomeMixes.getOrDefault(color, null);
|
||||
if (mix == null) {
|
||||
int average = getBiomeMix(biomeMixBuffer, color);
|
||||
mix = new int[4];
|
||||
System.arraycopy(biomeMixBuffer, 0, mix, 0, 3);
|
||||
mix[3] = average;
|
||||
biomeMixes.put(color, mix);
|
||||
}
|
||||
if (++index > 2) index = 0;
|
||||
int biomeId = mix[index];
|
||||
return getBiome(biomeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTypes getNearestBlock(int color) {
|
||||
int offsetColor = offsets.getOrDefault(color, 0);
|
||||
if (offsetColor != 0) {
|
||||
offsetColor = addRandomColor(color, offsetColor);
|
||||
} else {
|
||||
offsetColor = color;
|
||||
}
|
||||
BlockTypes res = super.getNearestBlock(offsetColor);
|
||||
if (res == null) return null;
|
||||
int newColor = getColor(res);
|
||||
{
|
||||
byte dr = (byte) (((color >> 16) & 0xFF) - ((newColor >> 16) & 0xFF));
|
||||
byte dg = (byte) (((color >> 8) & 0xFF) - ((newColor >> 8) & 0xFF));
|
||||
byte db = (byte) (((color >> 0) & 0xFF) - ((newColor >> 0) & 0xFF));
|
||||
offsets.put(color, (Integer) ((dr << 16) + (dg << 8) + (db << 0)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
@ -0,0 +1,855 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import sun.reflect.ConstructorAccessor;
|
||||
import sun.reflect.FieldAccessor;
|
||||
import sun.reflect.ReflectionFactory;
|
||||
|
||||
/**
|
||||
* @author DPOH-VAR
|
||||
* @version 1.0
|
||||
*/
|
||||
@SuppressWarnings({"UnusedDeclaration", "rawtypes"})
|
||||
public class ReflectionUtils {
|
||||
public static <T> T as(Class<T> t, Object o) {
|
||||
return t.isInstance(o) ? t.cast(o) : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Enum<?>> T addEnum(Class<T> enumType, String enumName) {
|
||||
return addEnum(enumType, enumName, new Class<?>[]{} , new Object[]{});
|
||||
}
|
||||
|
||||
public static <T extends Enum<?>> T addEnum(Class<T> enumType, String enumName, Class<?>[] additionalTypes, Object[] additionalValues) {
|
||||
|
||||
// 0. Sanity checks
|
||||
if (!Enum.class.isAssignableFrom(enumType)) {
|
||||
throw new RuntimeException("class " + enumType + " is not an instance of Enum");
|
||||
}
|
||||
// 1. Lookup "$VALUES" holder in enum class and get previous enum instances
|
||||
Field valuesField = null;
|
||||
Field[] fields = enumType.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getName().contains("$VALUES")) {
|
||||
valuesField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
AccessibleObject.setAccessible(new Field[]{valuesField}, true);
|
||||
|
||||
try {
|
||||
|
||||
// 2. Copy it
|
||||
T[] previousValues = (T[]) valuesField.get(enumType);
|
||||
List values = new ArrayList(Arrays.asList(previousValues));
|
||||
|
||||
// 3. build new enum
|
||||
T newValue = (T) makeEnum(enumType, // The target enum class
|
||||
enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
|
||||
values.size(),
|
||||
additionalTypes, // can be used to pass values to the enum constuctor
|
||||
additionalValues); // can be used to pass values to the enum constuctor
|
||||
|
||||
// 4. add new value
|
||||
values.add(newValue);
|
||||
|
||||
// 5. Set new values field
|
||||
setFailsafeFieldValue(valuesField, null,
|
||||
values.toArray((T[]) Array.newInstance(enumType, 0)));
|
||||
|
||||
// 6. Clean enum cache
|
||||
cleanEnumCache(enumType);
|
||||
return newValue;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends Enum<?>> void clearEnum(Class<T> enumType) {
|
||||
// 0. Sanity checks
|
||||
if (!Enum.class.isAssignableFrom(enumType)) {
|
||||
throw new RuntimeException("class " + enumType + " is not an instance of Enum");
|
||||
}
|
||||
// 1. Lookup "$VALUES" holder in enum class and get previous enum instances
|
||||
Field valuesField = null;
|
||||
Field[] fields = enumType.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getName().contains("$VALUES")) {
|
||||
valuesField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
AccessibleObject.setAccessible(new Field[]{valuesField}, true);
|
||||
try {
|
||||
setFailsafeFieldValue(valuesField, null, Array.newInstance(enumType, 0));
|
||||
// 6. Clean enum cache
|
||||
cleanEnumCache(enumType);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends Enum<?>> void copyEnum(T dest, String value, Class<?>[] additionalTypes, Object[] additionalValues) {
|
||||
try {
|
||||
Class<? extends Enum> clazz = dest.getClass();
|
||||
Object newEnum = makeEnum(clazz, value, dest.ordinal(), additionalTypes, additionalValues);
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers())) continue;
|
||||
field.setAccessible(true);
|
||||
Object newValue = field.get(newEnum);
|
||||
setField(field, dest, newValue);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static Object makeEnum(Class<?> enumClass, String value, int ordinal,
|
||||
Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {
|
||||
Object[] parms = new Object[additionalValues.length + 2];
|
||||
parms[0] = value;
|
||||
parms[1] = Integer.valueOf(ordinal);
|
||||
System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
|
||||
return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
|
||||
}
|
||||
|
||||
private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass,
|
||||
Class<?>[] additionalParameterTypes) throws NoSuchMethodException {
|
||||
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
|
||||
parameterTypes[0] = String.class;
|
||||
parameterTypes[1] = int.class;
|
||||
System.arraycopy(additionalParameterTypes, 0,
|
||||
parameterTypes, 2, additionalParameterTypes.length);
|
||||
return ReflectionFactory.getReflectionFactory().newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes));
|
||||
}
|
||||
|
||||
public static void setFailsafeFieldValue(Field field, Object target, Object value)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
|
||||
// let's make the field accessible
|
||||
field.setAccessible(true);
|
||||
|
||||
// next we change the modifier in the Field instance to
|
||||
// not be final anymore, thus tricking reflection into
|
||||
// letting us modify the static final field
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
int modifiers = modifiersField.getInt(field);
|
||||
|
||||
// blank out the final bit in the modifiers int
|
||||
modifiers &= ~Modifier.FINAL;
|
||||
modifiersField.setInt(field, modifiers);
|
||||
|
||||
try {
|
||||
FieldAccessor fa = ReflectionFactory.getReflectionFactory().newFieldAccessor(field, false);
|
||||
fa.set(target, value);
|
||||
} catch (NoSuchMethodError error) {
|
||||
field.set(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void blankField(Class<?> enumClass, String fieldName)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
for (Field field : Class.class.getDeclaredFields()) {
|
||||
if (field.getName().contains(fieldName)) {
|
||||
AccessibleObject.setAccessible(new Field[]{field}, true);
|
||||
setFailsafeFieldValue(field, enumClass, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void cleanEnumCache(Class<?> enumClass)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
|
||||
blankField(enumClass, "enumConstants"); // IBM JDK
|
||||
}
|
||||
|
||||
private static Class<?> UNMODIFIABLE_MAP = Collections.unmodifiableMap(Collections.EMPTY_MAP).getClass();
|
||||
|
||||
public static <T, V> Map<T, V> getMap(Map<T, V> map) {
|
||||
try {
|
||||
Class<? extends Map> clazz = map.getClass();
|
||||
if (clazz != UNMODIFIABLE_MAP) return map;
|
||||
Field m = clazz.getDeclaredField("m");
|
||||
m.setAccessible(true);
|
||||
return (Map<T, V>) m.get(map);
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> getList(List<T> list) {
|
||||
try {
|
||||
Class<? extends List> clazz = (Class<? extends List>) Class.forName("java.util.Collections$UnmodifiableList");
|
||||
if (!clazz.isInstance(list)) return list;
|
||||
Field m = clazz.getDeclaredField("list");
|
||||
m.setAccessible(true);
|
||||
return (List<T>) m.get(list);
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getHandle(final Object wrapper) {
|
||||
final Method getHandle = makeMethod(wrapper.getClass(), "getHandle");
|
||||
return callMethod(getHandle, wrapper);
|
||||
}
|
||||
|
||||
//Utils
|
||||
public static Method makeMethod(final Class<?> clazz, final String methodName, final Class<?>... paramaters) {
|
||||
try {
|
||||
return clazz.getDeclaredMethod(methodName, paramaters);
|
||||
} catch (final NoSuchMethodException ex) {
|
||||
return null;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T callMethod(final Method method, final Object instance, final Object... paramaters) {
|
||||
if (method == null) {
|
||||
throw new RuntimeException("No such method");
|
||||
}
|
||||
method.setAccessible(true);
|
||||
try {
|
||||
return (T) method.invoke(instance, paramaters);
|
||||
} catch (final InvocationTargetException ex) {
|
||||
throw new RuntimeException(ex.getCause());
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Constructor<T> makeConstructor(final Class<?> clazz, final Class<?>... paramaterTypes) {
|
||||
try {
|
||||
return (Constructor<T>) clazz.getConstructor(paramaterTypes);
|
||||
} catch (final NoSuchMethodException ex) {
|
||||
return null;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T callConstructor(final Constructor<T> constructor, final Object... paramaters) {
|
||||
if (constructor == null) {
|
||||
throw new RuntimeException("No such constructor");
|
||||
}
|
||||
constructor.setAccessible(true);
|
||||
try {
|
||||
return constructor.newInstance(paramaters);
|
||||
} catch (final InvocationTargetException ex) {
|
||||
throw new RuntimeException(ex.getCause());
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Field makeField(final Class<?> clazz, final String name) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (final NoSuchFieldException ex) {
|
||||
return null;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Field findField(final Class<?> clazz, final Class<?> type, int hasMods, int noMods) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (type == null || type.isAssignableFrom(field.getType())) {
|
||||
int mods = field.getModifiers();
|
||||
if ((mods & hasMods) == hasMods && (mods & noMods) == 0) {
|
||||
return setAccessible(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Field findField(final Class<?> clazz, final Class<?> type) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type) {
|
||||
return setAccessible(field);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Method findMethod(final Class<?> clazz, final Class<?> returnType, Class... params) {
|
||||
return findMethod(clazz, 0, returnType, params);
|
||||
}
|
||||
|
||||
public static Method findMethod(final Class<?> clazz, int index, int hasMods, int noMods, final Class<?> returnType, Class... params) {
|
||||
outer:
|
||||
for (Method method : sortMethods(clazz.getDeclaredMethods())) {
|
||||
if (returnType == null || method.getReturnType() == returnType) {
|
||||
Class<?>[] mp = method.getParameterTypes();
|
||||
int mods = method.getModifiers();
|
||||
if ((mods & hasMods) != hasMods || (mods & noMods) != 0) continue;
|
||||
if (params == null) {
|
||||
if (index-- == 0) return setAccessible(method);
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (mp.length == params.length) {
|
||||
for (int i = 0; i < mp.length; i++) {
|
||||
if (mp[i] != params[i]) continue outer;
|
||||
}
|
||||
if (index-- == 0) return setAccessible(method);
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Method[] sortMethods(Method[] methods) {
|
||||
Arrays.sort(methods, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||
return methods;
|
||||
}
|
||||
|
||||
public static Field[] sortFields(Field[] fields) {
|
||||
Arrays.sort(fields, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||
return fields;
|
||||
}
|
||||
|
||||
public static Method findMethod(final Class<?> clazz, int index, final Class<?> returnType, Class... params) {
|
||||
return findMethod(clazz, index, 0, 0, returnType, params);
|
||||
}
|
||||
|
||||
public static <T extends AccessibleObject> T setAccessible(final T ao) {
|
||||
ao.setAccessible(true);
|
||||
return ao;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getField(final Field field, final Object instance) {
|
||||
if (field == null) {
|
||||
throw new RuntimeException("No such field");
|
||||
}
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
return (T) field.get(instance);
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setField(String fieldName, Object instance, Object value) {
|
||||
try {
|
||||
Field field = instance.getClass().getDeclaredField(fieldName);
|
||||
setField(field, instance, value);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setField(final Field field, final Object instance, final Object value) {
|
||||
if (field == null) {
|
||||
throw new RuntimeException("No such field");
|
||||
}
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
field.set(instance, value);
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> getClass(final String name) {
|
||||
try {
|
||||
return Class.forName(name);
|
||||
} catch (final ClassNotFoundException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Class<? extends T> getClass(final String name, final Class<T> superClass) {
|
||||
try {
|
||||
return Class.forName(name).asSubclass(superClass);
|
||||
} catch (ClassCastException | ClassNotFoundException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get RefClass object by real class
|
||||
*
|
||||
* @param clazz class
|
||||
* @return RefClass based on passed class
|
||||
*/
|
||||
public static RefClass getRefClass(final Class clazz) {
|
||||
return new RefClass(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* RefClass - utility to simplify work with reflections.
|
||||
*/
|
||||
public static class RefClass {
|
||||
private final Class<?> clazz;
|
||||
|
||||
private RefClass(final Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* get passed class
|
||||
*
|
||||
* @return class
|
||||
*/
|
||||
public Class<?> getRealClass() {
|
||||
return this.clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* see {@link Class#isInstance(Object)}
|
||||
*
|
||||
* @param object the object to check
|
||||
* @return true if object is an instance of this class
|
||||
*/
|
||||
public boolean isInstance(final Object object) {
|
||||
return this.clazz.isInstance(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* get existing method by name and types
|
||||
*
|
||||
* @param name name
|
||||
* @param types method parameters. can be Class or RefClass
|
||||
* @return RefMethod object
|
||||
* @throws RuntimeException if method not found
|
||||
*/
|
||||
public RefMethod getMethod(final String name, final Object... types) throws NoSuchMethodException {
|
||||
try {
|
||||
final Class[] classes = new Class[types.length];
|
||||
int i = 0;
|
||||
for (final Object e : types) {
|
||||
if (e instanceof Class) {
|
||||
classes[i++] = (Class) e;
|
||||
} else if (e instanceof RefClass) {
|
||||
classes[i++] = ((RefClass) e).getRealClass();
|
||||
} else {
|
||||
classes[i++] = e.getClass();
|
||||
}
|
||||
}
|
||||
try {
|
||||
return new RefMethod(this.clazz.getMethod(name, classes));
|
||||
} catch (final NoSuchMethodException ignored) {
|
||||
return new RefMethod(this.clazz.getDeclaredMethod(name, classes));
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get existing constructor by types
|
||||
*
|
||||
* @param types parameters. can be Class or RefClass
|
||||
* @return RefMethod object
|
||||
* @throws RuntimeException if constructor not found
|
||||
*/
|
||||
public RefConstructor getConstructor(final Object... types) {
|
||||
try {
|
||||
final Class[] classes = new Class[types.length];
|
||||
int i = 0;
|
||||
for (final Object e : types) {
|
||||
if (e instanceof Class) {
|
||||
classes[i++] = (Class) e;
|
||||
} else if (e instanceof RefClass) {
|
||||
classes[i++] = ((RefClass) e).getRealClass();
|
||||
} else {
|
||||
classes[i++] = e.getClass();
|
||||
}
|
||||
}
|
||||
try {
|
||||
return new RefConstructor(this.clazz.getConstructor(classes));
|
||||
} catch (final NoSuchMethodException ignored) {
|
||||
return new RefConstructor(this.clazz.getDeclaredConstructor(classes));
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find method by type parameters
|
||||
*
|
||||
* @param types parameters. can be Class or RefClass
|
||||
* @return RefMethod object
|
||||
* @throws RuntimeException if method not found
|
||||
*/
|
||||
public RefMethod findMethod(final Object... types) {
|
||||
final Class[] classes = new Class[types.length];
|
||||
int t = 0;
|
||||
for (final Object e : types) {
|
||||
if (e instanceof Class) {
|
||||
classes[t++] = (Class) e;
|
||||
} else if (e instanceof RefClass) {
|
||||
classes[t++] = ((RefClass) e).getRealClass();
|
||||
} else {
|
||||
classes[t++] = e.getClass();
|
||||
}
|
||||
}
|
||||
final List<Method> methods = new ArrayList<>();
|
||||
Collections.addAll(methods, this.clazz.getMethods());
|
||||
Collections.addAll(methods, this.clazz.getDeclaredMethods());
|
||||
findMethod:
|
||||
for (final Method m : methods) {
|
||||
final Class<?>[] methodTypes = m.getParameterTypes();
|
||||
if (methodTypes.length != classes.length) {
|
||||
continue;
|
||||
}
|
||||
for (final Class aClass : classes) {
|
||||
if (!Arrays.equals(classes, methodTypes)) {
|
||||
continue findMethod;
|
||||
}
|
||||
return new RefMethod(m);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("no such method");
|
||||
}
|
||||
|
||||
/**
|
||||
* find method by name
|
||||
*
|
||||
* @param names possible names of method
|
||||
* @return RefMethod object
|
||||
* @throws RuntimeException if method not found
|
||||
*/
|
||||
public RefMethod findMethodByName(final String... names) {
|
||||
final List<Method> methods = new ArrayList<>();
|
||||
Collections.addAll(methods, this.clazz.getMethods());
|
||||
Collections.addAll(methods, this.clazz.getDeclaredMethods());
|
||||
for (final Method m : methods) {
|
||||
for (final String name : names) {
|
||||
if (m.getName().equals(name)) {
|
||||
return new RefMethod(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("no such method");
|
||||
}
|
||||
|
||||
/**
|
||||
* find method by return value
|
||||
*
|
||||
* @param type type of returned value
|
||||
* @return RefMethod
|
||||
* @throws RuntimeException if method not found
|
||||
*/
|
||||
public RefMethod findMethodByReturnType(final RefClass type) {
|
||||
return this.findMethodByReturnType(type.clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* find method by return value
|
||||
*
|
||||
* @param type type of returned value
|
||||
* @return RefMethod
|
||||
* @throws RuntimeException if method not found
|
||||
*/
|
||||
public RefMethod findMethodByReturnType(Class type) {
|
||||
if (type == null) {
|
||||
type = void.class;
|
||||
}
|
||||
final List<Method> methods = new ArrayList<>();
|
||||
Collections.addAll(methods, this.clazz.getMethods());
|
||||
Collections.addAll(methods, this.clazz.getDeclaredMethods());
|
||||
for (final Method m : methods) {
|
||||
if (type.equals(m.getReturnType())) {
|
||||
return new RefMethod(m);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("no such method");
|
||||
}
|
||||
|
||||
/**
|
||||
* find constructor by number of arguments
|
||||
*
|
||||
* @param number number of arguments
|
||||
* @return RefConstructor
|
||||
* @throws RuntimeException if constructor not found
|
||||
*/
|
||||
public RefConstructor findConstructor(final int number) {
|
||||
final List<Constructor> constructors = new ArrayList<>();
|
||||
Collections.addAll(constructors, this.clazz.getConstructors());
|
||||
Collections.addAll(constructors, this.clazz.getDeclaredConstructors());
|
||||
for (final Constructor m : constructors) {
|
||||
if (m.getParameterTypes().length == number) {
|
||||
return new RefConstructor(m);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("no such constructor");
|
||||
}
|
||||
|
||||
/**
|
||||
* get field by name
|
||||
*
|
||||
* @param name field name
|
||||
* @return RefField
|
||||
* @throws RuntimeException if field not found
|
||||
*/
|
||||
public RefField getField(final String name) {
|
||||
try {
|
||||
try {
|
||||
return new RefField(this.clazz.getField(name));
|
||||
} catch (final NoSuchFieldException ignored) {
|
||||
return new RefField(this.clazz.getDeclaredField(name));
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find field by type
|
||||
*
|
||||
* @param type field type
|
||||
* @return RefField
|
||||
* @throws RuntimeException if field not found
|
||||
*/
|
||||
public RefField findField(final RefClass type) {
|
||||
return this.findField(type.clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* find field by type
|
||||
*
|
||||
* @param type field type
|
||||
* @return RefField
|
||||
* @throws RuntimeException if field not found
|
||||
*/
|
||||
public RefField findField(Class type) {
|
||||
if (type == null) {
|
||||
type = void.class;
|
||||
}
|
||||
final List<Field> fields = new ArrayList<>();
|
||||
Collections.addAll(fields, this.clazz.getFields());
|
||||
Collections.addAll(fields, this.clazz.getDeclaredFields());
|
||||
for (final Field f : fields) {
|
||||
if (type.equals(f.getType())) {
|
||||
return new RefField(f);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("no such field");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method wrapper
|
||||
*/
|
||||
public static class RefMethod {
|
||||
private final Method method;
|
||||
|
||||
private RefMethod(final Method method) {
|
||||
this.method = method;
|
||||
method.setAccessible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return passed method
|
||||
*/
|
||||
public Method getRealMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return owner class of method
|
||||
*/
|
||||
public RefClass getRefClass() {
|
||||
return new RefClass(this.method.getDeclaringClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class of method return type
|
||||
*/
|
||||
public RefClass getReturnRefClass() {
|
||||
return new RefClass(this.method.getReturnType());
|
||||
}
|
||||
|
||||
/**
|
||||
* apply method to object
|
||||
*
|
||||
* @param e object to which the method is applied
|
||||
* @return RefExecutor with method call(...)
|
||||
*/
|
||||
public RefExecutor of(final Object e) {
|
||||
return new RefExecutor(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* call static method
|
||||
*
|
||||
* @param params sent parameters
|
||||
* @return return value
|
||||
*/
|
||||
public Object call(final Object... params) {
|
||||
try {
|
||||
return this.method.invoke(null, params);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public class RefExecutor {
|
||||
final Object e;
|
||||
|
||||
public RefExecutor(final Object e) {
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* apply method for selected object
|
||||
*
|
||||
* @param params sent parameters
|
||||
* @return return value
|
||||
* @throws RuntimeException if something went wrong
|
||||
*/
|
||||
public Object call(final Object... params) {
|
||||
try {
|
||||
return RefMethod.this.method.invoke(this.e, params);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor wrapper
|
||||
*/
|
||||
public static class RefConstructor {
|
||||
private final Constructor constructor;
|
||||
|
||||
private RefConstructor(final Constructor constructor) {
|
||||
this.constructor = constructor;
|
||||
constructor.setAccessible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return passed constructor
|
||||
*/
|
||||
public Constructor getRealConstructor() {
|
||||
return this.constructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return owner class of method
|
||||
*/
|
||||
public RefClass getRefClass() {
|
||||
return new RefClass(this.constructor.getDeclaringClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* create new instance with constructor
|
||||
*
|
||||
* @param params parameters for constructor
|
||||
* @return new object
|
||||
* @throws RuntimeException if something went wrong
|
||||
*/
|
||||
public Object create(final Object... params) {
|
||||
try {
|
||||
return this.constructor.newInstance(params);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RefField {
|
||||
private final Field field;
|
||||
|
||||
private RefField(final Field field) {
|
||||
this.field = field;
|
||||
field.setAccessible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return passed field
|
||||
*/
|
||||
public Field getRealField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return owner class of field
|
||||
*/
|
||||
public RefClass getRefClass() {
|
||||
return new RefClass(this.field.getDeclaringClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return type of field
|
||||
*/
|
||||
public RefClass getFieldRefClass() {
|
||||
return new RefClass(this.field.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* apply fiend for object
|
||||
*
|
||||
* @param e applied object
|
||||
* @return RefExecutor with getter and setter
|
||||
*/
|
||||
public RefExecutor of(final Object e) {
|
||||
return new RefExecutor(e);
|
||||
}
|
||||
|
||||
public class RefExecutor {
|
||||
final Object e;
|
||||
|
||||
public RefExecutor(final Object e) {
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* set field value for applied object
|
||||
*
|
||||
* @param param value
|
||||
*/
|
||||
public void set(final Object param) {
|
||||
try {
|
||||
RefField.this.field.set(this.e, param);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get field value for applied object
|
||||
*
|
||||
* @return value of field
|
||||
*/
|
||||
public Object get() {
|
||||
try {
|
||||
return RefField.this.field.get(this.e);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
450
worldedit-core/src/main/java/com/boydti/fawe/util/SetQueue.java
Normal file
450
worldedit-core/src/main/java/com/boydti/fawe/util/SetQueue.java
Normal file
@ -0,0 +1,450 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
import com.boydti.fawe.object.FaweQueue;
|
||||
import com.boydti.fawe.wrappers.WorldWrapper;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SetQueue {
|
||||
|
||||
/**
|
||||
* The implementation specific queue
|
||||
*/
|
||||
public static final SetQueue IMP = new SetQueue();
|
||||
private double targetTPS = 18;
|
||||
|
||||
public enum QueueStage {
|
||||
INACTIVE, ACTIVE, NONE;
|
||||
}
|
||||
|
||||
private final ConcurrentLinkedDeque<FaweQueue> activeQueues;
|
||||
private final ConcurrentLinkedDeque<FaweQueue> inactiveQueues;
|
||||
private final ConcurrentLinkedDeque<Runnable> tasks;
|
||||
|
||||
/**
|
||||
* Used to calculate elapsed time in milliseconds and ensure block placement doesn't lag the server
|
||||
*/
|
||||
private long last;
|
||||
private long allocate = 50;
|
||||
private long lastSuccess;
|
||||
|
||||
/**
|
||||
* A queue of tasks that will run when the queue is empty
|
||||
*/
|
||||
private final ConcurrentLinkedDeque<Runnable> emptyTasks = new ConcurrentLinkedDeque<>();
|
||||
|
||||
private ForkJoinPool pool = new ForkJoinPool();
|
||||
private ExecutorCompletionService completer = new ExecutorCompletionService(pool);
|
||||
|
||||
/**
|
||||
* @return ForkJoinPool
|
||||
* @see TaskManager#getPublicForkJoinPool()
|
||||
*/
|
||||
@Deprecated
|
||||
public ExecutorCompletionService getCompleterService() {
|
||||
return completer;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ForkJoinPool getForkJoinPool() {
|
||||
return pool;
|
||||
}
|
||||
|
||||
public void runMiscTasks() {
|
||||
while (Fawe.get().getTimer().isAbove(targetTPS)) {
|
||||
Runnable task = tasks.poll();
|
||||
if (task != null) {
|
||||
task.run();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SetQueue() {
|
||||
tasks = new ConcurrentLinkedDeque<>();
|
||||
activeQueues = new ConcurrentLinkedDeque();
|
||||
inactiveQueues = new ConcurrentLinkedDeque<>();
|
||||
if (TaskManager.IMP == null) return;
|
||||
TaskManager.IMP.repeat(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
boolean empty = (inactiveQueues.isEmpty() && activeQueues.isEmpty());
|
||||
boolean emptyTasks = tasks.isEmpty();
|
||||
if (emptyTasks && empty) {
|
||||
last = now;
|
||||
runEmptyTasks();
|
||||
return;
|
||||
}
|
||||
|
||||
targetTPS = 18 - Math.max(Settings.IMP.QUEUE.EXTRA_TIME_MS * 0.05, 0);
|
||||
|
||||
long diff = (50 + SetQueue.this.last) - (SetQueue.this.last = now);
|
||||
long absDiff = Math.abs(diff);
|
||||
if (diff == 0) {
|
||||
allocate = Math.min(50, allocate + 1);
|
||||
} else if (diff < 0) {
|
||||
allocate = Math.max(5, allocate + diff);
|
||||
} else if (!Fawe.get().getTimer().isAbove(targetTPS)) {
|
||||
allocate = Math.max(5, allocate - 1);
|
||||
}
|
||||
|
||||
long currentAllocate = allocate - absDiff;
|
||||
|
||||
if (!emptyTasks) {
|
||||
long taskAllocate = activeQueues.isEmpty() ? currentAllocate : 1 + (currentAllocate >> 1);
|
||||
long used = 0;
|
||||
boolean wait = false;
|
||||
do {
|
||||
Runnable task = tasks.poll();
|
||||
if (task == null) {
|
||||
if (wait) {
|
||||
synchronized (tasks) {
|
||||
tasks.wait(1);
|
||||
}
|
||||
task = tasks.poll();
|
||||
wait = false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (task != null) {
|
||||
task.run();
|
||||
wait = true;
|
||||
}
|
||||
} while ((used = System.currentTimeMillis() - now) < taskAllocate);
|
||||
currentAllocate -= used;
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
runEmptyTasks();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MemUtil.isMemoryFree()) {
|
||||
final int mem = MemUtil.calculateMemory();
|
||||
if (mem != Integer.MAX_VALUE) {
|
||||
allocate = Math.max(5, allocate - 1);
|
||||
if ((mem <= 1) && Settings.IMP.PREVENT_CRASHES) {
|
||||
for (FaweQueue queue : getAllQueues()) {
|
||||
queue.saveMemory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (SetQueue.this.forceChunkSet()) {
|
||||
System.gc();
|
||||
} else {
|
||||
SetQueue.this.runEmptyTasks();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FaweQueue queue = getNextQueue();
|
||||
if (queue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
long time = Settings.IMP.QUEUE.EXTRA_TIME_MS + currentAllocate - System.currentTimeMillis() + now;
|
||||
// Disable the async catcher as it can't discern async vs parallel
|
||||
boolean parallel = Settings.IMP.QUEUE.PARALLEL_THREADS > 1;
|
||||
queue.startSet(parallel);
|
||||
try {
|
||||
if (!queue.next(Settings.IMP.QUEUE.PARALLEL_THREADS, time) && queue.getStage() == QueueStage.ACTIVE) {
|
||||
queue.setStage(QueueStage.NONE);
|
||||
queue.runTasks();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
pool.awaitQuiescence(Settings.IMP.QUEUE.DISCARD_AFTER_MS, TimeUnit.MILLISECONDS);
|
||||
completer = new ExecutorCompletionService(pool);
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (pool.getQueuedSubmissionCount() != 0 || pool.getRunningThreadCount() != 0 || pool.getQueuedTaskCount() != 0) {
|
||||
// if (Fawe.get().isJava8())
|
||||
{
|
||||
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
// else {
|
||||
// pool.shutdown();
|
||||
// pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
// pool = new ForkJoinPool();
|
||||
// completer = new ExecutorCompletionService(pool);
|
||||
// }
|
||||
}
|
||||
queue.endSet(parallel);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
public QueueStage getStage(FaweQueue queue) {
|
||||
return queue.getStage();
|
||||
}
|
||||
|
||||
public boolean isStage(FaweQueue queue, QueueStage stage) {
|
||||
switch (stage) {
|
||||
case ACTIVE:
|
||||
return activeQueues.contains(queue);
|
||||
case INACTIVE:
|
||||
return inactiveQueues.contains(queue);
|
||||
case NONE:
|
||||
return !activeQueues.contains(queue) && !inactiveQueues.contains(queue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean enqueue(FaweQueue queue) {
|
||||
queue.setStage(QueueStage.ACTIVE);
|
||||
inactiveQueues.remove(queue);
|
||||
if (queue.size() > 0) {
|
||||
if (!activeQueues.contains(queue)) {
|
||||
queue.optimize();
|
||||
activeQueues.add(queue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void dequeue(FaweQueue queue) {
|
||||
queue.setStage(QueueStage.NONE);
|
||||
inactiveQueues.remove(queue);
|
||||
activeQueues.remove(queue);
|
||||
queue.runTasks();
|
||||
}
|
||||
|
||||
public Collection<FaweQueue> getAllQueues() {
|
||||
ArrayList<FaweQueue> list = new ArrayList<FaweQueue>(activeQueues.size() + inactiveQueues.size());
|
||||
list.addAll(inactiveQueues);
|
||||
list.addAll(activeQueues);
|
||||
return list;
|
||||
}
|
||||
|
||||
public Collection<FaweQueue> getActiveQueues() {
|
||||
return Collections.unmodifiableCollection(activeQueues);
|
||||
}
|
||||
|
||||
public Collection<FaweQueue> getInactiveQueues() {
|
||||
return Collections.unmodifiableCollection(inactiveQueues);
|
||||
}
|
||||
|
||||
public FaweQueue getNewQueue(World world, boolean fast, boolean autoqueue) {
|
||||
world = WorldWrapper.unwrap(world);
|
||||
if (world instanceof FaweQueue) return (FaweQueue) world;
|
||||
FaweQueue queue = Fawe.imp().getNewQueue(world, fast);
|
||||
if (autoqueue) {
|
||||
queue.setStage(QueueStage.INACTIVE);
|
||||
inactiveQueues.add(queue);
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
|
||||
public FaweQueue getNewQueue(String world, boolean fast, boolean autoqueue) {
|
||||
FaweQueue queue = Fawe.imp().getNewQueue(world, fast);
|
||||
if (autoqueue) {
|
||||
queue.setStage(QueueStage.INACTIVE);
|
||||
inactiveQueues.add(queue);
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
|
||||
public void flush(FaweQueue queue) {
|
||||
int parallelThreads;
|
||||
if (Fawe.get().isMainThread()) {
|
||||
parallelThreads = Settings.IMP.QUEUE.PARALLEL_THREADS;
|
||||
Settings.IMP.QUEUE.PARALLEL_THREADS = 1;
|
||||
} else {
|
||||
parallelThreads = 0;
|
||||
}
|
||||
try {
|
||||
queue.startSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1);
|
||||
queue.next(Settings.IMP.QUEUE.PARALLEL_THREADS, Long.MAX_VALUE);
|
||||
} catch (Throwable e) {
|
||||
pool.awaitQuiescence(Settings.IMP.QUEUE.DISCARD_AFTER_MS, TimeUnit.MILLISECONDS);
|
||||
completer = new ExecutorCompletionService(pool);
|
||||
MainUtil.handleError(e);
|
||||
} finally {
|
||||
queue.endSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1);
|
||||
queue.setStage(QueueStage.NONE);
|
||||
queue.runTasks();
|
||||
if (parallelThreads != 0) {
|
||||
Settings.IMP.QUEUE.PARALLEL_THREADS = parallelThreads;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FaweQueue getNextQueue() {
|
||||
long now = System.currentTimeMillis();
|
||||
while (!activeQueues.isEmpty()) {
|
||||
FaweQueue queue = activeQueues.peek();
|
||||
if (queue != null && queue.size() > 0) {
|
||||
queue.setModified(now);
|
||||
return queue;
|
||||
} else {
|
||||
queue.setStage(QueueStage.NONE);
|
||||
queue.runTasks();
|
||||
activeQueues.poll();
|
||||
}
|
||||
}
|
||||
int size = inactiveQueues.size();
|
||||
if (size > 0) {
|
||||
Iterator<FaweQueue> iter = inactiveQueues.iterator();
|
||||
try {
|
||||
int total = 0;
|
||||
FaweQueue firstNonEmpty = null;
|
||||
while (iter.hasNext()) {
|
||||
FaweQueue queue = iter.next();
|
||||
long age = now - queue.getModified();
|
||||
total += queue.size();
|
||||
if (queue.size() == 0) {
|
||||
if (age > Settings.IMP.QUEUE.DISCARD_AFTER_MS) {
|
||||
queue.setStage(QueueStage.NONE);
|
||||
queue.runTasks();
|
||||
iter.remove();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (firstNonEmpty == null) {
|
||||
firstNonEmpty = queue;
|
||||
}
|
||||
if (total > Settings.IMP.QUEUE.TARGET_SIZE) {
|
||||
firstNonEmpty.setModified(now);
|
||||
return firstNonEmpty;
|
||||
}
|
||||
if (age > Settings.IMP.QUEUE.MAX_WAIT_MS) {
|
||||
queue.setModified(now);
|
||||
return queue;
|
||||
}
|
||||
}
|
||||
} catch (ConcurrentModificationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean next() {
|
||||
while (activeQueues.size() > 0) {
|
||||
FaweQueue queue = activeQueues.poll();
|
||||
if (queue != null) {
|
||||
final boolean set = queue.next();
|
||||
if (set) {
|
||||
activeQueues.add(queue);
|
||||
return set;
|
||||
} else {
|
||||
queue.setStage(QueueStage.NONE);
|
||||
queue.runTasks();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inactiveQueues.size() > 0) {
|
||||
ArrayList<FaweQueue> tmp = new ArrayList<>(inactiveQueues);
|
||||
if (Settings.IMP.QUEUE.MAX_WAIT_MS != -1) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (lastSuccess == 0) {
|
||||
lastSuccess = now;
|
||||
}
|
||||
long diff = now - lastSuccess;
|
||||
if (diff > Settings.IMP.QUEUE.MAX_WAIT_MS) {
|
||||
for (FaweQueue queue : tmp) {
|
||||
boolean result = queue.next();
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (diff > Settings.IMP.QUEUE.DISCARD_AFTER_MS) {
|
||||
// These edits never finished
|
||||
for (FaweQueue queue : tmp) {
|
||||
queue.setStage(QueueStage.NONE);
|
||||
queue.runTasks();
|
||||
}
|
||||
inactiveQueues.clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (Settings.IMP.QUEUE.TARGET_SIZE != -1) {
|
||||
int total = 0;
|
||||
for (FaweQueue queue : tmp) {
|
||||
total += queue.size();
|
||||
}
|
||||
if (total > Settings.IMP.QUEUE.TARGET_SIZE) {
|
||||
for (FaweQueue queue : tmp) {
|
||||
boolean result = queue.next();
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean forceChunkSet() {
|
||||
return next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the this empty
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return activeQueues.size() == 0 && inactiveQueues.size() == 0;
|
||||
}
|
||||
|
||||
public void addTask(Runnable whenFree) {
|
||||
tasks.add(whenFree);
|
||||
synchronized (tasks) {
|
||||
tasks.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task to run when it is empty
|
||||
*
|
||||
* @param whenDone
|
||||
* @return
|
||||
*/
|
||||
public boolean addEmptyTask(final Runnable whenDone) {
|
||||
if (this.isEmpty()) {
|
||||
// Run
|
||||
this.runEmptyTasks();
|
||||
if (whenDone != null) {
|
||||
whenDone.run();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (whenDone != null) {
|
||||
this.emptyTasks.add(whenDone);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private synchronized boolean runEmptyTasks() {
|
||||
if (this.emptyTasks.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final ConcurrentLinkedDeque<Runnable> tmp = new ConcurrentLinkedDeque<>(this.emptyTasks);
|
||||
this.emptyTasks.clear();
|
||||
for (final Runnable runnable : tmp) {
|
||||
runnable.run();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,738 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.FlatteningPathIterator;
|
||||
import java.awt.geom.IllegalPathStateException;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* <a href="https://github.com/Sciss/ShapeInterpolator/blob/master/src/main/java/de/sciss/shapeint/ShapeInterpolator.java">Original source</a><br>
|
||||
* An interpolator for {@link Shape} objects.
|
||||
* This class can be used to morph between the geometries
|
||||
* of two relatively arbitrary shapes with the only restrictions being
|
||||
* that the two different numbers of sub-paths or two shapes with
|
||||
* disparate winding rules may not blend together in a pleasing
|
||||
* manner.
|
||||
* The ShapeEvaluator will do the best job it can if the shapes do
|
||||
* not match in winding rule or number of sub-paths, but the geometry
|
||||
* of the shapes may need to be adjusted by other means to make the
|
||||
* shapes more like each other for best aesthetic effect.
|
||||
* <p>
|
||||
* Note that the process of comparing two geometries and finding similar
|
||||
* structures between them to blend for the morphing operation can be
|
||||
* expensive.
|
||||
* Instances of this class will properly perform the necessary
|
||||
* geometric analysis of their arguments on every method call and attempt
|
||||
* to cache the information so that they can operate more quickly if called
|
||||
* multiple times in a row on the same pair of {@code Shape} objects.
|
||||
* As a result attempting to mutate a {@code Shape} object that is stored
|
||||
* in one of their keyframes may not have any effect if the associated
|
||||
* interpolator has already cached the geometry.
|
||||
* Also, it is advisable to use different instances of {@code ShapeEvaluator}
|
||||
* for every pair of keyframes being morphed so that the cached information
|
||||
* can be reused as much as possible.
|
||||
*/
|
||||
public class ShapeInterpolator {
|
||||
|
||||
private Shape savedV0;
|
||||
private Shape savedV1;
|
||||
private Geometry geom0;
|
||||
private Geometry geom1;
|
||||
|
||||
public static Shape apply(Shape v0, Shape v1, float fraction) {
|
||||
return apply(v0, v1, fraction, false);
|
||||
}
|
||||
|
||||
public static Shape apply(Shape v0, Shape v1, float fraction, boolean unionBounds) {
|
||||
final ShapeInterpolator instance = new ShapeInterpolator();
|
||||
return instance.evaluate(v0, v1, fraction, unionBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interpolated shape from tight bounds.
|
||||
*/
|
||||
public Shape evaluate(Shape v0, Shape v1, float fraction) {
|
||||
return evaluate(v0, v1, fraction, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interpolated shape.
|
||||
*
|
||||
* @param v0 the first shape
|
||||
* @param v1 the second shape
|
||||
* @param fraction the fraction from zero (just first shape) to one (just second shape)
|
||||
* @param unionBounds if `true`, the shape reports bounds which are the union of
|
||||
* the bounds of both shapes, if `false` it reports "tight" bounds
|
||||
* using the actual interpolated path.
|
||||
*/
|
||||
public Shape evaluate(Shape v0, Shape v1, float fraction, boolean unionBounds) {
|
||||
if (savedV0 != v0 || savedV1 != v1) {
|
||||
if (savedV0 == v1 && savedV1 == v0) {
|
||||
// Just swap the geometries
|
||||
final Geometry tmp = geom0;
|
||||
geom0 = geom1;
|
||||
geom1 = tmp;
|
||||
} else {
|
||||
recalculate(v0, v1);
|
||||
}
|
||||
savedV0 = v0;
|
||||
savedV1 = v1;
|
||||
}
|
||||
return getShape(fraction, unionBounds);
|
||||
}
|
||||
|
||||
private void recalculate(Shape v0, Shape v1) {
|
||||
geom0 = new Geometry(v0);
|
||||
geom1 = new Geometry(v1);
|
||||
final float[] tVals0 = geom0.getTVals();
|
||||
final float[] tVals1 = geom1.getTVals();
|
||||
final float[] masterTVals = mergeTVals(tVals0, tVals1);
|
||||
geom0.setTVals(masterTVals);
|
||||
geom1.setTVals(masterTVals);
|
||||
}
|
||||
|
||||
private Shape getShape(float fraction, boolean unionBounds) {
|
||||
return new MorphedShape(geom0, geom1, fraction, unionBounds);
|
||||
}
|
||||
|
||||
private static float[] mergeTVals(float[] tVals0, float[] tVals1) {
|
||||
final int count = sortTVals(tVals0, tVals1, null);
|
||||
final float[] newTVals = new float[count];
|
||||
sortTVals(tVals0, tVals1, newTVals);
|
||||
return newTVals;
|
||||
}
|
||||
|
||||
private static int sortTVals(float[] tVals0,
|
||||
float[] tVals1,
|
||||
float[] newTVals) {
|
||||
int i0 = 0;
|
||||
int i1 = 0;
|
||||
int numTVals = 0;
|
||||
while (i0 < tVals0.length && i1 < tVals1.length) {
|
||||
final float t0 = tVals0[i0];
|
||||
final float t1 = tVals1[i1];
|
||||
if (t0 <= t1) {
|
||||
if (newTVals != null) {
|
||||
newTVals[numTVals] = t0;
|
||||
}
|
||||
i0++;
|
||||
}
|
||||
if (t1 <= t0) {
|
||||
if (newTVals != null) {
|
||||
newTVals[numTVals] = t1;
|
||||
}
|
||||
i1++;
|
||||
}
|
||||
numTVals++;
|
||||
}
|
||||
return numTVals;
|
||||
}
|
||||
|
||||
private static float interp(float v0, float v1, float t) {
|
||||
return (v0 + ((v1 - v0) * t));
|
||||
}
|
||||
|
||||
private static class Geometry {
|
||||
static final float THIRD = (1f / 3f);
|
||||
static final float MIN_LEN = 0.001f;
|
||||
|
||||
final int windingRule;
|
||||
float[] bezierCoordinates;
|
||||
int numCoordinates;
|
||||
float[] myTVals;
|
||||
|
||||
public Geometry(Shape s) {
|
||||
// Multiple of 6 plus 2 more for initial move-to
|
||||
bezierCoordinates = new float[20];
|
||||
final PathIterator pi = s.getPathIterator(null);
|
||||
windingRule = pi.getWindingRule();
|
||||
if (pi.isDone()) {
|
||||
// We will have 1 segment and it will be all zeros
|
||||
// It will have 8 coordinates (2 for move-to, 6 for cubic)
|
||||
numCoordinates = 8;
|
||||
}
|
||||
final float[] coordinates = new float[6];
|
||||
int type = pi.currentSegment(coordinates);
|
||||
pi.next();
|
||||
if (type != PathIterator.SEG_MOVETO) {
|
||||
throw new IllegalPathStateException("missing initial move-to");
|
||||
}
|
||||
float curX, curY, movX, movY;
|
||||
bezierCoordinates[0] = curX = movX = coordinates[0];
|
||||
bezierCoordinates[1] = curY = movY = coordinates[1];
|
||||
float newX, newY;
|
||||
final Vector<Point2D.Float> savedPathEndPoints = new Vector<Point2D.Float>();
|
||||
numCoordinates = 2;
|
||||
while (!pi.isDone()) {
|
||||
switch (pi.currentSegment(coordinates)) {
|
||||
case PathIterator.SEG_MOVETO:
|
||||
if (curX != movX || curY != movY) {
|
||||
appendLineTo(curX, curY, movX, movY);
|
||||
curX = movX;
|
||||
curY = movY;
|
||||
}
|
||||
newX = coordinates[0];
|
||||
newY = coordinates[1];
|
||||
if (curX != newX || curY != newY) {
|
||||
savedPathEndPoints.add(new Point2D.Float(movX, movY));
|
||||
appendLineTo(curX, curY, newX, newY);
|
||||
curX = movX = newX;
|
||||
curY = movY = newY;
|
||||
}
|
||||
break;
|
||||
case PathIterator.SEG_CLOSE:
|
||||
if (curX != movX || curY != movY) {
|
||||
appendLineTo(curX, curY, movX, movY);
|
||||
curX = movX;
|
||||
curY = movY;
|
||||
}
|
||||
break;
|
||||
case PathIterator.SEG_LINETO:
|
||||
newX = coordinates[0];
|
||||
newY = coordinates[1];
|
||||
appendLineTo(curX, curY, newX, newY);
|
||||
curX = newX;
|
||||
curY = newY;
|
||||
break;
|
||||
case PathIterator.SEG_QUADTO:
|
||||
final float ctrlX = coordinates[0];
|
||||
final float ctrlY = coordinates[1];
|
||||
newX = coordinates[2];
|
||||
newY = coordinates[3];
|
||||
appendQuadTo(curX, curY, ctrlX, ctrlY, newX, newY);
|
||||
curX = newX;
|
||||
curY = newY;
|
||||
break;
|
||||
case PathIterator.SEG_CUBICTO:
|
||||
newX = coordinates[4];
|
||||
newY = coordinates[5];
|
||||
appendCubicTo(
|
||||
coordinates[0], coordinates[1],
|
||||
coordinates[2], coordinates[3],
|
||||
newX, newY);
|
||||
curX = newX;
|
||||
curY = newY;
|
||||
break;
|
||||
}
|
||||
pi.next();
|
||||
}
|
||||
// Add closing segment if either:
|
||||
// - we only have initial move-to - expand it to an empty cubic
|
||||
// - or we are not back to the starting point
|
||||
if ((numCoordinates < 8) || curX != movX || curY != movY) {
|
||||
appendLineTo(curX, curY, movX, movY);
|
||||
curX = movX;
|
||||
curY = movY;
|
||||
}
|
||||
// Now retrace our way back through all of the connecting
|
||||
// inter-sub-path segments
|
||||
for (int i = savedPathEndPoints.size() - 1; i >= 0; i--) {
|
||||
final Point2D.Float p = savedPathEndPoints.get(i);
|
||||
newX = p.x;
|
||||
newY = p.y;
|
||||
if (curX != newX || curY != newY) {
|
||||
appendLineTo(curX, curY, newX, newY);
|
||||
curX = newX;
|
||||
curY = newY;
|
||||
}
|
||||
}
|
||||
// Now find the segment endpoint with the smallest Y coordinate
|
||||
int minPt = 0;
|
||||
float minX = bezierCoordinates[0];
|
||||
float minY = bezierCoordinates[1];
|
||||
for (int ci = 6; ci < numCoordinates; ci += 6) {
|
||||
float x = bezierCoordinates[ci];
|
||||
float y = bezierCoordinates[ci + 1];
|
||||
if (y < minY || (y == minY && x < minX)) {
|
||||
minPt = ci;
|
||||
minX = x;
|
||||
minY = y;
|
||||
}
|
||||
}
|
||||
// If the smallest Y coordinate is not the first coordinate,
|
||||
// rotate the points so that it is...
|
||||
if (minPt > 0) {
|
||||
// Keep in mind that first 2 coordinates == last 2 coordinates
|
||||
final float[] newCoordinates = new float[numCoordinates];
|
||||
// Copy all coordinates from minPt to the end of the
|
||||
// array to the beginning of the new array
|
||||
System.arraycopy(bezierCoordinates, minPt,
|
||||
newCoordinates, 0,
|
||||
numCoordinates - minPt);
|
||||
// Now we do not want to copy 0,1 as they are duplicates
|
||||
// of the last 2 coordinates which we just copied. So
|
||||
// we start the source copy at index 2, but we still
|
||||
// copy a full minPt coordinates which copies the two
|
||||
// coordinates that were at minPt to the last two elements
|
||||
// of the array, thus ensuring that thew new array starts
|
||||
// and ends with the same pair of coordinates...
|
||||
System.arraycopy(bezierCoordinates, 2,
|
||||
newCoordinates, numCoordinates - minPt,
|
||||
minPt);
|
||||
bezierCoordinates = newCoordinates;
|
||||
}
|
||||
/* Clockwise enforcement:
|
||||
* - This technique is based on the formula for calculating
|
||||
* the area of a Polygon. The standard formula is:
|
||||
* Area(Poly) = 1/2 * sum(x[i]*y[i+1] - x[i+1]y[i])
|
||||
* - The returned area is negative if the polygon is
|
||||
* "mostly clockwise" and positive if the polygon is
|
||||
* "mostly counter-clockwise".
|
||||
* - One failure mode of the Area calculation is if the
|
||||
* Polygon is self-intersecting. This is due to the
|
||||
* fact that the areas on each side of the self-intersection
|
||||
* are bounded by segments which have opposite winding
|
||||
* direction. Thus, those areas will have opposite signs
|
||||
* on the accumulation of their area summations and end
|
||||
* up canceling each other out partially.
|
||||
* - This failure mode of the algorithm in determining the
|
||||
* exact magnitude of the area is not actually a big problem
|
||||
* for our needs here since we are only using the sign of
|
||||
* the resulting area to figure out the overall winding
|
||||
* direction of the path. If self-intersections cause
|
||||
* different parts of the path to disagree as to the
|
||||
* local winding direction, that is no matter as we just
|
||||
* wait for the final answer to tell us which winding
|
||||
* direction had greater representation. If the final
|
||||
* result is zero then the path was equal parts clockwise
|
||||
* and counter-clockwise and we do not care about which
|
||||
* way we order it as either way will require half of the
|
||||
* path to unwind and re-wind itself.
|
||||
*/
|
||||
float area = 0;
|
||||
// Note that first and last points are the same so we
|
||||
// do not need to process coordinates[0,1] against coordinates[n-2,n-1]
|
||||
curX = bezierCoordinates[0];
|
||||
curY = bezierCoordinates[1];
|
||||
for (int i = 2; i < numCoordinates; i += 2) {
|
||||
newX = bezierCoordinates[i];
|
||||
newY = bezierCoordinates[i + 1];
|
||||
area += curX * newY - newX * curY;
|
||||
curX = newX;
|
||||
curY = newY;
|
||||
}
|
||||
if (area < 0) {
|
||||
/* The area is negative so the shape was clockwise
|
||||
* in a Euclidean sense. But, our screen coordinate
|
||||
* systems have the origin in the upper left so they
|
||||
* are flipped. Thus, this path "looks" ccw on the
|
||||
* screen so we are flipping it to "look" clockwise.
|
||||
* Note that the first and last points are the same
|
||||
* so we do not need to swap them.
|
||||
* (Not that it matters whether the paths end up cw
|
||||
* or ccw in the end as long as all of them are the
|
||||
* same, but above we called this section "Clockwise
|
||||
* Enforcement", so we do not want to be liars. ;-)
|
||||
*/
|
||||
// Note that [0,1] do not need to be swapped with [n-2,n-1]
|
||||
// So first pair to swap is [2,3] and [n-4,n-3]
|
||||
int i = 2;
|
||||
int j = numCoordinates - 4;
|
||||
while (i < j) {
|
||||
curX = bezierCoordinates[i];
|
||||
curY = bezierCoordinates[i + 1];
|
||||
bezierCoordinates[i] = bezierCoordinates[j];
|
||||
bezierCoordinates[i + 1] = bezierCoordinates[j + 1];
|
||||
bezierCoordinates[j] = curX;
|
||||
bezierCoordinates[j + 1] = curY;
|
||||
i += 2;
|
||||
j -= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void appendLineTo(float x0, float y0,
|
||||
float x1, float y1) {
|
||||
appendCubicTo(// A third of the way from xy0 to xy1:
|
||||
interp(x0, x1, THIRD),
|
||||
interp(y0, y1, THIRD),
|
||||
// A third of the way from xy1 back to xy0:
|
||||
interp(x1, x0, THIRD),
|
||||
interp(y1, y0, THIRD),
|
||||
x1, y1);
|
||||
}
|
||||
|
||||
private void appendQuadTo(float x0, float y0,
|
||||
float ctrlX, float ctrlY,
|
||||
float x1, float y1) {
|
||||
appendCubicTo(// A third of the way from ctrl X/Y back to xy0:
|
||||
interp(ctrlX, x0, THIRD),
|
||||
interp(ctrlY, y0, THIRD),
|
||||
// A third of the way from ctrl X/Y to xy1:
|
||||
interp(ctrlX, x1, THIRD),
|
||||
interp(ctrlY, y1, THIRD),
|
||||
x1, y1);
|
||||
}
|
||||
|
||||
private void appendCubicTo(float ctrlX1, float ctrlY1,
|
||||
float ctrlX2, float ctrlY2,
|
||||
float x1, float y1) {
|
||||
if (numCoordinates + 6 > bezierCoordinates.length) {
|
||||
// Keep array size to a multiple of 6 plus 2
|
||||
int newsize = (numCoordinates - 2) * 2 + 2;
|
||||
final float[] newCoordinates = new float[newsize];
|
||||
System.arraycopy(bezierCoordinates, 0, newCoordinates, 0, numCoordinates);
|
||||
bezierCoordinates = newCoordinates;
|
||||
}
|
||||
bezierCoordinates[numCoordinates++] = ctrlX1;
|
||||
bezierCoordinates[numCoordinates++] = ctrlY1;
|
||||
bezierCoordinates[numCoordinates++] = ctrlX2;
|
||||
bezierCoordinates[numCoordinates++] = ctrlY2;
|
||||
bezierCoordinates[numCoordinates++] = x1;
|
||||
bezierCoordinates[numCoordinates++] = y1;
|
||||
}
|
||||
|
||||
public int getWindingRule() {
|
||||
return windingRule;
|
||||
}
|
||||
|
||||
public int getNumCoordinates() {
|
||||
return numCoordinates;
|
||||
}
|
||||
|
||||
public float getCoordinate(int i) {
|
||||
return bezierCoordinates[i];
|
||||
}
|
||||
|
||||
public float[] getTVals() {
|
||||
if (myTVals != null) {
|
||||
return myTVals;
|
||||
}
|
||||
|
||||
// assert(numCoordinates >= 8);
|
||||
// assert(((numCoordinates - 2) % 6) == 0);
|
||||
final float[] tVals = new float[(numCoordinates - 2) / 6 + 1];
|
||||
|
||||
// First calculate total "length" of path
|
||||
// Length of each segment is averaged between
|
||||
// the length between the endpoints (a lower bound for a cubic)
|
||||
// and the length of the control polygon (an upper bound)
|
||||
float segX = bezierCoordinates[0];
|
||||
float segY = bezierCoordinates[1];
|
||||
float tLen = 0;
|
||||
int ci = 2;
|
||||
int ti = 0;
|
||||
while (ci < numCoordinates) {
|
||||
float prevX, prevY, newX, newY;
|
||||
prevX = segX;
|
||||
prevY = segY;
|
||||
newX = bezierCoordinates[ci++];
|
||||
newY = bezierCoordinates[ci++];
|
||||
prevX -= newX;
|
||||
prevY -= newY;
|
||||
float len = (float) Math.sqrt(prevX * prevX + prevY * prevY);
|
||||
prevX = newX;
|
||||
prevY = newY;
|
||||
newX = bezierCoordinates[ci++];
|
||||
newY = bezierCoordinates[ci++];
|
||||
prevX -= newX;
|
||||
prevY -= newY;
|
||||
len += (float) Math.sqrt(prevX * prevX + prevY * prevY);
|
||||
prevX = newX;
|
||||
prevY = newY;
|
||||
newX = bezierCoordinates[ci++];
|
||||
newY = bezierCoordinates[ci++];
|
||||
prevX -= newX;
|
||||
prevY -= newY;
|
||||
len += (float) Math.sqrt(prevX * prevX + prevY * prevY);
|
||||
// len is now the total length of the control polygon
|
||||
segX -= newX;
|
||||
segY -= newY;
|
||||
len += (float) Math.sqrt(segX * segX + segY * segY);
|
||||
// len is now sum of linear length and control polygon length
|
||||
len /= 2;
|
||||
// len is now average of the two lengths
|
||||
|
||||
/* If the result is zero length then we will have problems
|
||||
* below trying to do the math and bookkeeping to split
|
||||
* the segment or pair it against the segments in the
|
||||
* other shape. Since these lengths are just estimates
|
||||
* to map the segments of the two shapes onto corresponding
|
||||
* segments of "approximately the same length", we will
|
||||
* simply modify the length of this segment to be at least
|
||||
* a minimum value and it will simply grow from zero or
|
||||
* near zero length to a non-trivial size as it morphs.
|
||||
*/
|
||||
if (len < MIN_LEN) {
|
||||
len = MIN_LEN;
|
||||
}
|
||||
tLen += len;
|
||||
tVals[ti++] = tLen;
|
||||
segX = newX;
|
||||
segY = newY;
|
||||
}
|
||||
|
||||
// Now set tVals for each segment to its proportional
|
||||
// part of the length
|
||||
float prevT = tVals[0];
|
||||
tVals[0] = 0;
|
||||
for (ti = 1; ti < tVals.length - 1; ti++) {
|
||||
final float nextT = tVals[ti];
|
||||
tVals[ti] = prevT / tLen;
|
||||
prevT = nextT;
|
||||
}
|
||||
tVals[ti] = 1;
|
||||
return (myTVals = tVals);
|
||||
}
|
||||
|
||||
public void setTVals(float[] newTVals) {
|
||||
final float[] oldCoordinates = bezierCoordinates;
|
||||
final float[] newCoordinates = new float[2 + (newTVals.length - 1) * 6];
|
||||
final float[] oldTVals = getTVals();
|
||||
int oldCi = 0;
|
||||
float x0, xc0, xc1, x1;
|
||||
float y0, yc0, yc1, y1;
|
||||
x0 = xc0 = xc1 = x1 = oldCoordinates[oldCi++];
|
||||
y0 = yc0 = yc1 = y1 = oldCoordinates[oldCi++];
|
||||
int newCi = 0;
|
||||
newCoordinates[newCi++] = x0;
|
||||
newCoordinates[newCi++] = y0;
|
||||
float t0 = 0;
|
||||
float t1 = 0;
|
||||
int oldTi = 1;
|
||||
int newTi = 1;
|
||||
while (newTi < newTVals.length) {
|
||||
if (t0 >= t1) {
|
||||
x0 = x1;
|
||||
y0 = y1;
|
||||
xc0 = oldCoordinates[oldCi++];
|
||||
yc0 = oldCoordinates[oldCi++];
|
||||
xc1 = oldCoordinates[oldCi++];
|
||||
yc1 = oldCoordinates[oldCi++];
|
||||
x1 = oldCoordinates[oldCi++];
|
||||
y1 = oldCoordinates[oldCi++];
|
||||
t1 = oldTVals[oldTi++];
|
||||
}
|
||||
float nt = newTVals[newTi++];
|
||||
// assert(nt > t0);
|
||||
if (nt < t1) {
|
||||
// Make nt proportional to [t0 => t1] range
|
||||
float relT = (nt - t0) / (t1 - t0);
|
||||
newCoordinates[newCi++] = x0 = interp(x0, xc0, relT);
|
||||
newCoordinates[newCi++] = y0 = interp(y0, yc0, relT);
|
||||
xc0 = interp(xc0, xc1, relT);
|
||||
yc0 = interp(yc0, yc1, relT);
|
||||
xc1 = interp(xc1, x1, relT);
|
||||
yc1 = interp(yc1, y1, relT);
|
||||
newCoordinates[newCi++] = x0 = interp(x0, xc0, relT);
|
||||
newCoordinates[newCi++] = y0 = interp(y0, yc0, relT);
|
||||
xc0 = interp(xc0, xc1, relT);
|
||||
yc0 = interp(yc0, yc1, relT);
|
||||
newCoordinates[newCi++] = x0 = interp(x0, xc0, relT);
|
||||
newCoordinates[newCi++] = y0 = interp(y0, yc0, relT);
|
||||
} else {
|
||||
newCoordinates[newCi++] = xc0;
|
||||
newCoordinates[newCi++] = yc0;
|
||||
newCoordinates[newCi++] = xc1;
|
||||
newCoordinates[newCi++] = yc1;
|
||||
newCoordinates[newCi++] = x1;
|
||||
newCoordinates[newCi++] = y1;
|
||||
}
|
||||
t0 = nt;
|
||||
}
|
||||
bezierCoordinates = newCoordinates;
|
||||
numCoordinates = newCoordinates.length;
|
||||
myTVals = newTVals;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MorphedShape implements Shape {
|
||||
final Geometry geom0;
|
||||
final Geometry geom1;
|
||||
final float t;
|
||||
final boolean unionBounds;
|
||||
|
||||
MorphedShape(Geometry geom0, Geometry geom1, float t, boolean unionBounds) {
|
||||
this.geom0 = geom0;
|
||||
this.geom1 = geom1;
|
||||
this.t = t;
|
||||
this.unionBounds = unionBounds;
|
||||
}
|
||||
|
||||
public Rectangle getBounds() {
|
||||
return getBounds2D().getBounds();
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D() {
|
||||
final int n = geom0.getNumCoordinates();
|
||||
float xMin, yMin, xMax, yMax;
|
||||
|
||||
if (unionBounds) {
|
||||
xMin = xMax = geom0.getCoordinate(0);
|
||||
yMin = yMax = geom0.getCoordinate(1);
|
||||
for (int i = 2; i < n; i += 2) {
|
||||
final float x = geom0.getCoordinate(i);
|
||||
final float y = geom0.getCoordinate(i + 1);
|
||||
if (xMin > x) {
|
||||
xMin = x;
|
||||
}
|
||||
if (yMin > y) {
|
||||
yMin = y;
|
||||
}
|
||||
if (xMax < x) {
|
||||
xMax = x;
|
||||
}
|
||||
if (yMax < y) {
|
||||
yMax = y;
|
||||
}
|
||||
}
|
||||
final int m = geom1.getNumCoordinates();
|
||||
for (int i = 0; i < m; i += 2) {
|
||||
final float x = geom1.getCoordinate(i);
|
||||
final float y = geom1.getCoordinate(i + 1);
|
||||
if (xMin > x) {
|
||||
xMin = x;
|
||||
}
|
||||
if (yMin > y) {
|
||||
yMin = y;
|
||||
}
|
||||
if (xMax < x) {
|
||||
xMax = x;
|
||||
}
|
||||
if (yMax < y) {
|
||||
yMax = y;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
xMin = xMax = interp(geom0.getCoordinate(0), geom1.getCoordinate(0), t);
|
||||
yMin = yMax = interp(geom0.getCoordinate(1), geom1.getCoordinate(1), t);
|
||||
for (int i = 2; i < n; i += 2) {
|
||||
final float x = interp(geom0.getCoordinate(i), geom1.getCoordinate(i), t);
|
||||
final float y = interp(geom0.getCoordinate(i + 1), geom1.getCoordinate(i + 1), t);
|
||||
if (xMin > x) {
|
||||
xMin = x;
|
||||
}
|
||||
if (yMin > y) {
|
||||
yMin = y;
|
||||
}
|
||||
if (xMax < x) {
|
||||
xMax = x;
|
||||
}
|
||||
if (yMax < y) {
|
||||
yMax = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Rectangle2D.Float(xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
}
|
||||
|
||||
public boolean contains(double x, double y) {
|
||||
return Path2D.contains(getPathIterator(null), x, y);
|
||||
}
|
||||
|
||||
public boolean contains(Point2D p) {
|
||||
return Path2D.contains(getPathIterator(null), p);
|
||||
}
|
||||
|
||||
public boolean intersects(double x, double y, double w, double h) {
|
||||
return Path2D.intersects(getPathIterator(null), x, y, w, h);
|
||||
}
|
||||
|
||||
public boolean intersects(Rectangle2D r) {
|
||||
return Path2D.intersects(getPathIterator(null), r);
|
||||
}
|
||||
|
||||
public boolean contains(double x, double y, double width, double height) {
|
||||
return Path2D.contains(getPathIterator(null), x, y, width, height);
|
||||
}
|
||||
|
||||
public boolean contains(Rectangle2D r) {
|
||||
return Path2D.contains(getPathIterator(null), r);
|
||||
}
|
||||
|
||||
public PathIterator getPathIterator(AffineTransform at) {
|
||||
return new Iterator(at, geom0, geom1, t);
|
||||
}
|
||||
|
||||
public PathIterator getPathIterator(AffineTransform at, double flatness) {
|
||||
return new FlatteningPathIterator(getPathIterator(at), flatness);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Iterator implements PathIterator {
|
||||
AffineTransform at;
|
||||
Geometry g0;
|
||||
Geometry g1;
|
||||
float t;
|
||||
int cIndex;
|
||||
|
||||
public Iterator(AffineTransform at,
|
||||
Geometry g0, Geometry g1,
|
||||
float t) {
|
||||
this.at = at;
|
||||
this.g0 = g0;
|
||||
this.g1 = g1;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @{inheritDoc}
|
||||
*/
|
||||
public int getWindingRule() {
|
||||
return (t < 0.5 ? g0.getWindingRule() : g1.getWindingRule());
|
||||
}
|
||||
|
||||
/**
|
||||
* @{inheritDoc}
|
||||
*/
|
||||
public boolean isDone() {
|
||||
return (cIndex > g0.getNumCoordinates());
|
||||
}
|
||||
|
||||
/**
|
||||
* @{inheritDoc}
|
||||
*/
|
||||
public void next() {
|
||||
if (cIndex == 0) {
|
||||
cIndex = 2;
|
||||
} else {
|
||||
cIndex += 6;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @{inheritDoc}
|
||||
*/
|
||||
public int currentSegment(float[] coordinates) {
|
||||
int type;
|
||||
int n;
|
||||
if (cIndex == 0) {
|
||||
type = SEG_MOVETO;
|
||||
n = 2;
|
||||
} else if (cIndex >= g0.getNumCoordinates()) {
|
||||
type = SEG_CLOSE;
|
||||
n = 0;
|
||||
} else {
|
||||
type = SEG_CUBICTO;
|
||||
n = 6;
|
||||
}
|
||||
if (n > 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
coordinates[i] = interp(
|
||||
g0.getCoordinate(cIndex + i),
|
||||
g1.getCoordinate(cIndex + i),
|
||||
t);
|
||||
}
|
||||
if (at != null) {
|
||||
at.transform(coordinates, 0, coordinates, 0, n / 2);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public int currentSegment(double[] coordinates) {
|
||||
final float[] temp = new float[6];
|
||||
final int res = currentSegment(temp);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
coordinates[i] = temp[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
453
worldedit-core/src/main/java/com/boydti/fawe/util/StringMan.java
Normal file
453
worldedit-core/src/main/java/com/boydti/fawe/util/StringMan.java
Normal file
@ -0,0 +1,453 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class StringMan {
|
||||
public static String replaceFromMap(final String string, final Map<String, String> replacements) {
|
||||
final StringBuilder sb = new StringBuilder(string);
|
||||
int size = string.length();
|
||||
for (final Entry<String, String> entry : replacements.entrySet()) {
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
final String key = entry.getKey();
|
||||
final String value = entry.getValue();
|
||||
int start = sb.indexOf(key, 0);
|
||||
while (start > -1) {
|
||||
final int end = start + key.length();
|
||||
final int nextSearchStart = start + value.length();
|
||||
sb.replace(start, end, value);
|
||||
size -= end - start;
|
||||
start = sb.indexOf(key, nextSearchStart);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static int findMatchingBracket(CharSequence sequence, int index) {
|
||||
char startC = sequence.charAt(index);
|
||||
char lookC = getMatchingBracket(startC);
|
||||
if (lookC == startC) return -1;
|
||||
boolean forward = isBracketForwards(startC);
|
||||
int increment = forward ? 1 : -1;
|
||||
int end = forward ? sequence.length() : -1;
|
||||
int count = 0;
|
||||
for (int i = index + increment; i != end; i += increment) {
|
||||
char c = sequence.charAt(i);
|
||||
if (c == startC) {
|
||||
count++;
|
||||
} else if (c == lookC && count-- == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean isBracketForwards(char c) {
|
||||
switch (c) {
|
||||
case '[':
|
||||
case '(':
|
||||
case '{':
|
||||
case '<':
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static char getMatchingBracket(char c) {
|
||||
switch (c) {
|
||||
case '[': return ']';
|
||||
case '(': return ')';
|
||||
case '{': return '}';
|
||||
case '<': return '>';
|
||||
case ']': return '[';
|
||||
case ')': return '(';
|
||||
case '}': return '{';
|
||||
case '>': return '<';
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
public static int parseInt(CharSequence string) {
|
||||
int val = 0;
|
||||
boolean neg = false;
|
||||
int numIndex = 1;
|
||||
int len = string.length();
|
||||
outer:
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
char c = string.charAt(i);
|
||||
switch (c) {
|
||||
case '-':
|
||||
val = -val;
|
||||
break;
|
||||
default:
|
||||
val = val + (c - 48) * numIndex;
|
||||
numIndex *= 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public static String removeFromSet(final String string, final Collection<String> replacements) {
|
||||
final StringBuilder sb = new StringBuilder(string);
|
||||
int size = string.length();
|
||||
for (final String key : replacements) {
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
int start = sb.indexOf(key, 0);
|
||||
while (start > -1) {
|
||||
final int end = start + key.length();
|
||||
final int nextSearchStart = start + 0;
|
||||
sb.delete(start, end);
|
||||
size -= end - start;
|
||||
start = sb.indexOf(key, nextSearchStart);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static int indexOf(String input, int start, char... values) {
|
||||
for (int i = start; i < input.length(); i++) {
|
||||
for (char c : values) {
|
||||
if (c == input.charAt(i)) return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static String toProperCase(String s) {
|
||||
return s.substring(0, 1).toUpperCase() +
|
||||
s.substring(1);
|
||||
}
|
||||
|
||||
public static List<String> split(String input, char delim) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
int start = 0;
|
||||
int bracket = 0;
|
||||
boolean inQuotes = false;
|
||||
for (int current = 0; current < input.length(); current++) {
|
||||
char currentChar = input.charAt(current);
|
||||
boolean atLastChar = (current == input.length() - 1);
|
||||
if (!atLastChar && (bracket > 0 || (currentChar == '{' && ++bracket > 0) || (current == '}' && --bracket <= 0)))
|
||||
continue;
|
||||
if (currentChar == '\"') inQuotes = !inQuotes; // toggle state
|
||||
if (atLastChar) result.add(input.substring(start));
|
||||
else if (currentChar == delim && !inQuotes) {
|
||||
String toAdd = input.substring(start, current);
|
||||
if (toAdd.startsWith("\"")) {
|
||||
toAdd = toAdd.substring(1, toAdd.length() - 1);
|
||||
}
|
||||
result.add(toAdd);
|
||||
start = current + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int intersection(final Set<String> options, final String[] toCheck) {
|
||||
int count = 0;
|
||||
for (final String check : toCheck) {
|
||||
if (options.contains(check)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static String padRight(String s, int n) {
|
||||
return String.format("%1$-" + n + "s", s);
|
||||
}
|
||||
|
||||
public static String padLeft(String s, int n) {
|
||||
return String.format("%1$" + n + "s", s);
|
||||
}
|
||||
|
||||
public static String getString(final Object obj) {
|
||||
if (obj == null) {
|
||||
return "null";
|
||||
}
|
||||
if (obj.getClass() == String.class) {
|
||||
return (String) obj;
|
||||
}
|
||||
if (obj.getClass().isArray()) {
|
||||
String result = "";
|
||||
String prefix = "";
|
||||
|
||||
for (int i = 0; i < Array.getLength(obj); i++) {
|
||||
result += prefix + getString(Array.get(obj, i));
|
||||
prefix = ",";
|
||||
}
|
||||
return "{ " + result + " }";
|
||||
} else if (obj instanceof Collection<?>) {
|
||||
String result = "";
|
||||
String prefix = "";
|
||||
for (final Object element : (Collection<?>) obj) {
|
||||
result += prefix + getString(element);
|
||||
prefix = ",";
|
||||
}
|
||||
return "( " + result + " )";
|
||||
} else {
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static String replaceFirst(final char c, final String s) {
|
||||
if (s == null) {
|
||||
return "";
|
||||
}
|
||||
if (s.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
char[] chars = s.toCharArray();
|
||||
final char[] newChars = new char[chars.length];
|
||||
int used = 0;
|
||||
boolean found = false;
|
||||
for (final char cc : chars) {
|
||||
if (!found && (c == cc)) {
|
||||
found = true;
|
||||
} else {
|
||||
newChars[used++] = cc;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
chars = new char[newChars.length - 1];
|
||||
System.arraycopy(newChars, 0, chars, 0, chars.length);
|
||||
return String.valueOf(chars);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String replaceAll(final String string, final Object... pairs) {
|
||||
final StringBuilder sb = new StringBuilder(string);
|
||||
for (int i = 0; i < pairs.length; i += 2) {
|
||||
final String key = pairs[i] + "";
|
||||
final String value = pairs[i + 1] + "";
|
||||
int start = sb.indexOf(key, 0);
|
||||
while (start > -1) {
|
||||
final int end = start + key.length();
|
||||
final int nextSearchStart = start + value.length();
|
||||
sb.replace(start, end, value);
|
||||
start = sb.indexOf(key, nextSearchStart);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean isAlphanumeric(final String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
final char c = str.charAt(i);
|
||||
if ((c < 0x30) || ((c >= 0x3a) && (c <= 0x40)) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isAlphanumericUnd(final CharSequence str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
final char c = str.charAt(i);
|
||||
if ((c < 0x30) || ((c >= 0x3a) && (c <= 0x40)) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a) || (c == '_')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isAlpha(final String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
final char c = str.charAt(i);
|
||||
if ((c <= 0x40) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String join(final Collection<?> collection, final String delimiter) {
|
||||
return join(collection.toArray(), delimiter);
|
||||
}
|
||||
|
||||
public static String joinOrdered(final Collection<?> collection, final String delimiter) {
|
||||
final Object[] array = collection.toArray();
|
||||
Arrays.sort(array, new Comparator<Object>() {
|
||||
@Override
|
||||
public int compare(final Object a, final Object b) {
|
||||
return a.hashCode() - b.hashCode();
|
||||
}
|
||||
|
||||
});
|
||||
return join(array, delimiter);
|
||||
}
|
||||
|
||||
public static String join(final Collection<?> collection, final char delimiter) {
|
||||
return join(collection.toArray(), delimiter + "");
|
||||
}
|
||||
|
||||
public static boolean isAsciiPrintable(final char c) {
|
||||
return (c >= ' ') && (c < '');
|
||||
}
|
||||
|
||||
public static boolean isAsciiPrintable(final String s) {
|
||||
for (final char c : s.toCharArray()) {
|
||||
if (!isAsciiPrintable(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int getLevenshteinDistance(String s, String t) {
|
||||
int n = s.length();
|
||||
int m = t.length();
|
||||
if (n == 0) {
|
||||
return m;
|
||||
} else if (m == 0) {
|
||||
return n;
|
||||
}
|
||||
if (n > m) {
|
||||
final String tmp = s;
|
||||
s = t;
|
||||
t = tmp;
|
||||
n = m;
|
||||
m = t.length();
|
||||
}
|
||||
int p[] = new int[n + 1];
|
||||
int d[] = new int[n + 1];
|
||||
int _d[];
|
||||
int i;
|
||||
int j;
|
||||
char t_j;
|
||||
int cost;
|
||||
for (i = 0; i <= n; i++) {
|
||||
p[i] = i;
|
||||
}
|
||||
for (j = 1; j <= m; j++) {
|
||||
t_j = t.charAt(j - 1);
|
||||
d[0] = j;
|
||||
|
||||
for (i = 1; i <= n; i++) {
|
||||
cost = s.charAt(i - 1) == t_j ? 0 : 1;
|
||||
d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
|
||||
}
|
||||
_d = p;
|
||||
p = d;
|
||||
d = _d;
|
||||
}
|
||||
return p[n];
|
||||
}
|
||||
|
||||
public static <T> String join(Collection<T> arr, final String delimiter, Function<T, String> funx) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
for (T obj : arr) {
|
||||
if (i > 0) {
|
||||
result.append(delimiter);
|
||||
}
|
||||
result.append(funx.apply(obj));
|
||||
i++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String join(final Object[] array, final String delimiter) {
|
||||
switch (array.length) {
|
||||
case 0:
|
||||
return "";
|
||||
case 1:
|
||||
return array[0].toString();
|
||||
default:
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (int i = 0, j = array.length; i < j; i++) {
|
||||
if (i > 0) {
|
||||
result.append(delimiter);
|
||||
}
|
||||
result.append(array[i]);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer toInteger(String string, int start, int end) {
|
||||
int value = 0;
|
||||
char char0 = string.charAt(0);
|
||||
boolean negative;
|
||||
if (char0 == '-') {
|
||||
negative = true;
|
||||
start++;
|
||||
}
|
||||
else negative = false;
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = string.charAt(i);
|
||||
switch (c) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
value = value * 10 + c - '0';
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return negative ? -value : value;
|
||||
}
|
||||
|
||||
public static String join(final int[] array, final String delimiter) {
|
||||
final Integer[] wrapped = new Integer[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
wrapped[i] = array[i];
|
||||
}
|
||||
return join(wrapped, delimiter);
|
||||
}
|
||||
|
||||
public static boolean isEqualToAny(final String a, final String... args) {
|
||||
for (final String arg : args) {
|
||||
if (StringMan.isEqual(a, arg)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isEqualIgnoreCaseToAny(final String a, final String... args) {
|
||||
for (final String arg : args) {
|
||||
if (StringMan.isEqualIgnoreCase(a, arg)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isEqual(final String a, final String b) {
|
||||
return ((a == b) || ((a != null) && (b != null) && (a.length() == b.length()) && (a.hashCode() == b.hashCode()) && a.equals(b)));
|
||||
}
|
||||
|
||||
public static boolean isEqualIgnoreCase(final String a, final String b) {
|
||||
return ((a == b) || ((a != null) && (b != null) && (a.length() == b.length()) && a.equalsIgnoreCase(b)));
|
||||
}
|
||||
|
||||
public static String repeat(final String s, final int n) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < n; i++) {
|
||||
sb.append(s);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,428 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
import com.boydti.fawe.object.FaweQueue;
|
||||
import com.boydti.fawe.object.RunnableVal;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public abstract class TaskManager {
|
||||
|
||||
public static TaskManager IMP;
|
||||
|
||||
private ForkJoinPool pool = new ForkJoinPool();
|
||||
|
||||
/**
|
||||
* Run a repeating task on the main thread
|
||||
*
|
||||
* @param r
|
||||
* @param interval in ticks
|
||||
* @return
|
||||
*/
|
||||
public abstract int repeat(final Runnable r, final int interval);
|
||||
|
||||
/**
|
||||
* Run a repeating task asynchronously
|
||||
*
|
||||
* @param r
|
||||
* @param interval in ticks
|
||||
* @return
|
||||
*/
|
||||
public abstract int repeatAsync(final Runnable r, final int interval);
|
||||
|
||||
/**
|
||||
* Run a task asynchronously
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
public abstract void async(final Runnable r);
|
||||
|
||||
/**
|
||||
* Run a task on the main thread
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
public abstract void task(final Runnable r);
|
||||
|
||||
/**
|
||||
* Get the public ForkJoinPool<br>
|
||||
* - ONLY SUBMIT SHORT LIVED TASKS<br>
|
||||
* - DO NOT USE SLEEP/WAIT/LOCKS IN ANY SUBMITTED TASKS<br>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ForkJoinPool getPublicForkJoinPool() {
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a buch of tasks in parallel using the shared thread pool
|
||||
*
|
||||
* @param runnables
|
||||
*/
|
||||
public void parallel(Collection<Runnable> runnables) {
|
||||
// if (!Fawe.get().isJava8()) {
|
||||
// ExecutorCompletionService c = new ExecutorCompletionService(pool);
|
||||
// for (Runnable run : runnables) {
|
||||
// c.submit(run, null);
|
||||
// }
|
||||
// try {
|
||||
// for (int i = 0; i < runnables.size(); i++) {
|
||||
// c.take();
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
for (Runnable run : runnables) {
|
||||
pool.submit(run);
|
||||
}
|
||||
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a bunch of tasks in parallel
|
||||
*
|
||||
* @param runnables The tasks to run
|
||||
* @param numThreads Number of threads (null = config.yml parallel threads)
|
||||
*/
|
||||
@Deprecated
|
||||
public void parallel(Collection<Runnable> runnables, @Nullable Integer numThreads) {
|
||||
if (runnables == null) {
|
||||
return;
|
||||
}
|
||||
if (numThreads == null) {
|
||||
numThreads = Settings.IMP.QUEUE.PARALLEL_THREADS;
|
||||
}
|
||||
if (numThreads <= 1) {
|
||||
for (Runnable run : runnables) {
|
||||
if (run != null) {
|
||||
run.run();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
int numRuns = runnables.size();
|
||||
int amountPerThread = 1 + numRuns / numThreads;
|
||||
final Runnable[][] split = new Runnable[numThreads][amountPerThread];
|
||||
Thread[] threads = new Thread[numThreads];
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
for (Runnable run : runnables) {
|
||||
split[i][j] = run;
|
||||
if (++i >= numThreads) {
|
||||
i = 0;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < threads.length; i++) {
|
||||
final Runnable[] toRun = split[i];
|
||||
Thread thread = threads[i] = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int j = 0; j < toRun.length; j++) {
|
||||
Runnable run = toRun[j];
|
||||
if (run != null) {
|
||||
run.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
thread.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable async catching for a specific task
|
||||
*
|
||||
* @param queue
|
||||
* @param run
|
||||
*/
|
||||
public void runUnsafe(FaweQueue queue, Runnable run) {
|
||||
queue.startSet(true);
|
||||
try {
|
||||
run.run();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
queue.endSet(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task on the current thread or asynchronously
|
||||
* - If it's already the main thread, it will jst call run()
|
||||
*
|
||||
* @param r
|
||||
* @param async
|
||||
*/
|
||||
public void taskNow(final Runnable r, boolean async) {
|
||||
if (async) {
|
||||
async(r);
|
||||
} else if (r != null) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task as soon as possible on the main thread
|
||||
* - Non blocking if not calling from the main thread
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
public void taskNowMain(final Runnable r) {
|
||||
if (r == null) {
|
||||
return;
|
||||
}
|
||||
if (Thread.currentThread() == Fawe.get().getMainThread()) {
|
||||
r.run();
|
||||
} else {
|
||||
task(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task as soon as possible not on the main thread
|
||||
*
|
||||
* @param r
|
||||
* @see com.boydti.fawe.Fawe#isMainThread()
|
||||
*/
|
||||
public void taskNowAsync(final Runnable r) {
|
||||
taskNow(r, Fawe.isMainThread());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task on the main thread at the next tick or now async
|
||||
*
|
||||
* @param r
|
||||
* @param async
|
||||
*/
|
||||
public void taskSoonMain(final Runnable r, boolean async) {
|
||||
if (async) {
|
||||
async(r);
|
||||
} else {
|
||||
task(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run a task later on the main thread
|
||||
*
|
||||
* @param r
|
||||
* @param delay in ticks
|
||||
*/
|
||||
public abstract void later(final Runnable r, final int delay);
|
||||
|
||||
/**
|
||||
* Run a task later asynchronously
|
||||
*
|
||||
* @param r
|
||||
* @param delay in ticks
|
||||
*/
|
||||
public abstract void laterAsync(final Runnable r, final int delay);
|
||||
|
||||
/**
|
||||
* Cancel a task
|
||||
*
|
||||
* @param task
|
||||
*/
|
||||
public abstract void cancel(final int task);
|
||||
|
||||
/**
|
||||
* Break up a task and run it in fragments of 5ms.<br>
|
||||
* - Each task will run on the main thread.<br>
|
||||
*
|
||||
* @param objects - The list of objects to run the task for
|
||||
* @param task - The task to run on each object
|
||||
* @param whenDone - When the object task completes
|
||||
* @param <T>
|
||||
*/
|
||||
public <T> void objectTask(Collection<T> objects, final RunnableVal<T> task, final Runnable whenDone) {
|
||||
final Iterator<T> iterator = objects.iterator();
|
||||
task(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long start = System.currentTimeMillis();
|
||||
boolean hasNext;
|
||||
while ((hasNext = iterator.hasNext()) && System.currentTimeMillis() - start < 5) {
|
||||
task.value = iterator.next();
|
||||
task.run();
|
||||
}
|
||||
if (!hasNext) {
|
||||
later(whenDone, 1);
|
||||
} else {
|
||||
later(this, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly run a task on the main thread, and wait for execution to finish:<br>
|
||||
* - Useful if you need to access something from the Bukkit API from another thread<br>
|
||||
* - Usualy wait time is around 25ms<br>
|
||||
*
|
||||
* @param function
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> T sync(final RunnableVal<T> function) {
|
||||
return sync(function, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public <T> T sync(final Supplier<T> function) {
|
||||
return sync(function, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public void wait(AtomicBoolean running, int timout) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
synchronized (running) {
|
||||
while (running.get()) {
|
||||
running.wait(timout);
|
||||
if (running.get() && System.currentTimeMillis() - start > Settings.IMP.QUEUE.DISCARD_AFTER_MS) {
|
||||
new RuntimeException("FAWE is taking a long time to execute a task (might just be a symptom): ").printStackTrace();
|
||||
Fawe.debug("For full debug information use: /fawe threads");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void notify(AtomicBoolean running) {
|
||||
running.set(false);
|
||||
synchronized (running) {
|
||||
running.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T syncWhenFree(final RunnableVal<T> function) {
|
||||
return syncWhenFree(function, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public void taskWhenFree(Runnable run) {
|
||||
if (Fawe.isMainThread()) {
|
||||
run.run();
|
||||
} else {
|
||||
SetQueue.IMP.addTask(run);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task on the main thread when the TPS is high enough, and wait for execution to finish:<br>
|
||||
* - Useful if you need to access something from the Bukkit API from another thread<br>
|
||||
* - Usualy wait time is around 25ms<br>
|
||||
*
|
||||
* @param function
|
||||
* @param timeout - How long to wait for execution
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> T syncWhenFree(final RunnableVal<T> function, int timeout) {
|
||||
if (Fawe.get().getMainThread() == Thread.currentThread()) {
|
||||
function.run();
|
||||
return function.value;
|
||||
}
|
||||
final AtomicBoolean running = new AtomicBoolean(true);
|
||||
RunnableVal<RuntimeException> run = new RunnableVal<RuntimeException>() {
|
||||
@Override
|
||||
public void run(RuntimeException value) {
|
||||
try {
|
||||
function.run();
|
||||
} catch (RuntimeException e) {
|
||||
this.value = e;
|
||||
} catch (Throwable neverHappens) {
|
||||
MainUtil.handleError(neverHappens);
|
||||
} finally {
|
||||
running.set(false);
|
||||
}
|
||||
synchronized (function) {
|
||||
function.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
SetQueue.IMP.addTask(run);
|
||||
try {
|
||||
synchronized (function) {
|
||||
while (running.get()) {
|
||||
function.wait(timeout);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
if (run.value != null) {
|
||||
throw run.value;
|
||||
}
|
||||
return function.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly run a task on the main thread, and wait for execution to finish:<br>
|
||||
* - Useful if you need to access something from the Bukkit API from another thread<br>
|
||||
* - Usualy wait time is around 25ms<br>
|
||||
*
|
||||
* @param function
|
||||
* @param timeout - How long to wait for execution
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> T sync(final RunnableVal<T> function, int timeout) {
|
||||
return sync((Supplier<T>) function, timeout);
|
||||
}
|
||||
|
||||
public <T> T sync(final Supplier<T> function, int timeout) {
|
||||
if (Fawe.get().getMainThread() == Thread.currentThread()) {
|
||||
return function.get();
|
||||
}
|
||||
final AtomicBoolean running = new AtomicBoolean(true);
|
||||
RunnableVal<Object> run = new RunnableVal<Object>() {
|
||||
@Override
|
||||
public void run(Object value) {
|
||||
try {
|
||||
this.value = function.get();
|
||||
} catch (RuntimeException e) {
|
||||
this.value = e;
|
||||
} catch (Throwable neverHappens) {
|
||||
MainUtil.handleError(neverHappens);
|
||||
} finally {
|
||||
running.set(false);
|
||||
}
|
||||
synchronized (function) {
|
||||
function.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
SetQueue.IMP.addTask(run);
|
||||
try {
|
||||
synchronized (function) {
|
||||
while (running.get()) {
|
||||
function.wait(timeout);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
if (run.value != null && run.value instanceof RuntimeException) {
|
||||
throw (RuntimeException) run.value;
|
||||
}
|
||||
return (T) run.value;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
public interface TextureHolder {
|
||||
TextureUtil getTextureUtil();
|
||||
}
|
1012
worldedit-core/src/main/java/com/boydti/fawe/util/TextureUtil.java
Normal file
1012
worldedit-core/src/main/java/com/boydti/fawe/util/TextureUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
128
worldedit-core/src/main/java/com/boydti/fawe/util/Updater.java
Normal file
128
worldedit-core/src/main/java/com/boydti/fawe/util/Updater.java
Normal file
@ -0,0 +1,128 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.FaweVersion;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.boydti.fawe.util.chat.Message;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Updater {
|
||||
private FaweVersion newVersion;
|
||||
private String changes;
|
||||
|
||||
private volatile boolean pending;
|
||||
private File pendingFile, destFile;
|
||||
private String versionString;
|
||||
|
||||
public synchronized String getChanges() {
|
||||
if (changes == null) {
|
||||
try (Scanner scanner = new Scanner(new URL("https://empcraft.com/fawe/cl?" + Integer.toHexString(Fawe.get().getVersion().hash)).openStream(), "UTF-8")) {
|
||||
changes = scanner.useDelimiter("\\A").next();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
public synchronized boolean isOutdated() {
|
||||
return newVersion != null;
|
||||
}
|
||||
|
||||
public boolean hasPending(FawePlayer fp) {
|
||||
return (pending && fp.hasPermission("fawe.admin"));
|
||||
}
|
||||
|
||||
public synchronized void confirmUpdate(FawePlayer fp) {
|
||||
if (pending && fp.hasPermission("fawe.admin")) {
|
||||
Fawe.debug("Updated FAWE to " + versionString + " @ " + pendingFile);
|
||||
String url = "https://empcraft.com/fawe/cl?" + Integer.toHexString(Fawe.get().getVersion().hash);
|
||||
new Message().prefix().text("A FAWE update is available:")
|
||||
.text("\n&8 - &a/fawe update &8 - &7Update the plugin")
|
||||
.cmdTip("fawe update")
|
||||
.text("\n&8 - &a/fawe changelog")
|
||||
.cmdTip("fawe changelog")
|
||||
.text("&8 - &7( &9&o" + url + " &7)")
|
||||
.link(url)
|
||||
.send(fp);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean installUpdate(FawePlayer fp) {
|
||||
if (pending && (fp == null || fp.hasPermission("fawe.admin")) && pendingFile.exists()) {
|
||||
pending = false;
|
||||
File outFileParent = destFile.getParentFile();
|
||||
if (!outFileParent.exists()) {
|
||||
outFileParent.mkdirs();
|
||||
}
|
||||
pendingFile.renameTo(destFile);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized void getUpdate(String platform, FaweVersion currentVersion) {
|
||||
if (currentVersion == null || platform == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String downloadUrl = "https://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/target/FastAsyncWorldEdit-%platform%-%version%.jar";
|
||||
String versionUrl = "https://empcraft.com/fawe/version.php?%platform%";
|
||||
URL url = new URL(versionUrl.replace("%platform%", platform));
|
||||
try (Scanner reader = new Scanner(url.openStream())) {
|
||||
this.versionString = reader.next();
|
||||
FaweVersion version = new FaweVersion(versionString);
|
||||
if (version.isNewer(newVersion != null ? newVersion : currentVersion)) {
|
||||
newVersion = version;
|
||||
URL download = new URL(downloadUrl.replaceAll("%platform%", platform).replaceAll("%version%", versionString));
|
||||
try (ReadableByteChannel rbc = Channels.newChannel(download.openStream())) {
|
||||
File jarFile = MainUtil.getJarFile();
|
||||
|
||||
File finalFile = new File(jarFile.getParent(), "update-confirm" + File.separator + jarFile.getName());
|
||||
File outFile = new File(jarFile.getParent(), "update-confirm" + File.separator + jarFile.getName().replace(".jar", ".part"));
|
||||
boolean exists = outFile.exists();
|
||||
if (exists) {
|
||||
outFile.delete();
|
||||
} else {
|
||||
File outFileParent = outFile.getParentFile();
|
||||
if (!outFileParent.exists()) {
|
||||
outFileParent.mkdirs();
|
||||
}
|
||||
}
|
||||
try (FileOutputStream fos = new FileOutputStream(outFile)) {
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
}
|
||||
outFile.renameTo(finalFile);
|
||||
|
||||
if (Settings.IMP.UPDATE.equalsIgnoreCase("true")) {
|
||||
pending = true;
|
||||
pendingFile = finalFile;
|
||||
destFile = new File(jarFile.getParent(), "update" + File.separator + jarFile.getName());
|
||||
|
||||
installUpdate(null);
|
||||
Fawe.debug("Updated FAWE to " + versionString + " @ " + pendingFile);
|
||||
MainUtil.sendAdmin("&a/restart&7 to update FAWE with these changes: &c/fawe changelog &7or&c " + "https://empcraft.com/fawe/cl?" + Integer.toHexString(currentVersion.hash));
|
||||
} else if (!Settings.IMP.UPDATE.equalsIgnoreCase("false")) {
|
||||
pendingFile = finalFile;
|
||||
destFile = new File(jarFile.getParent(), "update" + File.separator + jarFile.getName());
|
||||
pending = true;
|
||||
|
||||
for (final FawePlayer<?> player : Fawe.get().getCachedPlayers()) {
|
||||
confirmUpdate(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
}
|
||||
}
|
235
worldedit-core/src/main/java/com/boydti/fawe/util/WEManager.java
Normal file
235
worldedit-core/src/main/java/com/boydti/fawe/util/WEManager.java
Normal file
@ -0,0 +1,235 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
import com.boydti.fawe.object.FaweLocation;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.boydti.fawe.object.RegionWrapper;
|
||||
import com.boydti.fawe.object.exception.FaweException;
|
||||
import com.boydti.fawe.object.extent.NullExtent;
|
||||
import com.boydti.fawe.regions.FaweMask;
|
||||
import com.boydti.fawe.regions.FaweMaskManager;
|
||||
import com.sk89q.worldedit.Vector;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WEManager {
|
||||
|
||||
public final static WEManager IMP = new WEManager();
|
||||
|
||||
public final ArrayDeque<FaweMaskManager> managers = new ArrayDeque<>();
|
||||
|
||||
public void cancelEditSafe(Extent parent, BBC reason) throws FaweException {
|
||||
try {
|
||||
final Field field = AbstractDelegateExtent.class.getDeclaredField("extent");
|
||||
field.setAccessible(true);
|
||||
Object currentExtent = field.get(parent);
|
||||
if (!(currentExtent instanceof NullExtent)) {
|
||||
field.set(parent, new NullExtent((Extent) field.get(parent), reason));
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
throw new FaweException(reason);
|
||||
}
|
||||
|
||||
public void cancelEdit(Extent parent, BBC reason) throws WorldEditException {
|
||||
cancelEditSafe(parent, reason);
|
||||
}
|
||||
|
||||
public boolean maskContains(final HashSet<RegionWrapper> mask, final int x, final int z) {
|
||||
for (final RegionWrapper region : mask) {
|
||||
if ((x >= region.minX) && (x <= region.maxX) && (z >= region.minZ) && (z <= region.maxZ)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean maskContains(RegionWrapper[] mask, final int x, final int z) {
|
||||
switch (mask.length) {
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
return mask[0].isIn(x, z);
|
||||
default:
|
||||
for (final RegionWrapper region : mask) {
|
||||
if (region.isIn(x, z)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Region[] getMask(final FawePlayer<?> player) {
|
||||
return getMask(player, FaweMaskManager.MaskType.getDefaultMaskType());
|
||||
}
|
||||
|
||||
public boolean isIn(int x, int y, int z, Region region) {
|
||||
if (region.contains(x, y, z)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a player's mask
|
||||
*
|
||||
* @param player
|
||||
* @return
|
||||
*/
|
||||
public Region[] getMask(final FawePlayer<?> player, FaweMaskManager.MaskType type) {
|
||||
if (!Settings.IMP.REGION_RESTRICTIONS || player.hasPermission("fawe.bypass") || player.hasPermission("fawe.bypass.regions")) {
|
||||
return new Region[]{RegionWrapper.GLOBAL()};
|
||||
}
|
||||
FaweLocation loc = player.getLocation();
|
||||
String world = loc.world;
|
||||
if (!world.equals(player.getMeta("lastMaskWorld"))) {
|
||||
player.deleteMeta("lastMaskWorld");
|
||||
player.deleteMeta("lastMask");
|
||||
}
|
||||
player.setMeta("lastMaskWorld", world);
|
||||
Set<FaweMask> masks = player.getMeta("lastMask");
|
||||
Set<Region> backupRegions = new HashSet<>();
|
||||
Set<Region> regions = new HashSet<>();
|
||||
|
||||
|
||||
if (masks == null) {
|
||||
masks = new HashSet<>();
|
||||
} else {
|
||||
synchronized (masks) {
|
||||
boolean removed = false;
|
||||
if (!masks.isEmpty()) {
|
||||
Iterator<FaweMask> iter = masks.iterator();
|
||||
while (iter.hasNext()) {
|
||||
FaweMask mask = iter.next();
|
||||
if (mask.isValid(player, type)) {
|
||||
Region region = mask.getRegion();
|
||||
if (region.contains(loc.x, loc.y, loc.z)) {
|
||||
regions.add(region);
|
||||
} else {
|
||||
removed = true;
|
||||
backupRegions.add(region);
|
||||
}
|
||||
} else {
|
||||
removed = true;
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!removed) return regions.toArray(new Region[regions.size()]);
|
||||
}
|
||||
}
|
||||
Set<FaweMask> tmpMasks = new HashSet<>();
|
||||
for (final FaweMaskManager manager : managers) {
|
||||
if (player.hasPermission("fawe." + manager.getKey())) {
|
||||
try {
|
||||
final FaweMask mask = manager.getMask(player, FaweMaskManager.MaskType.getDefaultMaskType());
|
||||
if (mask != null) {
|
||||
regions.add(mask.getRegion());
|
||||
masks.add(mask);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!tmpMasks.isEmpty()) {
|
||||
masks = tmpMasks;
|
||||
regions = masks.stream().map(mask -> mask.getRegion()).collect(Collectors.toSet());
|
||||
} else {
|
||||
regions.addAll(backupRegions);
|
||||
}
|
||||
if (!masks.isEmpty()) {
|
||||
player.setMeta("lastMask", masks);
|
||||
} else {
|
||||
player.deleteMeta("lastMask");
|
||||
}
|
||||
return regions.toArray(new Region[regions.size()]);
|
||||
}
|
||||
|
||||
|
||||
public boolean intersects(final Region region1, final Region region2) {
|
||||
Vector rg1P1 = region1.getMinimumPoint();
|
||||
Vector rg1P2 = region1.getMaximumPoint();
|
||||
Vector rg2P1 = region2.getMinimumPoint();
|
||||
Vector rg2P2 = region2.getMaximumPoint();
|
||||
|
||||
return (rg1P1.getBlockX() <= rg2P2.getBlockX()) && (rg1P2.getBlockX() >= rg2P1.getBlockX()) && (rg1P1.getBlockZ() <= rg2P2.getBlockZ()) && (rg1P2.getBlockZ() >= rg2P1.getBlockZ());
|
||||
}
|
||||
|
||||
public boolean regionContains(final Region selection, final HashSet<Region> mask) {
|
||||
for (final Region region : mask) {
|
||||
if (this.intersects(region, selection)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean delay(final FawePlayer<?> player, final String command) {
|
||||
final long start = System.currentTimeMillis();
|
||||
return this.delay(player, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if ((System.currentTimeMillis() - start) > 1000) {
|
||||
BBC.WORLDEDIT_RUN.send(FawePlayer.wrap(player));
|
||||
}
|
||||
TaskManager.IMP.task(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final long start = System.currentTimeMillis();
|
||||
player.executeCommand(command.substring(1));
|
||||
TaskManager.IMP.later(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SetQueue.IMP.addEmptyTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if ((System.currentTimeMillis() - start) > 1000) {
|
||||
BBC.WORLDEDIT_COMPLETE.send(FawePlayer.wrap(player));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 2);
|
||||
}
|
||||
});
|
||||
} catch (final Exception e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
}
|
||||
}, false, false);
|
||||
}
|
||||
|
||||
public boolean delay(final FawePlayer<?> player, final Runnable whenDone, final boolean delayed, final boolean onlyDelayedExecution) {
|
||||
final boolean free = SetQueue.IMP.addEmptyTask(null);
|
||||
if (free) {
|
||||
if (delayed) {
|
||||
if (whenDone != null) {
|
||||
whenDone.run();
|
||||
}
|
||||
} else {
|
||||
if ((whenDone != null) && !onlyDelayedExecution) {
|
||||
whenDone.run();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!delayed && (player != null)) {
|
||||
BBC.WORLDEDIT_DELAYED.send(player);
|
||||
}
|
||||
SetQueue.IMP.addEmptyTask(whenDone);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.boydti.fawe.util.chat;
|
||||
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
|
||||
public interface ChatManager<T> {
|
||||
T builder();
|
||||
|
||||
void color(Message message, String color);
|
||||
|
||||
void tooltip(Message message, Message... tooltip);
|
||||
|
||||
void command(Message message, String command);
|
||||
|
||||
void text(Message message, String text);
|
||||
|
||||
void send(Message message, FawePlayer player);
|
||||
|
||||
void suggest(Message message, String command);
|
||||
|
||||
void link(Message message, String url);
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package com.boydti.fawe.util.chat;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Message {
|
||||
|
||||
private Object builder;
|
||||
private boolean active;
|
||||
|
||||
public Message() {
|
||||
try {
|
||||
reset(Fawe.get().getChatManager());
|
||||
} catch (Throwable e) {
|
||||
Fawe.debug("Doesn't support fancy chat for " + Fawe.imp().getPlatform());
|
||||
Fawe.get().setChatManager(new PlainChatManager());
|
||||
reset(Fawe.get().getChatManager());
|
||||
}
|
||||
active = !(Fawe.get().getChatManager() instanceof PlainChatManager);
|
||||
}
|
||||
|
||||
public Message(BBC caption, Object... args) {
|
||||
this(BBC.getPrefix() + caption.format(args));
|
||||
}
|
||||
|
||||
public Message(String text) {
|
||||
this();
|
||||
text(text);
|
||||
}
|
||||
|
||||
public <T> T $(ChatManager<T> manager) {
|
||||
return (T) this.builder;
|
||||
}
|
||||
|
||||
public <T> T reset(ChatManager<T> manager) {
|
||||
return (T) (this.builder = manager.builder());
|
||||
}
|
||||
|
||||
public Message activeText(String text) {
|
||||
if (active) {
|
||||
text(text);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean supportsInteraction() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Message text(BBC caption, Object... args) {
|
||||
return text(caption.format(args));
|
||||
}
|
||||
|
||||
public Message text(Object text) {
|
||||
Fawe.get().getChatManager().text(this, BBC.color(Objects.toString(text)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message link(String text) {
|
||||
Fawe.get().getChatManager().link(this, text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message tooltip(Message... tooltip) {
|
||||
Fawe.get().getChatManager().tooltip(this, tooltip);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message tooltip(String tooltip) {
|
||||
return tooltip(new Message(tooltip));
|
||||
}
|
||||
|
||||
public Message command(String command) {
|
||||
Fawe.get().getChatManager().command(this, (WorldEdit.getInstance().getConfiguration().noDoubleSlash ? "" : "/") + command);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message prefix() {
|
||||
return text(BBC.getPrefix());
|
||||
}
|
||||
|
||||
public Message newline() {
|
||||
return text("\n");
|
||||
}
|
||||
|
||||
public Message cmdTip(String commandAndTooltip) {
|
||||
return tooltip(commandAndTooltip).command(commandAndTooltip);
|
||||
}
|
||||
|
||||
public Message linkTip(String linkAndTooltip) {
|
||||
return tooltip(linkAndTooltip).link(linkAndTooltip);
|
||||
}
|
||||
|
||||
public Message cmdOptions(String prefix, String suffix, String... options) {
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
if (i != 0) text(" &8|&7 ");
|
||||
text("&7[&a" + options[i] + "&7]")
|
||||
.cmdTip(prefix + options[i] + suffix);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message suggestTip(String commandAndTooltip) {
|
||||
return tooltip(commandAndTooltip).suggest(commandAndTooltip);
|
||||
}
|
||||
|
||||
public Message suggest(String command) {
|
||||
Fawe.get().getChatManager().suggest(this, command);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message color(String color) {
|
||||
Fawe.get().getChatManager().color(this, BBC.color(color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void send(Actor player) {
|
||||
send(FawePlayer.wrap(player));
|
||||
}
|
||||
|
||||
public void send(FawePlayer player) {
|
||||
Fawe.get().getChatManager().send(this, player);
|
||||
}
|
||||
|
||||
public Message paginate(String baseCommand, int page, int totalPages) {
|
||||
if (!active) {
|
||||
return text(BBC.PAGE_FOOTER.f(baseCommand, page + 1));
|
||||
}
|
||||
if (page < totalPages && page > 1) { // Back | Next
|
||||
this.text("&f<<").command(baseCommand + " " + (page - 1)).text("&8 | ").text("&f>>")
|
||||
.command(baseCommand + " " + (page + 1));
|
||||
} else if (page <= 1 && totalPages > page) { // Next
|
||||
this.text("&8 -").text(" | ").text("&f>>")
|
||||
.command(baseCommand + " " + (page + 1));
|
||||
|
||||
} else if (page == totalPages && totalPages > 1) { // Back
|
||||
this.text("&f<<").command(baseCommand + " " + (page - 1)).text("&8 | ").text("- ");
|
||||
} else {
|
||||
this.text("&8 - | - ");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.boydti.fawe.util.chat;
|
||||
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PlainChatManager implements ChatManager<List<StringBuilder>> {
|
||||
|
||||
@Override
|
||||
public List<StringBuilder> builder() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void color(Message message, String color) {
|
||||
List<StringBuilder> parts = message.$(this);
|
||||
parts.get(parts.size() - 1).insert(0, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tooltip(Message message, Message... tooltips) {}
|
||||
|
||||
@Override
|
||||
public void command(Message message, String command) {}
|
||||
|
||||
@Override
|
||||
public void text(Message message, String text) {
|
||||
message.$(this).add(new StringBuilder(BBC.color(text)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Message plotMessage, FawePlayer player) {
|
||||
StringBuilder built = new StringBuilder();
|
||||
for (StringBuilder sb : plotMessage.$(this)) {
|
||||
built.append(sb);
|
||||
}
|
||||
player.sendMessage(built.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggest(Message plotMessage, String command) {}
|
||||
|
||||
@Override
|
||||
public void link(Message message, String url) {}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package com.boydti.fawe.util.chat;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.config.Commands;
|
||||
import com.boydti.fawe.util.MainUtil;
|
||||
import com.boydti.fawe.util.StringMan;
|
||||
import com.sk89q.minecraft.util.commands.CommandLocals;
|
||||
import com.sk89q.minecraft.util.commands.Link;
|
||||
import com.sk89q.worldedit.extension.platform.CommandManager;
|
||||
import com.sk89q.worldedit.util.command.CommandCallable;
|
||||
import com.sk89q.worldedit.util.command.CommandMapping;
|
||||
import com.sk89q.worldedit.util.command.Description;
|
||||
import com.sk89q.worldedit.util.command.Dispatcher;
|
||||
import com.sk89q.worldedit.util.command.Parameter;
|
||||
import com.sk89q.worldedit.util.command.PrimaryAliasComparator;
|
||||
import com.sk89q.worldedit.util.command.binding.Range;
|
||||
import com.sk89q.worldedit.util.command.parametric.ParameterData;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class UsageMessage extends Message {
|
||||
/**
|
||||
* Create a new usage box.
|
||||
*
|
||||
* @param command the command to describe
|
||||
* @param commandString the command that was used, such as "/we" or "/brush sphere"
|
||||
*/
|
||||
public UsageMessage(CommandCallable command, String commandString) {
|
||||
this(command, commandString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new usage box.
|
||||
*
|
||||
* @param command the command to describe
|
||||
* @param commandString the command that was used, such as "/we" or "/brush sphere"
|
||||
* @param locals list of locals to use
|
||||
*/
|
||||
public UsageMessage(CommandCallable command, String commandString, @Nullable CommandLocals locals) {
|
||||
checkNotNull(command);
|
||||
checkNotNull(commandString);
|
||||
if (command instanceof Dispatcher) {
|
||||
attachDispatcherUsage((Dispatcher) command, commandString, locals);
|
||||
} else {
|
||||
attachCommandUsage(command.getDescription(), commandString);
|
||||
}
|
||||
}
|
||||
|
||||
private void attachDispatcherUsage(Dispatcher dispatcher, String commandString, @Nullable CommandLocals locals) {
|
||||
prefix();
|
||||
text(BBC.HELP_HEADER_SUBCOMMANDS.f());
|
||||
String prefix = !commandString.isEmpty() ? commandString + " " : "";
|
||||
|
||||
List<CommandMapping> list = new ArrayList<CommandMapping>(dispatcher.getCommands());
|
||||
Collections.sort(list, new PrimaryAliasComparator(CommandManager.COMMAND_CLEAN_PATTERN));
|
||||
|
||||
for (CommandMapping mapping : list) {
|
||||
boolean perm = locals == null || mapping.getCallable().testPermission(locals);
|
||||
newline();
|
||||
String cmd = prefix + mapping.getPrimaryAlias();
|
||||
text((perm ? BBC.HELP_ITEM_ALLOWED : BBC.HELP_ITEM_DENIED).format(cmd, mapping.getDescription().getDescription()));
|
||||
command(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
protected String separateArg(String arg) {
|
||||
return " " + arg;
|
||||
}
|
||||
|
||||
private void attachCommandUsage(Description description, String commandString) {
|
||||
List<Parameter> params = description.getParameters();
|
||||
String[] usage;
|
||||
if (description.getUsage() != null) {
|
||||
usage = description.getUsage().split(" ", params.size());
|
||||
} else {
|
||||
usage = new String[params.size()];
|
||||
for (int i = 0; i < usage.length; i++) {
|
||||
Parameter param = params.get(i);
|
||||
boolean optional = param.isValueFlag() || param.isOptional();
|
||||
String arg;
|
||||
if (param.getFlag() != null) {
|
||||
arg = "-" + param.getFlag();
|
||||
if (param.isValueFlag())
|
||||
arg += param.getName();
|
||||
} else {
|
||||
arg = param.getName();
|
||||
if (param.getDefaultValue() != null && param.getDefaultValue().length > 0)
|
||||
arg += "=" + StringMan.join(param.getDefaultValue(), ",");
|
||||
}
|
||||
usage[i] = optional ? ("[" + arg + "]") : ("<" + arg + ">");
|
||||
}
|
||||
}
|
||||
|
||||
prefix();
|
||||
text("&cUsage: ");
|
||||
text("&7" + commandString);
|
||||
suggestTip(commandString + " ");
|
||||
for (int i = 0; i < usage.length; i++) {
|
||||
String argStr = usage[i];
|
||||
text(separateArg(argStr.replaceAll("[\\[|\\]|<|>]", "&0$0&7")));
|
||||
|
||||
if (params.isEmpty()) continue;
|
||||
Parameter param = params.get(i);
|
||||
|
||||
StringBuilder tooltip = new StringBuilder();
|
||||
String command = null;
|
||||
String webpage = null;
|
||||
|
||||
tooltip.append("Name: " + param.getName());
|
||||
if (param instanceof ParameterData) {
|
||||
ParameterData pd = (ParameterData) param;
|
||||
Type type = pd.getType();
|
||||
if (type instanceof Class) {
|
||||
tooltip.append("\nType: " + ((Class) type).getSimpleName());
|
||||
}
|
||||
|
||||
Range range = MainUtil.getOf(pd.getModifiers(), Range.class);
|
||||
if (range != null) {
|
||||
String min = range.min() == Double.MIN_VALUE ? "(-∞" : ("[" + range.min());
|
||||
String max = range.max() == Double.MAX_VALUE ? "∞)" : (range.max() + "]");
|
||||
tooltip.append("\nRange: " + min + "," + max);
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
Link link = (Link) ((Class) type).getAnnotation(Link.class);
|
||||
if (link != null) {
|
||||
if (link.value().startsWith("http")) webpage = link.value();
|
||||
else command = Commands.getAlias(link.clazz(), link.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
tooltip.append("\nOptional: " + (param.isOptional() || param.isValueFlag()));
|
||||
if (param.getDefaultValue() != null && param.getDefaultValue().length >= 0) {
|
||||
tooltip.append("\nDefault: " + param.getDefaultValue()[0]);
|
||||
} else if (argStr.contains("=")) {
|
||||
tooltip.append("\nDefault: " + argStr.split("[=|\\]|>]")[1]);
|
||||
}
|
||||
if (command != null || webpage != null) {
|
||||
tooltip.append("\nClick for more info");
|
||||
}
|
||||
tooltip(tooltip.toString());
|
||||
if (command != null) command(command);
|
||||
if (webpage != null) link(webpage);
|
||||
}
|
||||
|
||||
newline();
|
||||
if (description.getHelp() != null) {
|
||||
text("&cHelp: &7" + description.getHelp());
|
||||
} else if (description.getDescription() != null) {
|
||||
text("&cDescription: &7" + description.getDescription());
|
||||
} else {
|
||||
text("No further help is available.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.boydti.fawe.util.cui;
|
||||
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.sk89q.worldedit.internal.cui.CUIEvent;
|
||||
|
||||
public abstract class CUI {
|
||||
private final FawePlayer player;
|
||||
|
||||
public CUI(FawePlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public <T> FawePlayer<T> getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public abstract void dispatchCUIEvent(CUIEvent event);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.boydti.fawe.util.gui;
|
||||
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface FormBuilder<T> {
|
||||
FormBuilder setTitle(String text);
|
||||
|
||||
FormBuilder setIcon(URL icon);
|
||||
|
||||
FormBuilder addButton(String text, @Nullable URL image);
|
||||
|
||||
FormBuilder addDropdown(String text, int def, String... options);
|
||||
|
||||
FormBuilder addInput(String text, String placeholder, String def);
|
||||
|
||||
FormBuilder addLabel(String text);
|
||||
|
||||
FormBuilder addSlider(String text, double min, double max, int step, double def);
|
||||
|
||||
FormBuilder addStepSlider(String text, int def, String... options);
|
||||
|
||||
FormBuilder addToggle(String text, boolean def);
|
||||
|
||||
FormBuilder setResponder(Consumer<Map<Integer, Object>> handler);
|
||||
|
||||
void display(FawePlayer<T> fp);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.boydti.fawe.util.image;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public interface Drawable {
|
||||
public BufferedImage draw();
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package com.boydti.fawe.util.image;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.util.MainUtil;
|
||||
import com.boydti.fawe.util.MathMan;
|
||||
import com.sk89q.worldedit.util.command.parametric.ParameterException;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
public class ImageUtil {
|
||||
public static BufferedImage getScaledInstance(BufferedImage img,
|
||||
int targetWidth,
|
||||
int targetHeight,
|
||||
Object hint,
|
||||
boolean higherQuality)
|
||||
{
|
||||
if (img.getHeight() == targetHeight && img.getWidth() == targetWidth) {
|
||||
return img;
|
||||
}
|
||||
int type = (img.getTransparency() == Transparency.OPAQUE) ?
|
||||
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
|
||||
BufferedImage ret = (BufferedImage)img;
|
||||
int w, h;
|
||||
if (higherQuality) {
|
||||
// Use multi-step technique: start with original size, then
|
||||
// scale down in multiple passes with drawImage()
|
||||
// until the target size is reached
|
||||
w = img.getWidth();
|
||||
h = img.getHeight();
|
||||
} else {
|
||||
// Use one-step technique: scale directly from original
|
||||
// size to target size with a single drawImage() call
|
||||
w = targetWidth;
|
||||
h = targetHeight;
|
||||
}
|
||||
|
||||
do {
|
||||
if (higherQuality && w > targetWidth) {
|
||||
w /= 2;
|
||||
if (w < targetWidth) {
|
||||
w = targetWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (higherQuality && h > targetHeight) {
|
||||
h /= 2;
|
||||
if (h < targetHeight) {
|
||||
h = targetHeight;
|
||||
}
|
||||
}
|
||||
|
||||
BufferedImage tmp = new BufferedImage(w, h, type);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
|
||||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
|
||||
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
|
||||
g2.drawImage(ret, 0, 0, w, h, null);
|
||||
g2.dispose();
|
||||
|
||||
ret = tmp;
|
||||
} while (w != targetWidth || h != targetHeight);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void fadeAlpha(BufferedImage image) {
|
||||
int[] raw = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
int centerX = width / 2;
|
||||
int centerZ = height / 2;
|
||||
|
||||
float invRadiusX = 1f / centerX;
|
||||
float invRadiusZ = 1f / centerZ;
|
||||
|
||||
float[] sqrX = new float[width];
|
||||
float[] sqrZ = new float[height];
|
||||
for (int x = 0; x < width; x++) {
|
||||
float distance = Math.abs(x - centerX) * invRadiusX;
|
||||
sqrX[x] = distance * distance;
|
||||
}
|
||||
for (int z = 0; z < height; z++) {
|
||||
float distance = Math.abs(z - centerZ) * invRadiusZ;
|
||||
sqrZ[z] = distance * distance;
|
||||
}
|
||||
|
||||
for (int z = 0, index = 0; z < height; z++) {
|
||||
float dz2 = sqrZ[z];
|
||||
for (int x = 0; x < width; x++, index++) {
|
||||
int color = raw[index];
|
||||
int alpha = (color >> 24) & 0xFF;
|
||||
if (alpha != 0) {
|
||||
float dx2 = sqrX[x];
|
||||
float distSqr = dz2 + dx2;
|
||||
if (distSqr > 1) raw[index] = 0;
|
||||
else {
|
||||
alpha = (int) (alpha * (1 - distSqr));
|
||||
raw[index] = (color & 0x00FFFFFF) + (alpha << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void scaleAlpha(BufferedImage image, double alphaScale) {
|
||||
int[] raw = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
int defined = (MathMan.clamp((int) (255 * alphaScale), 0, 255)) << 24;
|
||||
for (int i = 0; i < raw.length; i++) {
|
||||
int color = raw[i];
|
||||
int alpha = ((color >> 24) & 0xFF);
|
||||
switch (alpha) {
|
||||
case 0:
|
||||
continue;
|
||||
case 255:
|
||||
raw[i] = (color & 0x00FFFFFF) + defined;
|
||||
continue;
|
||||
default:
|
||||
alpha = MathMan.clamp((int) (alpha * alphaScale), 0, 255);
|
||||
raw[i] = (color & 0x00FFFFFF) + (alpha << 24);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getColor(BufferedImage image) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
long totalRed = 0;
|
||||
long totalGreen = 0;
|
||||
long totalBlue = 0;
|
||||
long totalAlpha = 0;
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
int color = image.getRGB(x, y);
|
||||
totalRed += (color >> 16) & 0xFF;
|
||||
totalGreen += (color >> 8) & 0xFF;
|
||||
totalBlue += (color >> 0) & 0xFF;
|
||||
totalAlpha += (color >> 24) & 0xFF;
|
||||
}
|
||||
}
|
||||
int a = width * height;
|
||||
int red = (int) (totalRed / a);
|
||||
int green = (int) (totalGreen / a);
|
||||
int blue = (int) (totalBlue / a);
|
||||
int alpha = (int) (totalAlpha / a);
|
||||
return (alpha << 24) + (red << 16) + (green << 8) + (blue << 0);
|
||||
}
|
||||
|
||||
public static BufferedImage getImage(String arg) throws ParameterException {
|
||||
try {
|
||||
if (arg.startsWith("http")) {
|
||||
if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) {
|
||||
arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png";
|
||||
}
|
||||
URL url = new URL(arg);
|
||||
BufferedImage img = MainUtil.readImage(url);
|
||||
if (img == null) {
|
||||
throw new IOException("Failed to read " + url + ", please try again later");
|
||||
}
|
||||
return img;
|
||||
} else if (arg.startsWith("file:/")) {
|
||||
arg = arg.replaceFirst("file:/+", "");
|
||||
File file = MainUtil.getFile(MainUtil.getFile(Fawe.imp().getDirectory(), com.boydti.fawe.config.Settings.IMP.PATHS.HEIGHTMAP), arg);
|
||||
return MainUtil.readImage(file);
|
||||
} else {
|
||||
throw new ParameterException("Invalid image " + arg);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ParameterException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.boydti.fawe.util.image;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface ImageViewer extends Closeable{
|
||||
public void view(Drawable drawable);
|
||||
}
|
@ -0,0 +1,419 @@
|
||||
package com.boydti.fawe.util.metrics;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.FaweVersion;
|
||||
import com.boydti.fawe.configuration.file.YamlConfiguration;
|
||||
import com.boydti.fawe.object.RunnableVal;
|
||||
import com.boydti.fawe.object.io.PGZIPOutputStream;
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
/**
|
||||
* bStats collects some data for plugin authors.
|
||||
*
|
||||
* Check out https://bStats.org/ to learn more about bStats!
|
||||
*/
|
||||
public class BStats implements Closeable {
|
||||
|
||||
// The version of this bStats class
|
||||
public static final int B_STATS_VERSION = 1;
|
||||
|
||||
// The url to which the data is sent
|
||||
private final String url;
|
||||
|
||||
// The plugin
|
||||
private final String plugin;
|
||||
private final String platform;
|
||||
private final boolean online;
|
||||
private final String serverVersion;
|
||||
private final String pluginVersion;
|
||||
private Timer timer;
|
||||
private Gson gson = new Gson();
|
||||
|
||||
// Is bStats enabled on this server?
|
||||
private volatile boolean enabled;
|
||||
|
||||
// The uuid of the server
|
||||
private UUID serverUUID;
|
||||
|
||||
// Should failed requests be logged?
|
||||
private boolean logFailedRequests = false;
|
||||
|
||||
// A list with all known metrics class objects including this one
|
||||
private static Class<?> usedMetricsClass;
|
||||
private static final ConcurrentLinkedQueue<Object> knownMetricsInstances = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public BStats() {
|
||||
this("FastAsyncWorldEdit", Fawe.get().getVersion(), Fawe.imp().getPlatformVersion(), Fawe.imp().getPlatform(), Fawe.imp().isOnlineMode());
|
||||
}
|
||||
|
||||
public int getPlayerCount() {
|
||||
return Fawe.imp() == null ? 1 : Fawe.imp().getPlayerCount();
|
||||
}
|
||||
|
||||
private BStats(String plugin, FaweVersion faweVersion, String serverVersion, String platform, boolean online) {
|
||||
this.url = "https://bStats.org/submitData/" + platform;
|
||||
this.plugin = plugin;
|
||||
this.pluginVersion = "" + faweVersion;
|
||||
this.serverVersion = serverVersion;
|
||||
this.platform = platform;
|
||||
this.online = online;
|
||||
|
||||
File configFile = new File(getJarFile().getParentFile(), "bStats" + File.separator + "config.yml");
|
||||
if (!configFile.exists()) {
|
||||
configFile.getParentFile().mkdirs();
|
||||
try {
|
||||
configFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
|
||||
|
||||
if (config.isSet("serverUuid")) {
|
||||
try {
|
||||
serverUUID = UUID.fromString(config.getString("serverUuid"));
|
||||
} catch (IllegalArgumentException ignore) {}
|
||||
}
|
||||
// Check if the config file exists
|
||||
if (serverUUID == null) {
|
||||
|
||||
// Add default values
|
||||
config.addDefault("enabled", true);
|
||||
// Every server gets it's unique random id.
|
||||
config.addDefault("serverUuid", (serverUUID = UUID.randomUUID()).toString());
|
||||
// Should failed request be logged?
|
||||
config.addDefault("logFailedRequests", false);
|
||||
|
||||
// Inform the server owners about bStats
|
||||
config.options().header(
|
||||
"bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
|
||||
"To honor their work, you should not disable it.\n" +
|
||||
"This has nearly no effect on the server performance!\n" +
|
||||
"Check out https://bStats.org/ to learn more :)"
|
||||
).copyDefaults(true);
|
||||
try {
|
||||
config.save(configFile);
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
|
||||
|
||||
if (usedMetricsClass != null) {
|
||||
// Already an instance of this class
|
||||
linkMetrics(this);
|
||||
return;
|
||||
}
|
||||
this.usedMetricsClass = getFirstBStatsClass();
|
||||
if (usedMetricsClass == null) {
|
||||
// Failed to get first metrics class
|
||||
return;
|
||||
}
|
||||
if (usedMetricsClass == getClass()) {
|
||||
// We are the first! :)
|
||||
linkMetrics(this);
|
||||
enabled = true;
|
||||
} else {
|
||||
// We aren't the first so we link to the first metrics class
|
||||
try {
|
||||
usedMetricsClass.getMethod("linkMetrics", Object.class).invoke(null,this);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
if (logFailedRequests) {
|
||||
System.out.println("Failed to link to first metrics class " + usedMetricsClass.getName() + "!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (enabled) {
|
||||
startSubmitting();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Links an other metrics class with this class.
|
||||
* This method is called using Reflection.
|
||||
*
|
||||
* @param metrics An object of the metrics class to link.
|
||||
*/
|
||||
public static void linkMetrics(Object metrics) {
|
||||
if (!knownMetricsInstances.contains(metrics)) knownMetricsInstances.add(metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin specific data.
|
||||
* This method is called using Reflection.
|
||||
*
|
||||
* @return The plugin specific data.
|
||||
*/
|
||||
public JsonObject getPluginData() {
|
||||
JsonObject data = new JsonObject();
|
||||
|
||||
data.addProperty("pluginName", plugin);
|
||||
data.addProperty("pluginVersion", pluginVersion);
|
||||
|
||||
JsonArray customCharts = new JsonArray();
|
||||
data.add("customCharts", customCharts);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void startSubmitting() {
|
||||
this.timer = new Timer(true);
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!enabled) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
submitData();
|
||||
}
|
||||
// No 2m delay, as this is only started after the server is loaded
|
||||
}, 0, 1000*60*30);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
enabled = false;
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server specific data.
|
||||
*
|
||||
* @return The server specific data.
|
||||
*/
|
||||
private JsonObject getServerData() {
|
||||
int playerAmount = getPlayerCount();
|
||||
int onlineMode = online ? 1 : 0;
|
||||
|
||||
int managedServers = 1;
|
||||
|
||||
// OS/Java specific data
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
String osName = System.getProperty("os.name");
|
||||
String osArch = System.getProperty("os.arch");
|
||||
String osVersion = System.getProperty("os.version");
|
||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
JsonObject data = new JsonObject();
|
||||
|
||||
data.addProperty("serverUUID", serverUUID.toString());
|
||||
|
||||
data.addProperty("playerAmount", playerAmount);
|
||||
data.addProperty("managedServers", managedServers);
|
||||
data.addProperty("onlineMode", onlineMode);
|
||||
data.addProperty(platform + "Version", serverVersion);
|
||||
|
||||
data.addProperty("javaVersion", javaVersion);
|
||||
data.addProperty("osName", osName);
|
||||
data.addProperty("osArch", osArch);
|
||||
data.addProperty("osVersion", osVersion);
|
||||
data.addProperty("coreCount", coreCount);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the data and sends it afterwards.
|
||||
*/
|
||||
private void submitData() {
|
||||
final JsonObject data = getServerData();
|
||||
|
||||
final JsonArray pluginData = new JsonArray();
|
||||
// Search for all other bStats Metrics classes to get their plugin data
|
||||
for (Object metrics : knownMetricsInstances) {
|
||||
Object plugin = TaskManager.IMP.sync(new RunnableVal<Object>() {
|
||||
@Override
|
||||
public void run(Object value) {
|
||||
try {
|
||||
this.value = metrics.getClass().getMethod("getPluginData").invoke(metrics);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NullPointerException | JsonSyntaxException ignored) {}
|
||||
}
|
||||
});
|
||||
if (plugin != null) {
|
||||
if (plugin instanceof JsonObject) {
|
||||
pluginData.add((JsonObject) plugin);
|
||||
} else {
|
||||
pluginData.add(gson.fromJson(plugin.toString(), JsonObject.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.add("plugins", pluginData);
|
||||
|
||||
try {
|
||||
// Send the data
|
||||
sendData(data);
|
||||
} catch (Exception e) {
|
||||
// Something went wrong! :(
|
||||
if (logFailedRequests) {
|
||||
System.err.println("Could not submit plugin stats!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first bStat Metrics class.
|
||||
*
|
||||
* @return The first bStats metrics class.
|
||||
*/
|
||||
private Class<?> getFirstBStatsClass() {
|
||||
Path configPath = getJarFile().toPath().getParent().resolve("bStats");
|
||||
configPath.toFile().mkdirs();
|
||||
File tempFile = new File(configPath.toFile(), "temp.txt");
|
||||
|
||||
try {
|
||||
String className = readFile(tempFile);
|
||||
if (className != null) {
|
||||
try {
|
||||
// Let's check if a class with the given name exists.
|
||||
return Class.forName(className);
|
||||
} catch (ClassNotFoundException ignored) { }
|
||||
}
|
||||
writeFile(tempFile, getClass().getName());
|
||||
return getClass();
|
||||
} catch (IOException e) {
|
||||
if (logFailedRequests) {
|
||||
System.err.println("Failed to get first bStats class!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private File getJarFile() {
|
||||
try {
|
||||
URL url = BStats.class.getProtectionDomain().getCodeSource().getLocation();
|
||||
return new File(new URL(url.toURI().toString().split("\\!")[0].replaceAll("jar:file", "file")).toURI().getPath());
|
||||
} catch (MalformedURLException | URISyntaxException | SecurityException e) {
|
||||
return new File(".", "plugins");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the first line of the file.
|
||||
*
|
||||
* @param file The file to read. Cannot be null.
|
||||
* @return The first line of the file or <code>null</code> if the file does not exist or is empty.
|
||||
* @throws IOException If something did not work :(
|
||||
*/
|
||||
private String readFile(File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
try (
|
||||
FileReader fileReader = new FileReader(file);
|
||||
BufferedReader bufferedReader = new BufferedReader(fileReader);
|
||||
) {
|
||||
return bufferedReader.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a String to a file. It also adds a note for the user,
|
||||
*
|
||||
* @param file The file to write to. Cannot be null.
|
||||
* @param lines The lines to write.
|
||||
* @throws IOException If something did not work :(
|
||||
*/
|
||||
private void writeFile(File file, String... lines) throws IOException {
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
try (
|
||||
FileWriter fileWriter = new FileWriter(file);
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)
|
||||
) {
|
||||
for (String line : lines) {
|
||||
bufferedWriter.write(line);
|
||||
bufferedWriter.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the data to the bStats server.
|
||||
*
|
||||
* @param data The data to send.
|
||||
* @throws Exception If the request failed.
|
||||
*/
|
||||
private void sendData(JsonObject data) throws Exception {
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("Data cannot be null");
|
||||
}
|
||||
|
||||
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
|
||||
|
||||
// Compress the data to save bandwidth
|
||||
byte[] compressedData = compress(data.toString());
|
||||
|
||||
// Add headers
|
||||
connection.setRequestMethod("POST");
|
||||
connection.addRequestProperty("Accept", "application/json");
|
||||
connection.addRequestProperty("Connection", "close");
|
||||
connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
|
||||
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
|
||||
connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
|
||||
connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
|
||||
|
||||
// Send data
|
||||
connection.setDoOutput(true);
|
||||
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
|
||||
outputStream.write(compressedData);
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
|
||||
connection.getInputStream().close(); // We don't care about the response - Just send our data :)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gzips the given String.
|
||||
*
|
||||
* @param str The string to gzip.
|
||||
* @return The gzipped String.
|
||||
* @throws IOException If the compression failed.
|
||||
*/
|
||||
private byte[] compress(final String str) throws IOException {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
PGZIPOutputStream gzip = new PGZIPOutputStream(outputStream);
|
||||
gzip.write(str.getBytes("UTF-8"));
|
||||
gzip.close();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.boydti.fawe.util.task;
|
||||
|
||||
public interface DelayedTask<T> {
|
||||
int getDelay(T previousResult);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.boydti.fawe.util.task;
|
||||
|
||||
public interface ReceiveTask<T> {
|
||||
void run(T previous);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.boydti.fawe.util.task;
|
||||
|
||||
public interface ReturnTask<T> {
|
||||
T run();
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.boydti.fawe.util.task;
|
||||
|
||||
public interface Task<T, V> {
|
||||
T run(V previousResult);
|
||||
}
|
@ -0,0 +1,574 @@
|
||||
package com.boydti.fawe.util.task;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.object.FaweQueue;
|
||||
import com.boydti.fawe.object.Metadatable;
|
||||
import com.boydti.fawe.object.RunnableVal;
|
||||
import com.boydti.fawe.util.SetQueue;
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TaskBuilder extends Metadatable {
|
||||
|
||||
private final ForkJoinPool pool = new ForkJoinPool();
|
||||
private final ArrayDeque<RunnableTask> tasks;
|
||||
private Object result = null;
|
||||
private Thread.UncaughtExceptionHandler handler;
|
||||
|
||||
public TaskBuilder() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public TaskBuilder(Thread.UncaughtExceptionHandler handler) {
|
||||
tasks = new ArrayDeque<>();
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public TaskBuilder async(Task task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.ASYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder async(ReceiveTask task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.ASYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder async(ReturnTask task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.ASYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder async(Runnable task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.ASYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder sync(Task task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.SYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder sync(ReceiveTask task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.SYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder sync(ReturnTask task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.SYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder sync(Runnable task) {
|
||||
tasks.add(RunnableTask.adapt(task, TaskType.SYNC));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder delay(int ticks) {
|
||||
tasks.add(RunnableDelayedTask.adapt(ticks));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder delay(DelayedTask task) {
|
||||
tasks.add(RunnableDelayedTask.adapt(task));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run some sync tasks in parallel<br>
|
||||
* - All sync parallel tasks which occur directly after each other will be run at the same time
|
||||
*
|
||||
* @param run
|
||||
* @return this
|
||||
*/
|
||||
public TaskBuilder syncParallel(Runnable run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.SYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder syncParallel(Task run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.SYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder syncParallel(ReceiveTask run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.SYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder syncParallel(ReturnTask run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.SYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run some async tasks in parallel<br>
|
||||
* - All async parallel tasks which occur directly after each other will be run at the same time
|
||||
*
|
||||
* @param run
|
||||
* @return this
|
||||
*/
|
||||
public TaskBuilder asyncParallel(Runnable run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.ASYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder asyncParallel(Task run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.ASYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder asyncParallel(ReceiveTask run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.ASYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder asyncParallel(ReturnTask run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.ASYNC_PARALLEL));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a split task when the server has free time<br>
|
||||
* - i.e. To maintain high tps
|
||||
* - Use the split() method within task execution
|
||||
* - FAWE will be able to pause execution at these points
|
||||
*
|
||||
* @param run
|
||||
* @return this
|
||||
*/
|
||||
public TaskBuilder syncWhenFree(SplitTask run) {
|
||||
tasks.add(run);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder syncWhenFree(Task run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.SYNC_WHEN_FREE));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder syncWhenFree(ReceiveTask run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.SYNC_WHEN_FREE));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder syncWhenFree(ReturnTask run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.SYNC_WHEN_FREE));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder abortIfTrue(Task<Boolean, Object> run) {
|
||||
tasks.add(RunnableTask.adapt(run, TaskType.ABORT));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder abortIfTrue(final Runnable run) {
|
||||
tasks.add(RunnableTask.adapt(new Task<Boolean, Boolean>() {
|
||||
@Override
|
||||
public Boolean run(Boolean previous) {
|
||||
if (previous == Boolean.TRUE) run.run();
|
||||
return previous == Boolean.TRUE;
|
||||
}
|
||||
}, TaskType.ABORT));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder abortIfNull(final Runnable run) {
|
||||
tasks.add(RunnableTask.adapt(new Task<Boolean, Object>() {
|
||||
@Override
|
||||
public Boolean run(Object previous) {
|
||||
if (previous == null) run.run();
|
||||
return previous == null;
|
||||
}
|
||||
}, TaskType.ABORT));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder abortIfEqual(final Runnable run, final Object other) {
|
||||
tasks.add(RunnableTask.adapt(new Task<Boolean, Object>() {
|
||||
@Override
|
||||
public Boolean run(Object previous) {
|
||||
if (Objects.equals(previous, other)) run.run();
|
||||
return Objects.equals(previous, other);
|
||||
}
|
||||
}, TaskType.ABORT));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TaskBuilder abortIfNotEqual(final Runnable run, final Object other) {
|
||||
tasks.add(RunnableTask.adapt(new Task<Boolean, Object>() {
|
||||
@Override
|
||||
public Boolean run(Object previous) {
|
||||
if (!Objects.equals(previous, other)) run.run();
|
||||
return !Objects.equals(previous, other);
|
||||
}
|
||||
}, TaskType.ABORT));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Have all async tasks run on a new thread<br>
|
||||
* - As opposed to trying to using the current thread
|
||||
*/
|
||||
public void buildAsync() {
|
||||
TaskManager.IMP.async(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
build();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins execution of the tasks<br>
|
||||
* - The builder will attempt to run on the current thread if possible
|
||||
*/
|
||||
public void build() {
|
||||
RunnableTask peek;
|
||||
while ((peek = tasks.peek()) != null) {
|
||||
try {
|
||||
switch (peek.type) {
|
||||
case DELAY:
|
||||
DelayedTask task = (DelayedTask) tasks.poll();
|
||||
RunnableTask next = tasks.peek();
|
||||
if (next != null) {
|
||||
switch (next.type) {
|
||||
case SYNC:
|
||||
case ABORT:
|
||||
case SYNC_PARALLEL:
|
||||
TaskManager.IMP.later(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
build();
|
||||
}
|
||||
}, task.getDelay(result));
|
||||
return;
|
||||
default:
|
||||
TaskManager.IMP.laterAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
build();
|
||||
}
|
||||
}, task.getDelay(result));
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SYNC:
|
||||
case SYNC_PARALLEL:
|
||||
if (!Fawe.isMainThread()) {
|
||||
TaskManager.IMP.sync(new RunnableVal() {
|
||||
@Override
|
||||
public void run(Object value) {
|
||||
build();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SYNC_WHEN_FREE:
|
||||
case ASYNC:
|
||||
case ASYNC_PARALLEL:
|
||||
if (Fawe.isMainThread()) {
|
||||
TaskManager.IMP.async(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
build();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
RunnableTask task = tasks.poll();
|
||||
task.value = result;
|
||||
switch (task.type) {
|
||||
case ABORT:
|
||||
if (((Task<Boolean, Object>) task).run(result)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SYNC:
|
||||
result = task.exec(result);
|
||||
break;
|
||||
case SYNC_WHEN_FREE:
|
||||
if (task instanceof SplitTask) {
|
||||
SplitTask splitTask = (SplitTask) task;
|
||||
result = splitTask.execSplit(result);
|
||||
} else {
|
||||
result = TaskManager.IMP.syncWhenFree(task);
|
||||
}
|
||||
break;
|
||||
case ASYNC:
|
||||
result = task.exec(result);
|
||||
continue;
|
||||
case SYNC_PARALLEL:
|
||||
case ASYNC_PARALLEL:
|
||||
final ArrayList<RunnableTask> parallel = new ArrayList<RunnableTask>();
|
||||
parallel.add(task);
|
||||
RunnableTask next = tasks.peek();
|
||||
while (next != null && next.type == task.type) {
|
||||
parallel.add(next);
|
||||
tasks.poll();
|
||||
next = tasks.peek();
|
||||
}
|
||||
for (RunnableTask current : parallel) {
|
||||
pool.submit(current);
|
||||
}
|
||||
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
result = null;
|
||||
for (RunnableTask current : parallel) {
|
||||
if (current.value != null) {
|
||||
result = current.value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (task.isAborted()) {
|
||||
return;
|
||||
}
|
||||
} catch (TaskAbortException abort) {
|
||||
return;
|
||||
} catch (Throwable e1) {
|
||||
if (handler != null) {
|
||||
try {
|
||||
handler.uncaughtException(Thread.currentThread(), e1);
|
||||
} catch (Throwable e2) {
|
||||
e1.printStackTrace();
|
||||
e2.printStackTrace();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TaskAbortException extends RuntimeException {
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private FaweQueue queue;
|
||||
private long last;
|
||||
private long start;
|
||||
private Object asyncWaitLock = new Object();
|
||||
private Object syncWaitLock = new Object();
|
||||
private boolean finished;
|
||||
|
||||
private static abstract class RunnableTask<T> extends RunnableVal<T> {
|
||||
public final TaskType type;
|
||||
private boolean aborted;
|
||||
|
||||
public RunnableTask(TaskType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void abortNextTasks() {
|
||||
this.aborted = true;
|
||||
}
|
||||
|
||||
public boolean isAborted() {
|
||||
return aborted;
|
||||
}
|
||||
|
||||
public static RunnableTask adapt(final Task task, TaskType type) {
|
||||
return new RunnableTask(type) {
|
||||
@Override
|
||||
public Object exec(Object previous) {
|
||||
return task.run(previous);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static RunnableTask adapt(final ReturnTask task, TaskType type) {
|
||||
return new RunnableTask(type) {
|
||||
@Override
|
||||
public Object exec(Object previous) {
|
||||
return task.run();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static RunnableTask adapt(final ReceiveTask task, TaskType type) {
|
||||
return new RunnableTask(type) {
|
||||
@Override
|
||||
public Object exec(Object previous) {
|
||||
task.run(previous);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static RunnableTask adapt(final Runnable run, TaskType type) {
|
||||
return new RunnableTask(type) {
|
||||
@Override
|
||||
public Object exec(Object previous) {
|
||||
if (run instanceof RunnableVal) {
|
||||
((RunnableVal) run).value = this.value;
|
||||
return this.value = ((RunnableVal) run).runAndGet();
|
||||
}
|
||||
run.run();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public abstract T exec(Object previous);
|
||||
|
||||
@Override
|
||||
public final void run(T value) {
|
||||
this.value = exec(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class RunnableDelayedTask extends RunnableTask {
|
||||
|
||||
public RunnableDelayedTask(TaskType type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object exec(Object previous) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
public abstract int delay(Object previous);
|
||||
|
||||
public static RunnableDelayedTask adapt(final DelayedTask task) {
|
||||
return new RunnableDelayedTask(TaskType.DELAY) {
|
||||
@Override
|
||||
public int delay(Object previous) {
|
||||
return task.getDelay(previous);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static RunnableDelayedTask adapt(final int time) {
|
||||
return new RunnableDelayedTask(TaskType.DELAY) {
|
||||
@Override
|
||||
public int delay(Object previous) {
|
||||
return time;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class SplitTask extends RunnableTask {
|
||||
|
||||
private final long allocation;
|
||||
private final FaweQueue queue;
|
||||
private long last;
|
||||
private long start;
|
||||
private Object asyncWaitLock = new Object();
|
||||
private Object syncWaitLock = new Object();
|
||||
|
||||
private boolean finished;
|
||||
private boolean waitingAsync = true;
|
||||
private boolean waitingSync = false;
|
||||
|
||||
public SplitTask() {
|
||||
this(20);
|
||||
}
|
||||
|
||||
public SplitTask(long allocation) {
|
||||
super(TaskType.SYNC_WHEN_FREE);
|
||||
this.allocation = allocation;
|
||||
this.queue = SetQueue.IMP.getNewQueue((String) null, true, false);
|
||||
}
|
||||
|
||||
public Object execSplit(final Object previous) {
|
||||
this.value = previous;
|
||||
final Thread thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
synchronized (asyncWaitLock) {
|
||||
asyncWaitLock.notifyAll();
|
||||
asyncWaitLock.wait(Long.MAX_VALUE);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
exec(previous);
|
||||
finished = true;
|
||||
waitingAsync = true;
|
||||
waitingSync = false;
|
||||
synchronized (syncWaitLock) {
|
||||
syncWaitLock.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
synchronized (asyncWaitLock) {
|
||||
thread.start();
|
||||
asyncWaitLock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
while (thread.isAlive()) {
|
||||
TaskManager.IMP.syncWhenFree(new RunnableVal() {
|
||||
@Override
|
||||
public void run(Object ignore) {
|
||||
queue.startSet(true);
|
||||
start = System.currentTimeMillis();
|
||||
try {
|
||||
if (!finished) {
|
||||
synchronized (asyncWaitLock) {
|
||||
while (!waitingAsync) asyncWaitLock.wait(1);
|
||||
asyncWaitLock.notifyAll();
|
||||
}
|
||||
waitingSync = true;
|
||||
synchronized (syncWaitLock) {
|
||||
syncWaitLock.notifyAll();
|
||||
syncWaitLock.wait();
|
||||
}
|
||||
waitingSync = false;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
queue.endSet(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void split() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - start > allocation) {
|
||||
try {
|
||||
synchronized (syncWaitLock) {
|
||||
while (!waitingSync) syncWaitLock.wait(1);
|
||||
syncWaitLock.notifyAll();
|
||||
}
|
||||
waitingAsync = true;
|
||||
synchronized (asyncWaitLock) {
|
||||
asyncWaitLock.notifyAll();
|
||||
asyncWaitLock.wait();
|
||||
}
|
||||
waitingAsync = false;
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum TaskType {
|
||||
SYNC,
|
||||
ASYNC,
|
||||
SYNC_PARALLEL,
|
||||
ASYNC_PARALLEL,
|
||||
SYNC_WHEN_FREE,
|
||||
DELAY,
|
||||
ABORT
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.boydti.fawe.util.terrain;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
import static com.boydti.fawe.util.MathMan.pairInt;
|
||||
|
||||
public final class Erosion {
|
||||
private final int area;
|
||||
private float[][] terrainHeight;
|
||||
private float[][] waterHeight;
|
||||
|
||||
private long[] queue_2;
|
||||
private long[] queue;
|
||||
private int queueIndex;
|
||||
|
||||
public Erosion(int width, int length) {
|
||||
this.area = width * length;
|
||||
queue = new long[area];
|
||||
Arrays.fill(queue, -1);
|
||||
|
||||
}
|
||||
|
||||
public void addWater(int x, int z, float amt) {
|
||||
waterHeight[x][z] += amt;
|
||||
queue[queueIndex++] = pairInt(x, z);
|
||||
}
|
||||
|
||||
public void propogateWater() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user