buildcraft.lib.tile.TileBC_Neptune.java Source code

Java tutorial

Introduction

Here is the source code for buildcraft.lib.tile.TileBC_Neptune.java

Source

/* Copyright (c) 2016 SpaceToad and the BuildCraft team
 * 
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
 * distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */
package buildcraft.lib.tile;

import java.io.IOException;
import java.util.Set;

import javax.annotation.Nonnull;

import com.google.common.collect.Sets;
import com.mojang.authlib.GameProfile;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.Container;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SPacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.Explosion;
import net.minecraft.world.World;

import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.items.IItemHandlerModifiable;

import buildcraft.api.core.BCDebugging;
import buildcraft.api.core.BCLog;
import buildcraft.api.permission.IPlayerOwned;

import buildcraft.lib.cap.CapabilityHelper;
import buildcraft.lib.client.render.DetatchedRenderer.IDetachedRenderer;
import buildcraft.lib.debug.BCAdvDebugging;
import buildcraft.lib.debug.IAdvDebugTarget;
import buildcraft.lib.delta.DeltaManager;
import buildcraft.lib.delta.DeltaManager.EnumDeltaMessage;
import buildcraft.lib.migrate.BCVersion;
import buildcraft.lib.misc.MessageUtil;
import buildcraft.lib.misc.PermissionUtil;
import buildcraft.lib.misc.PermissionUtil.PermissionBlock;
import buildcraft.lib.misc.data.IdAllocator;
import buildcraft.lib.net.IPayloadReceiver;
import buildcraft.lib.net.IPayloadWriter;
import buildcraft.lib.net.MessageUpdateTile;
import buildcraft.lib.net.PacketBufferBC;
import buildcraft.lib.registry.TagManager;
import buildcraft.lib.registry.TagManager.EnumTagType;
import buildcraft.lib.registry.TagManager.EnumTagTypeMulti;
import buildcraft.lib.tile.item.ItemHandlerManager;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

public abstract class TileBC_Neptune extends TileEntity implements IPayloadReceiver, IAdvDebugTarget, IPlayerOwned {
    public static final boolean DEBUG_PARTICLES = BCDebugging.shouldDebugLog("tile.debug.particles");

    protected static final IdAllocator IDS = new IdAllocator("tile");

    /** Used for sending all data used for rendering the tile on a client. This does not include items, power, stages,
     * etc (Unless some are shown in the world) */
    public static final int NET_RENDER_DATA = IDS.allocId("RENDER_DATA");
    /** Used for sending all data in the GUI. Basically what has been omitted from {@link #NET_RENDER_DATA} that is
     * shown in the GUI. */
    public static final int NET_GUI_DATA = IDS.allocId("GUI_DATA");
    /** Used for sending the data that would normally be sent with {@link Container#detectAndSendChanges()}. Note that
     * if no bytes are written then the update message won't be sent. You should detect if any changes have been made to
     * the gui since the last tick, so you don't resend duplicate information if nothing has changed by the next
     * tick. */
    public static final int NET_GUI_TICK = IDS.allocId("GUI_TICK");

    public static final int NET_REN_DELTA_SINGLE = IDS.allocId("REN_DELTA_SINGLE");
    public static final int NET_REN_DELTA_CLEAR = IDS.allocId("REN_DELTA_CLEAR");
    public static final int NET_GUI_DELTA_SINGLE = IDS.allocId("GUI_DELTA_SINGLE");
    public static final int NET_GUI_DELTA_CLEAR = IDS.allocId("GUI_DELTA_CLEAR");

    /** Used for detailed debugging for inspecting every part of the current tile. For example, tanks use this to
     * display which other tanks makeup the whole structure. */
    public static final int NET_ADV_DEBUG = IDS.allocId("DEBUG_DATA");
    public static final int NET_ADV_DEBUG_DISABLE = IDS.allocId("DEBUG_DISABLE");

    /** Used to tell the client to redraw the block. */
    public static final int NET_REDRAW = IDS.allocId("REDRAW");

    protected final CapabilityHelper caps = new CapabilityHelper();
    protected final ItemHandlerManager itemManager = caps.addProvider(new ItemHandlerManager(this::onSlotChange));

    /** Handles all of the players that are currently using this tile (have a GUI open) */
    private final Set<EntityPlayer> usingPlayers = Sets.newIdentityHashSet();
    private GameProfile owner;

    protected final DeltaManager deltaManager = new DeltaManager((gui, type, writer) -> {
        final int id;
        if (type == EnumDeltaMessage.ADD_SINGLE) {
            id = gui ? NET_GUI_DELTA_SINGLE : NET_REN_DELTA_SINGLE;
        } else if (type == EnumDeltaMessage.SET_VALUE) {
            id = gui ? NET_GUI_DELTA_CLEAR : NET_REN_DELTA_CLEAR;
        } else {
            throw new IllegalArgumentException("Unknown delta message type " + type);
        }
        if (gui) {
            this.createAndSendGuiMessage(id, writer);
        } else {
            this.createAndSendMessage(id, writer);
        }
    });

