com.comphenix.blockpatcher.Calculations.java Source code

Java tutorial

Introduction

Here is the source code for com.comphenix.blockpatcher.Calculations.java

Source

package com.comphenix.blockpatcher;

/*
 *  BlockPatcher - Safely convert one block ID to another for the client only 
 *  Copyright (C) 2012 Kristian S. Stangeland
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the 
 *  GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; 
 *  if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 
 *  02111-1307 USA
 *
 *  Credits:
 *   * Parts of this code was adapted from the Bukkit plugin Orebfuscator by lishid. 
 */

import java.util.concurrent.TimeUnit;

import net.minecraft.server.v1_8_R1.Block;
import net.minecraft.server.v1_8_R1.ChunkMap;
import net.minecraft.server.v1_8_R1.IBlockData;
import net.minecraft.server.v1_8_R1.MultiBlockChangeInfo;
import net.minecraft.server.v1_8_R1.PacketPlayOutMultiBlockChange;

import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

import com.comphenix.blockpatcher.lookup.ConversionLookup;
import com.comphenix.blockpatcher.lookup.SegmentLookup;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.google.common.base.Stopwatch;

/**
 * Used to translate block IDs.
 * 
 * @author Kristian
 */
class Calculations {
    // Used to pass around detailed information about chunks
    private static class ChunkInfo {
        public int chunkX;
        public int chunkZ;
        public int chunkMask;
        //public int extraMask;
        public int chunkSectionNumber;
        public byte[] data;
        public Player player;
        public int startIndex;
        public int blockSize;
    }

    // Useful Minecraft constants
    private static final int CHUNK_SEGMENTS = 16;

    // Used to get a chunk's specific lookup table
    private EventScheduler scheduler;
    private ConversionCache cache;

    public Calculations(ConversionCache cache, EventScheduler scheduler) {
        this.cache = cache;
        this.scheduler = scheduler;
    }

    public boolean isImportantChunkBulk(PacketContainer packet, Player player) throws FieldAccessException {
        StructureModifier<int[]> intArrays = packet.getSpecificModifier(int[].class);
        int[] x = intArrays.read(0);
        int[] z = intArrays.read(1);

        for (int i = 0; i < x.length; i++) {
            if (Math.abs(x[i] - (((int) player.getLocation().getX()) >> 4)) == 0
                    && Math.abs(z[i] - (((int) player.getLocation().getZ())) >> 4) == 0) {
                return true;
            }
        }
        return false;
    }

    public boolean isImportantChunk(PacketContainer packet, Player player) throws FieldAccessException {
        StructureModifier<Integer> ints = packet.getSpecificModifier(int.class);
        int x = ints.read(0);
        int y = ints.read(1);

        if (Math.abs(x - (((int) player.getLocation().getX()) >> 4)) == 0
                && Math.abs(y - (((int) player.getLocation().getZ())) >> 4) == 0) {
            return true;
        }
        return false;
    }

    public void translateMapChunkBulk(PacketContainer packet, Player player) throws FieldAccessException {
        StructureModifier<int[]> intArrays = packet.getSpecificModifier(int[].class);

        int[] x = intArrays.read(0); // a
        int[] z = intArrays.read(1); // b

        ChunkMap[] chunkMaps = packet.getSpecificModifier(ChunkMap[].class).read(0);

        ChunkInfo[] infos = new ChunkInfo[x.length];

        for (int chunkNum = 0; chunkNum < infos.length; chunkNum++) {
            // Create an info objects
            ChunkInfo info = new ChunkInfo();
            infos[chunkNum] = info;
            info.player = player;
            info.chunkX = x[chunkNum];
            info.chunkZ = z[chunkNum];

            ChunkMap chunkMap = chunkMaps[chunkNum];

            info.chunkMask = chunkMap.b;
            info.data = chunkMap.a;

            translateChunkInfoAndObfuscate(info, info.data);
        }

    }

