smpships.entity.EntityShipBlock.java Source code

Java tutorial

Introduction

Here is the source code for smpships.entity.EntityShipBlock.java

Source

package smpships.entity;

import static net.minecraft.util.EnumChatFormatting.GREEN;
import static net.minecraft.util.EnumChatFormatting.RED;

import java.util.ArrayList;
import java.util.List;

import smpships.SMPships;
import smpships.ShipRegistry;
import smpships.block.ShipBlock;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityBoat;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;

import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

/**
 * A {@code BlockEntity} for the control of the ship - a {@link ShipBlock} becomes
 * one of these, all other blocks become a {@code BlockEntity}
 * This is the parent entity, which has many {@code BlockEntity}s as children
 * <p>
 * March 2013<br>
 * Released under the <a href=http://www.gnu.org/copyleft/gpl.html>GNU General Public License</a>
 * 
 * @author Grumdot
 * @author MineS1m0n
 * @see EntityBlock
 */
public class EntityShipBlock extends EntityBlock {

    private boolean field_70279_a = false; //apparently makes for smoother rotation (more frequent updates) - thanks Awger

    private double speedMultiplier = 0.07;
    private int boatPosRotationIncrements;
    private double boatX;
    private double boatY;
    private double boatZ;
    private double boatYaw;
    private double boatPitch;
    @SideOnly(Side.CLIENT)
    private double velocityX;
    @SideOnly(Side.CLIENT)
    private double velocityY;
    @SideOnly(Side.CLIENT)
    private double velocityZ;

    // TODO allow captain to name his ship
    public String name = "";
    /** The captain's name */
    public String captain = "";
    // TODO allow captain to name mates who are also allowed to steer the ship
    public List<String> mates = new ArrayList<String>();
    // TODO how many levels of blocks should be forced to be below water level
    public float draft;
    /** 
     * True if the entity is updated for the first time.
     * 
     * @see Entity#firstUpdate
     */
    private boolean firstUpdate;
    /** the actual children */
    public List<EntityBlock> children = new ArrayList<EntityBlock>();

    // constructor #1, not used by me but often called by Minecraft, client side
    public EntityShipBlock(World world) {
        super(world);
        xOff = 0; //parent doesn't use offset
        yOff = 0;
        zOff = 0;
        draft = 1.0F;
        firstUpdate = false;
        field_70279_a = true; //apparently makes for smoother rotation (more frequent updates) - thanks Awger

    }

    // constructor #2 used by me
    public EntityShipBlock(World world, double x, double y, double z, int blockId, int meta, ShipRegistry reg) {

        this(world);

        this.blockID = blockId;
        this.metadata = meta;
        parent = null;
        name = reg.getName();
        captain = reg.getCaptain();
        mates = reg.getMates();

        this.setPosition(x, y, z);
    }

    @Override
    public boolean shouldRiderSit() {
        return false;
    }

    @Override
    public boolean interactFirst(EntityPlayer player) {
        if (!worldObj.isRemote) {

            if (captain.isEmpty()) {
                captain = player.getEntityName();
                player.addChatMessage(GREEN + "Welcome aboard your new ship!");
            }

            if (player.isSneaking()) {
                if (captain.equals(player.getEntityName())) {
                    if (riddenByEntity instanceof EntityPlayer && riddenByEntity != player) {
                        player.addChatMessage(RED + "You cannot drop anchor while somebody is stearing!");
                        return true;
                    }
                    toBlocks();
                    player.addChatMessage(GREEN + "Anchor has been dropped!");
                } else
                    player.addChatMessage(RED + "Only the captain is allowed to drop anchor!");
                return true;
            }

            ItemStack currentItem = player.inventory.getCurrentItem();
            if (currentItem != null && currentItem.itemID == SMPships.journal.itemID) {
                if (captain.equals(player.getEntityName()))
                    player.addChatMessage(EnumChatFormatting.GOLD + "The journal is WIP!");
                else
                    player.addChatMessage(RED + "Only the captain is allowed to write into the journal!");
                return true;
            }

            if (captain.equals(player.getEntityName()) || mates.contains(player.getEntityName())) {
                if (riddenByEntity instanceof EntityPlayer && riddenByEntity != player)
                    return true;
                player.mountEntity(this);
            } else
                player.addChatMessage(RED + "If you want to steer this ship ask the captain " + captain + "!");

            return true;
        }
        return true;
    } // end of interactFirst()