    public TileBC_Neptune() {
        caps.addProvider(itemManager);
    }

    public static <T extends TileBC_Neptune> void registerTile(Class<T> tileClass, String id) {
        String regName = TagManager.getTag(id, EnumTagType.REGISTRY_NAME);
        String[] alternatives = TagManager.getMultiTag(id, EnumTagTypeMulti.OLD_REGISTRY_NAME);
        GameRegistry.registerTileEntityWithAlternatives(tileClass, regName, alternatives);
    }

    // ##################
    //
    // Misc overridables
    //
    // ##################

    /** Checks to see if this tile can update. The base implementation only checks to see if it has a world. */
    public boolean cannotUpdate() {
        return !hasWorld();
    }

    @Override
    public boolean shouldRefresh(World world, BlockPos pos, IBlockState oldState, IBlockState newState) {
        return oldState.getBlock() != newState.getBlock();
    }

    /** Called whenever the block holding this tile is exploded. Called by
     * {@link Block#onBlockExploded(World, BlockPos, Explosion)} */
    public void onExplode(Explosion explosion) {
        onRemove();
    }

    /** Called whenever the block is removed. Called by {@link #onExplode(Explosion)}, and
     * {@link Block#breakBlock(World, BlockPos, IBlockState)} */
    public void onRemove() {
    }

    public void onPlacedBy(EntityLivingBase placer, ItemStack stack) {
        if (!placer.world.isRemote) {
            if (placer instanceof EntityPlayer) {
                EntityPlayer player = (EntityPlayer) placer;
                this.owner = player.getGameProfile();
                if (!owner.isComplete()) {
                    throw new IllegalArgumentException("Incomplete owner! ( " + placer + " -> " + owner + " )");
                }
            }
        }
    }

    public void onPlayerOpen(EntityPlayer player) {
        if (owner == null) {
            owner = player.getGameProfile();
            if (!owner.isComplete()) {
                owner = null;
            }
        }
        usingPlayers.add(player);
        sendNetworkUpdate(NET_GUI_DATA, player);
    }

    public void onPlayerClose(EntityPlayer player) {
        usingPlayers.remove(player);
    }

    @Override
    public final boolean hasCapability(Capability<?> capability, EnumFacing facing) {
        return getCapability(capability, facing) != null;
    }

    @Override
    public <T> T getCapability(Capability<T> capability, EnumFacing facing) {
        return caps.getCapability(capability, facing);
    }

    // Item caps
    protected void onSlotChange(IItemHandlerModifiable handler, int slot, @Nonnull ItemStack before,
            @Nonnull ItemStack after) {
        if (world.isBlockLoaded(getPos())) {
            markDirty();
        }
    }

    // ##################
    //
    // Permission related
    //
    // ##################

    @Override
    public GameProfile getOwner() {
        return owner;
    }

    public PermissionUtil.PermissionBlock getPermBlock() {
        return new PermissionBlock(this, getPos());
    }

    public boolean canEditOther(BlockPos other) {
        return PermissionUtil.hasPermission(PermissionUtil.PERM_EDIT, getPermBlock(),
                PermissionUtil.createFrom(getWorld(), other));
    }

    public boolean canPlayerEdit(EntityPlayer player) {
        return PermissionUtil.hasPermission(PermissionUtil.PERM_EDIT, player, getPermBlock());
    }

    public boolean canInteractWith(EntityPlayer player) {
        if (world.getTileEntity(getPos()) != this) {
            return false;
        }
        if (player.getDistanceSq(this.pos.getX() + 0.5D, this.pos.getY() + 0.5D, this.pos.getZ() + 0.5D) > 64.0D) {
            return false;
        }
        // edit rather than view because you can normally change the contents from gui interaction
        return canPlayerEdit(player);
    }

    // ##################
    //
    // Network helpers
    //
    // ##################

    /** Tells MC to redraw this block. Note that this sends the NET_REDRAW message. */
    public final void redrawBlock() {
        if (hasWorld()) {
            if (world.isRemote) {
                IBlockState state = world.getBlockState(pos);
                world.notifyBlockUpdate(pos, state, state, 0);

                if (DEBUG_PARTICLES) {
                    double x = getPos().getX() + 0.5;
                    double y = getPos().getY() + 0.5;
                    double z = getPos().getZ() + 0.5;
                    world.spawnParticle(EnumParticleTypes.HEART, x, y, z, 0, 0, 0);
                }
            } else {
                sendNetworkUpdate(NET_REDRAW);
            }
        }
    }