    public void translateMapChunk(PacketContainer packet, Player player) throws FieldAccessException {
        StructureModifier<Integer> ints = packet.getSpecificModifier(int.class);

        ChunkMap chunkMap = packet.getSpecificModifier(ChunkMap.class).read(0);

        // Create an info objects
        ChunkInfo info = new ChunkInfo();
        info.player = player;
        info.chunkX = ints.read(0); // packet.a;
        info.chunkZ = ints.read(1); // packet.b;

        info.chunkMask = chunkMap.b;
        info.data = chunkMap.a;

        info.startIndex = 0;

        translateChunkInfoAndObfuscate(info, info.data);
    }

    public void translateBlockChange(PacketContainer packet, Player player) throws FieldAccessException {
        BlockPosition position = packet.getBlockPositionModifier().read(0);
        IBlockData block = packet.getSpecificModifier(IBlockData.class).read(0);

        ConversionLookup lookup = cache.loadCacheOrDefault(player, position.getX() >> 4, position.getY() >> 4,
                position.getZ() >> 4);

        int blockID = Block.getId(block.getBlock());
        int data = block.getBlock().toLegacyData(block);

        int newBlockID = lookup.getBlockLookup(blockID);
        int newData = lookup.getDataLookup(blockID, data);

        packet.getSpecificModifier(IBlockData.class).write(0, Block.getById(newBlockID).fromLegacyData(newData));
    }

    public void translateMultiBlockChange(PacketContainer packet, Player player) throws FieldAccessException {
        ChunkCoordIntPair coord = packet.getChunkCoordIntPairs().read(0);

        MultiBlockChangeInfo[] changeInfos = packet.getSpecificModifier(MultiBlockChangeInfo[].class).read(0);

        // Get the correct table
        SegmentLookup lookup = cache.loadCacheOrDefault(player, coord.getChunkX(), coord.getChunkZ());

        for (int i = 0; i < changeInfos.length; i++) {
            MultiBlockChangeInfo changeInfo = changeInfos[i];
            IBlockData block = changeInfo.c();
            short d = changeInfo.b();

            int chunkY = (d & 0x00FF) >> 4;

            int blockID = Block.getId(block.getBlock());
            int data = block.getBlock().toLegacyData(block);

            int newBlockID = lookup.getBlockLookup(blockID, chunkY);
            int newData = lookup.getDataLookup(blockID, data, chunkY);

            changeInfos[i] = new MultiBlockChangeInfo((PacketPlayOutMultiBlockChange) packet.getHandle(),
                    changeInfo.b(), Block.getById(newBlockID).fromLegacyData(newData));
        }
    }

    public void translateFallingObject(PacketContainer packet, Player player) throws FieldAccessException {
        StructureModifier<Integer> ints = packet.getSpecificModifier(int.class);

        int type = ints.read(9);
        int rawData = ints.read(10);

        int blockID = ((((rawData) & 0xFF)) | ((rawData & 0xF00) << 4));

        int data = rawData >> 12 & 15;

        // Falling object (only block ID)
        if (type == 70) {
            int x = ints.read(1) / 32;
            int y = ints.read(2) / 32;
            int z = ints.read(3) / 32;

            // Get the correct table
            ConversionLookup lookup = cache.loadCacheOrDefault(player, x >> 4, y >> 4, z >> 4);

            int newBlockID = lookup.getBlockLookup(blockID);
            int newData = lookup.getDataLookup(blockID, data);

            ints.write(10, newBlockID + (newData << 12));
        }
    }

    @SuppressWarnings("deprecation")
    public void translateDroppedItem(PacketContainer packet, Player player, EventScheduler scheduler)
            throws FieldAccessException {

        StructureModifier<Integer> ints = packet.getSpecificModifier(int.class);

        // Minecraft 1.3.2 or lower
        if (ints.size() > 4) {

            int itemsID = ints.read(4);
            int count = ints.read(5);
            int data = ints.read(6);

            ItemStack stack = new ItemStack(itemsID, count, (short) data);
            scheduler.computeItemConversion(new ItemStack[] { stack }, player, false);

            // Make sure it has changed
            if (stack.getTypeId() != itemsID || stack.getAmount() != count || stack.getDurability() != data) {
                ints.write(4, stack.getTypeId());
                ints.write(5, stack.getAmount());
                ints.write(6, (int) stack.getDurability());
            }

            // Minecraft 1.4.2
        } else {
            StructureModifier<ItemStack> stacks = packet.getItemModifier();

            // Very simple
            if (stacks.size() > 0)
                scheduler.computeItemConversion(new ItemStack[] { stacks.read(0) }, player, false);
            else
                throw new IllegalStateException("Unrecognized packet structure.");
        }
    }

