package com.plotsquared.bukkit.util.block; import com.intellectualcrafters.plot.object.ChunkLoc; import com.intellectualcrafters.plot.object.ChunkWrapper; import com.intellectualcrafters.plot.object.PseudoRandom; import com.intellectualcrafters.plot.util.ChunkManager; import com.intellectualcrafters.plot.util.MainUtil; import com.intellectualcrafters.plot.util.ReflectionUtils; import com.intellectualcrafters.plot.util.TaskManager; import com.intellectualcrafters.plot.util.block.BasicLocalBlockQueue; import com.plotsquared.bukkit.util.SendChunk; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Material; import org.bukkit.World; import static com.intellectualcrafters.plot.util.ReflectionUtils.getRefClass; public class BukkitLocalQueue_1_8_3 extends BukkitLocalQueue<char[]> { private final SendChunk sendChunk; private final HashMap<ChunkWrapper, Chunk> toUpdate = new HashMap<>(); private final ReflectionUtils.RefMethod methodGetHandleChunk; private final ReflectionUtils.RefMethod methodGetHandleWorld; private final ReflectionUtils.RefMethod methodInitLighting; private final ReflectionUtils.RefConstructor classBlockPositionConstructor; private final ReflectionUtils.RefConstructor classChunkSectionConstructor; private final ReflectionUtils.RefMethod methodX; private final ReflectionUtils.RefMethod methodAreNeighborsLoaded; private final ReflectionUtils.RefField fieldSections; private final ReflectionUtils.RefField fieldWorld; private final ReflectionUtils.RefMethod methodGetIdArray; private final ReflectionUtils.RefMethod methodGetWorld; private final ReflectionUtils.RefField tileEntityListTick; public BukkitLocalQueue_1_8_3(String world) throws NoSuchMethodException, ClassNotFoundException, NoSuchFieldException { super(world); ReflectionUtils.RefClass classCraftChunk = getRefClass("{cb}.CraftChunk"); ReflectionUtils.RefClass classCraftWorld = getRefClass("{cb}.CraftWorld"); 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.methodX = classWorld.getMethod("x", classBlockPosition.getRealClass()); this.fieldSections = classChunk.getField("sections"); this.fieldWorld = classChunk.getField("world"); ReflectionUtils.RefClass classChunkSection = getRefClass("{nms}.ChunkSection"); this.methodGetIdArray = classChunkSection.getMethod("getIdArray"); this.methodAreNeighborsLoaded = classChunk.getMethod("areNeighborsLoaded", int.class); this.classChunkSectionConstructor = classChunkSection.getConstructor(int.class, boolean.class, char[].class); this.tileEntityListTick = classWorld.getField("tileEntityList"); this.methodGetHandleWorld = classCraftWorld.getMethod("getHandle"); this.methodGetWorld = classChunk.getMethod("getWorld"); this.sendChunk = new SendChunk(); TaskManager.runTaskRepeat(new Runnable() { @Override public void run() { if (BukkitLocalQueue_1_8_3.this.toUpdate.isEmpty()) { return; } int count = 0; ArrayList<Chunk> chunks = new ArrayList<>(); Iterator<Map.Entry<ChunkWrapper, Chunk>> i = BukkitLocalQueue_1_8_3.this.toUpdate.entrySet().iterator(); while (i.hasNext() && count < 128) { chunks.add(i.next().getValue()); i.remove(); count++; } if (count == 0) { return; } update(chunks); } }, 1); 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(); ChunkWrapper wrapper = new ChunkWrapper(getWorld(), lc.getX(), lc.getZ()); if (!this.toUpdate.containsKey(wrapper)) { this.toUpdate.put(wrapper, chunk); } 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<?> clazz = c.getClass(); Field sections1 = clazz.getDeclaredField("sections"); sections1.setAccessible(true); Field tileEntities = clazz.getDeclaredField("tileEntities"); Field entitySlices = clazz.getDeclaredField("entitySlices"); Object[] sections = (Object[]) sections1.get(c); Map<?, ?> tiles = (Map<?, ?>) tileEntities.get(c); Collection<?>[] entities = (Collection<?>[]) entitySlices.get(c); Method getX = null; Method getY = null; Method getZ = 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 (getX == null) { Class<? extends Object> clazz2 = pos.getClass().getSuperclass(); getX = clazz2.getDeclaredMethod("getX"); getY = clazz2.getDeclaredMethod("getY"); getZ = clazz2.getDeclaredMethod("getZ"); } int lx = (int) getX.invoke(pos) & 15; int ly = (int) getY.invoke(pos); int lz = (int) getZ.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, newArray); continue; } char[] currentArray = getIdArray(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; currentArray[k] = 0; continue; default: currentArray[k] = n; continue; } } if (fill) { fs.setCount(j, Short.MAX_VALUE); } } // Clear } catch (IllegalArgumentException | SecurityException | ReflectiveOperationException e) { e.printStackTrace(); } fixLighting(chunk, fs, true); } public Object newChunkSection(int i, boolean flag, char[] ids) throws ReflectiveOperationException { return this.classChunkSectionConstructor.create(i, flag, ids); } public char[] getIdArray(Object obj) { return (char[]) this.methodGetIdArray.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(); if (fixAll && !(boolean) this.methodAreNeighborsLoaded.of(c).call(1)) { World world = chunk.getWorld(); ChunkWrapper wrapper = new ChunkWrapper(getWorld(), chunk.getX(), chunk.getZ()); for (int x = wrapper.x - 1; x <= wrapper.x + 1; x++) { for (int z = wrapper.z - 1; z <= wrapper.z + 1; z++) { if (x != 0 && z != 0) { Chunk other = world.getChunkAt(x, z); while (!other.isLoaded()) { other.load(true); } ChunkManager.manager.loadChunk(wrapper.world, new ChunkLoc(x, z), true); } } } /* if (!(boolean) methodAreNeighborsLoaded.of(c).call(1)) { return false; } */ } this.methodInitLighting.of(c).call(); if (bc.getTotalRelight() == 0 && !fixAll) { return true; } 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.methodX.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 = getIdArray(section); 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(sections, x, y, z)) { continue; } Object pos = this.classBlockPositionConstructor.create(X + x, y, Z + z); relight.call(pos); } } } return true; } catch (Throwable e) { e.printStackTrace(); } return false; } public boolean isSurrounded(Object[] 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) { return i != 0 && Material.getMaterial(i).isOccluding(); } public int getId(Object[] 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]; Object section = sections[i]; if (section == null) { return 0; } char[] array = getIdArray(section); int j = MainUtil.CACHE_J[y][x][z]; return array[j] >> 4; } public void update(Collection<Chunk> chunks) { if (chunks.isEmpty()) { return; } if (!MainUtil.canSendChunk) { for (Chunk chunk : chunks) { chunk.getWorld().refreshChunk(chunk.getX(), chunk.getZ()); chunk.unload(true, false); chunk.load(); } return; } try { this.sendChunk.sendChunk(chunks); } catch (Throwable e) { e.printStackTrace(); MainUtil.canSendChunk = false; } } @Override public void refreshChunk(int x, int z) { update(Arrays.asList(Bukkit.getWorld(getWorld()).getChunkAt(x, z))); } }