    /** Sends a network update update of the specified ID. */
    public final void sendNetworkUpdate(int id) {
        if (hasWorld()) {
            MessageUpdateTile message = createNetworkUpdate(id);
            MessageUtil.sendToAllWatching(this.world, this.getPos(), message);
        }
    }

    /** Sends {@link #NET_GUI_TICK}. */
    public final void sendNetworkGuiTick() {
        if (hasWorld() && !world.isRemote) {
            MessageUpdateTile message = createNetworkUpdate(NET_GUI_TICK);
            if (message.getPayloadSize() <= Short.BYTES) {
                return;
            }
            for (EntityPlayer player : usingPlayers) {
                MessageUtil.getWrapper().sendTo(message, (EntityPlayerMP) player);
            }
        }
    }

    public final void sendNetworkGuiUpdate(int id) {
        if (hasWorld()) {
            for (EntityPlayer player : usingPlayers) {
                sendNetworkUpdate(id, player);
            }
        }
    }

    public final void sendNetworkUpdate(int id, EntityPlayer target) {
        if (hasWorld() && target instanceof EntityPlayerMP) {
            MessageUpdateTile message = createNetworkUpdate(id);
            MessageUtil.getWrapper().sendTo(message, (EntityPlayerMP) target);
        }
    }

    public final MessageUpdateTile createNetworkUpdate(final int id) {
        if (hasWorld()) {
            final Side side = world.isRemote ? Side.CLIENT : Side.SERVER;
            return createMessage(id, (buffer) -> writePayload(id, buffer, side));
        } else {
            BCLog.logger.warn("Did not have a world at " + getPos() + "!");
        }
        return null;
    }

    public final void createAndSendMessage(int id, IPayloadWriter writer) {
        if (hasWorld()) {
            IMessage message = createMessage(id, writer);
            MessageUtil.sendToAllWatching(this.world, this.getPos(), message);
        }
    }

    public final void createAndSendGuiMessage(int id, IPayloadWriter writer) {
        if (hasWorld()) {
            IMessage message = createMessage(id, writer);
            MessageUtil.sendToPlayers(usingPlayers, message);
        }
    }

    public final MessageUpdateTile createMessage(int id, IPayloadWriter writer) {
        PacketBufferBC buffer = new PacketBufferBC(Unpooled.buffer());
        buffer.writeShort(id);
        writer.write(buffer);
        return new MessageUpdateTile(getPos(), buffer);
    }

