/** * 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.*; import net.minecraft.world.World; import net.minecraftforge.common.util.Constants; 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 void 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", (NBTTagCompound) getUpgradeNBTData( TurtleSide.Left ).copy() ); } if( m_upgradeNBTData.containsKey( TurtleSide.Right ) ) { nbttagcompound.setTag( "rightUpgradeNBT", (NBTTagCompound) getUpgradeNBTData( TurtleSide.Right ).copy() ); } } 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", (NBTTagCompound) getUpgradeNBTData( TurtleSide.Left ).copy() ); } if( m_upgradeNBTData.containsKey( TurtleSide.Right ) ) { nbttagcompound.setTag( "rightUpgradeNBT", (NBTTagCompound) 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" ); } @Override public World getWorld() { return m_owner.getWorld(); } @Override public BlockPos getPosition() { return m_owner.getPos(); } @Override public boolean teleportTo( World world, 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(); 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; } // Create a new turtle if( world.isBlockLoaded( pos ) && world.setBlockState( pos, oldBlock.getDefaultState(), 3 ) ) { 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( m_owner ); newTurtle.createServerComputer().setWorld( world ); newTurtle.createServerComputer().setPosition( pos ); // Remove the old turtle oldWorld.setBlockToAir( oldPos ); // Make sure everybody knows about it newTurtle.updateInput(); newTurtle.updateOutput(); return true; } } // Something went wrong, remove the newly created turtle world.setBlockToAir( pos ); } return false; } @Override public Vec3 getVisualPosition( float f ) { Vec3 offset = getRenderOffset( f ); BlockPos pos = m_owner.getPos(); return new Vec3( 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 forward = DirectionUtil.toYawAngle( getDirection() ); float yaw = forward; 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; } @Override public EnumFacing getDirection() { return m_direction; } @Override public void setDirection( 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(); } } @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; } @Override public Object[] executeCommand( ILuaContext context, 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 ]; for( int i = 0; i < returnValues.length; ++i ) { returnValues[ i ] = response[ i + 2 ]; } return returnValues; } } } } @Override public void playAnimation( 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( TurtleSide side ) { if( m_upgrades.containsKey( side ) ) { return m_upgrades.get( side ); } return null; } @Override public void setUpgrade( 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( TurtleSide side ) { if( m_peripherals.containsKey( side ) ) { return m_peripherals.get( side ); } return null; } @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( TurtleSide side ) { m_owner.updateBlock(); } public boolean saveBlockChange( BlockPos coordinates, IBlockState previousState ) { // Overriden by CCEdu return false; } public Vec3 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 Vec3( distance * (double)dir.getFrontOffsetX(), distance * (double)dir.getFrontOffsetY(), distance * (double)dir.getFrontOffsetZ() ); } default: { return new Vec3( 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() == TurtleUpgradeType.Peripheral ) { peripheral = upgrade.createPeripheral( this, side ); } int dir = toDirection( side ); if( peripheral != null ) { if( !m_peripherals.containsKey( side ) ) { serverComputer.setPeripheral( dir, peripheral ); serverComputer.setRedstoneInput( dir, 0 ); serverComputer.setBundledRedstoneInput( dir, 0 ); m_peripherals.put( side, peripheral ); } else if( !m_peripherals.get( side ).equals( peripheral ) ) { serverComputer.setPeripheral( dir, peripheral ); serverComputer.setRedstoneInput( dir, 0 ); serverComputer.setBundledRedstoneInput( dir, 0 ); 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; for( int i=0; i= 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; } }