970 lines
30 KiB
Java
970 lines
30 KiB
Java
/*
|
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
|
* Send enquiries to dratcliffe@gmail.com
|
|
*/
|
|
package dan200.computercraft.shared.turtle.core;
|
|
|
|
import com.google.common.base.Objects;
|
|
import com.mojang.authlib.GameProfile;
|
|
import dan200.computercraft.ComputerCraft;
|
|
import dan200.computercraft.api.lua.ILuaCallback;
|
|
import dan200.computercraft.api.lua.MethodResult;
|
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
|
import dan200.computercraft.api.turtle.*;
|
|
import dan200.computercraft.core.computer.ComputerSide;
|
|
import dan200.computercraft.shared.TurtleUpgrades;
|
|
import dan200.computercraft.shared.computer.blocks.ComputerProxy;
|
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
|
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.Holiday;
|
|
import dan200.computercraft.shared.util.HolidayUtil;
|
|
import dan200.computercraft.shared.util.InventoryDelegate;
|
|
import net.minecraft.block.Block;
|
|
import net.minecraft.block.BlockState;
|
|
import net.minecraft.entity.Entity;
|
|
import net.minecraft.entity.MoverType;
|
|
import net.minecraft.fluid.IFluidState;
|
|
import net.minecraft.inventory.IInventory;
|
|
import net.minecraft.item.DyeColor;
|
|
import net.minecraft.nbt.CompoundNBT;
|
|
import net.minecraft.particles.ParticleTypes;
|
|
import net.minecraft.tags.FluidTags;
|
|
import net.minecraft.tileentity.TileEntity;
|
|
import net.minecraft.util.Direction;
|
|
import net.minecraft.util.EntityPredicates;
|
|
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 net.minecraftforge.items.IItemHandlerModifiable;
|
|
import net.minecraftforge.items.wrapper.InvWrapper;
|
|
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import java.util.*;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR;
|
|
import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED;
|
|
|
|
public class TurtleBrain implements ITurtleAccess
|
|
{
|
|
public static final String NBT_RIGHT_UPGRADE = "RightUpgrade";
|
|
public static final String NBT_RIGHT_UPGRADE_DATA = "RightUpgradeNbt";
|
|
public static final String NBT_LEFT_UPGRADE = "LeftUpgrade";
|
|
public static final String NBT_LEFT_UPGRADE_DATA = "LeftUpgradeNbt";
|
|
public static final String NBT_FUEL = "Fuel";
|
|
public static final String NBT_OVERLAY = "Overlay";
|
|
|
|
private static final String NBT_SLOT = "Slot";
|
|
|
|
private static final int ANIM_DURATION = 8;
|
|
|
|
private TileTurtle owner;
|
|
private ComputerProxy proxy;
|
|
private GameProfile owningPlayer;
|
|
|
|
private final IInventory inventory = (InventoryDelegate) () -> owner;
|
|
private final IItemHandlerModifiable inventoryWrapper = new InvWrapper( inventory );
|
|
|
|
private final Queue<TurtleCommandQueueEntry> commandQueue = new ArrayDeque<>();
|
|
private int commandsIssued = 0;
|
|
|
|
private final Map<TurtleSide, ITurtleUpgrade> upgrades = new EnumMap<>( TurtleSide.class );
|
|
private final Map<TurtleSide, IPeripheral> peripherals = new EnumMap<>( TurtleSide.class );
|
|
private final Map<TurtleSide, CompoundNBT> upgradeNBTData = new EnumMap<>( TurtleSide.class );
|
|
|
|
private int selectedSlot = 0;
|
|
private int fuelLevel = 0;
|
|
private int colourHex = -1;
|
|
private ResourceLocation overlay = null;
|
|
|
|
private TurtleAnimation animation = TurtleAnimation.NONE;
|
|
private int animationProgress = 0;
|
|
private int lastAnimationProgress = 0;
|
|
|
|
TurtlePlayer cachedPlayer;
|
|
|
|
public TurtleBrain( TileTurtle turtle )
|
|
{
|
|
owner = turtle;
|
|
}
|
|
|
|
public void setOwner( TileTurtle owner )
|
|
{
|
|
this.owner = owner;
|
|
}
|
|
|
|
public TileTurtle getOwner()
|
|
{
|
|
return owner;
|
|
}
|
|
|
|
public ComputerProxy getProxy()
|
|
{
|
|
if( proxy == null ) proxy = new ComputerProxy( () -> owner );
|
|
return proxy;
|
|
}
|
|
|
|
public ComputerFamily getFamily()
|
|
{
|
|
return owner.getFamily();
|
|
}
|
|
|
|
public void setupComputer( ServerComputer computer )
|
|
{
|
|
updatePeripherals( computer );
|
|
}
|
|
|
|
public void update()
|
|
{
|
|
World world = getWorld();
|
|
if( !world.isClientSide )
|
|
{
|
|
// Advance movement
|
|
updateCommands();
|
|
|
|
// The block may have been broken while the command was executing (for instance, if a block explodes
|
|
// when being mined). If so, abort.
|
|
if( owner.isRemoved() ) return;
|
|
}
|
|
|
|
// Advance animation
|
|
updateAnimation();
|
|
|
|
// Advance upgrades
|
|
if( !upgrades.isEmpty() )
|
|
{
|
|
for( Map.Entry<TurtleSide, ITurtleUpgrade> entry : upgrades.entrySet() )
|
|
{
|
|
entry.getValue().update( this, entry.getKey() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read common data for saving and client synchronisation.
|
|
*
|
|
* @param nbt The tag to read from
|
|
*/
|
|
private void readCommon( CompoundNBT nbt )
|
|
{
|
|
// Read fields
|
|
colourHex = nbt.contains( NBT_COLOUR ) ? nbt.getInt( NBT_COLOUR ) : -1;
|
|
fuelLevel = nbt.contains( NBT_FUEL ) ? nbt.getInt( NBT_FUEL ) : 0;
|
|
overlay = nbt.contains( NBT_OVERLAY ) ? new ResourceLocation( nbt.getString( NBT_OVERLAY ) ) : null;
|
|
|
|
// Read upgrades
|
|
setUpgradeDirect( TurtleSide.LEFT, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null );
|
|
setUpgradeDirect( TurtleSide.RIGHT, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null );
|
|
|
|
// NBT
|
|
upgradeNBTData.clear();
|
|
if( nbt.contains( NBT_LEFT_UPGRADE_DATA ) )
|
|
{
|
|
upgradeNBTData.put( TurtleSide.LEFT, nbt.getCompound( NBT_LEFT_UPGRADE_DATA ).copy() );
|
|
}
|
|
if( nbt.contains( NBT_RIGHT_UPGRADE_DATA ) )
|
|
{
|
|
upgradeNBTData.put( TurtleSide.RIGHT, nbt.getCompound( NBT_RIGHT_UPGRADE_DATA ).copy() );
|
|
}
|
|
}
|
|
|
|
private void writeCommon( CompoundNBT nbt )
|
|
{
|
|
nbt.putInt( NBT_FUEL, fuelLevel );
|
|
if( colourHex != -1 ) nbt.putInt( NBT_COLOUR, colourHex );
|
|
if( overlay != null ) nbt.putString( NBT_OVERLAY, overlay.toString() );
|
|
|
|
// Write upgrades
|
|
String leftUpgradeId = getUpgradeId( getUpgrade( TurtleSide.LEFT ) );
|
|
if( leftUpgradeId != null ) nbt.putString( NBT_LEFT_UPGRADE, leftUpgradeId );
|
|
String rightUpgradeId = getUpgradeId( getUpgrade( TurtleSide.RIGHT ) );
|
|
if( rightUpgradeId != null ) nbt.putString( NBT_RIGHT_UPGRADE, rightUpgradeId );
|
|
|
|
// Write upgrade NBT
|
|
if( upgradeNBTData.containsKey( TurtleSide.LEFT ) )
|
|
{
|
|
nbt.put( NBT_LEFT_UPGRADE_DATA, getUpgradeNBTData( TurtleSide.LEFT ).copy() );
|
|
}
|
|
if( upgradeNBTData.containsKey( TurtleSide.RIGHT ) )
|
|
{
|
|
nbt.put( NBT_RIGHT_UPGRADE_DATA, getUpgradeNBTData( TurtleSide.RIGHT ).copy() );
|
|
}
|
|
}
|
|
|
|
public void readFromNBT( CompoundNBT nbt )
|
|
{
|
|
readCommon( nbt );
|
|
|
|
// Read state
|
|
selectedSlot = nbt.getInt( NBT_SLOT );
|
|
|
|
// Read owner
|
|
if( nbt.contains( "Owner", Constants.NBT.TAG_COMPOUND ) )
|
|
{
|
|
CompoundNBT owner = nbt.getCompound( "Owner" );
|
|
owningPlayer = new GameProfile(
|
|
new UUID( owner.getLong( "UpperId" ), owner.getLong( "LowerId" ) ),
|
|
owner.getString( "Name" )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
owningPlayer = null;
|
|
}
|
|
}
|
|
|
|
public CompoundNBT writeToNBT( CompoundNBT nbt )
|
|
{
|
|
writeCommon( nbt );
|
|
|
|
// Write state
|
|
nbt.putInt( NBT_SLOT, selectedSlot );
|
|
|
|
// Write owner
|
|
if( owningPlayer != null )
|
|
{
|
|
CompoundNBT owner = new CompoundNBT();
|
|
nbt.put( "Owner", owner );
|
|
|
|
owner.putLong( "UpperId", owningPlayer.getId().getMostSignificantBits() );
|
|
owner.putLong( "LowerId", owningPlayer.getId().getLeastSignificantBits() );
|
|
owner.putString( "Name", owningPlayer.getName() );
|
|
}
|
|
|
|
return nbt;
|
|
}
|
|
|
|
private static String getUpgradeId( ITurtleUpgrade upgrade )
|
|
{
|
|
return upgrade != null ? upgrade.getUpgradeID().toString() : null;
|
|
}
|
|
|
|
public void readDescription( CompoundNBT nbt )
|
|
{
|
|
readCommon( nbt );
|
|
|
|
// Animation
|
|
TurtleAnimation anim = TurtleAnimation.values()[nbt.getInt( "Animation" )];
|
|
if( anim != animation &&
|
|
anim != TurtleAnimation.WAIT &&
|
|
anim != TurtleAnimation.SHORT_WAIT &&
|
|
anim != TurtleAnimation.NONE )
|
|
{
|
|
animation = anim;
|
|
animationProgress = 0;
|
|
lastAnimationProgress = 0;
|
|
}
|
|
}
|
|
|
|
public void writeDescription( CompoundNBT nbt )
|
|
{
|
|
writeCommon( nbt );
|
|
nbt.putInt( "Animation", animation.ordinal() );
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public World getWorld()
|
|
{
|
|
return owner.getLevel();
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public BlockPos getPosition()
|
|
{
|
|
return owner.getBlockPos();
|
|
}
|
|
|
|
@Override
|
|
public boolean teleportTo( @Nonnull World world, @Nonnull BlockPos pos )
|
|
{
|
|
if( world.isClientSide || getWorld().isClientSide )
|
|
{
|
|
throw new UnsupportedOperationException( "Cannot teleport on the client" );
|
|
}
|
|
|
|
// Cache info about the old turtle (so we don't access this after we delete ourselves)
|
|
World oldWorld = getWorld();
|
|
TileTurtle oldOwner = owner;
|
|
BlockPos oldPos = owner.getBlockPos();
|
|
BlockState oldBlock = owner.getBlockState();
|
|
|
|
if( oldWorld == world && oldPos.equals( pos ) )
|
|
{
|
|
// Teleporting to the current position is a no-op
|
|
return true;
|
|
}
|
|
|
|
// Ensure the chunk is loaded
|
|
if( !world.isAreaLoaded( pos, 0 ) ) return false;
|
|
|
|
// Ensure we're inside the world border
|
|
if( !world.getWorldBorder().isWithinBounds( pos ) ) return false;
|
|
|
|
IFluidState existingFluid = world.getBlockState( pos ).getFluidState();
|
|
BlockState newState = oldBlock
|
|
// We only mark this as waterlogged when travelling into a source block. This prevents us from spreading
|
|
// fluid by creating a new source when moving into a block, causing the next block to be almost full and
|
|
// then moving into that.
|
|
.setValue( WATERLOGGED, existingFluid.is( FluidTags.WATER ) && existingFluid.isSource() );
|
|
|
|
oldOwner.notifyMoveStart();
|
|
|
|
try
|
|
{
|
|
// Create a new turtle
|
|
if( world.setBlock( pos, newState, 0 ) )
|
|
{
|
|
Block block = world.getBlockState( pos ).getBlock();
|
|
if( block == oldBlock.getBlock() )
|
|
{
|
|
TileEntity newTile = world.getBlockEntity( pos );
|
|
if( newTile instanceof TileTurtle )
|
|
{
|
|
// Copy the old turtle state into the new turtle
|
|
TileTurtle newTurtle = (TileTurtle) newTile;
|
|
newTurtle.setLevelAndPosition( world, pos );
|
|
newTurtle.transferStateFrom( oldOwner );
|
|
newTurtle.createServerComputer().setWorld( world );
|
|
newTurtle.createServerComputer().setPosition( pos );
|
|
|
|
// Remove the old turtle
|
|
oldWorld.removeBlock( oldPos, false );
|
|
|
|
// Make sure everybody knows about it
|
|
newTurtle.updateBlock();
|
|
newTurtle.updateInput();
|
|
newTurtle.updateOutput();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Something went wrong, remove the newly created turtle
|
|
world.removeBlock( pos, false );
|
|
}
|
|
}
|
|
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 = owner.getBlockPos();
|
|
return new Vec3d(
|
|
pos.getX() + 0.5 + offset.x,
|
|
pos.getY() + 0.5 + offset.y,
|
|
pos.getZ() + 0.5 + offset.z
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public float getVisualYaw( float f )
|
|
{
|
|
float yaw = getDirection().toYRot();
|
|
switch( animation )
|
|
{
|
|
case TURN_LEFT:
|
|
{
|
|
yaw += 90.0f * (1.0f - getAnimationFraction( f ));
|
|
if( yaw >= 360.0f )
|
|
{
|
|
yaw -= 360.0f;
|
|
}
|
|
break;
|
|
}
|
|
case TURN_RIGHT:
|
|
{
|
|
yaw += -90.0f * (1.0f - getAnimationFraction( f ));
|
|
if( yaw < 0.0f )
|
|
{
|
|
yaw += 360.0f;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return yaw;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Direction getDirection()
|
|
{
|
|
return owner.getDirection();
|
|
}
|
|
|
|
@Override
|
|
public void setDirection( @Nonnull Direction dir )
|
|
{
|
|
owner.setDirection( dir );
|
|
}
|
|
|
|
@Override
|
|
public int getSelectedSlot()
|
|
{
|
|
return selectedSlot;
|
|
}
|
|
|
|
@Override
|
|
public void setSelectedSlot( int slot )
|
|
{
|
|
if( getWorld().isClientSide ) throw new UnsupportedOperationException( "Cannot set the slot on the client" );
|
|
|
|
if( slot >= 0 && slot < owner.getContainerSize() )
|
|
{
|
|
selectedSlot = slot;
|
|
owner.onTileEntityChange();
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public IInventory getInventory()
|
|
{
|
|
return inventory;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public IItemHandlerModifiable getItemHandler()
|
|
{
|
|
return inventoryWrapper;
|
|
}
|
|
|
|
@Override
|
|
public boolean isFuelNeeded()
|
|
{
|
|
return ComputerCraft.turtlesNeedFuel;
|
|
}
|
|
|
|
@Override
|
|
public int getFuelLevel()
|
|
{
|
|
return Math.min( fuelLevel, getFuelLimit() );
|
|
}
|
|
|
|
@Override
|
|
public void setFuelLevel( int level )
|
|
{
|
|
fuelLevel = Math.min( level, getFuelLimit() );
|
|
owner.onTileEntityChange();
|
|
}
|
|
|
|
@Override
|
|
public int getFuelLimit()
|
|
{
|
|
if( owner.getFamily() == ComputerFamily.ADVANCED )
|
|
{
|
|
return ComputerCraft.advancedTurtleFuelLimit;
|
|
}
|
|
else
|
|
{
|
|
return ComputerCraft.turtleFuelLimit;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean consumeFuel( int fuel )
|
|
{
|
|
if( getWorld().isClientSide ) throw new UnsupportedOperationException( "Cannot consume fuel on the client" );
|
|
|
|
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().isClientSide ) throw new UnsupportedOperationException( "Cannot add fuel on the client" );
|
|
|
|
int addition = Math.max( fuel, 0 );
|
|
setFuelLevel( getFuelLevel() + addition );
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public MethodResult executeCommand( @Nonnull ITurtleCommand command )
|
|
{
|
|
if( getWorld().isClientSide ) throw new UnsupportedOperationException( "Cannot run commands on the client" );
|
|
if( commandQueue.size() > 16 ) return MethodResult.of( false, "Too many ongoing turtle commands" );
|
|
|
|
commandQueue.offer( new TurtleCommandQueueEntry( ++commandsIssued, command ) );
|
|
int commandID = commandsIssued;
|
|
return new CommandCallback( commandID ).pull;
|
|
}
|
|
|
|
@Override
|
|
public void playAnimation( @Nonnull TurtleAnimation animation )
|
|
{
|
|
if( getWorld().isClientSide ) throw new UnsupportedOperationException( "Cannot play animations on the client" );
|
|
|
|
this.animation = animation;
|
|
if( this.animation == TurtleAnimation.SHORT_WAIT )
|
|
{
|
|
animationProgress = ANIM_DURATION / 2;
|
|
lastAnimationProgress = ANIM_DURATION / 2;
|
|
}
|
|
else
|
|
{
|
|
animationProgress = 0;
|
|
lastAnimationProgress = 0;
|
|
}
|
|
owner.updateBlock();
|
|
}
|
|
|
|
public ResourceLocation getOverlay()
|
|
{
|
|
return overlay;
|
|
}
|
|
|
|
public void setOverlay( ResourceLocation overlay )
|
|
{
|
|
if( !Objects.equal( this.overlay, overlay ) )
|
|
{
|
|
this.overlay = overlay;
|
|
owner.updateBlock();
|
|
}
|
|
}
|
|
|
|
public DyeColor getDyeColour()
|
|
{
|
|
if( colourHex == -1 ) return null;
|
|
Colour colour = Colour.fromHex( colourHex );
|
|
return colour == null ? null : DyeColor.byId( 15 - colour.ordinal() );
|
|
}
|
|
|
|
public void setDyeColour( DyeColor dyeColour )
|
|
{
|
|
int newColour = -1;
|
|
if( dyeColour != null )
|
|
{
|
|
newColour = Colour.values()[15 - dyeColour.getId()].getHex();
|
|
}
|
|
if( colourHex != newColour )
|
|
{
|
|
colourHex = newColour;
|
|
owner.updateBlock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setColour( int colour )
|
|
{
|
|
if( colour >= 0 && colour <= 0xFFFFFF )
|
|
{
|
|
if( colourHex != colour )
|
|
{
|
|
colourHex = colour;
|
|
owner.updateBlock();
|
|
}
|
|
}
|
|
else if( colourHex != -1 )
|
|
{
|
|
colourHex = -1;
|
|
owner.updateBlock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getColour()
|
|
{
|
|
return colourHex;
|
|
}
|
|
|
|
public void setOwningPlayer( GameProfile profile )
|
|
{
|
|
owningPlayer = profile;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public GameProfile getOwningPlayer()
|
|
{
|
|
return owningPlayer;
|
|
}
|
|
|
|
@Override
|
|
public ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side )
|
|
{
|
|
return upgrades.get( side );
|
|
}
|
|
|
|
@Override
|
|
public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
|
|
{
|
|
if( !setUpgradeDirect( side, upgrade ) ) return;
|
|
|
|
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
|
|
// either the block is newly placed (and so won't have changed) or is being updated with /data, which calls
|
|
// updateBlock for us.
|
|
if( owner.getLevel() != null )
|
|
{
|
|
owner.updateBlock();
|
|
owner.updateInput();
|
|
}
|
|
}
|
|
|
|
private boolean setUpgradeDirect( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
|
|
{
|
|
// Remove old upgrade
|
|
if( upgrades.containsKey( side ) )
|
|
{
|
|
if( upgrades.get( side ) == upgrade ) return false;
|
|
upgrades.remove( side );
|
|
}
|
|
else
|
|
{
|
|
if( upgrade == null ) return false;
|
|
}
|
|
|
|
upgradeNBTData.remove( side );
|
|
|
|
// Set new upgrade
|
|
if( upgrade != null ) upgrades.put( side, upgrade );
|
|
|
|
// Notify clients and create peripherals
|
|
if( owner.getLevel() != null )
|
|
{
|
|
updatePeripherals( owner.createServerComputer() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public IPeripheral getPeripheral( @Nonnull TurtleSide side )
|
|
{
|
|
return peripherals.get( side );
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public CompoundNBT getUpgradeNBTData( TurtleSide side )
|
|
{
|
|
CompoundNBT nbt = upgradeNBTData.get( side );
|
|
if( nbt == null ) upgradeNBTData.put( side, nbt = new CompoundNBT() );
|
|
return nbt;
|
|
}
|
|
|
|
@Override
|
|
public void updateUpgradeNBTData( @Nonnull TurtleSide side )
|
|
{
|
|
owner.updateBlock();
|
|
}
|
|
|
|
public Vec3d getRenderOffset( float f )
|
|
{
|
|
switch( animation )
|
|
{
|
|
case MOVE_FORWARD:
|
|
case MOVE_BACK:
|
|
case MOVE_UP:
|
|
case MOVE_DOWN:
|
|
{
|
|
// Get direction
|
|
Direction dir;
|
|
switch( animation )
|
|
{
|
|
case MOVE_FORWARD:
|
|
default:
|
|
dir = getDirection();
|
|
break;
|
|
case MOVE_BACK:
|
|
dir = getDirection().getOpposite();
|
|
break;
|
|
case MOVE_UP:
|
|
dir = Direction.UP;
|
|
break;
|
|
case MOVE_DOWN:
|
|
dir = Direction.DOWN;
|
|
break;
|
|
}
|
|
|
|
double distance = -1.0 + getAnimationFraction( f );
|
|
return new Vec3d(
|
|
distance * dir.getStepX(),
|
|
distance * dir.getStepY(),
|
|
distance * dir.getStepZ()
|
|
);
|
|
}
|
|
default:
|
|
{
|
|
return Vec3d.ZERO;
|
|
}
|
|
}
|
|
}
|
|
|
|
public float getToolRenderAngle( TurtleSide side, float f )
|
|
{
|
|
return (side == TurtleSide.LEFT && animation == TurtleAnimation.SWING_LEFT_TOOL) ||
|
|
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
|
|
? 45.0f * (float) Math.sin( getAnimationFraction( f ) * Math.PI )
|
|
: 0.0f;
|
|
}
|
|
|
|
private static ComputerSide toDirection( TurtleSide side )
|
|
{
|
|
switch( side )
|
|
{
|
|
case LEFT:
|
|
return ComputerSide.LEFT;
|
|
case RIGHT:
|
|
default:
|
|
return ComputerSide.RIGHT;
|
|
}
|
|
}
|
|
|
|
private void updatePeripherals( ServerComputer serverComputer )
|
|
{
|
|
if( serverComputer == null ) 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 );
|
|
}
|
|
|
|
IPeripheral existing = peripherals.get( side );
|
|
if( existing == peripheral || (existing != null && peripheral != null && existing.equals( peripheral )) )
|
|
{
|
|
// If the peripheral is the same, just use that.
|
|
peripheral = existing;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise update our map
|
|
peripherals.put( side, peripheral );
|
|
}
|
|
|
|
// Always update the computer: it may not be the same computer as before!
|
|
serverComputer.setPeripheral( toDirection( side ), peripheral );
|
|
}
|
|
}
|
|
|
|
private void updateCommands()
|
|
{
|
|
if( animation != TurtleAnimation.NONE || commandQueue.isEmpty() ) return;
|
|
|
|
// If we've got a computer, ensure that we're allowed to perform work.
|
|
ServerComputer computer = owner.getServerComputer();
|
|
if( computer != null && !computer.getComputer().getMainThreadMonitor().canWork() ) return;
|
|
|
|
// Pull a new command
|
|
TurtleCommandQueueEntry nextCommand = commandQueue.poll();
|
|
if( nextCommand == null ) return;
|
|
|
|
// Execute the command
|
|
long start = System.nanoTime();
|
|
TurtleCommandResult result = nextCommand.command.execute( this );
|
|
long end = System.nanoTime();
|
|
|
|
// Dispatch the callback
|
|
if( computer == null ) return;
|
|
computer.getComputer().getMainThreadMonitor().trackWork( end - start, TimeUnit.NANOSECONDS );
|
|
int callbackID = nextCommand.callbackID;
|
|
if( callbackID < 0 ) return;
|
|
|
|
if( result != null && result.isSuccess() )
|
|
{
|
|
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
|
|
{
|
|
computer.queueEvent( "turtle_response", new Object[] {
|
|
callbackID, false, result != null ? result.getErrorMessage() : null,
|
|
} );
|
|
}
|
|
}
|
|
|
|
private void updateAnimation()
|
|
{
|
|
if( animation != TurtleAnimation.NONE )
|
|
{
|
|
World world = getWorld();
|
|
|
|
if( ComputerCraft.turtlesCanPush )
|
|
{
|
|
// Advance entity pushing
|
|
if( animation == TurtleAnimation.MOVE_FORWARD ||
|
|
animation == TurtleAnimation.MOVE_BACK ||
|
|
animation == TurtleAnimation.MOVE_UP ||
|
|
animation == TurtleAnimation.MOVE_DOWN )
|
|
{
|
|
BlockPos pos = getPosition();
|
|
Direction moveDir;
|
|
switch( animation )
|
|
{
|
|
case MOVE_FORWARD:
|
|
default:
|
|
moveDir = getDirection();
|
|
break;
|
|
case MOVE_BACK:
|
|
moveDir = getDirection().getOpposite();
|
|
break;
|
|
case MOVE_UP:
|
|
moveDir = Direction.UP;
|
|
break;
|
|
case MOVE_DOWN:
|
|
moveDir = Direction.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) (animationProgress + 1) / ANIM_DURATION;
|
|
float push = Math.max( pushFrac + 0.0125f, 0.0f );
|
|
if( moveDir.getStepX() < 0 )
|
|
{
|
|
minX += moveDir.getStepX() * push;
|
|
}
|
|
else
|
|
{
|
|
maxX -= moveDir.getStepX() * push;
|
|
}
|
|
|
|
if( moveDir.getStepY() < 0 )
|
|
{
|
|
minY += moveDir.getStepY() * push;
|
|
}
|
|
else
|
|
{
|
|
maxY -= moveDir.getStepY() * push;
|
|
}
|
|
|
|
if( moveDir.getStepZ() < 0 )
|
|
{
|
|
minZ += moveDir.getStepZ() * push;
|
|
}
|
|
else
|
|
{
|
|
maxZ -= moveDir.getStepZ() * push;
|
|
}
|
|
|
|
AxisAlignedBB aabb = new AxisAlignedBB( minX, minY, minZ, maxX, maxY, maxZ );
|
|
List<Entity> list = world.getEntitiesOfClass( Entity.class, aabb, EntityPredicates.NO_SPECTATORS );
|
|
if( !list.isEmpty() )
|
|
{
|
|
double pushStep = 1.0f / ANIM_DURATION;
|
|
double pushStepX = moveDir.getStepX() * pushStep;
|
|
double pushStepY = moveDir.getStepY() * pushStep;
|
|
double pushStepZ = moveDir.getStepZ() * pushStep;
|
|
for( Entity entity : list )
|
|
{
|
|
entity.move( MoverType.PISTON, new Vec3d( pushStepX, pushStepY, pushStepZ ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Advance valentines day easter egg
|
|
if( world.isClientSide && animation == TurtleAnimation.MOVE_FORWARD && 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.x + world.random.nextGaussian() * 0.1;
|
|
double y = position.y + 0.5 + world.random.nextGaussian() * 0.1;
|
|
double z = position.z + world.random.nextGaussian() * 0.1;
|
|
world.addParticle(
|
|
ParticleTypes.HEART, x, y, z,
|
|
world.random.nextGaussian() * 0.02,
|
|
world.random.nextGaussian() * 0.02,
|
|
world.random.nextGaussian() * 0.02
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for anim completion
|
|
lastAnimationProgress = animationProgress;
|
|
if( ++animationProgress >= ANIM_DURATION )
|
|
{
|
|
animation = TurtleAnimation.NONE;
|
|
animationProgress = 0;
|
|
lastAnimationProgress = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private float getAnimationFraction( float f )
|
|
{
|
|
float next = (float) animationProgress / ANIM_DURATION;
|
|
float previous = (float) lastAnimationProgress / ANIM_DURATION;
|
|
return previous + (next - previous) * f;
|
|
}
|
|
|
|
private static final class CommandCallback implements ILuaCallback
|
|
{
|
|
final MethodResult pull = MethodResult.pullEvent( "turtle_response", this );
|
|
private final int command;
|
|
|
|
CommandCallback( int command )
|
|
{
|
|
this.command = command;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public MethodResult resume( Object[] response )
|
|
{
|
|
if( response.length < 3 || !(response[1] instanceof Number) || !(response[2] instanceof Boolean) )
|
|
{
|
|
return pull;
|
|
}
|
|
|
|
if( ((Number) response[1]).intValue() != command ) return pull;
|
|
|
|
return MethodResult.of( Arrays.copyOfRange( response, 2, response.length ) );
|
|
}
|
|
}
|
|
}
|