CC-Tweaked/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java

970 lines
30 KiB
Java
Raw Normal View History

/*
* 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;
Replace getMethodNames/callMethod with annotations (#447) When creating a peripheral or custom Lua object, one must implement two methods: - getMethodNames(): String[] - Returns the name of the methods - callMethod(int, ...): Object[] - Invokes the method using an index in the above array. This has a couple of problems: - It's somewhat unwieldy to use - you need to keep track of array indices, which leads to ugly code. - Functions which yield (for instance, those which run on the main thread) are blocking. This means we need to spawn new threads for each CC-side yield. We replace this system with a few changes: - @LuaFunction annotation: One may annotate a public instance method with this annotation. This then exposes a peripheral/lua object method. Furthermore, this method can accept and return a variety of types, which often makes functions cleaner (e.g. can return an int rather than an Object[], and specify and int argument rather than Object[]). - MethodResult: Instead of returning an Object[] and having blocking yields, functions return a MethodResult. This either contains an immediate return, or an instruction to yield with some continuation to resume with. MethodResult is then interpreted by the Lua runtime (i.e. Cobalt), rather than our weird bodgey hacks before. This means we no longer spawn new threads when yielding within CC. - Methods accept IArguments instead of a raw Object array. This has a few benefits: - Consistent argument handling - people no longer need to use ArgumentHelper (as it doesn't exist!), or even be aware of its existence - you're rather forced into using it. - More efficient code in some cases. We provide a Cobalt-specific implementation of IArguments, which avoids the boxing/unboxing when handling numbers and binary strings.
2020-05-15 12:21:16 +00:00
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;
2019-10-04 18:44:34 +00:00
import dan200.computercraft.shared.util.InventoryDelegate;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
2017-05-11 00:08:26 +00:00
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;
2017-05-06 23:07:42 +00:00
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() );
}
}
}
/**
2019-11-25 09:15:20 +00:00
* 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() );
}
2017-05-06 23:07:42 +00:00
@Nonnull
@Override
public World getWorld()
{
return owner.getLevel();
}
2017-05-06 23:07:42 +00:00
@Nonnull
@Override
public BlockPos getPosition()
{
return owner.getBlockPos();
}
@Override
2017-05-06 23:07:42 +00:00
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
2019-06-09 08:07:31 +00:00
if( !world.isAreaLoaded( pos, 0 ) ) return false;
// Ensure we're inside the world border
if( !world.getWorldBorder().isWithinBounds( pos ) ) return false;
2017-05-03 14:03:47 +00:00
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() );
2017-05-03 14:03:47 +00:00
oldOwner.notifyMoveStart();
try
{
// Create a new turtle
if( world.setBlock( pos, newState, 0 ) )
{
2017-05-03 14:03:47 +00:00
Block block = world.getBlockState( pos ).getBlock();
if( block == oldBlock.getBlock() )
{
TileEntity newTile = world.getBlockEntity( pos );
if( newTile instanceof TileTurtle )
2017-05-03 14:03:47 +00:00
{
// Copy the old turtle state into the new turtle
TileTurtle newTurtle = (TileTurtle) newTile;
newTurtle.setLevelAndPosition( world, pos );
2017-05-03 14:03:47 +00:00
newTurtle.transferStateFrom( oldOwner );
newTurtle.createServerComputer().setWorld( world );
newTurtle.createServerComputer().setPosition( pos );
// Remove the old turtle
oldWorld.removeBlock( oldPos, false );
2017-05-03 14:03:47 +00:00
// Make sure everybody knows about it
newTurtle.updateBlock();
newTurtle.updateInput();
newTurtle.updateOutput();
return true;
}
}
2017-05-03 14:03:47 +00:00
// Something went wrong, remove the newly created turtle
world.removeBlock( pos, false );
2017-05-03 14:03:47 +00:00
}
}
finally
{
// whatever happens, unblock old turtle in case it's still in world
oldOwner.notifyMoveEnd();
}
return false;
}
2017-05-06 23:07:42 +00:00
@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;
}
2017-05-06 23:07:42 +00:00
@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();
}
}
2017-05-06 23:07:42 +00:00
@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 );
}
2017-05-06 23:07:42 +00:00
@Nonnull
@Override
Replace getMethodNames/callMethod with annotations (#447) When creating a peripheral or custom Lua object, one must implement two methods: - getMethodNames(): String[] - Returns the name of the methods - callMethod(int, ...): Object[] - Invokes the method using an index in the above array. This has a couple of problems: - It's somewhat unwieldy to use - you need to keep track of array indices, which leads to ugly code. - Functions which yield (for instance, those which run on the main thread) are blocking. This means we need to spawn new threads for each CC-side yield. We replace this system with a few changes: - @LuaFunction annotation: One may annotate a public instance method with this annotation. This then exposes a peripheral/lua object method. Furthermore, this method can accept and return a variety of types, which often makes functions cleaner (e.g. can return an int rather than an Object[], and specify and int argument rather than Object[]). - MethodResult: Instead of returning an Object[] and having blocking yields, functions return a MethodResult. This either contains an immediate return, or an instruction to yield with some continuation to resume with. MethodResult is then interpreted by the Lua runtime (i.e. Cobalt), rather than our weird bodgey hacks before. This means we no longer spawn new threads when yielding within CC. - Methods accept IArguments instead of a raw Object array. This has a few benefits: - Consistent argument handling - people no longer need to use ArgumentHelper (as it doesn't exist!), or even be aware of its existence - you're rather forced into using it. - More efficient code in some cases. We provide a Cobalt-specific implementation of IArguments, which avoids the boxing/unboxing when handling numbers and binary strings.
2020-05-15 12:21:16 +00:00
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;
Replace getMethodNames/callMethod with annotations (#447) When creating a peripheral or custom Lua object, one must implement two methods: - getMethodNames(): String[] - Returns the name of the methods - callMethod(int, ...): Object[] - Invokes the method using an index in the above array. This has a couple of problems: - It's somewhat unwieldy to use - you need to keep track of array indices, which leads to ugly code. - Functions which yield (for instance, those which run on the main thread) are blocking. This means we need to spawn new threads for each CC-side yield. We replace this system with a few changes: - @LuaFunction annotation: One may annotate a public instance method with this annotation. This then exposes a peripheral/lua object method. Furthermore, this method can accept and return a variety of types, which often makes functions cleaner (e.g. can return an int rather than an Object[], and specify and int argument rather than Object[]). - MethodResult: Instead of returning an Object[] and having blocking yields, functions return a MethodResult. This either contains an immediate return, or an instruction to yield with some continuation to resume with. MethodResult is then interpreted by the Lua runtime (i.e. Cobalt), rather than our weird bodgey hacks before. This means we no longer spawn new threads when yielding within CC. - Methods accept IArguments instead of a raw Object array. This has a few benefits: - Consistent argument handling - people no longer need to use ArgumentHelper (as it doesn't exist!), or even be aware of its existence - you're rather forced into using it. - More efficient code in some cases. We provide a Cobalt-specific implementation of IArguments, which avoids the boxing/unboxing when handling numbers and binary strings.
2020-05-15 12:21:16 +00:00
return new CommandCallback( commandID ).pull;
}
@Override
2017-05-06 23:07:42 +00:00
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
2017-05-06 23:07:42 +00:00
public ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side )
{
return upgrades.get( side );
}
@Override
2017-05-06 23:07:42 +00:00
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
2017-05-06 23:07:42 +00:00
public IPeripheral getPeripheral( @Nonnull TurtleSide side )
{
return peripherals.get( side );
}
2017-05-06 23:07:42 +00:00
@Nonnull
@Override
public CompoundNBT getUpgradeNBTData( TurtleSide side )
{
CompoundNBT nbt = upgradeNBTData.get( side );
if( nbt == null ) upgradeNBTData.put( side, nbt = new CompoundNBT() );
return nbt;
}
@Override
2017-05-06 23:07:42 +00:00
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;
}
Replace getMethodNames/callMethod with annotations (#447) When creating a peripheral or custom Lua object, one must implement two methods: - getMethodNames(): String[] - Returns the name of the methods - callMethod(int, ...): Object[] - Invokes the method using an index in the above array. This has a couple of problems: - It's somewhat unwieldy to use - you need to keep track of array indices, which leads to ugly code. - Functions which yield (for instance, those which run on the main thread) are blocking. This means we need to spawn new threads for each CC-side yield. We replace this system with a few changes: - @LuaFunction annotation: One may annotate a public instance method with this annotation. This then exposes a peripheral/lua object method. Furthermore, this method can accept and return a variety of types, which often makes functions cleaner (e.g. can return an int rather than an Object[], and specify and int argument rather than Object[]). - MethodResult: Instead of returning an Object[] and having blocking yields, functions return a MethodResult. This either contains an immediate return, or an instruction to yield with some continuation to resume with. MethodResult is then interpreted by the Lua runtime (i.e. Cobalt), rather than our weird bodgey hacks before. This means we no longer spawn new threads when yielding within CC. - Methods accept IArguments instead of a raw Object array. This has a few benefits: - Consistent argument handling - people no longer need to use ArgumentHelper (as it doesn't exist!), or even be aware of its existence - you're rather forced into using it. - More efficient code in some cases. We provide a Cobalt-specific implementation of IArguments, which avoids the boxing/unboxing when handling numbers and binary strings.
2020-05-15 12:21:16 +00:00
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 ) );
}
}
}