CC-Tweaked/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java

569 lines
16 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.peripheral.diskdrive;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.MediaProviders;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.util.CapabilityUtil;
import dan200.computercraft.shared.util.DefaultInventory;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.RecordUtil;
import net.minecraft.block.BlockState;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fml.network.NetworkHooks;
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.HashMap;
import java.util.Map;
import java.util.Set;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
import static net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY;
public final class TileDiskDrive extends TileGeneric implements DefaultInventory, ITickableTileEntity, INameable, INamedContainerProvider
{
private static final String NBT_NAME = "CustomName";
private static final String NBT_ITEM = "Item";
private static class MountInfo
{
String mountPath;
}
ITextComponent customName;
private final Map<IComputerAccess, MountInfo> computers = new HashMap<>();
2017-05-11 00:08:26 +00:00
@Nonnull
private ItemStack diskStack = ItemStack.EMPTY;
private LazyOptional<IItemHandlerModifiable> itemHandlerCap;
private LazyOptional<IPeripheral> peripheralCap;
private IMount diskMount = null;
private boolean recordQueued = false;
private boolean recordPlaying = false;
private boolean restartRecord = false;
private boolean ejectQueued;
public TileDiskDrive( TileEntityType<TileDiskDrive> type )
{
super( type );
}
@Override
public void destroy()
{
ejectContents( true );
if( recordPlaying ) stopRecord();
}
@Override
protected void invalidateCaps()
{
super.invalidateCaps();
itemHandlerCap = CapabilityUtil.invalidate( itemHandlerCap );
peripheralCap = CapabilityUtil.invalidate( peripheralCap );
}
@Nonnull
@Override
public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTraceResult hit )
{
if( player.isCrouching() )
{
// Try to put a disk into the drive
ItemStack disk = player.getItemInHand( hand );
if( disk.isEmpty() ) return ActionResultType.PASS;
if( !getLevel().isClientSide && getItem( 0 ).isEmpty() && MediaProviders.get( disk ) != null )
{
setDiskStack( disk );
player.setItemInHand( hand, ItemStack.EMPTY );
}
return ActionResultType.SUCCESS;
}
else
{
// Open the GUI
if( !getLevel().isClientSide ) NetworkHooks.openGui( (ServerPlayerEntity) player, this );
return ActionResultType.SUCCESS;
}
}
public Direction getDirection()
{
return getBlockState().getValue( BlockDiskDrive.FACING );
}
@Override
public void load( @Nonnull CompoundNBT nbt )
{
super.load( nbt );
customName = nbt.contains( NBT_NAME ) ? ITextComponent.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null;
if( nbt.contains( NBT_ITEM ) )
{
CompoundNBT item = nbt.getCompound( NBT_ITEM );
diskStack = ItemStack.of( item );
diskMount = null;
}
}
2017-05-06 23:07:42 +00:00
@Nonnull
@Override
public CompoundNBT save( @Nonnull CompoundNBT nbt )
{
if( customName != null ) nbt.putString( NBT_NAME, ITextComponent.Serializer.toJson( customName ) );
if( !diskStack.isEmpty() )
2017-05-01 14:48:44 +00:00
{
CompoundNBT item = new CompoundNBT();
diskStack.save( item );
nbt.put( NBT_ITEM, item );
}
return super.save( nbt );
}
@Override
public void tick()
{
2017-05-01 14:48:44 +00:00
// Ejection
if( ejectQueued )
2017-05-01 14:48:44 +00:00
{
ejectContents( false );
ejectQueued = false;
2017-05-01 14:48:44 +00:00
}
2017-05-01 14:48:44 +00:00
// Music
synchronized( this )
{
if( !level.isClientSide && recordPlaying != recordQueued || restartRecord )
2017-05-01 14:48:44 +00:00
{
restartRecord = false;
if( recordQueued )
2017-05-01 14:48:44 +00:00
{
IMedia contents = getDiskMedia();
SoundEvent record = contents != null ? contents.getAudio( diskStack ) : null;
2017-05-01 14:48:44 +00:00
if( record != null )
{
recordPlaying = true;
playRecord();
2017-05-01 14:48:44 +00:00
}
else
{
recordQueued = false;
2017-05-01 14:48:44 +00:00
}
}
else
{
stopRecord();
recordPlaying = false;
2017-05-01 14:48:44 +00:00
}
}
}
}
// IInventory implementation
@Override
public int getContainerSize()
{
return 1;
}
2017-05-11 00:08:26 +00:00
@Override
public boolean isEmpty()
{
return diskStack.isEmpty();
2017-05-11 00:08:26 +00:00
}
@Nonnull
@Override
public ItemStack getItem( int slot )
{
return diskStack;
}
2017-05-11 00:08:26 +00:00
@Nonnull
@Override
public ItemStack removeItemNoUpdate( int slot )
{
ItemStack result = diskStack;
diskStack = ItemStack.EMPTY;
diskMount = null;
return result;
}
2017-05-11 00:08:26 +00:00
@Nonnull
@Override
public ItemStack removeItem( int slot, int count )
{
if( diskStack.isEmpty() ) return ItemStack.EMPTY;
if( diskStack.getCount() <= count )
2017-05-01 14:48:44 +00:00
{
ItemStack disk = diskStack;
setItem( slot, ItemStack.EMPTY );
2017-05-01 14:48:44 +00:00
return disk;
}
ItemStack part = diskStack.split( count );
setItem( slot, diskStack.isEmpty() ? ItemStack.EMPTY : diskStack );
2017-05-01 14:48:44 +00:00
return part;
}
@Override
public void setItem( int slot, @Nonnull ItemStack stack )
{
if( getLevel().isClientSide )
2017-05-01 14:48:44 +00:00
{
diskStack = stack;
diskMount = null;
setChanged();
2017-05-01 14:48:44 +00:00
return;
}
synchronized( this )
{
if( InventoryUtil.areItemsStackable( stack, diskStack ) )
2017-05-01 14:48:44 +00:00
{
diskStack = stack;
2017-05-01 14:48:44 +00:00
return;
}
2017-05-01 14:48:44 +00:00
// Unmount old disk
if( !diskStack.isEmpty() )
2017-05-01 14:48:44 +00:00
{
// TODO: Is this iteration thread safe?
Set<IComputerAccess> computers = this.computers.keySet();
for( IComputerAccess computer : computers ) unmountDisk( computer );
2017-05-01 14:48:44 +00:00
}
2017-05-01 14:48:44 +00:00
// Stop music
if( recordPlaying )
2017-05-01 14:48:44 +00:00
{
stopRecord();
recordPlaying = false;
recordQueued = false;
2017-05-01 14:48:44 +00:00
}
2017-05-01 14:48:44 +00:00
// Swap disk over
diskStack = stack;
diskMount = null;
setChanged();
2017-05-01 14:48:44 +00:00
// Mount new disk
if( !diskStack.isEmpty() )
2017-05-01 14:48:44 +00:00
{
Set<IComputerAccess> computers = this.computers.keySet();
for( IComputerAccess computer : computers ) mountDisk( computer );
2017-05-01 14:48:44 +00:00
}
}
}
@Override
public void setChanged()
{
if( !level.isClientSide ) updateBlockState();
super.setChanged();
}
@Override
public boolean stillValid( @Nonnull PlayerEntity player )
{
return isUsable( player, false );
}
@Override
public void clearContent()
{
setItem( 0, ItemStack.EMPTY );
}
@Nonnull
2019-04-02 12:17:06 +00:00
ItemStack getDiskStack()
{
return getItem( 0 );
}
2019-04-02 12:17:06 +00:00
void setDiskStack( @Nonnull ItemStack stack )
{
setItem( 0, stack );
}
2019-04-02 12:17:06 +00:00
private IMedia getDiskMedia()
{
return MediaProviders.get( getDiskStack() );
}
2019-04-02 12:17:06 +00:00
String getDiskMountPath( IComputerAccess computer )
{
synchronized( this )
{
MountInfo info = computers.get( computer );
2019-04-02 12:17:06 +00:00
return info != null ? info.mountPath : null;
}
}
2019-04-02 12:17:06 +00:00
void mount( IComputerAccess computer )
{
synchronized( this )
{
computers.put( computer, new MountInfo() );
mountDisk( computer );
}
}
2019-04-02 12:17:06 +00:00
void unmount( IComputerAccess computer )
{
synchronized( this )
{
unmountDisk( computer );
computers.remove( computer );
}
}
2019-04-02 12:17:06 +00:00
void playDiskAudio()
{
synchronized( this )
{
IMedia media = getDiskMedia();
if( media != null && media.getAudioTitle( diskStack ) != null )
{
recordQueued = true;
restartRecord = recordPlaying;
}
}
}
2019-04-02 12:17:06 +00:00
void stopDiskAudio()
{
synchronized( this )
{
recordQueued = false;
restartRecord = false;
}
}
2019-04-02 12:17:06 +00:00
void ejectDisk()
{
synchronized( this )
{
ejectQueued = true;
}
}
// private methods
2017-05-01 14:48:44 +00:00
private synchronized void mountDisk( IComputerAccess computer )
{
if( !diskStack.isEmpty() )
2017-05-01 14:48:44 +00:00
{
MountInfo info = computers.get( computer );
2017-05-01 14:48:44 +00:00
IMedia contents = getDiskMedia();
if( contents != null )
{
if( diskMount == null )
2017-05-01 14:48:44 +00:00
{
diskMount = contents.createDataMount( diskStack, getLevel() );
2017-05-01 14:48:44 +00:00
}
if( diskMount != null )
2017-05-01 14:48:44 +00:00
{
if( diskMount instanceof IWritableMount )
2017-05-01 14:48:44 +00:00
{
// Try mounting at the lowest numbered "disk" name we can
int n = 1;
while( info.mountPath == null )
{
info.mountPath = computer.mountWritable( n == 1 ? "disk" : "disk" + n, (IWritableMount) diskMount );
n++;
2017-05-01 14:48:44 +00:00
}
}
else
{
// Try mounting at the lowest numbered "disk" name we can
int n = 1;
while( info.mountPath == null )
{
info.mountPath = computer.mount( n == 1 ? "disk" : "disk" + n, diskMount );
n++;
2017-05-01 14:48:44 +00:00
}
}
}
else
{
info.mountPath = null;
}
}
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
computer.queueEvent( "disk", computer.getAttachmentName() );
2017-05-01 14:48:44 +00:00
}
}
private synchronized void unmountDisk( IComputerAccess computer )
{
if( !diskStack.isEmpty() )
2017-05-01 14:48:44 +00:00
{
MountInfo info = computers.get( computer );
assert info != null;
2017-05-01 14:48:44 +00:00
if( info.mountPath != null )
{
computer.unmount( info.mountPath );
info.mountPath = null;
}
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
computer.queueEvent( "disk_eject", computer.getAttachmentName() );
2017-05-01 14:48:44 +00:00
}
}
private void updateBlockState()
{
if( remove || level == null ) return;
if( !diskStack.isEmpty() )
2017-05-01 14:48:44 +00:00
{
IMedia contents = getDiskMedia();
updateBlockState( contents != null ? DiskDriveState.FULL : DiskDriveState.INVALID );
2017-05-01 14:48:44 +00:00
}
else
{
updateBlockState( DiskDriveState.EMPTY );
2017-05-01 14:48:44 +00:00
}
}
private void updateBlockState( DiskDriveState state )
{
BlockState blockState = getBlockState();
if( blockState.getValue( BlockDiskDrive.STATE ) == state ) return;
getLevel().setBlockAndUpdate( getBlockPos(), blockState.setValue( BlockDiskDrive.STATE, state ) );
}
private synchronized void ejectContents( boolean destroyed )
{
if( getLevel().isClientSide || diskStack.isEmpty() ) return;
// Remove the disks from the inventory
ItemStack disks = diskStack;
setDiskStack( ItemStack.EMPTY );
// Spawn the item in the world
int xOff = 0;
int zOff = 0;
if( !destroyed )
2017-05-01 14:48:44 +00:00
{
Direction dir = getDirection();
xOff = dir.getStepX();
zOff = dir.getStepZ();
2017-05-01 14:48:44 +00:00
}
BlockPos pos = getBlockPos();
double x = pos.getX() + 0.5 + xOff * 0.5;
double y = pos.getY() + 0.75;
double z = pos.getZ() + 0.5 + zOff * 0.5;
ItemEntity entityitem = new ItemEntity( getLevel(), x, y, z, disks );
entityitem.setDeltaMovement( xOff * 0.15, 0, zOff * 0.15 );
getLevel().addFreshEntity( entityitem );
if( !destroyed ) getLevel().globalLevelEvent( 1000, getBlockPos(), 0 );
}
// Private methods
2017-05-01 14:48:44 +00:00
private void playRecord()
{
IMedia contents = getDiskMedia();
SoundEvent record = contents != null ? contents.getAudio( diskStack ) : null;
if( record != null )
{
RecordUtil.playRecord( record, contents.getAudioTitle( diskStack ), getLevel(), getBlockPos() );
}
else
{
RecordUtil.playRecord( null, null, getLevel(), getBlockPos() );
}
2017-05-01 14:48:44 +00:00
}
private void stopRecord()
{
RecordUtil.playRecord( null, null, getLevel(), getBlockPos() );
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability( @Nonnull Capability<T> cap, @Nullable final Direction side )
{
if( cap == ITEM_HANDLER_CAPABILITY )
{
if( itemHandlerCap == null ) itemHandlerCap = LazyOptional.of( () -> new InvWrapper( this ) );
return itemHandlerCap.cast();
}
if( cap == CAPABILITY_PERIPHERAL )
{
if( peripheralCap == null ) peripheralCap = LazyOptional.of( () -> new DiskDrivePeripheral( this ) );
return peripheralCap.cast();
}
return super.getCapability( cap, side );
}
@Override
public boolean hasCustomName()
{
return customName != null;
}
2017-05-11 00:08:26 +00:00
@Nullable
@Override
public ITextComponent getCustomName()
{
return customName;
}
@Nonnull
@Override
public ITextComponent getName()
{
return customName != null ? customName : new TranslationTextComponent( getBlockState().getBlock().getDescriptionId() );
}
@Nonnull
@Override
public ITextComponent getDisplayName()
{
return INameable.super.getDisplayName();
}
@Nonnull
@Override
public Container createMenu( int id, @Nonnull PlayerInventory inventory, @Nonnull PlayerEntity player )
{
return new ContainerDiskDrive( id, inventory, this );
}
}