Java tutorial
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