    @Override
    public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
        handleUpdateTag(pkt.getNbtCompound());
    }

    @Override
    public SPacketUpdateTileEntity getUpdatePacket() {
        IBlockState state = getWorld().getBlockState(getPos());
        int meta = state.getBlock().getMetaFromState(state);
        return new SPacketUpdateTileEntity(getPos(), meta, getUpdateTag());
    }

    @Override
    public NBTTagCompound getUpdateTag() {
        ByteBuf buf = Unpooled.buffer();
        buf.writeShort(NET_RENDER_DATA);
        writePayload(NET_RENDER_DATA, new PacketBufferBC(buf), world.isRemote ? Side.CLIENT : Side.SERVER);
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);

        NBTTagCompound nbt = super.getUpdateTag();
        nbt.setByteArray("d", bytes);
        return nbt;
    }

    @Override
    public void handleUpdateTag(NBTTagCompound tag) {
        super.readFromNBT(tag);
        byte[] bytes = tag.getByteArray("d");
        ByteBuf buf = Unpooled.copiedBuffer(bytes);

        try {
            int id = buf.readUnsignedShort();
            readPayload(id, new PacketBufferBC(buf), world.isRemote ? Side.CLIENT : Side.SERVER, null);
            // Make sure that we actually read the entire message rather than just discarding it
            MessageUtil.ensureEmpty(buf, world.isRemote, getClass().getSimpleName());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        spawnReceiveParticles();
    }

    private void spawnReceiveParticles() {
        if (DEBUG_PARTICLES) {
            if (world != null) {
                double x = getPos().getX() + 0.5;
                double y = getPos().getY() + 0.5;
                double z = getPos().getZ() + 0.5;
                double dx = Math.random() - 0.5;
                double dy = Math.random() - 1;
                double dz = Math.random() - 0.5;
                world.spawnParticle(EnumParticleTypes.DAMAGE_INDICATOR, x, y, z, dx, dy, dz);
            }
        }
    }

    @Override
    public final IMessage receivePayload(MessageContext ctx, PacketBufferBC buffer) throws IOException {
        int id = buffer.readUnsignedShort();
        readPayload(id, buffer, ctx.side, ctx);

        // Make sure that we actually read the entire message rather than just discarding it
        MessageUtil.ensureEmpty(buffer, world.isRemote, getClass().getSimpleName());

        if (ctx.side == Side.CLIENT) {
            spawnReceiveParticles();
        }
        return null;
    }

    // ######################
    //
    // Network overridables
    //
    // ######################

    public void writePayload(int id, PacketBufferBC buffer, Side side) {
        // write render data with gui data
        if (id == NET_GUI_DATA) {

            writePayload(NET_RENDER_DATA, buffer, side);

            if (side == Side.SERVER) {
                MessageUtil.writeGameProfile(buffer, owner);
            }
        }
        if (side == Side.SERVER) {
            if (id == NET_RENDER_DATA) {
                deltaManager.writeDeltaState(false, buffer);
            } else if (id == NET_GUI_DATA) {
                deltaManager.writeDeltaState(true, buffer);
            }
        }
    }

    /** @param ctx The context. Will be null if this is a generic update payload
     * @throws IOException if something went wrong */
    public void readPayload(int id, PacketBufferBC buffer, Side side, MessageContext ctx) throws IOException {
        // read render data with gui data
        if (id == NET_GUI_DATA) {
            readPayload(NET_RENDER_DATA, buffer, side, ctx);

            if (side == Side.CLIENT) {
                owner = MessageUtil.readGameProfile(buffer);
            }
        }
        if (side == Side.CLIENT) {
            if (id == NET_RENDER_DATA)
                deltaManager.receiveDeltaData(false, EnumDeltaMessage.CURRENT_STATE, buffer);
            else if (id == NET_GUI_DATA)
                deltaManager.receiveDeltaData(true, EnumDeltaMessage.CURRENT_STATE, buffer);
            else if (id == NET_REN_DELTA_SINGLE)
                deltaManager.receiveDeltaData(false, EnumDeltaMessage.ADD_SINGLE, buffer);
            else if (id == NET_GUI_DELTA_SINGLE)
                deltaManager.receiveDeltaData(true, EnumDeltaMessage.ADD_SINGLE, buffer);
            else if (id == NET_REN_DELTA_CLEAR)
                deltaManager.receiveDeltaData(false, EnumDeltaMessage.SET_VALUE, buffer);
            else if (id == NET_GUI_DELTA_CLEAR)
                deltaManager.receiveDeltaData(true, EnumDeltaMessage.SET_VALUE, buffer);
            else if (id == NET_REDRAW)
                redrawBlock();
        }
    }

    // ######################
    //
    // NBT handling
    //
    // ######################

    @Override
    public void readFromNBT(NBTTagCompound nbt) {
        super.readFromNBT(nbt);
        int version = nbt.getInteger("data-version");
        if (version != BCVersion.CURRENT.dataVersion) {
            migrateOldNBT(version, nbt);
        }
        deltaManager.readFromNBT(nbt.getCompoundTag("deltas"));
        if (nbt.hasKey("owner")) {
            owner = NBTUtil.readGameProfileFromNBT(nbt.getCompoundTag("owner"));
            if (owner != null && !owner.isComplete()) {
                owner = null;
            }
        }
        if (nbt.hasKey("items", Constants.NBT.TAG_COMPOUND)) {
            itemManager.deserializeNBT(nbt.getCompoundTag("items"));
        }
    }

    protected void migrateOldNBT(int version, NBTTagCompound nbt) {
    }

    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound nbt) {
        super.writeToNBT(nbt);
        nbt.setInteger("data-version", BCVersion.CURRENT.dataVersion);
        nbt.setTag("deltas", deltaManager.writeToNBT());
        if (owner != null && owner.isComplete()) {
            nbt.setTag("owner", NBTUtil.writeGameProfile(new NBTTagCompound(), owner));
        }
        NBTTagCompound items = itemManager.serializeNBT();
        if (!items.hasNoTags()) {
            nbt.setTag("items", items);
        }

        return nbt;
    }

    @Override
    protected void setWorldCreate(World world) {
        // The default impl doesn't actually set the world for some reason :/
        setWorld(world);
    }

    // ##################
    //
    // Advanced debugging
    //
    // ##################

    public boolean isBeingDebugged() {
        return BCAdvDebugging.isBeingDebugged(this);
    }

    public void enableDebugging() {
        if (getWorld() == null || getWorld().isRemote) {
            return;
        }
        BCAdvDebugging.setCurrentDebugTarget(this);
    }

    @Override
    public void disableDebugging() {
        sendNetworkUpdate(NET_ADV_DEBUG_DISABLE);
    }

    @Override
    public boolean doesExistInWorld() {
        return hasWorld() && getWorld().getTileEntity(getPos()) == this;
    }

    @Override
    public void sendDebugState() {
        sendNetworkUpdate(NET_ADV_DEBUG);
    }

    @Override
    @SideOnly(Side.CLIENT)
    public IDetachedRenderer getDebugRenderer() {
        return null;
    }
}