package com.plotsquared.bukkit.util.block; import com.intellectualcrafters.plot.object.ChunkWrapper; import com.intellectualcrafters.plot.object.PseudoRandom; import com.intellectualcrafters.plot.util.MainUtil; import com.intellectualcrafters.plot.util.ReflectionUtils; import com.intellectualcrafters.plot.util.block.BasicLocalBlockQueue; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.bukkit.Chunk; import org.bukkit.Material; import org.bukkit.World; import static com.intellectualcrafters.plot.util.ReflectionUtils.getRefClass; public class BukkitLocalQueue_1_9 extends BukkitLocalQueue<char[]> { private final Object air; // private final HashMap<ChunkWrapper, Chunk> toUpdate = new HashMap<>(); private final ReflectionUtils.RefMethod methodGetHandleChunk; private final ReflectionUtils.RefMethod methodInitLighting; private final ReflectionUtils.RefConstructor classBlockPositionConstructor; private final ReflectionUtils.RefConstructor classChunkSectionConstructor; private final ReflectionUtils.RefMethod methodW; private final ReflectionUtils.RefMethod methodAreNeighborsLoaded; private final ReflectionUtils.RefField fieldSections; private final ReflectionUtils.RefField fieldWorld; private final ReflectionUtils.RefMethod methodGetBlocks; private final ReflectionUtils.RefMethod methodGetType; private final ReflectionUtils.RefMethod methodSetType; private final ReflectionUtils.RefMethod methodGetCombinedId; private final ReflectionUtils.RefMethod methodGetByCombinedId; private final ReflectionUtils.RefMethod methodGetWorld; private final ReflectionUtils.RefField tileEntityListTick; public BukkitLocalQueue_1_9(String world) throws NoSuchMethodException, ClassNotFoundException, NoSuchFieldException { super(world); ReflectionUtils.RefClass classCraftChunk = getRefClass("{cb}.CraftChunk"); this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle"); ReflectionUtils.RefClass classChunk = getRefClass("{nms}.Chunk"); this.methodInitLighting = classChunk.getMethod("initLighting"); ReflectionUtils.RefClass classBlockPosition = getRefClass("{nms}.BlockPosition"); this.classBlockPositionConstructor = classBlockPosition.getConstructor(int.class, int.class, int.class); ReflectionUtils.RefClass classWorld = getRefClass("{nms}.World"); this.tileEntityListTick = classWorld.getField("tileEntityListTick"); this.methodGetWorld = classChunk.getMethod("getWorld"); this.methodW = classWorld.getMethod("w", classBlockPosition.getRealClass()); this.fieldSections = classChunk.getField("sections"); this.fieldWorld = classChunk.getField("world"); ReflectionUtils.RefClass classBlock = getRefClass("{nms}.Block"); ReflectionUtils.RefClass classIBlockData = getRefClass("{nms}.IBlockData"); this.methodGetCombinedId = classBlock.getMethod("getCombinedId", classIBlockData.getRealClass()); this.methodGetByCombinedId = classBlock.getMethod("getByCombinedId", int.class); ReflectionUtils.RefClass classChunkSection = getRefClass("{nms}.ChunkSection"); this.methodGetBlocks = classChunkSection.getMethod("getBlocks"); this.methodGetType = classChunkSection.getMethod("getType", int.class, int.class, int.class); this.methodSetType = classChunkSection.getMethod("setType", int.class, int.class, int.class, classIBlockData.getRealClass()); this.methodAreNeighborsLoaded = classChunk.getMethod("areNeighborsLoaded", int.class); this.classChunkSectionConstructor = classChunkSection.getConstructor(int.class, boolean.class, char[].class); this.air = this.methodGetByCombinedId.call(0); MainUtil.initCache(); } @Override public LocalChunk<char[]> getLocalChunk(int x, int z) { return new CharLocalChunk_1_8_3(this, x, z); } public class CharLocalChunk_1_8_3 extends CharLocalChunk { public short[] count; public short[] air; public short[] relight; public CharLocalChunk_1_8_3(BasicLocalBlockQueue parent, int x, int z) { super(parent, x, z); this.count = new short[16]; this.air = new short[16]; this.relight = new short[16]; } @Override public void setBlock(int x, int y, int z, int id, int data) { int i = MainUtil.CACHE_I[y][x][z]; int j = MainUtil.CACHE_J[y][x][z]; char[] vs = this.blocks[i]; if (vs == null) { vs = this.blocks[i] = new char[4096]; this.count[i]++; } else if (vs[j] == 0) { this.count[i]++; } switch (id) { case 0: this.air[i]++; vs[j] = (char) 1; return; case 10: case 11: case 39: case 40: case 51: case 74: case 89: case 122: case 124: case 138: case 169: this.relight[i]++; case 2: case 4: case 13: case 14: case 15: case 20: case 21: case 22: case 30: case 32: case 37: case 41: case 42: case 45: case 46: case 47: case 48: case 49: case 55: case 56: case 57: case 58: case 60: case 7: case 8: case 9: case 73: case 78: case 79: case 80: case 81: case 82: case 83: case 85: case 87: case 88: case 101: case 102: case 103: case 110: case 112: case 113: case 121: case 129: case 133: case 165: case 166: case 170: case 172: case 173: case 174: case 181: case 182: case 188: case 189: case 190: case 191: case 192: vs[j] = (char) (id << 4); return; case 130: case 76: case 62: this.relight[i]++; case 54: case 146: case 61: case 65: case 68: case 50: if (data < 2) { data = 2; } default: vs[j] = (char) ((id << 4) + data); return; } } public char[] getIdArray(int i) { return this.blocks[i]; } public int getCount(int i) { return this.count[i]; } public int getAir(int i) { return this.air[i]; } public void setCount(int i, short value) { this.count[i] = value; } public int getRelight(int i) { return this.relight[i]; } public int getTotalCount() { int total = 0; for (int i = 0; i < 16; i++) { total += this.count[i]; } return total; } public int getTotalRelight() { if (getTotalCount() == 0) { Arrays.fill(this.count, (short) 1); Arrays.fill(this.relight, Short.MAX_VALUE); return Short.MAX_VALUE; } int total = 0; for (int i = 0; i < 16; i++) { total += this.relight[i]; } return total; } } @Override public void setBlocks(LocalChunk lc) { CharLocalChunk_1_8_3 fs = (CharLocalChunk_1_8_3) lc; Chunk chunk = getChunk(lc.getX(), lc.getZ()); chunk.load(true); World world = chunk.getWorld(); try { boolean flag = world.getEnvironment() == World.Environment.NORMAL; // Sections Method getHandle = chunk.getClass().getDeclaredMethod("getHandle"); Object c = getHandle.invoke(chunk); Object w = this.methodGetWorld.of(c).call(); Class<? extends Object> clazz = c.getClass(); Field sf = clazz.getDeclaredField("sections"); sf.setAccessible(true); Field tf = clazz.getDeclaredField("tileEntities"); Field entitySlices = clazz.getDeclaredField("entitySlices"); Object[] sections = (Object[]) sf.get(c); Map<?, ?> tiles = (Map<?, ?>) tf.get(c); Collection<?>[] entities = (Collection<?>[]) entitySlices.get(c); Method xm = null; Method ym = null; Method zm = null; // Trim tiles boolean removed = false; Set<Map.Entry<?, ?>> entrySet = (Set<Map.Entry<?, ?>>) (Set<?>) tiles.entrySet(); Iterator<Map.Entry<?, ?>> iterator = entrySet.iterator(); while (iterator.hasNext()) { Map.Entry<?, ?> tile = iterator.next(); Object pos = tile.getKey(); if (xm == null) { Class<?> clazz2 = pos.getClass().getSuperclass(); xm = clazz2.getDeclaredMethod("getX"); ym = clazz2.getDeclaredMethod("getY"); zm = clazz2.getDeclaredMethod("getZ"); } int lx = (int) xm.invoke(pos) & 15; int ly = (int) ym.invoke(pos); int lz = (int) zm.invoke(pos) & 15; int j = MainUtil.CACHE_I[ly][lx][lz]; int k = MainUtil.CACHE_J[ly][lx][lz]; char[] array = fs.getIdArray(j); if (array == null) { continue; } if (array[k] != 0) { removed = true; iterator.remove(); } } if (removed) { ((Collection) this.tileEntityListTick.of(w).get()).clear(); } // Trim entities for (int i = 0; i < 16; i++) { if (entities[i] != null && fs.getCount(i) >= 4096) { entities[i].clear(); } } // Efficiently merge sections for (int j = 0; j < sections.length; j++) { if (fs.getCount(j) == 0) { continue; } char[] newArray = fs.getIdArray(j); if (newArray == null) { continue; } Object section = sections[j]; if (section == null || fs.getCount(j) >= 4096) { section = sections[j] = newChunkSection(j << 4, flag, fs.getIdArray(j)); continue; } Object currentArray = getBlocks(section); ReflectionUtils.RefMethod.RefExecutor setType = this.methodSetType.of(section); boolean fill = true; for (int k = 0; k < newArray.length; k++) { char n = newArray[k]; switch (n) { case 0: fill = false; continue; case 1: { fill = false; int x = MainUtil.x_loc[j][k]; int y = MainUtil.y_loc[j][k]; int z = MainUtil.z_loc[j][k]; setType.call(x, y & 15, z, this.air); continue; } default: int x = MainUtil.x_loc[j][k]; int y = MainUtil.y_loc[j][k]; int z = MainUtil.z_loc[j][k]; int id = n >> 4; int data = n & 15; Object iBlock = this.methodGetByCombinedId.call((int) (id & 0xFFF) + (data << 12)); setType.call(x, y & 15, z, iBlock); } } if (fill) { fs.setCount(j, Short.MAX_VALUE); } } // Clear } catch (IllegalArgumentException | SecurityException | ReflectiveOperationException e) { e.printStackTrace(); } fixLighting(chunk, fs, true); refreshChunk(fs.getX(), fs.getZ()); } public Object newChunkSection(int i, boolean flag, char[] ids) throws ReflectiveOperationException { return this.classChunkSectionConstructor.create(i, flag, ids); } public Object getBlocks(Object obj) { return this.methodGetBlocks.of(obj).call(); } @Override public void fixChunkLighting(int x, int z) { Object c = this.methodGetHandleChunk.of(getChunk(x, z)).call(); this.methodInitLighting.of(c).call(); } public boolean fixLighting(Chunk chunk, CharLocalChunk_1_8_3 bc, boolean fixAll) { try { if (!chunk.isLoaded()) { chunk.load(false); } else { chunk.unload(true, false); chunk.load(false); } // Initialize lighting Object c = this.methodGetHandleChunk.of(chunk).call(); ChunkWrapper wrapper = new ChunkWrapper(getWorld(), bc.getX(), bc.getZ()); Object[] result = disableLighting(chunk); enableLighting(result); this.methodInitLighting.of(c).call(); if (bc.getTotalRelight() != 0 || fixAll) { Object[] sections = (Object[]) this.fieldSections.of(c).get(); Object w = this.fieldWorld.of(c).get(); int X = chunk.getX() << 4; int Z = chunk.getZ() << 4; ReflectionUtils.RefMethod.RefExecutor relight = this.methodW.of(w); for (int j = 0; j < sections.length; j++) { Object section = sections[j]; if (section == null) { continue; } if (bc.getRelight(j) == 0 && !fixAll || bc.getCount(j) == 0 || bc.getCount(j) >= 4096 && bc.getAir(j) == 0) { continue; } char[] array = bc.getIdArray(j); if (array != null) { int l = PseudoRandom.random.random(2); for (int k = 0; k < array.length; k++) { int i = array[k]; if (i < 16) { continue; } short id = (short) (i >> 4); switch (id) { // Lighting default: if (!fixAll) { continue; } if ((k & 1) == l) { l = 1 - l; continue; } case 10: case 11: case 39: case 40: case 50: case 51: case 62: case 74: case 76: case 89: case 122: case 124: case 130: case 138: case 169: int x = MainUtil.x_loc[j][k]; int y = MainUtil.y_loc[j][k]; int z = MainUtil.z_loc[j][k]; if (isSurrounded(bc.blocks, x, y, z)) { continue; } Object pos = this.classBlockPositionConstructor.create(X + x, y, Z + z); relight.call(pos); } } } } } resetLighting(result); return true; } catch (Throwable e) { e.printStackTrace(); } return false; } @Override public void refreshChunk(int x, int z) { getBukkitWorld().refreshChunk(x, z); } public boolean isSurrounded(char[][] sections, int x, int y, int z) { return isSolid(getId(sections, x, y + 1, z)) && isSolid(getId(sections, x + 1, y - 1, z)) && isSolid(getId(sections, x - 1, y, z)) && isSolid(getId(sections, x, y, z + 1)) && isSolid(getId(sections, x, y, z - 1)); } public boolean isSolid(int i) { if (i != 0) { Material material = Material.getMaterial(i); return material != null && Material.getMaterial(i).isOccluding(); } return false; } public int getId(char[] section, int x, int y, int z) { if (section == null) { return 0; } int j = MainUtil.CACHE_J[y][x][z]; return section[j] >> 4; } public int getId(char[][] sections, int x, int y, int z) { if (x < 0 || x > 15 || z < 0 || z > 15) { return 1; } if (y < 0 || y > 255) { return 1; } int i = MainUtil.CACHE_I[y][x][z]; char[] section = sections[i]; if (section == null) { return 0; } return getId(section, x, y, z); } }