/* * This file is part of ComputerCraft - http://www.computercraft.info * Copyright Daniel Ratcliffe, 2011-2016. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ package dan200.computercraft.shared.turtle.core; import com.google.common.base.Objects; import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.turtle.*; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.IComputer; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.Holiday; import dan200.computercraft.shared.util.HolidayUtil; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.inventory.IInventory; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.common.util.Constants; import javax.annotation.Nonnull; import java.lang.ref.WeakReference; import java.util.*; public class TurtleBrain implements ITurtleAccess { private static int s_nextInstanceID = 0; private static Map> s_allClientBrains = new HashMap>(); public static int assignInstanceID() { return s_nextInstanceID++; } public static TurtleBrain getClientBrain( int instanceID ) { if( instanceID >= 0 ) { WeakReference ref = s_allClientBrains.get( instanceID ); if( ref != null ) { TurtleBrain brain = ref.get(); if( brain != null ) { return brain; } else { s_allClientBrains.remove( instanceID ); } } } return null; } public static void setClientBrain( int instanceID, TurtleBrain brain ) { if( instanceID >= 0 ) { if( getClientBrain( instanceID ) != brain ) { s_allClientBrains.put( instanceID, new WeakReference( brain ) ); } } } public static void cleanupBrains() { if( s_allClientBrains.size() > 0 ) { Iterator>> it = s_allClientBrains.entrySet().iterator(); while( it.hasNext() ) { Map.Entry> entry = it.next(); WeakReference ref = entry.getValue(); if( ref != null ) { TurtleBrain brain = ref.get(); if( brain == null ) { it.remove(); } } } } } private static final int ANIM_DURATION = 8; private TileTurtle m_owner; private LinkedList m_commandQueue; private int m_commandsIssued; private Map m_upgrades; private Map m_peripherals; private Map m_upgradeNBTData; private int m_selectedSlot; private int m_fuelLevel; private Colour m_colour; private ResourceLocation m_overlay; private int m_instanceID; private EnumFacing m_direction; private TurtleAnimation m_animation; private int m_animationProgress; private int m_lastAnimationProgress; public TurtleBrain( TileTurtle turtle ) { m_owner = turtle; m_commandQueue = new LinkedList(); m_commandsIssued = 0; m_upgrades = new HashMap(); m_peripherals = new HashMap(); m_upgradeNBTData = new HashMap(); m_selectedSlot = 0; m_fuelLevel = 0; m_colour = null; m_overlay = null; m_instanceID = -1; m_direction = EnumFacing.NORTH; m_animation = TurtleAnimation.None; m_animationProgress = 0; m_lastAnimationProgress = 0; } public TurtleBrain getFutureSelf() { if( getOwner().getWorld().isRemote ) { TurtleBrain futureSelf = getClientBrain( m_instanceID ); if( futureSelf != null ) { return futureSelf; } } return this; } public void setOwner( TileTurtle owner ) { m_owner = owner; } public TileTurtle getOwner() { return m_owner; } public ComputerFamily getFamily() { return m_owner.getFamily(); } public void setupComputer( ServerComputer computer ) { updatePeripherals( computer ); } public void update() { World world = getWorld(); if( !world.isRemote ) { // Advance movement updateCommands(); } // Advance animation updateAnimation(); // Advance upgrades if( !m_upgrades.isEmpty() ) { for( Map.Entry entry : m_upgrades.entrySet() ) { entry.getValue().update( this, entry.getKey() ); } } } public void readFromNBT( NBTTagCompound nbttagcompound ) { // Read state m_direction = EnumFacing.getFront( nbttagcompound.getInteger( "dir" ) ); m_selectedSlot = nbttagcompound.getInteger( "selectedSlot" ); if( nbttagcompound.hasKey( "fuelLevel" ) ) { m_fuelLevel = nbttagcompound.getInteger( "fuelLevel" ); } else { m_fuelLevel = 0; } // Read colour if( nbttagcompound.hasKey( "colourIndex" ) ) { m_colour = Colour.values()[ nbttagcompound.getInteger( "colourIndex" ) ]; } else { m_colour = null; } // Read overlay if( nbttagcompound.hasKey( "overlay_mod" ) ) { String overlay_mod = nbttagcompound.getString( "overlay_mod" ); if( nbttagcompound.hasKey( "overlay_path" ) ) { String overlay_path = nbttagcompound.getString( "overlay_path" ); m_overlay = new ResourceLocation( overlay_mod, overlay_path ); } else { m_overlay = null; } } else { m_overlay = null; } // Read upgrades // (pre-1.4 turtles will have a "subType" variable, newer things will have "leftUpgrade" and "rightUpgrade") ITurtleUpgrade leftUpgrade = null; ITurtleUpgrade rightUpgrade = null; if( nbttagcompound.hasKey( "subType" ) ) { // Loading a pre-1.4 world int subType = nbttagcompound.getInteger( "subType" ); if( (subType & 0x1) > 0 ) { leftUpgrade = ComputerCraft.Upgrades.diamondPickaxe; } if( (subType & 0x2) > 0 ) { rightUpgrade = ComputerCraft.Upgrades.wirelessModem; } } else { // Loading a post-1.4 world if( nbttagcompound.hasKey( "leftUpgrade" ) ) { if( nbttagcompound.getTagId( "leftUpgrade" ) == Constants.NBT.TAG_STRING ) { leftUpgrade = ComputerCraft.getTurtleUpgrade( nbttagcompound.getString( "leftUpgrade" ) ); } else { leftUpgrade = ComputerCraft.getTurtleUpgrade( nbttagcompound.getShort( "leftUpgrade" ) ); } } if( nbttagcompound.hasKey( "rightUpgrade" ) ) { if( nbttagcompound.getTagId( "rightUpgrade" ) == Constants.NBT.TAG_STRING ) { rightUpgrade = ComputerCraft.getTurtleUpgrade( nbttagcompound.getString( "rightUpgrade" ) ); } else { rightUpgrade = ComputerCraft.getTurtleUpgrade( nbttagcompound.getShort( "rightUpgrade" ) ); } } } setUpgrade( TurtleSide.Left, leftUpgrade ); setUpgrade( TurtleSide.Right, rightUpgrade ); // NBT m_upgradeNBTData.clear(); if( nbttagcompound.hasKey( "leftUpgradeNBT" ) ) { m_upgradeNBTData.put( TurtleSide.Left, (NBTTagCompound) nbttagcompound.getCompoundTag( "leftUpgradeNBT" ).copy() ); } if( nbttagcompound.hasKey( "rightUpgradeNBT" ) ) { m_upgradeNBTData.put( TurtleSide.Right, (NBTTagCompound) nbttagcompound.getCompoundTag( "rightUpgradeNBT" ).copy() ); } } public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound ) { // Write state nbttagcompound.setInteger( "dir", m_direction.getIndex() ); nbttagcompound.setInteger( "selectedSlot", m_selectedSlot ); nbttagcompound.setInteger( "fuelLevel", m_fuelLevel ); // Write upgrades String leftUpgradeID = getUpgradeID( getUpgrade( TurtleSide.Left ) ); if( leftUpgradeID != null ) { nbttagcompound.setString( "leftUpgrade", leftUpgradeID ); } String rightUpgradeID = getUpgradeID( getUpgrade( TurtleSide.Right ) ); if( rightUpgradeID != null ) { nbttagcompound.setString( "rightUpgrade", rightUpgradeID ); } // Write colour if( m_colour != null ) { nbttagcompound.setInteger( "colourIndex", m_colour.ordinal() ); } // Write overlay if( m_overlay != null ) { nbttagcompound.setString( "overlay_mod", m_overlay.getResourceDomain() ); nbttagcompound.setString( "overlay_path", m_overlay.getResourcePath() ); } // Write NBT if( m_upgradeNBTData.containsKey( TurtleSide.Left ) ) { nbttagcompound.setTag( "leftUpgradeNBT", getUpgradeNBTData( TurtleSide.Left ).copy() ); } if( m_upgradeNBTData.containsKey( TurtleSide.Right ) ) { nbttagcompound.setTag( "rightUpgradeNBT", getUpgradeNBTData( TurtleSide.Right ).copy() ); } return nbttagcompound; } private String getUpgradeID( ITurtleUpgrade upgrade ) { if( upgrade != null ) { return upgrade.getUpgradeID().toString(); } return null; } public void writeDescription( NBTTagCompound nbttagcompound ) { // Upgrades String leftUpgradeID = getUpgradeID( getUpgrade( TurtleSide.Left ) ); if( leftUpgradeID != null ) { nbttagcompound.setString( "leftUpgrade", leftUpgradeID ); } String rightUpgradeID = getUpgradeID( getUpgrade( TurtleSide.Right ) ); if( rightUpgradeID != null ) { nbttagcompound.setString( "rightUpgrade", rightUpgradeID ); } // NBT if( m_upgradeNBTData.containsKey( TurtleSide.Left ) ) { nbttagcompound.setTag( "leftUpgradeNBT", getUpgradeNBTData( TurtleSide.Left ).copy() ); } if( m_upgradeNBTData.containsKey( TurtleSide.Right ) ) { nbttagcompound.setTag( "rightUpgradeNBT", getUpgradeNBTData( TurtleSide.Right ).copy() ); } // Colour if( m_colour != null ) { nbttagcompound.setInteger( "colourIndex", m_colour.ordinal() ); } // Overlay if( m_overlay != null ) { nbttagcompound.setString( "overlay_mod", m_overlay.getResourceDomain() ); nbttagcompound.setString( "overlay_path", m_overlay.getResourcePath() ); } // Animation if( m_instanceID < 0 ) { m_instanceID = assignInstanceID(); } nbttagcompound.setInteger( "brainInstanceID", m_instanceID ); nbttagcompound.setInteger( "animation", m_animation.ordinal() ); nbttagcompound.setInteger( "direction", m_direction.getIndex() ); nbttagcompound.setInteger( "fuelLevel", m_fuelLevel ); } public void readDescription( NBTTagCompound nbttagcompound ) { // Upgrades if( nbttagcompound.hasKey( "leftUpgrade" ) ) { setUpgrade( TurtleSide.Left, ComputerCraft.getTurtleUpgrade( nbttagcompound.getString( "leftUpgrade" ) ) ); } else { setUpgrade( TurtleSide.Left, null ); } if( nbttagcompound.hasKey( "rightUpgrade" ) ) { setUpgrade( TurtleSide.Right, ComputerCraft.getTurtleUpgrade( nbttagcompound.getString( "rightUpgrade" ) ) ); } else { setUpgrade( TurtleSide.Right, null ); } // NBT m_upgradeNBTData.clear(); if( nbttagcompound.hasKey( "leftUpgradeNBT" ) ) { m_upgradeNBTData.put( TurtleSide.Left, (NBTTagCompound) nbttagcompound.getCompoundTag( "leftUpgradeNBT" ).copy() ); } if( nbttagcompound.hasKey( "rightUpgradeNBT" ) ) { m_upgradeNBTData.put( TurtleSide.Right, (NBTTagCompound)nbttagcompound.getCompoundTag( "rightUpgradeNBT" ).copy() ); } // Colour if( nbttagcompound.hasKey( "colourIndex" ) ) { m_colour = Colour.values()[ nbttagcompound.getInteger( "colourIndex" ) ]; } else { m_colour = null; } // Overlay if( nbttagcompound.hasKey( "overlay_mod" ) && nbttagcompound.hasKey( "overlay_path" ) ) { String overlay_mod = nbttagcompound.getString( "overlay_mod" ); String overlay_path = nbttagcompound.getString( "overlay_path" ); m_overlay = new ResourceLocation( overlay_mod, overlay_path ); } else { m_overlay = null; } // Animation m_instanceID = nbttagcompound.getInteger( "brainInstanceID" ); setClientBrain( m_instanceID, this ); TurtleAnimation anim = TurtleAnimation.values()[ nbttagcompound.getInteger( "animation" ) ]; if( anim != m_animation && anim != TurtleAnimation.Wait && anim != TurtleAnimation.ShortWait && anim != TurtleAnimation.None ) { m_animation = TurtleAnimation.values()[ nbttagcompound.getInteger( "animation" ) ]; m_animationProgress = 0; m_lastAnimationProgress = 0; } m_direction = EnumFacing.getFront( nbttagcompound.getInteger( "direction" ) ); m_fuelLevel = nbttagcompound.getInteger( "fuelLevel" ); } @Nonnull @Override public World getWorld() { return m_owner.getWorld(); } @Nonnull @Override public BlockPos getPosition() { return m_owner.getPos(); } @Override public boolean teleportTo( @Nonnull World world, @Nonnull BlockPos pos ) { if( world.isRemote || getWorld().isRemote ) { throw new UnsupportedOperationException(); } // Cache info about the old turtle (so we don't access this after we delete ourselves) World oldWorld = getWorld(); TileTurtle oldOwner = m_owner; BlockPos oldPos = m_owner.getPos(); Block oldBlock = m_owner.getBlock(); if( oldWorld == world && oldPos.equals( pos ) ) { // Teleporting to the current position is a no-op return true; } if ( !world.isBlockLoaded( pos ) ) { return false; } oldOwner.notifyMoveStart(); try { // Create a new turtle if( world.setBlockState( pos, oldBlock.getDefaultState(), 0 ) ) { Block block = world.getBlockState( pos ).getBlock(); if( block == oldBlock ) { TileEntity newTile = world.getTileEntity( pos ); if( newTile != null && newTile instanceof TileTurtle ) { // Copy the old turtle state into the new turtle TileTurtle newTurtle = (TileTurtle)newTile; newTurtle.setWorldObj( world ); newTurtle.setPos( pos ); newTurtle.transferStateFrom( oldOwner ); newTurtle.createServerComputer().setWorld( world ); newTurtle.createServerComputer().setPosition( pos ); // Remove the old turtle oldWorld.setBlockToAir( oldPos ); // Make sure everybody knows about it newTurtle.updateBlock(); newTurtle.updateInput(); newTurtle.updateOutput(); return true; } } // Something went wrong, remove the newly created turtle world.setBlockToAir( pos ); } } finally { // whatever happens, unblock old turtle in case it's still in world oldOwner.notifyMoveEnd(); } return false; } @Nonnull @Override public Vec3d getVisualPosition( float f ) { Vec3d offset = getRenderOffset( f ); BlockPos pos = m_owner.getPos(); return new Vec3d( pos.getX() + 0.5 + offset.xCoord, pos.getY() + 0.5 + offset.yCoord, pos.getZ() + 0.5 + offset.zCoord ); } @Override public float getVisualYaw( float f ) { float yaw = DirectionUtil.toYawAngle( getDirection() ); switch( m_animation ) { case TurnLeft: { yaw += 90.0f * (1.0f - getAnimationFraction( f )); if( yaw >= 360.0f ) { yaw -= 360.0f; } break; } case TurnRight: { yaw += -90.0f * (1.0f - getAnimationFraction( f )); if( yaw < 0.0f ) { yaw += 360.0f; } break; } } return yaw; } @Nonnull @Override public EnumFacing getDirection() { return m_direction; } @Override public void setDirection( @Nonnull EnumFacing dir ) { if( dir.getAxis() == EnumFacing.Axis.Y ) { dir = EnumFacing.NORTH; } m_direction = dir; m_owner.updateOutput(); m_owner.updateInput(); m_owner.onTileEntityChange(); } @Override public int getSelectedSlot() { return m_selectedSlot; } @Override public void setSelectedSlot( int slot ) { if( getWorld().isRemote ) { throw new UnsupportedOperationException(); } if( slot >= 0 && slot < m_owner.getSizeInventory() ) { m_selectedSlot = slot; m_owner.onTileEntityChange(); } } @Nonnull @Override public IInventory getInventory() { return m_owner; } @Override public boolean isFuelNeeded() { return ComputerCraft.turtlesNeedFuel; } @Override public int getFuelLevel() { return Math.min( m_fuelLevel, getFuelLimit() ); } @Override public void setFuelLevel( int level ) { m_fuelLevel = Math.min( level, getFuelLimit() ); m_owner.onTileEntityChange(); } @Override public int getFuelLimit() { if( m_owner.getFamily() == ComputerFamily.Advanced ) { return ComputerCraft.advancedTurtleFuelLimit; } else { return ComputerCraft.turtleFuelLimit; } } @Override public boolean consumeFuel( int fuel ) { if( getWorld().isRemote ) { throw new UnsupportedOperationException(); } if( !isFuelNeeded() ) { return true; } int consumption = Math.max( fuel, 0 ); if( getFuelLevel() >= consumption ) { setFuelLevel( getFuelLevel() - consumption ); return true; } return false; } @Override public void addFuel( int fuel ) { if( getWorld().isRemote ) { throw new UnsupportedOperationException(); } int addition = Math.max( fuel, 0 ); setFuelLevel( getFuelLevel() + addition ); } private int issueCommand( ITurtleCommand command ) { m_commandQueue.offer( new TurtleCommandQueueEntry( ++m_commandsIssued, command ) ); return m_commandsIssued; } @Nonnull @Override public Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException { if( getWorld().isRemote ) { throw new UnsupportedOperationException(); } // Issue command int commandID = issueCommand( command ); // Wait for response while( true ) { Object[] response = context.pullEvent( "turtle_response" ); if( response.length >= 3 && response[ 1 ] instanceof Number && response[ 2 ] instanceof Boolean ) { if( ( (Number) response[ 1 ] ).intValue() == commandID ) { Object[] returnValues = new Object[ response.length - 2 ]; System.arraycopy( response, 2, returnValues, 0, returnValues.length ); return returnValues; } } } } @Override public void playAnimation( @Nonnull TurtleAnimation animation ) { if( getWorld().isRemote ) { throw new UnsupportedOperationException(); } m_animation = animation; if( m_animation == TurtleAnimation.ShortWait ) { m_animationProgress = ANIM_DURATION / 2; m_lastAnimationProgress = ANIM_DURATION / 2; } else { m_animationProgress = 0; m_lastAnimationProgress = 0; } m_owner.updateBlock(); } @Override public int getDyeColour() { return (m_colour != null) ? m_colour.ordinal() : -1; } public ResourceLocation getOverlay() { return m_overlay; } public void setOverlay( ResourceLocation overlay ) { if( !Objects.equal(m_overlay, overlay) ) { m_overlay = overlay; m_owner.updateBlock(); } } @Override public void setDyeColour( int dyeColour ) { Colour newColour = null; if( dyeColour >= 0 && dyeColour < 16 ) { newColour = Colour.values()[ dyeColour ]; } if( m_colour != newColour ) { m_colour = newColour; m_owner.updateBlock(); } } @Override public ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side ) { if( m_upgrades.containsKey( side ) ) { return m_upgrades.get( side ); } return null; } @Override public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade ) { // Remove old upgrade if( m_upgrades.containsKey( side ) ) { if( m_upgrades.get( side ) == upgrade ) { return; } m_upgrades.remove( side ); } else { if( upgrade == null ) { return; } } if( m_upgradeNBTData.containsKey( side ) ) { m_upgradeNBTData.remove( side ); } // Set new upgrade if( upgrade != null ) { m_upgrades.put( side, upgrade ); } // Notify clients and create peripherals if( m_owner.getWorld() != null ) { updatePeripherals( m_owner.createServerComputer() ); m_owner.updateBlock(); } } @Override public IPeripheral getPeripheral( @Nonnull TurtleSide side ) { if( m_peripherals.containsKey( side ) ) { return m_peripherals.get( side ); } return null; } @Nonnull @Override public NBTTagCompound getUpgradeNBTData( TurtleSide side ) { if( !m_upgradeNBTData.containsKey( side ) ) { m_upgradeNBTData.put( side, new NBTTagCompound() ); } return m_upgradeNBTData.get( side ); } @Override public void updateUpgradeNBTData( @Nonnull TurtleSide side ) { m_owner.updateBlock(); } public boolean saveBlockChange( BlockPos coordinates, IBlockState previousState ) { // Overriden by CCEdu return false; } public Vec3d getRenderOffset( float f ) { switch( m_animation ) { case MoveForward: case MoveBack: case MoveUp: case MoveDown: { // Get direction EnumFacing dir; switch( m_animation ) { case MoveForward: default: { dir = getDirection(); break; } case MoveBack: { dir = getDirection().getOpposite(); break; } case MoveUp: { dir = EnumFacing.UP; break; } case MoveDown: { dir = EnumFacing.DOWN; break; } } double distance = -1.0 + (double)getAnimationFraction( f ); return new Vec3d( distance * (double)dir.getFrontOffsetX(), distance * (double)dir.getFrontOffsetY(), distance * (double)dir.getFrontOffsetZ() ); } default: { return new Vec3d( 0.0, 0.0, 0.0 ); } } } public float getToolRenderAngle( TurtleSide side, float f ) { if( (side == TurtleSide.Left && m_animation == TurtleAnimation.SwingLeftTool) || (side == TurtleSide.Right && m_animation == TurtleAnimation.SwingRightTool) ) { return 45.0f * (float)Math.sin( (double) getAnimationFraction( f ) * Math.PI ); } return 0.0f; } private int toDirection( TurtleSide side ) { switch( side ) { case Left: { return 5; } case Right: default: { return 4; } } } public void updatePeripherals( ServerComputer serverComputer ) { if( serverComputer == null ) { // Nothing to do return; } // Update peripherals for( TurtleSide side : TurtleSide.values() ) { ITurtleUpgrade upgrade = getUpgrade( side ); IPeripheral peripheral = null; if( upgrade != null && upgrade.getType().isPeripheral() ) { peripheral = upgrade.createPeripheral( this, side ); } int dir = toDirection( side ); if( peripheral != null ) { if( !m_peripherals.containsKey( side ) ) { serverComputer.setPeripheral( dir, peripheral ); m_peripherals.put( side, peripheral ); } else if( !m_peripherals.get( side ).equals( peripheral ) ) { serverComputer.setPeripheral( dir, peripheral ); m_peripherals.remove( side ); m_peripherals.put( side, peripheral ); } } else if( m_peripherals.containsKey( side ) ) { serverComputer.setPeripheral( dir, null ); m_peripherals.remove( side ); } } } private void updateCommands() { if( m_animation == TurtleAnimation.None ) { // Pull a new command TurtleCommandQueueEntry nextCommand = null; if( m_commandQueue.peek() != null ) { nextCommand = m_commandQueue.remove(); } if( nextCommand != null ) { // Execute the command TurtleCommandResult result = nextCommand.command.execute( this ); // Dispatch the callback int callbackID = nextCommand.callbackID; if( callbackID >= 0 ) { if( result != null && result.isSuccess() ) { IComputer computer = m_owner.getComputer(); if( computer != null ) { Object[] results = result.getResults(); if( results != null ) { Object[] arguments = new Object[ results.length + 2 ]; arguments[0] = callbackID; arguments[1] = true; System.arraycopy( results, 0, arguments, 2, results.length ); computer.queueEvent( "turtle_response", arguments ); } else { computer.queueEvent( "turtle_response", new Object[] { callbackID, true } ); } } } else { IComputer computer = m_owner.getComputer(); if( computer != null ) { computer.queueEvent( "turtle_response", new Object[] { callbackID, false, ( result != null ) ? result.getErrorMessage() : null } ); } } } } } } private void updateAnimation() { if( m_animation != TurtleAnimation.None ) { World world = this.getWorld(); if( ComputerCraft.turtlesCanPush ) { // Advance entity pushing if( m_animation == TurtleAnimation.MoveForward || m_animation == TurtleAnimation.MoveBack || m_animation == TurtleAnimation.MoveUp || m_animation == TurtleAnimation.MoveDown ) { BlockPos pos = getPosition(); EnumFacing moveDir; switch( m_animation ) { case MoveForward: default: { moveDir = m_direction; break; } case MoveBack: { moveDir = m_direction.getOpposite(); break; } case MoveUp: { moveDir = EnumFacing.UP; break; } case MoveDown: { moveDir = EnumFacing.DOWN; break; } } double minX = pos.getX(); double minY = pos.getY(); double minZ = pos.getZ(); double maxX = minX + 1.0; double maxY = minY + 1.0; double maxZ = minZ + 1.0; float pushFrac = 1.0f - ((float)(m_animationProgress + 1) / (float)ANIM_DURATION); float push = Math.max( pushFrac + 0.0125f, 0.0f ); if (moveDir.getFrontOffsetX() < 0) { minX += (double)((float)moveDir.getFrontOffsetX() * push); } else { maxX -= (double)((float)moveDir.getFrontOffsetX() * push); } if (moveDir.getFrontOffsetY() < 0) { minY += (double)((float)moveDir.getFrontOffsetY() * push); } else { maxY -= (double)((float)moveDir.getFrontOffsetY() * push); } if (moveDir.getFrontOffsetZ() < 0) { minZ += (double)((float)moveDir.getFrontOffsetZ() * push); } else { maxZ -= (double)((float)moveDir.getFrontOffsetZ() * push); } AxisAlignedBB aabb = new AxisAlignedBB( minX, minY, minZ, maxX, maxY, maxZ ); List list = world.getEntitiesWithinAABBExcludingEntity( null, aabb ); if( !list.isEmpty() ) { double pushStep = 1.0f / (float) ANIM_DURATION; double pushStepX = (double) moveDir.getFrontOffsetX() * pushStep; double pushStepY = (double) moveDir.getFrontOffsetY() * pushStep; double pushStepZ = (double) moveDir.getFrontOffsetZ() * pushStep; for (Entity entity : list) { entity.moveEntity( pushStepX, pushStepY, pushStepZ ); } } } } // Advance valentines day easter egg if( world.isRemote && m_animation == TurtleAnimation.MoveForward && m_animationProgress == 4 ) { // Spawn love pfx if valentines day Holiday currentHoliday = HolidayUtil.getCurrentHoliday(); if( currentHoliday == Holiday.Valentines ) { Vec3d position = getVisualPosition( 1.0f ); if( position != null ) { double x = position.xCoord + world.rand.nextGaussian() * 0.1; double y = position.yCoord + 0.5 + world.rand.nextGaussian() * 0.1; double z = position.zCoord + world.rand.nextGaussian() * 0.1; world.spawnParticle( EnumParticleTypes.HEART, x, y, z, world.rand.nextGaussian() * 0.02, world.rand.nextGaussian() * 0.02, world.rand.nextGaussian() * 0.02 ); } } } // Wait for anim completion m_lastAnimationProgress = m_animationProgress; if( ++m_animationProgress >= ANIM_DURATION ) { m_animation = TurtleAnimation.None; m_animationProgress = 0; m_lastAnimationProgress = 0; } } } private float getAnimationFraction( float f ) { float next = (float)m_animationProgress / ANIM_DURATION; float previous = (float)m_lastAnimationProgress / ANIM_DURATION; return previous + (next - previous) * f; } }