    public void translateDroppedItemMetadata(PacketContainer packet, Player player, EventScheduler scheduler) {
        Entity entity = packet.getEntityModifier(player.getWorld()).read(0);

        if (entity instanceof Item) {
            // Great. Get the item from the DataWatcher
            WrappedDataWatcher original = new WrappedDataWatcher(packet.getWatchableCollectionModifier().read(0));

            // Clone it
            WrappedDataWatcher watcher = original.deepClone();

            // Allow mods to convert it and write back the result
            scheduler.computeItemConversion(new ItemStack[] { watcher.getItemStack(10) }, player, false);
            packet.getWatchableCollectionModifier().write(0, watcher.getWatchableObjects());
        }
    }

    private boolean isChunkLoaded(World world, int x, int z) {
        return world.isChunkLoaded(x, z);
    }

    private void translateChunkInfoAndObfuscate(ChunkInfo info, byte[] returnData) {
        // Compute chunk number
        for (int i = 0; i < CHUNK_SEGMENTS; i++) {
            if ((info.chunkMask & (1 << i)) > 0) {
                info.chunkSectionNumber++;
            }
        }

        info.blockSize = 4096 * info.chunkSectionNumber;

        if (info.startIndex + info.blockSize > info.data.length) {
            return;
        }

        // Make sure the chunk is loaded 
        if (isChunkLoaded(info.player.getWorld(), info.chunkX, info.chunkZ)) {
            // Invoke the event
            SegmentLookup baseLookup = cache.getDefaultLookupTable();
            SegmentLookup lookup = scheduler.getChunkConversion(baseLookup, info.player, info.chunkX, info.chunkZ);

            // Save the result to the cache, if it's not the default
            if (!baseLookup.equals(lookup)) {
                cache.saveCache(info.player, info.chunkX, info.chunkZ, lookup);
            } else {
                cache.saveCache(info.player, info.chunkX, info.chunkZ, null);
            }

            translate(lookup, info);
        }
    }

    private void translate(SegmentLookup lookup, ChunkInfo info) {
        // Loop over 16x16x16 chunks in the 16x256x16 column
        int idIndexModifier = 0;

        int idOffset = info.startIndex;

        //Stopwatch watch = Stopwatch.createStarted();

        for (int i = 0; i < 16; i++) {
            // If the bitmask indicates this chunk is sent
            if ((info.chunkMask & 1 << i) > 0) {

                ConversionLookup view = lookup.getSegmentView(i);

                int relativeIDStart = idIndexModifier * 8192;
                int blockIndex = idOffset + relativeIDStart;

                for (int y = 0; y < 16; y++) {
                    for (int z = 0; z < 16; z++) {
                        for (int x = 0; x < 16; x++) {
                            int blockID = (((info.data[blockIndex + 1] & 0xFF) << 4)
                                    | ((info.data[blockIndex] & 0xFF) >>> 4));
                            int data = info.data[blockIndex] & 15;

                            // Transform block
                            int newBlockID = view.getBlockLookup(blockID);
                            int newData = view.getDataLookup(blockID, data);

                            info.data[blockIndex] = (byte) ((newBlockID << 4) | newData);
                            info.data[blockIndex + 1] = (byte) (newBlockID >> 4);

                            blockIndex += 2;
                        }
                    }
                }

                idIndexModifier++;
            }
        }

        //watch.stop();
        //System.out.println(String.format("Processed x: %s, z: %s in %s ms.", 
        //                info.chunkX, info.chunkZ, 
        //                getMilliseconds(watch))
        //);

        // We're done
    }

    public static double getMilliseconds(Stopwatch watch) {
        return watch.elapsed(TimeUnit.NANOSECONDS) / 1000000.0;
    }
}