    public boolean addChild(EntityBlock child) { //use this instead of children.add(be) to ensure children maintain link back to parent    
        boolean success = children.add(child);
        if (success)
            child.parent = this; //link both ways
        return success;
    }

    public boolean removeChild(EntityBlock child) {
        boolean success = children.remove(child);
        if (success)
            child.parent = null;
        return success;
    }

    public boolean safelyRemoveChild(EntityBlock child) {
        boolean success = removeChild(child);
        if (success) {
            child.setDead();
            child.worldObj.setBlock(MathHelper.floor_double(child.posX), MathHelper.floor_double(child.posY),
                    MathHelper.floor_double(child.posZ), child.blockID, child.metadata, 2);
        }
        return success;
    }

    public boolean toBlocks() {
        List<EntityBlock> copy = new ArrayList<EntityBlock>();
        copy.addAll(children);
        for (EntityBlock child : copy)
            safelyRemoveChild(child);
        worldObj.setBlock(MathHelper.floor_double(posX), MathHelper.floor_double(posY),
                MathHelper.floor_double(posZ), blockID, metadata, 2);
        setDead();
        return true;
    }

    @Override
    public void onUpdate() {

        super.onUpdate();

        if (!worldObj.isRemote && firstUpdate) { //kill zombie kids, spawn kids into world, on server only

            firstUpdate = false; //only do this stuff on load from disk

            //kill zombies
            killZombieKids(); //because parents recreate their children, old instances will linger, and must be removed

            //spawn children into world
            for (EntityBlock child : children)
                worldObj.spawnEntityInWorld(child);

        }

        /* 
         * note because we aren't registering BlockEntity in this mod, must manually spawn 
         * all child entities on the client (usually any spawned on the server will also spawn on client, but only if registered), 
         * but not here (too frequent).  Instead do it in readControlBlockSpawnData()
         */

        // move
        this.prevPosX = this.posX;
        this.prevPosY = this.posY;
        this.prevPosZ = this.posZ;
        byte b0 = 5;
        double d0 = 0.0D;

        for (int i = 0; i < b0; ++i) {
            double d1 = this.boundingBox.minY
                    + (this.boundingBox.maxY - this.boundingBox.minY) * (double) (i + 0) / (double) b0 - 0.125D;
            double d2 = this.boundingBox.minY
                    + (this.boundingBox.maxY - this.boundingBox.minY) * (double) (i + 1) / (double) b0 - 0.125D;
            AxisAlignedBB axisalignedbb = AxisAlignedBB.getAABBPool().getAABB(this.boundingBox.minX, d1,
                    this.boundingBox.minZ, this.boundingBox.maxX, d2, this.boundingBox.maxZ);

            if (this.worldObj.isAABBInMaterial(axisalignedbb, Material.water)) {
                d0 += 1.0D / (double) b0;
            }
        }

        double d3 = Math.sqrt(this.motionX * this.motionX + this.motionZ * this.motionZ);
        double d4;
        double d5;

        if (d3 > 0.26249999999999996D) {
            d4 = Math.cos((double) this.rotationYaw * Math.PI / 180.0D);
            d5 = Math.sin((double) this.rotationYaw * Math.PI / 180.0D);

            for (int j = 0; (double) j < 1.0D + d3 * 60.0D; ++j) {
                double d6 = (double) (this.rand.nextFloat() * 2.0F - 1.0F);
                double d7 = (double) (this.rand.nextInt(2) * 2 - 1) * 0.7D;
                double d8;
                double d9;

                if (this.rand.nextBoolean()) {
                    d8 = this.posX - d4 * d6 * 0.8D + d5 * d7;
                    d9 = this.posZ - d5 * d6 * 0.8D - d4 * d7;
                    this.worldObj.spawnParticle("splash", d8, this.posY - 0.125D, d9, this.motionX, this.motionY,
                            this.motionZ);
                } else {
                    d8 = this.posX + d4 + d5 * d6 * 0.7D;
                    d9 = this.posZ + d5 - d4 * d6 * 0.7D;
                    this.worldObj.spawnParticle("splash", d8, this.posY - 0.125D, d9, this.motionX, this.motionY,
                            this.motionZ);
                }
            }
        }

        double d10;
        double d11;

        if (this.worldObj.isRemote && this.field_70279_a) {
            if (this.boatPosRotationIncrements > 0) {
                d4 = this.posX + (this.boatX - this.posX) / (double) this.boatPosRotationIncrements;
                d5 = this.posY + (this.boatY - this.posY) / (double) this.boatPosRotationIncrements;
                d11 = this.posZ + (this.boatZ - this.posZ) / (double) this.boatPosRotationIncrements;
                d10 = MathHelper.wrapAngleTo180_double(this.boatYaw - (double) this.rotationYaw);
                this.rotationYaw = (float) ((double) this.rotationYaw
                        + d10 / (double) this.boatPosRotationIncrements);
                this.rotationPitch = (float) ((double) this.rotationPitch
                        + (this.boatPitch - (double) this.rotationPitch) / (double) this.boatPosRotationIncrements);
                --this.boatPosRotationIncrements;
                this.setPosition(d4, d5, d11);
                this.setRotation(this.rotationYaw, this.rotationPitch);
            } else {
                d4 = this.posX + this.motionX;
                d5 = this.posY + this.motionY;
                d11 = this.posZ + this.motionZ;
                this.setPosition(d4, d5, d11);

                if (this.onGround) {
                    this.motionX *= 0.5D;
                    this.motionY *= 0.5D;
                    this.motionZ *= 0.5D;
                }

                this.motionX *= 0.9900000095367432D;
                this.motionY *= 0.949999988079071D;
                this.motionZ *= 0.9900000095367432D;
            }
        } else {
            if (d0 < 1.0D) {
                d4 = d0 * 2.0D - 1.0D;
                this.motionY += 0.03999999910593033D * d4;
            } else {
                if (this.motionY < 0.0D) {
                    this.motionY /= 2.0D;
                }

                this.motionY += 0.007000000216066837D;
            }

            if (this.riddenByEntity != null && this.riddenByEntity instanceof EntityLivingBase) {
                d4 = (double) ((EntityLivingBase) this.riddenByEntity).moveForward;

                if (d4 > 0.0D) {
                    d5 = -Math.sin((double) (this.riddenByEntity.rotationYaw * (float) Math.PI / 180.0F));
                    d11 = Math.cos((double) (this.riddenByEntity.rotationYaw * (float) Math.PI / 180.0F));
                    this.motionX += d5 * this.speedMultiplier * 0.05000000074505806D;
                    this.motionZ += d11 * this.speedMultiplier * 0.05000000074505806D;
                }
            }

            d4 = Math.sqrt(this.motionX * this.motionX + this.motionZ * this.motionZ);

            if (d4 > 0.35D) {
                d5 = 0.35D / d4;
                this.motionX *= d5;
                this.motionZ *= d5;
                d4 = 0.35D;
            }

            if (d4 > d3 && this.speedMultiplier < 0.35D) {
                this.speedMultiplier += (0.35D - this.speedMultiplier) / 35.0D;

                if (this.speedMultiplier > 0.35D) {
                    this.speedMultiplier = 0.35D;
                }
            } else {
                this.speedMultiplier -= (this.speedMultiplier - 0.07D) / 35.0D;

                if (this.speedMultiplier < 0.07D) {
                    this.speedMultiplier = 0.07D;
                }
            }

            if (this.onGround) {
                this.motionX *= 0.5D;
                this.motionY *= 0.5D;
                this.motionZ *= 0.5D;
            }

            this.moveEntity(this.motionX, this.motionY, this.motionZ);

            if (this.isCollidedHorizontally && d3 > 0.2D) {
                if (!this.worldObj.isRemote && !this.isDead) {
                    this.setDead();
                    int k;

                    for (k = 0; k < 3; ++k) {
                        this.dropItemWithOffset(Block.planks.blockID, 1, 0.0F);
                    }

                    for (k = 0; k < 2; ++k) {
                        this.dropItemWithOffset(Item.stick.itemID, 1, 0.0F);
                    }
                }
            } else {
                this.motionX *= 0.9900000095367432D;
                this.motionY *= 0.949999988079071D;
                this.motionZ *= 0.9900000095367432D;
            }

            this.rotationPitch = 0.0F;
            d5 = (double) this.rotationYaw;
            d11 = this.prevPosX - this.posX;
            d10 = this.prevPosZ - this.posZ;

            if (d11 * d11 + d10 * d10 > 0.001D) {
                d5 = (double) ((float) (Math.atan2(d10, d11) * 180.0D / Math.PI));
            }

            double d12 = MathHelper.wrapAngleTo180_double(d5 - (double) this.rotationYaw);

            if (d12 > 20.0D) {
                d12 = 20.0D;
            }

            if (d12 < -20.0D) {
                d12 = -20.0D;
            }

            this.rotationYaw = (float) ((double) this.rotationYaw + d12);
            this.setRotation(this.rotationYaw, this.rotationPitch);

            if (!this.worldObj.isRemote) {
                List list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this,
                        this.boundingBox.expand(0.20000000298023224D, 0.0D, 0.20000000298023224D));
                int l;

                if (list != null && !list.isEmpty()) {
                    for (l = 0; l < list.size(); ++l) {
                        Entity entity = (Entity) list.get(l);

                        if (entity != this.riddenByEntity && entity.canBePushed() && entity instanceof EntityBoat) {
                            entity.applyEntityCollision(this);
                        }
                    }
                }

                for (l = 0; l < 4; ++l) {
                    int i1 = MathHelper.floor_double(this.posX + ((double) (l % 2) - 0.5D) * 0.8D);
                    int j1 = MathHelper.floor_double(this.posZ + ((double) (l / 2) - 0.5D) * 0.8D);

                    for (int k1 = 0; k1 < 2; ++k1) {
                        int l1 = MathHelper.floor_double(this.posY) + k1;
                        int i2 = this.worldObj.getBlockId(i1, l1, j1);

                        if (i2 == Block.snow.blockID) {
                            this.worldObj.setBlockToAir(i1, l1, j1);
                        } else if (i2 == Block.waterlily.blockID) {
                            this.worldObj.destroyBlock(i1, l1, j1, true);
                        }
                    }
                }

                if (this.riddenByEntity != null && this.riddenByEntity.isDead) {
                    this.riddenByEntity = null;
                }
            }
        }

