{"action":"created","comment":{"url":"https://api.github.com/repos/PaperMC/Paper/pulls/comments/68509595","id":68509595,"diff_hunk":"@@ -0,0 +1,997 @@\n+From b4c276d011ddd33ab02eae4f3b87db4c9fb71e1c Mon Sep 17 00:00:00 2001\n+From: BuildTools <unconfigured@null.spigotmc.org>\n+Date: Fri, 24 Jun 2016 03:25:10 +0200\n+Subject: [PATCH] Anti-Xray\n+\n+\n+diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java\n+index e3fe8f4..fbcc680 100644\n+--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java\n++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java\n+@@ -1,7 +1,9 @@\n+ package com.destroystokyo.paper;\n+ \n++import java.util.Arrays;\n+ import java.util.List;\n+ \n++import com.destroystokyo.paper.antixray.AntiXray;\n+ import org.bukkit.Bukkit;\n+ import org.bukkit.configuration.file.YamlConfiguration;\n+ import org.spigotmc.SpigotWorldConfig;\n+@@ -382,4 +384,34 @@ public class PaperWorldConfig {\n+     private void elytraHitWallDamage() {\n+         elytraHitWallDamage = getBoolean(\"elytra-hit-wall-damage\", true);\n+     }\n++\n++    public boolean antiXray;\n++    public int engineMode;\n++    public List<Object> hiddenBlocks;\n++    public List<Object> replaceBlocks;\n++    public int maxChunkY;\n++    public boolean asynchronous;\n++    public int neighborsMode;\n++    public AntiXray antiXrayInstance;\n++    private void antiXray() {\n++        antiXray = getBoolean(\"anti-xray.enabled\", false);\n++        log(\"Anti X-Ray: \" + antiXray);\n++        engineMode = getInt(\"anti-xray.engine-mode\", 3);\n++        log(\"\\tEngine Mode: \" + engineMode + \" (\" + (engineMode == 1 ? \"hidden ores\" : engineMode == 2 ? \"fake ores\" : engineMode == 3 ? \"fake ores every third block\" : \"unknown\") + \")\");\n++        hiddenBlocks = getList(\"anti-xray.hide-blocks\", Arrays.asList(new Object[] {\n++            \"gold_ore\", \"iron_ore\", \"coal_ore\", \"lapis_ore\", \"mossy_cobblestone\", \"obsidian\", \"chest\", \"diamond_ore\", \"redstone_ore\", \"lit_redstone_ore\", \"clay\", \"emerald_ore\", \"ender_chest\"\n++        }));\n++        log(\"\\tHidden Blocks: \" + hiddenBlocks);\n++        replaceBlocks = getList(\"anti-xray.replace-blocks\", Arrays.asList(new Object[] {\n++            \"stone\", \"planks\"\n++        }));\n++        log(\"\\tReplace Blocks: \" + replaceBlocks + \" (for engine mode 2 and 3)\");\n++        maxChunkY = getInt(\"anti-xray.max-chunk-y\", 3);\n++        log(\"\\tMax Chunk Y: \" + maxChunkY + \" (obfuscate up to \" + ((maxChunkY + 1) * 16) + \" blocks)\");\n++        asynchronous = getBoolean(\"anti-xray.asynchronous\", true);\n++        log(\"\\tAsynchronous: \" + asynchronous);\n++        neighborsMode = getInt(\"anti-xray.neighbors-mode\", 2);\n++        log(\"\\tNeighbors Mode: \" + neighborsMode + \" (\" + (neighborsMode == 1 ? \"MC default, sometimes the edges of chunks can't be obfuscated\" : neighborsMode == 2 ? \"wait until neighbor chunks are loaded\" : neighborsMode == 3 ? \"load neighbor chunks\" : \"unknown\") + \")\");\n++        antiXrayInstance = new AntiXray(this);\n++    }\n+ }\n+diff --git a/src/main/java/com/destroystokyo/paper/antixray/AntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/AntiXray.java\n+new file mode 100644\n+index 0000000..def27cf\n+--- /dev/null\n++++ b/src/main/java/com/destroystokyo/paper/antixray/AntiXray.java\n+@@ -0,0 +1,286 @@\n++package com.destroystokyo.paper.antixray;\n++\n++import java.util.HashSet;\n++import java.util.Set;\n++import java.util.concurrent.Executors;\n++import java.util.concurrent.ExecutorService;\n++\n++import com.destroystokyo.paper.PaperWorldConfig;\n++import net.minecraft.server.Block;\n++import net.minecraft.server.BlockPosition;\n++import net.minecraft.server.Blocks;\n++import net.minecraft.server.Chunk;\n++import net.minecraft.server.ChunkSection;\n++import net.minecraft.server.DataBits;\n++import net.minecraft.server.DataPaletteBlock;\n++import net.minecraft.server.IBlockData;\n++import net.minecraft.server.PacketDataSerializer;\n++import net.minecraft.server.PacketPlayOutMapChunk;\n++import net.minecraft.server.World;\n++\n++public class AntiXray {\n++    private static final ExecutorService executorService = Executors.newSingleThreadExecutor();\n++    private final boolean antiXray;\n++    private final int engineMode;\n++    private final int maxChunkY;\n++    private final boolean asynchronous;\n++    private final int neighborsMode;\n++    // Used to keep track of which blocks to obfuscate\n++    private final boolean[] obfuscateBlocks = new boolean[Short.MAX_VALUE];\n++    // Used to select a random replacement ore\n++    private final IBlockData[] replacementOres;\n++\n++    public AntiXray(PaperWorldConfig config) {\n++        antiXray = config.antiXray;\n++        engineMode = config.engineMode;\n++        maxChunkY = config.maxChunkY;\n++        asynchronous = config.asynchronous;\n++        neighborsMode = config.neighborsMode;\n++        // Set all listed blocks as true to be obfuscated\n++        for (Object id : (engineMode == 1) ? config.hiddenBlocks : config.replaceBlocks) {\n++            Block block = Block.getByName(String.valueOf(id));\n++\n++            if (block != null) {\n++                int intId = Block.getId(block);\n++                obfuscateBlocks[intId] = true;\n++            }\n++        }\n++\n++        Set<IBlockData> replacementOreList = new HashSet<IBlockData>();\n++\n++        for (Object id : config.hiddenBlocks) {\n++            Block block = Block.getByName(String.valueOf(id));\n++            // Check it exists and is not a tile entity\n++            if (block != null && !block.isTileEntity()) {\n++                // Add it to the list of replacement blocks\n++                replacementOreList.add(block.getBlockData());\n++            }\n++        }\n++\n++        replacementOres = replacementOreList.toArray(new IBlockData[replacementOreList.size()]);\n++    }\n++\n++    public IBlockData[] getPredefinedBlockData(Chunk chunk, int chunkY) {\n++        if (antiXray && chunkY <= maxChunkY) {\n++            switch (engineMode) {\n++                case 1:\n++                    switch (chunk.world.getWorld().getEnvironment()) {\n++                        case NETHER:\n++                            return new IBlockData[] {Blocks.NETHERRACK.getBlockData()};\n++                        case THE_END:\n++                            return new IBlockData[] {Blocks.END_STONE.getBlockData()};\n++                        default:\n++                            return new IBlockData[] {Blocks.STONE.getBlockData()};\n++                    }\n++                case 2:\n++                case 3:\n++                    return replacementOres;\n++            }\n++        }\n++\n++        return null;\n++    }\n++\n++    public boolean onPacketCreate(Chunk chunk, int chunkSectionSelector) {\n++        if (antiXray) {\n++            if (neighborsMode == 1) {\n++                return true;\n++            } else if (neighborsMode == 2) {\n++                if (chunk.world.getChunkIfLoaded(chunk.locX - 1, chunk.locZ) == null || chunk.world.getChunkIfLoaded(chunk.locX + 1, chunk.locZ) == null || chunk.world.getChunkIfLoaded(chunk.locX, chunk.locZ - 1) == null || chunk.world.getChunkIfLoaded(chunk.locX, chunk.locZ + 1) == null) {\n++                    return false;\n++                } else {\n++                    return true;\n++                }\n++            } else if (neighborsMode == 3) {\n++                chunk.world.getChunkAt(chunk.locX - 1, chunk.locZ);\n++                chunk.world.getChunkAt(chunk.locX + 1, chunk.locZ);\n++                chunk.world.getChunkAt(chunk.locX, chunk.locZ - 1);\n++                chunk.world.getChunkAt(chunk.locX, chunk.locZ + 1);\n++                return true;\n++            }\n++        }\n++\n++        return true;\n++    }\n++\n++    public void createPacket(PacketPlayOutMapChunk packetPlayOutMapChunk, PacketDataSerializer packetDataSerializer, Chunk chunk, boolean writeSkyLightArray, int chunkSectionSelector) {\n++        if (antiXray) {\n++            Chunk[] nearbyChunks = {chunk.world.getChunkIfLoaded(chunk.locX - 1, chunk.locZ), chunk.world.getChunkIfLoaded(chunk.locX + 1, chunk.locZ), chunk.world.getChunkIfLoaded(chunk.locX, chunk.locZ - 1), chunk.world.getChunkIfLoaded(chunk.locX, chunk.locZ + 1)};\n++\n++            if (asynchronous) {\n++                executorService.execute(new ObfuscatorRunnable(packetPlayOutMapChunk, packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector, nearbyChunks));\n++            } else {\n++                packetPlayOutMapChunk.setWrittenChunkSections(packetPlayOutMapChunk.a(packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector, nearbyChunks));\n++                packetPlayOutMapChunk.setReady(true);\n++            }\n++        } else {\n++            packetPlayOutMapChunk.setWrittenChunkSections(packetPlayOutMapChunk.a(packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector, null));\n++            packetPlayOutMapChunk.setReady(true);\n++        }\n++    }\n++\n++    public void serializeOrObfuscate(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkY, DataPaletteBlock dataPaletteBlock, DataBits dataBits, Chunk[] nearbyChunks) {\n++        long[] dataBitsArray = dataBits.a();\n++\n++        if (antiXray && chunkY <= maxChunkY && dataPaletteBlock.getCurrentPredefinedBlockData() != null && dataPaletteBlock.getCurrentPredefinedBlockData().length > 0 && nearbyChunks != null) {\n++            // The iterator marking which random ore we should use next\n++            int randomOre = 0;\n++            // Boolean used to check if the engine mode is 3 (used for the initial value of x in the inner loop)\n++            boolean engineMode3 = engineMode == 3;\n++            // Increment the inner loop by 3 in engine mode 3 for more efficiency\n++            int increment = engineMode3 ? 3 : 1;\n++            // Stores the last dataBits-array-index which was obfuscated\n++            int dataBitsIndex = 0;\n++            // Stores the last data which was obfuscated but not written to the packet\n++            long currentData = dataBitsArray[0];\n++            // Write the length of the dataBits-array to the packet as it is in vanilla\n++            packetDataSerializer.d(dataBitsArray.length);\n++            int xMin = nearbyChunks[0] == null ? 1 : 0;\n++            int xMax = nearbyChunks[1] == null ? 15 : 16;\n++            int zMin = nearbyChunks[2] == null ? 1 : 0;\n++            int zMax = nearbyChunks[3] == null ? 15 : 16;\n++            // Write the dataBits-array to the packet\n++            // Work through all blocks in the chunkSection\n++            for (int y = 0; y < 16; y++) {\n++                for (int z = zMin; z < zMax; z++) {\n++                    // Shift the initial value of x and increment by 3 in engine mode 3\n++                    int x;\n++\n++                    if (engineMode3) {\n++                        x = (y + z) % 3;\n++\n++                        if (x < xMin) {\n++                            x += 3;\n++                        }\n++                    } else {\n++                        x = xMin;\n++                    }\n++\n++                    for (; x < xMax; x += increment) {\n++                        // Calculate the blockIndex from y, z, x and get the blockData from dataPaletteBlock\n++                        // More efficient because we may use the blockIndex again later\n++                        int blockIndex = y << 8 | z << 4 | x;\n++                        IBlockData blockData = dataPaletteBlock.a(blockIndex);\n++                        // Check if the block should be obfuscated\n++                        if (obfuscateBlocks[Block.getId(blockData.getBlock())]) {\n++                            // Check if the nearby blocks are not transparent, we can obfuscate\n++                            if (isHiddenBlock(x, y, z, chunk, chunkY, nearbyChunks)) {\n++                                // Get one of the predefined blocks which can be used for obfuscation\n++                                if (randomOre >= dataPaletteBlock.getCurrentPredefinedBlockData().length) {\n++                                    randomOre = 0;\n++                                }\n++\n++                                int newBlockData = dataPaletteBlock.getCurrentPredefinedBlockData()[randomOre++];\n++                                // Get the current dataBits-array-index for the block index\n++                                int currentDataBitsIndex = dataBits.getArrayIndex(blockIndex);\n++                                // Check if it has been changed\n++                                if (currentDataBitsIndex != dataBitsIndex) {\n++                                    // If so, we can write the last obfuscated data to the packet because it is finished with the obfuscation\n++                                    packetDataSerializer.writeLong(currentData);\n++                                    dataBitsIndex++;\n++                                    // We can also write all further content of the dataBits-array to the packet (until currentDataBitsIndex is reached)\n++                                    // because they didn't change\n++                                    while (dataBitsIndex < currentDataBitsIndex) {\n++                                        packetDataSerializer.writeLong(dataBitsArray[dataBitsIndex]);\n++                                        dataBitsIndex++;\n++                                    }\n++                                    // Now we get the data which has to be obfuscated\n++                                    currentData = dataBitsArray[dataBitsIndex];\n++                                }\n++                                // Obfuscate currentData\n++                                currentData = dataBits.obfuscate(blockIndex, newBlockData, currentData);\n++                                // Check if the data of the current block is spitted to the next dataBits-array-index\n++                                if (dataBits.isSplitted(blockIndex, dataBitsIndex)) {\n++                                    // If so, we can write currentData to the packet because it is finished with the obfuscation\n++                                    packetDataSerializer.writeLong(currentData);\n++                                    dataBitsIndex++;\n++                                    // Get the data at the next index\n++                                    currentData = dataBitsArray[dataBitsIndex];\n++                                    // And obfuscate it\n++                                    currentData = dataBits.obfuscateSplittedPart(blockIndex, newBlockData, currentData);\n++                                }\n++                            }\n++                        }\n++                    }\n++                }\n++            }\n++            // Write the rest of the dataBits-array to the packet\n++            packetDataSerializer.writeLong(currentData);\n++            dataBitsIndex++;\n++\n++            while (dataBitsIndex < dataBitsArray.length) {\n++                packetDataSerializer.writeLong(dataBitsArray[dataBitsIndex]);\n++                dataBitsIndex++;\n++            }\n++        } else {\n++            packetDataSerializer.a(dataBitsArray);\n++        }\n++    }\n++\n++    public void updateNearbyBlocks(World world, BlockPosition position) {\n++        if (antiXray) {\n++            // 2 is the radius, we shouldn't change it as that would make it exponentially slower\n++            updateNearbyBlocks(world, position, 2, false);\n++        }\n++    }\n++\n++    private void updateNearbyBlocks(World world, BlockPosition position, int radius, boolean updateSelf) {\n++        // If the block in question is loaded\n++        if (world.isLoaded(position)) {\n++            // Get block id\n++            Block block = world.getType(position).getBlock();\n++            // See if it needs update\n++            if (updateSelf && obfuscateBlocks[Block.getId(block)]) {\n++                // Send the update\n++                world.notify(position);\n++            }\n++            // Check other blocks for updates\n++            if (radius > 0) {\n++                updateNearbyBlocks(world, position.east(), radius - 1, true);\n++                updateNearbyBlocks(world, position.west(), radius - 1, true);\n++                updateNearbyBlocks(world, position.up(), radius - 1, true);\n++                updateNearbyBlocks(world, position.down(), radius - 1, true);\n++                updateNearbyBlocks(world, position.south(), radius - 1, true);\n++                updateNearbyBlocks(world, position.north(), radius - 1, true);\n++            }\n++        }\n++    }\n++\n++    private static boolean isHiddenBlock(int x, int y, int z, Chunk chunk, int chunkY, Chunk[] nearbyChunks) {\n++        return isSolidBlock(getType(x, y + 1, z, chunk, chunkY, nearbyChunks).getBlock())\n++            && isSolidBlock(getType(x + 1, y, z, chunk, chunkY, nearbyChunks).getBlock())\n++            && isSolidBlock(getType(x - 1, y, z, chunk, chunkY, nearbyChunks).getBlock())\n++            && isSolidBlock(getType(x, y, z + 1, chunk, chunkY, nearbyChunks).getBlock())\n++            && isSolidBlock(getType(x, y, z - 1, chunk, chunkY, nearbyChunks).getBlock())\n++            && isSolidBlock(getType(x, y - 1, z, chunk, chunkY, nearbyChunks).getBlock());\n++    }\n++\n++    private static IBlockData getType(int x, int y, int z, Chunk chunk, int chunkY, Chunk[] nearbyChunks) {\n++        if (x < 0) {\n++            chunk = nearbyChunks[0];\n++        } else if (x > 15) {\n++            chunk = nearbyChunks[1];\n++        } else if (z < 0) {\n++            chunk = nearbyChunks[2];\n++        } else if (z > 15) {\n++            chunk = nearbyChunks[3];\n++        }\n++\n++        int blockY = (chunkY << 4) + y;\n++\n++        if (blockY >= 0 && blockY >> 4 < chunk.getSections().length) {\n++            ChunkSection chunkSection = chunk.getSections()[blockY >> 4];\n++\n++            if (chunkSection != Chunk.a) {\n++                return chunkSection.getType(x & 15, y & 15, z & 15);\n++            }\n++        }\n++\n++        return Blocks.AIR.getBlockData();\n++    }\n++\n++    private static boolean isSolidBlock(Block block)\n++    {\n++        return block.isOccluding(block.getBlockData()) && block != Blocks.MOB_SPAWNER && block != Blocks.BARRIER;\n++    }\n++}\n+diff --git a/src/main/java/com/destroystokyo/paper/antixray/Lock.java b/src/main/java/com/destroystokyo/paper/antixray/Lock.java\n+new file mode 100644\n+index 0000000..64759ea\n+--- /dev/null\n++++ b/src/main/java/com/destroystokyo/paper/antixray/Lock.java\n+@@ -0,0 +1,26 @@\n++package com.destroystokyo.paper.antixray;\n++\n++import java.util.concurrent.atomic.AtomicInteger;\n++\n++public class Lock {\n++    private final AtomicInteger lockCount = new AtomicInteger();\n++\n++    public void lock() {\n++        lockCount.incrementAndGet();\n++    }\n++\n++    public synchronized void unlock() {\n++        lockCount.decrementAndGet();\n++        notifyAll();\n++    }\n++\n++    public synchronized void waitUntilUnlock() {\n++        try {\n++            while (lockCount.get() > 0) {\n++                wait();\n++            }\n++        } catch (InterruptedException e) {\n++            Thread.currentThread().interrupt();\n++        }\n++    }\n++}\n+diff --git a/src/main/java/com/destroystokyo/paper/antixray/ObfuscatorRunnable.java b/src/main/java/com/destroystokyo/paper/antixray/ObfuscatorRunnable.java\n+new file mode 100644\n+index 0000000..e6bdaa7\n+--- /dev/null\n++++ b/src/main/java/com/destroystokyo/paper/antixray/ObfuscatorRunnable.java\n+@@ -0,0 +1,53 @@\n++package com.destroystokyo.paper.antixray;\n++\n++import net.minecraft.server.Chunk;\n++import net.minecraft.server.PacketDataSerializer;\n++import net.minecraft.server.PacketPlayOutMapChunk;\n++\n++public class ObfuscatorRunnable implements Runnable {\n++    private final PacketPlayOutMapChunk packetPlayOutMapChunk;\n++    private final PacketDataSerializer packetDataSerializer;\n++    private final Chunk chunk;\n++    private final boolean writeSkyLightArray;\n++    private final int chunkSectionSelector;\n++\tprivate final Chunk[] nearbyChunks;\n++\n++    public ObfuscatorRunnable(PacketPlayOutMapChunk packetPlayOutMapChunk, PacketDataSerializer packetDataSerializer, Chunk chunk, boolean writeSkyLightArray, int chunkSectionSelector, Chunk[] nearbyChunks) {\n++        chunk.blocksLock.lock();\n++        chunk.dataLock.lock();\n++\n++\t\tif (nearbyChunks != null) {\n++\t\t\tfor (Chunk nearbyChunk : nearbyChunks) {\n++\t\t\t\tif (nearbyChunk != null) {\n++\t\t\t\t\tnearbyChunk.blocksLock.lock();\n++\t\t\t\t}\n++\t\t\t}\n++\t\t}\n++\n++        this.packetPlayOutMapChunk = packetPlayOutMapChunk;\n++        this.packetDataSerializer = packetDataSerializer;\n++        this.chunk = chunk;\n++        this.nearbyChunks = nearbyChunks;\n++        this.writeSkyLightArray = writeSkyLightArray;\n++        this.chunkSectionSelector = chunkSectionSelector;\n++    }\n++\n++    @Override\n++    public void run() {\n++        try {\n++\t\t\tpacketPlayOutMapChunk.setWrittenChunkSections(packetPlayOutMapChunk.a(packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector, nearbyChunks));\n++\t\t\tpacketPlayOutMapChunk.setReady(true);\n++        } finally {\n++            chunk.blocksLock.unlock();\n++            chunk.dataLock.unlock();\n++\n++\t\t\tif (nearbyChunks != null) {\n++\t\t\t\tfor (Chunk nearbyChunk : nearbyChunks) {\n++\t\t\t\t\tif (nearbyChunk != null) {\n++\t\t\t\t\t\tnearbyChunk.blocksLock.unlock();\n++\t\t\t\t\t}\n++\t\t\t\t}\n++\t\t\t}\n++        }\n++    }\n++}\n+diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java\n+index 30c3d6c..c0c2492 100644\n+--- a/src/main/java/net/minecraft/server/Chunk.java\n++++ b/src/main/java/net/minecraft/server/Chunk.java\n+@@ -1,5 +1,6 @@\n+ package net.minecraft.server;\n+ \n++import com.destroystokyo.paper.antixray.Lock; // Paper - Async-Anti-Xray - Used to lock data which is used while creating chunk-packets asynchronously (those locks freeze the main thread but they occur very rare, worst case is \"synchonous-anti-xray-behavior\")\n+ import com.destroystokyo.paper.exception.ServerInternalException;\n+ import com.google.common.base.Predicate;\n+ import com.google.common.collect.Maps;\n+@@ -21,6 +22,8 @@ import org.bukkit.craftbukkit.util.CraftMagicNumbers; // Paper\n+ \n+ public class Chunk {\n+ \n++    public final Lock blocksLock = new Lock(); // Paper - Async-Anti-Xray - Locks blocks and sections and is applied before a packet of this chunk or a neighbor-chunk is added to the obfuscation-executor-service on the main-thread until the obfuscator-thread is finished to ensure \"single-thread-behavior\" and thread-safety\n++    public final Lock dataLock = new Lock(); // Paper - Async-Anti-Xray - Locks sky light, emitted light and biome index and is applied before a packet of this chunk is added to the obfuscation-executor-service on the main-thread until the obfuscator-thread is finished to ensure \"single-thread-behavior\" and thread-safety\n+     private static final Logger e = LogManager.getLogger();\n+     public static final ChunkSection a = null;\n+     private final ChunkSection[] sections;\n+@@ -129,7 +132,7 @@ public class Chunk {\n+                         int j1 = i1 >> 4;\n+ \n+                         if (this.sections[j1] == Chunk.a) {\n+-                            this.sections[j1] = new ChunkSection(j1 << 4, flag1);\n++                            this.sections[j1] = new ChunkSection(j1 << 4, flag1, world.paperConfig.antiXrayInstance.getPredefinedBlockData(this, j1)); // Paper - Anti-Xray - Add blocks used for obfuscation to the DataPalette\n+                         }\n+ \n+                         this.sections[j1].setType(k, i1 & 15, l, iblockdata);\n+@@ -214,6 +217,7 @@ public class Chunk {\n+                                 ChunkSection chunksection = this.sections[i1 >> 4];\n+ \n+                                 if (chunksection != Chunk.a) {\n++                                    this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+                                     chunksection.a(j, i1 & 15, k, l);\n+                                     this.world.m(new BlockPosition((this.locX << 4) + j, i1, (this.locZ << 4) + k));\n+                                 }\n+@@ -334,6 +338,7 @@ public class Chunk {\n+                     for (l1 = i1; l1 < l; ++l1) {\n+                         chunksection = this.sections[l1 >> 4];\n+                         if (chunksection != Chunk.a) {\n++                            this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+                             chunksection.a(i, l1 & 15, k, 15);\n+                             this.world.m(new BlockPosition((this.locX << 4) + i, l1, (this.locZ << 4) + k));\n+                         }\n+@@ -342,6 +347,7 @@ public class Chunk {\n+                     for (l1 = l; l1 < i1; ++l1) {\n+                         chunksection = this.sections[l1 >> 4];\n+                         if (chunksection != Chunk.a) {\n++                            this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+                             chunksection.a(i, l1 & 15, k, 0);\n+                             this.world.m(new BlockPosition((this.locX << 4) + i, l1, (this.locZ << 4) + k));\n+                         }\n+@@ -365,6 +371,7 @@ public class Chunk {\n+                     ChunkSection chunksection1 = this.sections[i1 >> 4];\n+ \n+                     if (chunksection1 != Chunk.a) {\n++                        this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+                         chunksection1.a(i, i1 & 15, k, l1);\n+                     }\n+                 }\n+@@ -495,11 +502,13 @@ public class Chunk {\n+                     return null;\n+                 }\n+ \n+-                chunksection = new ChunkSection(j >> 4 << 4, !this.world.worldProvider.m());\n++                chunksection = new ChunkSection(j >> 4 << 4, !this.world.worldProvider.m(), this.world.paperConfig.antiXrayInstance.getPredefinedBlockData(this, j >> 4)); // Paper - Anti-Xray - Add blocks used for obfuscation to the DataPalette\n++                this.blocksLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+                 this.sections[j >> 4] = chunksection;\n+                 flag = j >= i1;\n+             }\n+ \n++            this.blocksLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+             chunksection.setType(i, j & 15, k, iblockdata);\n+             if (block1 != block) {\n+                 if (!this.world.isClientSide) {\n+@@ -580,7 +589,8 @@ public class Chunk {\n+         ChunkSection chunksection = this.sections[k >> 4];\n+ \n+         if (chunksection == Chunk.a) {\n+-            chunksection = new ChunkSection(k >> 4 << 4, !this.world.worldProvider.m());\n++            chunksection = new ChunkSection(k >> 4 << 4, !this.world.worldProvider.m(), this.world.paperConfig.antiXrayInstance.getPredefinedBlockData(this, k >> 4)); // Paper - Anti-Xray - Add blocks used for obfuscation to the DataPalette\n++            this.blocksLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+             this.sections[k >> 4] = chunksection;\n+             this.initLighting();\n+         }\n+@@ -588,9 +598,11 @@ public class Chunk {\n+         this.s = true;\n+         if (enumskyblock == EnumSkyBlock.SKY) {\n+             if (!this.world.worldProvider.m()) {\n++                this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+                 chunksection.a(j, k & 15, l, i);\n+             }\n+         } else if (enumskyblock == EnumSkyBlock.BLOCK) {\n++            this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+             chunksection.b(j, k & 15, l, i);\n+         }\n+ \n+@@ -1177,6 +1189,7 @@ public class Chunk {\n+         if (k == 255) {\n+             biomebase = worldchunkmanager.getBiome(blockposition, Biomes.c);\n+             k = BiomeBase.a(biomebase);\n++            this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+             this.g[j << 4 | i] = (byte) (k & 255);\n+         }\n+ \n+@@ -1192,6 +1205,7 @@ public class Chunk {\n+         if (this.g.length != abyte.length) {\n+             Chunk.e.warn(\"Could not set level chunk biomes, array length is {} instead of {}\", new Object[] { Integer.valueOf(abyte.length), Integer.valueOf(this.g.length)});\n+         } else {\n++            this.dataLock.waitUntilUnlock(); // Paper - Async-Anti-Xray - Note: Lock is applied on the same thread (main thread) -> no race condition\n+             System.arraycopy(abyte, 0, this.g, 0, this.g.length);\n+         }\n+     }\n+diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java\n+index 584a77d..a5ca686 100644\n+--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java\n++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java\n+@@ -377,7 +377,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {\n+         for (int k = 0; k < nbttaglist.size(); ++k) {\n+             NBTTagCompound nbttagcompound1 = nbttaglist.get(k);\n+             byte b0 = nbttagcompound1.getByte(\"Y\");\n+-            ChunkSection chunksection = new ChunkSection(b0 << 4, flag1);\n++            ChunkSection chunksection = new ChunkSection(b0 << 4, flag1, world.paperConfig.antiXrayInstance.getPredefinedBlockData(chunk, b0)); // Paper - Anti-Xray - Add blocks used for obfuscation to the DataPalette\n+             byte[] abyte = nbttagcompound1.getByteArray(\"Blocks\");\n+             NibbleArray nibblearray = new NibbleArray(nbttagcompound1.getByteArray(\"Data\"));\n+             NibbleArray nibblearray1 = nbttagcompound1.hasKeyOfType(\"Add\", 7) ? new NibbleArray(nbttagcompound1.getByteArray(\"Add\")) : null;\n+diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java\n+index 27d0f99..3d25a0d 100644\n+--- a/src/main/java/net/minecraft/server/ChunkSection.java\n++++ b/src/main/java/net/minecraft/server/ChunkSection.java\n+@@ -1,5 +1,7 @@\n+ package net.minecraft.server;\n+ \n++import javax.annotation.Nullable; // Paper - Anti-Xray\n++\n+ public class ChunkSection {\n+ \n+     private final int yPos;\n+@@ -9,9 +11,15 @@ public class ChunkSection {\n+     private NibbleArray emittedLight;\n+     private NibbleArray skyLight;\n+ \n++    // Paper start - Anti-Xray - Support default constructor\n+     public ChunkSection(int i, boolean flag) {\n++        this(i, flag, (IBlockData[]) null);\n++    }\n++    // Paper end\n++\n++    public ChunkSection(int i, boolean flag, @Nullable IBlockData[] predefinedBlockData) { // Paper - Anti-Xray - Blocks used for obfuscation\n+         this.yPos = i;\n+-        this.blockIds = new DataPaletteBlock();\n++        this.blockIds = new DataPaletteBlock(predefinedBlockData); // Paper - Anti-Xray - Add blocks used for obfuscation to the DataPalette\n+         this.emittedLight = new NibbleArray();\n+         if (flag) {\n+             this.skyLight = new NibbleArray();\n+@@ -19,10 +27,16 @@ public class ChunkSection {\n+ \n+     }\n+ \n+-    // CraftBukkit start\n++    // Paper start - Anti-Xray - Support default constructor\n+     public ChunkSection(int y, boolean flag, char[] blockIds) {\n++        this(y, flag, blockIds, null);\n++    }\n++    // Paper end\n++\n++    // CraftBukkit start\n++    public ChunkSection(int y, boolean flag, char[] blockIds, @Nullable IBlockData[] predefinedBlockData) { // Paper - Anti-Xray - Blocks used for obfuscation\n+         this.yPos = y;\n+-        this.blockIds = new DataPaletteBlock();\n++        this.blockIds = new DataPaletteBlock(predefinedBlockData); // Paper - Anti-Xray - Add blocks used for obfuscation to the DataPalette\n+         for (int i = 0; i < blockIds.length; i++) {\n+             int xx = i & 15;\n+             int yy = (i >> 8) & 15;\n+diff --git a/src/main/java/net/minecraft/server/DataBits.java b/src/main/java/net/minecraft/server/DataBits.java\n+index fa0fd8a..bb65bd6 100644\n+--- a/src/main/java/net/minecraft/server/DataBits.java\n++++ b/src/main/java/net/minecraft/server/DataBits.java\n+@@ -35,6 +35,27 @@ public class DataBits {\n+ \n+     }\n+ \n++    // Paper start - Anti-Xray - Add methods used inside the obfuscator loop to set fake-blocks to data \n++    public long obfuscate(int blockIndex, int bits, long data) { // Paper - Anti-Xray - First part of this.a(int i, int j), doesn't modify this.a but the data-paramater and returns it\n++        int j1 = (blockIndex * this.b) % 64;\n++        return data & ~(this.c << j1) | ((long) bits & this.c) << j1;\n++    }\n++\n++    public long obfuscateSplittedPart(int blockIndex, int bits, long data) { // Paper - Anti-Xray - Second part of this.a(int i, int j), doesn't modify this.a but the data-paramater and returns it\n++        int k1 = 64 - (blockIndex * this.b) % 64;\n++        int l1 = this.b - k1;\n++        return data >>> l1 << l1 | ((long) bits & this.c) >> k1;\n++    }\n++\n++    public int getArrayIndex(int blockIndex) { // Paper - Anti-Xray - Returns the array index of blockIndex for this.a\n++        return (blockIndex * this.b) / 64;\n++    }\n++\n++    public boolean isSplitted(int blockIndex, int arrayIndex) { // Paper - Anti-Xray - Returns true if and only if the block data is splitted over two array indexes\n++        return arrayIndex != ((blockIndex + 1) * this.b - 1) / 64;\n++    }\n++    // Paper end\n++\n+     public int a(int i) {\n+         //Validate.inclusiveBetween(0L, (long) (this.d - 1), (long) i); // Paper\n+         int j = i * this.b;\n+diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java\n+index 1f2fe87..c052906 100644\n+--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java\n++++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java\n+@@ -9,11 +9,46 @@ public class DataPaletteBlock implements DataPaletteExpandable {\n+     protected DataBits b;\n+     protected DataPalette c;\n+     private int e;\n++    private final IBlockData[] predefinedBlockData; // Paper - Anti-Xray - Blocks used for obfuscation (since 1.9 MC uses DataPalettes which have to be changed if more different blocks are used in a ChunkSection -> to avoid that while setting the fake-blocks we add them from the beginning)\n++    private final int[] currentPredefinedBlockData; // Paper - Anti-Xray - Current used data bits of blocks used for obfuscation\n+ \n++    // Paper start - Anti-Xray - Support default constructor\n+     public DataPaletteBlock() {\n+-        this.b(4);\n++        this(null);\n++    }\n++    // Paper end\n++\n++    // Paper start - Anti-Xray - Modified constructor\n++    public DataPaletteBlock(@Nullable IBlockData[] predefinedBlockData) { // Paper - Anti-Xray - Add blocks used for obfuscation to the DataPalette\n++        this.predefinedBlockData = predefinedBlockData;\n++        if (predefinedBlockData == null) { // Paper - Anti-Xray - default constructor\n++            this.currentPredefinedBlockData = null;\n++            this.b(4);\n++        } else {\n++            this.currentPredefinedBlockData = new int[predefinedBlockData.length];\n++            int maxIndex = predefinedBlockData.length; // Paper - Anti-Xray - count bits of the maximum array index (+1 because AIR is also added) -> array length\n++            int bitCount = 0;\n++            while (maxIndex != 0) {\n++                maxIndex >>= 1;\n++                bitCount++;\n++            }\n++            this.b(bitCount == 0 ? 4 : bitCount); // Paper - Anti-Xray - initialize a DataPalette with bitCount\n++        }\n++    }\n++    // Paper end\n++\n++    // Paper start - Anti-Xray - Getters\n++    @Nullable\n++    public IBlockData[] getPredefinedBlockData() {\n++        return this.predefinedBlockData;\n+     }\n+ \n++    @Nullable\n++    public int[] getCurrentPredefinedBlockData() {\n++        return this.currentPredefinedBlockData;\n++    }\n++    // Paper end\n++\n+     private static int b(int i, int j, int k) {\n+         return j << 8 | k << 4 | i;\n+     }\n+@@ -32,6 +67,13 @@ public class DataPaletteBlock implements DataPaletteExpandable {\n+             }\n+ \n+             this.c.a(DataPaletteBlock.a);\n++            // Paper start - Anti-Xray - Add blocks used for obfuscation to the DataPalette and update the array with the current data bits\n++            if (this.predefinedBlockData != null) {\n++                for (int j = 0; j < this.predefinedBlockData.length; j++) {\n++                    this.currentPredefinedBlockData[j] = this.c.a(this.predefinedBlockData[j]);\n++                }\n++            }\n++            // Paper end\n+             this.b = new DataBits(this.e, 4096);\n+         }\n+     }\n+@@ -67,12 +109,20 @@ public class DataPaletteBlock implements DataPaletteExpandable {\n+         return this.a(b(i, j, k));\n+     }\n+ \n+-    protected IBlockData a(int i) {\n++    public IBlockData a(int i) { // Paper - Anti-Xray - protected -> public (Used inside the obfuscator loop)\n+         IBlockData iblockdata = this.c.a(this.b.a(i));\n+ \n+         return iblockdata == null ? DataPaletteBlock.a : iblockdata;\n+     }\n+ \n++    // Paper start - (Async-)Anti-Xray - Called instead of this.b(PacketDataSerializer packetdataserializer) (with nearbyChunks as parameter for the async part)\n++    public void serializeOrObfuscate(PacketDataSerializer packetdataserializer, Chunk chunk, int y, Chunk[] nearbyChunks) {\n++        packetdataserializer.writeByte(this.e);\n++        this.c.b(packetdataserializer);\n++        chunk.world.paperConfig.antiXrayInstance.serializeOrObfuscate(packetdataserializer, chunk, y, this, this.b, nearbyChunks);\n++    }\n++    // Paper end\n++\n+     public void b(PacketDataSerializer packetdataserializer) {\n+         packetdataserializer.writeByte(this.e);\n+         this.c.b(packetdataserializer);\n+diff --git a/src/main/java/net/minecraft/server/EntityFallingBlock.java b/src/main/java/net/minecraft/server/EntityFallingBlock.java\n+index 0d8c438..37947bb 100644\n+--- a/src/main/java/net/minecraft/server/EntityFallingBlock.java\n++++ b/src/main/java/net/minecraft/server/EntityFallingBlock.java\n+@@ -71,6 +71,7 @@ public class EntityFallingBlock extends Entity {\n+                 blockposition = new BlockPosition(this);\n+                 if (this.world.getType(blockposition).getBlock() == block && !CraftEventFactory.callEntityChangeBlockEvent(this, blockposition.getX(), blockposition.getY(), blockposition.getZ(), Blocks.AIR, 0).isCancelled()) {\n+                     this.world.setAir(blockposition);\n++                    this.world.paperConfig.antiXrayInstance.updateNearbyBlocks(this.world, blockposition); // Paper - Anti-Xray\n+                 } else if (!this.world.isClientSide) {\n+                     this.die();\n+                     return;\n+@@ -117,6 +118,7 @@ public class EntityFallingBlock extends Entity {\n+                                     return;\n+                                 }\n+                                 this.world.setTypeAndData(blockposition, this.block, 3);\n++                                this.world.paperConfig.antiXrayInstance.updateNearbyBlocks(this.world, blockposition); // Paper - Anti-Xray\n+                                 // CraftBukkit end\n+                                 if (block instanceof BlockFalling) {\n+                                     ((BlockFalling) block).a_(this.world, blockposition);\n+diff --git a/src/main/java/net/minecraft/server/Explosion.java b/src/main/java/net/minecraft/server/Explosion.java\n+index 0b9bc91..6a6e2ee 100644\n+--- a/src/main/java/net/minecraft/server/Explosion.java\n++++ b/src/main/java/net/minecraft/server/Explosion.java\n+@@ -233,6 +233,7 @@ public class Explosion {\n+                 blockposition = (BlockPosition) iterator.next();\n+                 IBlockData iblockdata = this.world.getType(blockposition);\n+                 Block block = iblockdata.getBlock();\n++                this.world.paperConfig.antiXrayInstance.updateNearbyBlocks(this.world, blockposition); // Paper - Anti-Xray\n+ \n+                 if (flag) {\n+                     double d0 = (double) ((float) blockposition.getX() + this.world.random.nextFloat());\n+diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java\n+index d59fca4..1da7390 100644\n+--- a/src/main/java/net/minecraft/server/NetworkManager.java\n++++ b/src/main/java/net/minecraft/server/NetworkManager.java\n+@@ -138,8 +138,8 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {\n+     }\n+ \n+     public void sendPacket(Packet<?> packet) {\n+-        if (this.isConnected()) {\n+-            this.m();\n++        if (this.isConnected() && this.m() && !(packet instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) packet).isReady())) { // Paper - Async-Anti-Xray - Add not ready chunk packets and all packets if the queue contains a not ready chunk packet to the queue to keep the packet-order\n++            //this.m(); // Paper - Async-Anti-Xray - Move to if-statement\n+             this.a(packet, (GenericFutureListener[]) null);\n+         } else {\n+             this.j.writeLock().lock();\n+@@ -154,8 +154,8 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {\n+     }\n+ \n+     public void sendPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener, GenericFutureListener<? extends Future<? super Void>>... agenericfuturelistener) {\n+-        if (this.isConnected()) {\n+-            this.m();\n++        if (this.isConnected() && this.m() && !(packet instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) packet).isReady())) { // Paper - Async-Anti-Xray - Add not ready chunk packets and all packets if the queue contains a not ready chunk packet to the queue to keep the packet-order\n++            //this.m(); // Paper - Async-Anti-Xray - Move to if-statement\n+             this.a(packet, (GenericFutureListener[]) ArrayUtils.add(agenericfuturelistener, 0, genericfuturelistener));\n+         } else {\n+             this.j.writeLock().lock();\n+@@ -210,21 +210,28 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {\n+ \n+     }\n+ \n+-    private void m() {\n++    private boolean m() { // Paper - Async-Anti-Xray - void -> boolean used in sendPacket\n+         if (this.channel != null && this.channel.isOpen()) {\n+             this.j.readLock().lock();\n+ \n+             try {\n+                 while (!this.i.isEmpty()) {\n+-                    NetworkManager.QueuedPacket networkmanager_queuedpacket = (NetworkManager.QueuedPacket) this.i.poll();\n++                    NetworkManager.QueuedPacket networkmanager_queuedpacket = (NetworkManager.QueuedPacket) this.i.peek(); // Paper - Async-Anti-Xray - poll() -> peek()\n+ \n+-                    this.a(networkmanager_queuedpacket.a, networkmanager_queuedpacket.b);\n++                    if (networkmanager_queuedpacket.a instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) networkmanager_queuedpacket.a).isReady()) { // Paper - Async-Anti-Xray - Return false if the queue contains a not ready chunk packet\n++                        return false; // Paper - Aync-Anti-Xray","path":"Spigot-Server-Patches/0167-Anti-Xray.patch","position":808,"original_position":808,"commit_id":"ac4bce75d46d919412f148212a5777f04356ba77","original_commit_id":"ac4bce75d46d919412f148212a5777f04356ba77","user":{"login":"Spottedleaf","id":6100722,"avatar_url":"https://avatars.githubusercontent.com/u/6100722?v=3","gravatar_id":"","url":"https://api.github.com/users/Spottedleaf","html_url":"https://github.com/Spottedleaf","followers_url":"https://api.github.com/users/Spottedleaf/followers","following_url":"https://api.github.com/users/Spottedleaf/following{/other_user}","gists_url":"https://api.github.com/users/Spottedleaf/gists{/gist_id}","starred_url":"https://api.github.com/users/Spottedleaf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Spottedleaf/subscriptions","organizations_url":"https://api.github.com/users/Spottedleaf/orgs","repos_url":"https://api.github.com/users/Spottedleaf/repos","events_url":"https://api.github.com/users/Spottedleaf/events{/privacy}","received_events_url":"https://api.github.com/users/Spottedleaf/received_events","type":"User","site_admin":false},"body":"Good point.","created_at":"2016-06-26T21:01:29Z","updated_at":"2016-06-26T21:01:29Z","html_url":"https://github.com/PaperMC/Paper/pull/349#discussion_r68509595","pull_request_url":"https://api.github.com/repos/PaperMC/Paper/pulls/349","_links":{"self":{"href":"https://api.github.com/repos/PaperMC/Paper/pulls/comments/68509595"},"html":{"href":"https://github.com/PaperMC/Paper/pull/349#discussion_r68509595"},"pull_request":{"href":"https://api.github.com/repos/PaperMC/Paper/pulls/349"}}},"pull_request":{"url":"https://api.github.com/repos/PaperMC/Paper/pulls/349","id":75053559,"html_url":"https://github.com/PaperMC/Paper/pull/349","diff_url":"https://github.com/PaperMC/Paper/pull/349.diff","patch_url":"https://github.com/PaperMC/Paper/pull/349.patch","issue_url":"https://api.github.com/repos/PaperMC/Paper/issues/349","number":349,"state":"open","locked":false,"title":"Anti-Xray","user":{"login":"stonar96","id":18699205,"avatar_url":"https://avatars.githubusercontent.com/u/18699205?v=3","gravatar_id":"","url":"https://api.github.com/users/stonar96","html_url":"https://github.com/stonar96","followers_url":"https://api.github.com/users/stonar96/followers","following_url":"https://api.github.com/users/stonar96/following{/other_user}","gists_url":"https://api.github.com/users/stonar96/gists{/gist_id}","starred_url":"https://api.github.com/users/stonar96/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stonar96/subscriptions","organizations_url":"https://api.github.com/users/stonar96/orgs","repos_url":"https://api.github.com/users/stonar96/repos","events_url":"https://api.github.com/users/stonar96/events{/privacy}","received_events_url":"https://api.github.com/users/stonar96/received_events","type":"User","site_admin":false},"body":"Paper inbuilt Anti-Xray. Asynchronous mode is configurable.","created_at":"2016-06-24T01:45:57Z","updated_at":"2016-06-26T21:01:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bcdcd3cb9fc44cec158b82d14e35577498bee379","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/PaperMC/Paper/pulls/349/commits","review_comments_url":"https://api.github.com/repos/PaperMC/Paper/pulls/349/comments","review_comment_url":"https://api.github.com/repos/PaperMC/Paper/pulls/comments{/number}","comments_url":"https://api.github.com/repos/PaperMC/Paper/issues/349/comments","statuses_url":"https://api.github.com/repos/PaperMC/Paper/statuses/ac4bce75d46d919412f148212a5777f04356ba77","head":{"label":"stonar96:master","ref":"master","sha":"ac4bce75d46d919412f148212a5777f04356ba77","user":{"login":"stonar96","id":18699205,"avatar_url":"https://avatars.githubusercontent.com/u/18699205?v=3","gravatar_id":"","url":"https://api.github.com/users/stonar96","html_url":"https://github.com/stonar96","followers_url":"https://api.github.com/users/stonar96/followers","following_url":"https://api.github.com/users/stonar96/following{/other_user}","gists_url":"https://api.github.com/users/stonar96/gists{/gist_id}","starred_url":"https://api.github.com/users/stonar96/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stonar96/subscriptions","organizations_url":"https://api.github.com/users/stonar96/orgs","repos_url":"https://api.github.com/users/stonar96/repos","events_url":"https://api.github.com/users/stonar96/events{/privacy}","received_events_url":"https://api.github.com/users/stonar96/received_events","type":"User","site_admin":false},"repo":{"id":61843116,"name":"Paper","full_name":"stonar96/Paper","owner":{"login":"stonar96","id":18699205,"avatar_url":"https://avatars.githubusercontent.com/u/18699205?v=3","gravatar_id":"","url":"https://api.github.com/users/stonar96","html_url":"https://github.com/stonar96","followers_url":"https://api.github.com/users/stonar96/followers","following_url":"https://api.github.com/users/stonar96/following{/other_user}","gists_url":"https://api.github.com/users/stonar96/gists{/gist_id}","starred_url":"https://api.github.com/users/stonar96/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stonar96/subscriptions","organizations_url":"https://api.github.com/users/stonar96/orgs","repos_url":"https://api.github.com/users/stonar96/repos","events_url":"https://api.github.com/users/stonar96/events{/privacy}","received_events_url":"https://api.github.com/users/stonar96/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/stonar96/Paper","description":"High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.","fork":true,"url":"https://api.github.com/repos/stonar96/Paper","forks_url":"https://api.github.com/repos/stonar96/Paper/forks","keys_url":"https://api.github.com/repos/stonar96/Paper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/stonar96/Paper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/stonar96/Paper/teams","hooks_url":"https://api.github.com/repos/stonar96/Paper/hooks","issue_events_url":"https://api.github.com/repos/stonar96/Paper/issues/events{/number}","events_url":"https://api.github.com/repos/stonar96/Paper/events","assignees_url":"https://api.github.com/repos/stonar96/Paper/assignees{/user}","branches_url":"https://api.github.com/repos/stonar96/Paper/branches{/branch}","tags_url":"https://api.github.com/repos/stonar96/Paper/tags","blobs_url":"https://api.github.com/repos/stonar96/Paper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/stonar96/Paper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/stonar96/Paper/git/refs{/sha}","trees_url":"https://api.github.com/repos/stonar96/Paper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/stonar96/Paper/statuses/{sha}","languages_url":"https://api.github.com/repos/stonar96/Paper/languages","stargazers_url":"https://api.github.com/repos/stonar96/Paper/stargazers","contributors_url":"https://api.github.com/repos/stonar96/Paper/contributors","subscribers_url":"https://api.github.com/repos/stonar96/Paper/subscribers","subscription_url":"https://api.github.com/repos/stonar96/Paper/subscription","commits_url":"https://api.github.com/repos/stonar96/Paper/commits{/sha}","git_commits_url":"https://api.github.com/repos/stonar96/Paper/git/commits{/sha}","comments_url":"https://api.github.com/repos/stonar96/Paper/comments{/number}","issue_comment_url":"https://api.github.com/repos/stonar96/Paper/issues/comments{/number}","contents_url":"https://api.github.com/repos/stonar96/Paper/contents/{+path}","compare_url":"https://api.github.com/repos/stonar96/Paper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/stonar96/Paper/merges","archive_url":"https://api.github.com/repos/stonar96/Paper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/stonar96/Paper/downloads","issues_url":"https://api.github.com/repos/stonar96/Paper/issues{/number}","pulls_url":"https://api.github.com/repos/stonar96/Paper/pulls{/number}","milestones_url":"https://api.github.com/repos/stonar96/Paper/milestones{/number}","notifications_url":"https://api.github.com/repos/stonar96/Paper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/stonar96/Paper/labels{/name}","releases_url":"https://api.github.com/repos/stonar96/Paper/releases{/id}","deployments_url":"https://api.github.com/repos/stonar96/Paper/deployments","created_at":"2016-06-23T23:22:53Z","updated_at":"2016-06-23T23:22:54Z","pushed_at":"2016-06-24T01:37:40Z","git_url":"git://github.com/stonar96/Paper.git","ssh_url":"git@github.com:stonar96/Paper.git","clone_url":"https://github.com/stonar96/Paper.git","svn_url":"https://github.com/stonar96/Paper","homepage":"https://aquifermc.org/","size":6559,"stargazers_count":0,"watchers_count":0,"language":"Shell","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"PaperMC:master","ref":"master","sha":"45d9b4c497a1e42ad157bd0189f7836c9928d621","user":{"login":"PaperMC","id":7608950,"avatar_url":"https://avatars.githubusercontent.com/u/7608950?v=3","gravatar_id":"","url":"https://api.github.com/users/PaperMC","html_url":"https://github.com/PaperMC","followers_url":"https://api.github.com/users/PaperMC/followers","following_url":"https://api.github.com/users/PaperMC/following{/other_user}","gists_url":"https://api.github.com/users/PaperMC/gists{/gist_id}","starred_url":"https://api.github.com/users/PaperMC/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PaperMC/subscriptions","organizations_url":"https://api.github.com/users/PaperMC/orgs","repos_url":"https://api.github.com/users/PaperMC/repos","events_url":"https://api.github.com/users/PaperMC/events{/privacy}","received_events_url":"https://api.github.com/users/PaperMC/received_events","type":"Organization","site_admin":false},"repo":{"id":50160261,"name":"Paper","full_name":"PaperMC/Paper","owner":{"login":"PaperMC","id":7608950,"avatar_url":"https://avatars.githubusercontent.com/u/7608950?v=3","gravatar_id":"","url":"https://api.github.com/users/PaperMC","html_url":"https://github.com/PaperMC","followers_url":"https://api.github.com/users/PaperMC/followers","following_url":"https://api.github.com/users/PaperMC/following{/other_user}","gists_url":"https://api.github.com/users/PaperMC/gists{/gist_id}","starred_url":"https://api.github.com/users/PaperMC/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PaperMC/subscriptions","organizations_url":"https://api.github.com/users/PaperMC/orgs","repos_url":"https://api.github.com/users/PaperMC/repos","events_url":"https://api.github.com/users/PaperMC/events{/privacy}","received_events_url":"https://api.github.com/users/PaperMC/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/PaperMC/Paper","description":"High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.","fork":false,"url":"https://api.github.com/repos/PaperMC/Paper","forks_url":"https://api.github.com/repos/PaperMC/Paper/forks","keys_url":"https://api.github.com/repos/PaperMC/Paper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PaperMC/Paper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PaperMC/Paper/teams","hooks_url":"https://api.github.com/repos/PaperMC/Paper/hooks","issue_events_url":"https://api.github.com/repos/PaperMC/Paper/issues/events{/number}","events_url":"https://api.github.com/repos/PaperMC/Paper/events","assignees_url":"https://api.github.com/repos/PaperMC/Paper/assignees{/user}","branches_url":"https://api.github.com/repos/PaperMC/Paper/branches{/branch}","tags_url":"https://api.github.com/repos/PaperMC/Paper/tags","blobs_url":"https://api.github.com/repos/PaperMC/Paper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PaperMC/Paper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PaperMC/Paper/git/refs{/sha}","trees_url":"https://api.github.com/repos/PaperMC/Paper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PaperMC/Paper/statuses/{sha}","languages_url":"https://api.github.com/repos/PaperMC/Paper/languages","stargazers_url":"https://api.github.com/repos/PaperMC/Paper/stargazers","contributors_url":"https://api.github.com/repos/PaperMC/Paper/contributors","subscribers_url":"https://api.github.com/repos/PaperMC/Paper/subscribers","subscription_url":"https://api.github.com/repos/PaperMC/Paper/subscription","commits_url":"https://api.github.com/repos/PaperMC/Paper/commits{/sha}","git_commits_url":"https://api.github.com/repos/PaperMC/Paper/git/commits{/sha}","comments_url":"https://api.github.com/repos/PaperMC/Paper/comments{/number}","issue_comment_url":"https://api.github.com/repos/PaperMC/Paper/issues/comments{/number}","contents_url":"https://api.github.com/repos/PaperMC/Paper/contents/{+path}","compare_url":"https://api.github.com/repos/PaperMC/Paper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PaperMC/Paper/merges","archive_url":"https://api.github.com/repos/PaperMC/Paper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PaperMC/Paper/downloads","issues_url":"https://api.github.com/repos/PaperMC/Paper/issues{/number}","pulls_url":"https://api.github.com/repos/PaperMC/Paper/pulls{/number}","milestones_url":"https://api.github.com/repos/PaperMC/Paper/milestones{/number}","notifications_url":"https://api.github.com/repos/PaperMC/Paper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PaperMC/Paper/labels{/name}","releases_url":"https://api.github.com/repos/PaperMC/Paper/releases{/id}","deployments_url":"https://api.github.com/repos/PaperMC/Paper/deployments","created_at":"2016-01-22T05:40:19Z","updated_at":"2016-06-25T03:50:50Z","pushed_at":"2016-06-26T19:14:15Z","git_url":"git://github.com/PaperMC/Paper.git","ssh_url":"git@github.com:PaperMC/Paper.git","clone_url":"https://github.com/PaperMC/Paper.git","svn_url":"https://github.com/PaperMC/Paper","homepage":"https://aquifermc.org/","size":7613,"stargazers_count":96,"watchers_count":96,"language":"Shell","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":54,"mirror_url":null,"open_issues_count":31,"forks":54,"open_issues":31,"watchers":96,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/PaperMC/Paper/pulls/349"},"html":{"href":"https://github.com/PaperMC/Paper/pull/349"},"issue":{"href":"https://api.github.com/repos/PaperMC/Paper/issues/349"},"comments":{"href":"https://api.github.com/repos/PaperMC/Paper/issues/349/comments"},"review_comments":{"href":"https://api.github.com/repos/PaperMC/Paper/pulls/349/comments"},"review_comment":{"href":"https://api.github.com/repos/PaperMC/Paper/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/PaperMC/Paper/pulls/349/commits"},"statuses":{"href":"https://api.github.com/repos/PaperMC/Paper/statuses/ac4bce75d46d919412f148212a5777f04356ba77"}}}}
{"actor":{"display_login":"Spottedleaf"}}