        rotationYaw += 1;
        float rad = rotationYaw * (float) Math.PI / 180.0F;
        float sin = MathHelper.sin(rad);
        float cos = MathHelper.cos(rad);
        // tell children to move
        for (EntityBlock child : children) {
            child.onUpdate();
            // TODO get rotation to work with different offsets than 1
            child.setLocationAndAngles(posX - 2 * cos - 1 * cos, posY + child.yOff, posZ + 2 * sin + 1 * sin,
                    rotationYaw, rotationPitch);
        }

    } // end of onUpdate()

    public void updateRiderPosition() {
        if (this.riddenByEntity != null) {
            double d0 = Math.cos((double) this.rotationYaw * Math.PI / 180.0D) * 0.4D;
            double d1 = Math.sin((double) this.rotationYaw * Math.PI / 180.0D) * 0.4D;
            this.riddenByEntity.setPosition(this.posX + d0,
                    this.posY + this.getMountedYOffset() + this.riddenByEntity.getYOffset(), this.posZ + d1);
        }
    }

    public String toString() {
        String str = "\n## Start Ship Reg Entry ##" + "\nremote = " + worldObj.isRemote + "\nx = " + posX + " y = "
                + posY + " z = " + posZ + "\nname=" + name + "\ncaptain=" + captain;
        for (int i = 0; i < mates.size(); i++)
            str += "\n mate " + i + "=" + mates.get(i);
        str += "\n draft = " + draft;

        for (int i = 0; i < children.size(); i++)
            str += "\n  # " + i + " " + children.get(i).toString();
        str += "\n## End Ship Reg Entry ##\n";
        return str;
    }

    // ######################################
    // reading and writing extra data to disk
    // ######################################

    @Override
    protected void writeControlBlockEntityToNBT(NBTTagCompound par1NBTTagCompound) { //runs on server. happens when quit or pause game, also automatically every few seconds

        par1NBTTagCompound.setString("name", this.name);
        par1NBTTagCompound.setString("captain", this.captain);
        int i = 0;
        for (String mate : mates)
            par1NBTTagCompound.setString("mate" + i, mates.get(i));
        par1NBTTagCompound.setFloat("draft", draft);
        par1NBTTagCompound.setInteger("bid", blockID);
        par1NBTTagCompound.setInteger("metaData", metadata);

        par1NBTTagCompound.setInteger("kidsCount", children.size()); // store kids
        int[][] eeArrays = getKidListAsArrays();
        par1NBTTagCompound.setIntArray("xOff", eeArrays[0]);
        par1NBTTagCompound.setIntArray("yOff", eeArrays[1]);
        par1NBTTagCompound.setIntArray("zOff", eeArrays[2]);
        par1NBTTagCompound.setIntArray("blockId", eeArrays[3]);
        par1NBTTagCompound.setIntArray("meta", eeArrays[4]);

        //Don't kill all kids as planned here: it doesn't happen only when you quit - see onUpdate()     

    } // writeControlEntityFromNBT()

    @Override
    protected void readControlBlockEntityFromNBT(NBTTagCompound data) { //runs on the server

        this.firstUpdate = true;

        this.name = data.getString("name");
        this.captain = data.getString("captain");
        for (int i = 0; data.hasKey("mate" + i); i++)
            mates.set(i, data.getString("mate" + i));
        this.draft = data.getFloat("draft");
        this.blockID = data.getInteger("bid");
        this.metadata = data.getInteger("metaData");

        int numOfKids = data.getInteger("kidsCount"); //load all kids
        int[][] kids = new int[5][numOfKids];
        kids[0] = data.getIntArray("xOff");
        kids[1] = data.getIntArray("yOff");
        kids[2] = data.getIntArray("zOff");
        kids[3] = data.getIntArray("blockId");
        kids[4] = data.getIntArray("meta");

        //recreate all kids
        for (int i = 0; i < numOfKids; i++) {
            EntityBlock be = new EntityBlock(worldObj, posX + kids[0][i], posY + kids[1][i], posZ + kids[2][i],
                    kids[3][i], kids[4][i], this);
            addChild(be);
        }

        // here appears to be too early to successfully call spawnToWorld: the relevant chunks don't yet exist - will have to spawn them in onUpdate()?

    } // end of readControlBlockEntityFromNBT()

    public int[][] getKidListAsArrays() { //get each field from all children as an array, for writeControlBlockEntityToNBT    
        int[][] result = new int[5][children.size()];
        for (int i = 0; i < result[0].length; i++) {
            result[0][i] = children.get(i).xOff;
            result[1][i] = children.get(i).yOff;
            result[2][i] = children.get(i).zOff;
            result[3][i] = children.get(i).blockID;
            result[4][i] = children.get(i).metadata;
        }
        return result;
    }

    /**
     * destroys all child BlockEntities that are not in the children list of and control
     * block, and have been replaced with new BlockEntities generated in readFromNBT()
     */
    protected int killZombieKids() {
        int count = 0;
        for (int i = 0; i < worldObj.loadedEntityList.size(); i++) {
            Entity ent = (Entity) this.worldObj.loadedEntityList.get(i);
            if (ent instanceof EntityBlock)
                if (((EntityBlock) ent).blockID == 0) { //lazy: any zombie won't have had correct block id read from NBT, so will = 0
                    ent.setDead();
                    count++;
                }
        }
        return count;
    }

    // ##################################
    // IEntityAdditionalSpawnData methods
    // ##################################

    @Override
    protected void writeControlBlockSpawnData(ByteArrayDataOutput data) { // runs on server

        data.writeUTF(name);
        data.writeUTF(captain);

        // write mates
        data.writeInt(mates.size());
        for (String mate : mates)
            data.writeUTF(mate);

        data.writeFloat(draft);

        data.writeInt(children.size());
        for (int i = 0; i < children.size(); i++) { // write child data

            data.writeInt(children.get(i).xOff);
            data.writeInt(children.get(i).yOff);
            data.writeInt(children.get(i).zOff);
            data.writeInt(children.get(i).blockID);
            data.writeInt(children.get(i).metadata);

        }

    } // end of writeControlBlockSpawnData()

    @Override
    protected void readControlBlockSpawnData(ByteArrayDataInput data) { //runs on clients

        name = data.readUTF();
        captain = data.readUTF();

        // read mates
        int mateCount = data.readInt();
        for (int i = 0; i < mateCount; i++)
            mates.set(i, data.readUTF());

        draft = data.readFloat();

        int kidCount = data.readInt(); //load kids, add to parent
        for (int i = 0; i < kidCount; i++)
            addChild(new EntityBlock(worldObj, posX + data.readInt(), posY + data.readInt(), posZ + data.readInt(),
                    data.readInt(), data.readInt(), this)); // x y z b m

        // ensure all children are spawned in CLIENT world, spawn any missing (spawning in client is unusual, but needed because BlockEntity is not registered
        if (worldObj.isRemote)
            for (EntityBlock be : children) // remote check should be redundant in this method
                if (!worldObj.loadedEntityList.contains(be))
                    worldObj.spawnEntityInWorld(be);

    } // end of readControlBlockSpawnData()

} // end of class