1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-28 00:12:16 +00:00

Started work on upgrading to 1.16.1. Not in a compilable state yet

This commit is contained in:
Alex Evelyn 2020-07-07 13:27:13 -04:00
parent cb66ef7e30
commit 605e1f6b9b
No known key found for this signature in database
GPG Key ID: 828BEF752BC4273C
513 changed files with 48117 additions and 534 deletions

View File

@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version '0.2.5-SNAPSHOT'
id 'fabric-loom' version '0.4-SNAPSHOT'
id 'maven-publish'
}
@ -28,7 +28,7 @@ configurations {
dependencies {
minecraft "com.mojang:minecraft:${mc_version}"
mappings "net.fabricmc:yarn:${mc_version}+build.${mappings_version}"
mappings "net.fabricmc:yarn:${mc_version}+build.${mappings_version}:v2"
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"

View File

@ -2,12 +2,12 @@
mod_version=1.83.2+build.9
# Minecraft properties
mc_version=1.14.4
mappings_version=13
mc_version=1.16.1
mappings_version=1
# Dependencies
cloth_config_version=1.8
fabric_api_version=0.4.1+build.245-1.14
fabric_loader_version=0.7.2+build.174
jankson_version=1.1.2
modmenu_version=1.7.15.1.14.4+build.127
cloth_config_version=4.5.6
fabric_api_version=0.14.1+build.372-1.16
fabric_loader_version=0.8.9+build.203
jankson_version=1.2.0
modmenu_version=1.12.2+build.17

View File

@ -1,5 +1,6 @@
#Tue Jul 07 13:15:43 EDT 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -0,0 +1,233 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.client.proxy.ComputerCraftProxyClient;
import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.apis.http.websocket.Websocket;
import dan200.computercraft.core.filesystem.ResourceMount;
import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.items.ItemComputer;
import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.peripheral.diskdrive.BlockDiskDrive;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull;
import dan200.computercraft.shared.peripheral.modem.wired.ItemBlockCable;
import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem;
import dan200.computercraft.shared.peripheral.monitor.BlockMonitor;
import dan200.computercraft.shared.peripheral.printer.BlockPrinter;
import dan200.computercraft.shared.peripheral.speaker.BlockSpeaker;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.items.ItemTurtle;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.Config;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.resource.ReloadableResourceManager;
import net.minecraft.util.Identifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
public final class ComputerCraft implements ModInitializer
{
public static final String MOD_ID = "computercraft";
public static final int DATAFIXER_VERSION = 0;
// Configuration options
public static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
public static final String[] DEFAULT_HTTP_BLACKLIST = new String[] {
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fd00::/8",
};
public static int computerSpaceLimit = 1000 * 1000;
public static int floppySpaceLimit = 125 * 1000;
public static int maximumFilesOpen = 128;
public static boolean disable_lua51_features = false;
public static String default_computer_settings = "";
public static boolean debug_enable = true;
public static boolean logPeripheralErrors = false;
public static int computer_threads = 1;
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
public static long maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( 5 );
public static boolean http_enable = true;
public static boolean http_websocket_enable = true;
public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST );
public static AddressPredicate http_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST );
public static int httpTimeout = 30000;
public static int httpMaxRequests = 16;
public static long httpMaxDownload = 16 * 1024 * 1024;
public static long httpMaxUpload = 4 * 1024 * 1024;
public static int httpMaxWebsockets = 4;
public static int httpMaxWebsocketMessage = Websocket.MAX_MESSAGE_SIZE;
public static boolean enableCommandBlock = false;
public static int modem_range = 64;
public static int modem_highAltitudeRange = 384;
public static int modem_rangeDuringStorm = 64;
public static int modem_highAltitudeRangeDuringStorm = 384;
public static int maxNotesPerTick = 8;
public static boolean turtlesNeedFuel = true;
public static int turtleFuelLimit = 20000;
public static int advancedTurtleFuelLimit = 100000;
public static boolean turtlesObeyBlockProtection = true;
public static boolean turtlesCanPush = true;
public static EnumSet<TurtleAction> turtleDisabledActions = EnumSet.noneOf( TurtleAction.class );
public static final int terminalWidth_computer = 51;
public static final int terminalHeight_computer = 19;
public static final int terminalWidth_turtle = 39;
public static final int terminalHeight_turtle = 13;
public static final int terminalWidth_pocketComputer = 26;
public static final int terminalHeight_pocketComputer = 20;
// Blocks and Items
public static final class Blocks
{
public static BlockComputer computerNormal;
public static BlockComputer computerAdvanced;
public static BlockComputer computerCommand;
public static BlockTurtle turtleNormal;
public static BlockTurtle turtleAdvanced;
public static BlockSpeaker speaker;
public static BlockDiskDrive diskDrive;
public static BlockPrinter printer;
public static BlockMonitor monitorNormal;
public static BlockMonitor monitorAdvanced;
public static BlockWirelessModem wirelessModemNormal;
public static BlockWirelessModem wirelessModemAdvanced;
public static BlockWiredModemFull wiredModemFull;
public static BlockCable cable;
}
public static final class Items
{
public static ItemComputer computerNormal;
public static ItemComputer computerAdvanced;
public static ItemComputer computerCommand;
public static ItemPocketComputer pocketComputerNormal;
public static ItemPocketComputer pocketComputerAdvanced;
public static ItemTurtle turtleNormal;
public static ItemTurtle turtleAdvanced;
public static ItemDisk disk;
public static ItemTreasureDisk treasureDisk;
public static ItemPrintout printedPage;
public static ItemPrintout printedPages;
public static ItemPrintout printedBook;
public static ItemBlockCable.Cable cable;
public static ItemBlockCable.WiredModem wiredModem;
}
public static final class TurtleUpgrades
{
public static TurtleModem wirelessModemNormal;
public static TurtleModem wirelessModemAdvanced;
public static TurtleSpeaker speaker;
public static TurtleCraftingTable craftingTable;
public static TurtleSword diamondSword;
public static TurtleShovel diamondShovel;
public static TurtleTool diamondPickaxe;
public static TurtleAxe diamondAxe;
public static TurtleHoe diamondHoe;
}
public static final class PocketUpgrades
{
public static PocketModem wirelessModemNormal;
public static PocketModem wirelessModemAdvanced;
public static PocketSpeaker speaker;
}
// Registries
public static final ClientComputerRegistry clientComputerRegistry = new ClientComputerRegistry();
public static final ServerComputerRegistry serverComputerRegistry = new ServerComputerRegistry();
// Logging
public static final Logger log = LogManager.getLogger( MOD_ID );
// Implementation
public static ComputerCraft instance;
public ComputerCraft()
{
instance = this;
}
@Override
public void onInitialize()
{
Config.load( Paths.get( FabricLoader.getInstance().getConfigDirectory().getPath(), MOD_ID + ".json5" ) );
ComputerCraftProxyCommon.setup();
if( FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT )
{
ComputerCraftProxyClient.setup();
}
}
public static String getVersion()
{
return "${version}";
}
static IMount createResourceMount( String domain, String subPath )
{
ReloadableResourceManager manager = ComputerCraftProxyCommon.getServer().getDataManager();
ResourceMount mount = new ResourceMount( domain, subPath, manager );
return mount.exists( "" ) ? mount : null;
}
public static InputStream getResourceFile( String domain, String subPath )
{
ReloadableResourceManager manager = ComputerCraftProxyCommon.getServer().getDataManager();
try
{
return manager.getResource( new Identifier( domain, subPath ) ).getInputStream();
}
catch( IOException ignored )
{
return null;
}
}
}

View File

@ -0,0 +1,150 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft;
import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.shared.*;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
import dan200.computercraft.shared.peripheral.modem.wired.TileWiredModemFull;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.wired.WiredNode;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
public final class ComputerCraftAPIImpl implements IComputerCraftAPI
{
public static final ComputerCraftAPIImpl INSTANCE = new ComputerCraftAPIImpl();
private ComputerCraftAPIImpl()
{
}
@Nonnull
@Override
public String getInstalledVersion()
{
return "${version}";
}
@Override
public int createUniqueNumberedSaveDir( @Nonnull World world, @Nonnull String parentSubPath )
{
return IDAssigner.getNextId( world, parentSubPath );
}
@Override
public IWritableMount createSaveDirMount( @Nonnull World world, @Nonnull String subPath, long capacity )
{
try
{
return new FileMount( new File( IDAssigner.getDir( world ), subPath ), capacity );
}
catch( Exception e )
{
return null;
}
}
@Override
public IMount createResourceMount( @Nonnull String domain, @Nonnull String subPath )
{
return ComputerCraft.createResourceMount( domain, subPath );
}
@Override
public void registerPeripheralProvider( @Nonnull IPeripheralProvider provider )
{
Peripherals.register( provider );
}
@Override
public void registerTurtleUpgrade( @Nonnull ITurtleUpgrade upgrade )
{
TurtleUpgrades.register( upgrade );
}
@Override
public void registerBundledRedstoneProvider( @Nonnull IBundledRedstoneProvider provider )
{
BundledRedstone.register( provider );
}
@Override
public int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side )
{
return BundledRedstone.getDefaultOutput( world, pos, side );
}
@Override
public void registerMediaProvider( @Nonnull IMediaProvider provider )
{
MediaProviders.register( provider );
}
@Override
public void registerPocketUpgrade( @Nonnull IPocketUpgrade upgrade )
{
PocketUpgrades.register( upgrade );
}
@Nonnull
@Override
public IPacketNetwork getWirelessNetwork()
{
return WirelessNetwork.getUniversal();
}
@Override
public void registerAPIFactory( @Nonnull ILuaAPIFactory factory )
{
ApiFactories.register( factory );
}
@Nonnull
@Override
public IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )
{
return new WiredNode( element );
}
@Nullable
@Override
public IWiredElement getWiredElementAt( @Nonnull BlockView world, @Nonnull BlockPos pos, @Nonnull Direction side )
{
BlockEntity tile = world.getBlockEntity( pos );
if( tile instanceof TileCable )
{
return ((TileCable) tile).getElement( side );
}
else if( tile instanceof TileWiredModemFull )
{
return ((TileWiredModemFull) tile).getElement();
}
return null;
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeType;
import net.minecraft.item.ItemConvertible;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import javax.annotation.Nonnull;
/**
* A base class for {@link ITurtleUpgrade}s.
*
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade
{
private final Identifier id;
private final TurtleUpgradeType type;
private final String adjective;
private final ItemStack stack;
protected AbstractTurtleUpgrade( Identifier id, TurtleUpgradeType type, String adjective, ItemStack stack )
{
this.id = id;
this.type = type;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractTurtleUpgrade( Identifier id, TurtleUpgradeType type, String adjective, ItemConvertible item )
{
this( id, type, adjective, new ItemStack( item ) );
}
protected AbstractTurtleUpgrade( Identifier id, TurtleUpgradeType type, ItemStack stack )
{
this( id, type, Util.createTranslationKey( "upgrade", id ) + ".adjective", stack );
}
protected AbstractTurtleUpgrade( Identifier id, TurtleUpgradeType type, ItemConvertible item )
{
this( id, type, new ItemStack( item ) );
}
@Nonnull
@Override
public final Identifier getUpgradeID()
{
return id;
}
@Nonnull
@Override
public final String getUnlocalisedAdjective()
{
return adjective;
}
@Nonnull
@Override
public final TurtleUpgradeType getType()
{
return type;
}
@Nonnull
@Override
public final ItemStack getCraftingItem()
{
return stack;
}
}

View File

@ -0,0 +1,303 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The static entry point to the ComputerCraft API.
* Members in this class must be called after mod_ComputerCraft has been initialised,
* but may be called before it is fully loaded.
*/
public final class ComputerCraftAPI
{
@Nonnull
public static String getInstalledVersion()
{
return getInstance().getInstalledVersion();
}
@Nonnull
public static String getAPIVersion()
{
return "${version}";
}
/**
* Creates a numbered directory in a subfolder of the save directory for a given world, and returns that number.
*
* Use in conjunction with createSaveDirMount() to create a unique place for your peripherals or media items to store files.
*
* @param world The world for which the save dir should be created. This should be the server side world object.
* @param parentSubPath The folder path within the save directory where the new directory should be created. eg: "computercraft/disk"
* @return The numerical value of the name of the new folder, or -1 if the folder could not be created for some reason.
*
* eg: if createUniqueNumberedSaveDir( world, "computer/disk" ) was called returns 42, then "computer/disk/42" is now
* available for writing.
* @see #createSaveDirMount(World, String, long)
*/
public static int createUniqueNumberedSaveDir( @Nonnull World world, @Nonnull String parentSubPath )
{
return getInstance().createUniqueNumberedSaveDir( world, parentSubPath );
}
/**
* Creates a file system mount that maps to a subfolder of the save directory for a given world, and returns it.
*
* Use in conjunction with IComputerAccess.mount() or IComputerAccess.mountWritable() to mount a folder from the
* users save directory onto a computers file system.
*
* @param world The world for which the save dir can be found. This should be the server side world object.
* @param subPath The folder path within the save directory that the mount should map to. eg: "computer/disk/42".
* Use createUniqueNumberedSaveDir() to create a new numbered folder to use.
* @param capacity The amount of data that can be stored in the directory before it fills up, in bytes.
* @return The mount, or null if it could be created for some reason. Use IComputerAccess.mount() or IComputerAccess.mountWritable()
* to mount this on a Computers' file system.
* @see #createUniqueNumberedSaveDir(World, String)
* @see IComputerAccess#mount(String, IMount)
* @see IComputerAccess#mountWritable(String, IWritableMount)
* @see IMount
* @see IWritableMount
*/
@Nullable
public static IWritableMount createSaveDirMount( @Nonnull World world, @Nonnull String subPath, long capacity )
{
return getInstance().createSaveDirMount( world, subPath, capacity );
}
/**
* Creates a file system mount to a resource folder, and returns it.
*
* Use in conjunction with {@link IComputerAccess#mount} or {@link IComputerAccess#mountWritable} to mount a
* resource folder onto a computer's file system.
*
* The files in this mount will be a combination of files in all mod jar, and data packs that contain
* resources with the same domain and path.
*
* @param domain The domain under which to look for resources. eg: "mymod".
* @param subPath The subPath under which to look for resources. eg: "lua/myfiles".
* @return The mount, or {@code null} if it could be created for some reason.
* @see IComputerAccess#mount(String, IMount)
* @see IComputerAccess#mountWritable(String, IWritableMount)
* @see IMount
*/
@Nullable
public static IMount createResourceMount( @Nonnull String domain, @Nonnull String subPath )
{
return getInstance().createResourceMount( domain, subPath );
}
/**
* Creates a file system mount to a resource folder, and returns it.
*
* Use in conjunction with {@link IComputerAccess#mount} or {@link IComputerAccess#mountWritable} to mount a
* resource folder onto a computer's file system.
*
* The files in this mount will be a combination of files in all mod jar, and data packs that contain
* resources with the same domain and path.
*
* @param klass The mod class to which the files belong.
* @param domain The domain under which to look for resources. eg: "mymod".
* @param subPath The subPath under which to look for resources. eg: "lua/myfiles".
* @return The mount, or {@code null} if it could be created for some reason.
* @see IComputerAccess#mount(String, IMount)
* @see IComputerAccess#mountWritable(String, IWritableMount)
* @see IMount
* @deprecated Use {@link #createResourceMount(String, String)} instead.
*/
@Nullable
@Deprecated
public static IMount createResourceMount( Class<?> klass, @Nonnull String domain, @Nonnull String subPath )
{
return getInstance().createResourceMount( domain, subPath );
}
/**
* Registers a peripheral provider to convert blocks into {@link IPeripheral} implementations.
*
* @param provider The peripheral provider to register.
* @see IPeripheral
* @see IPeripheralProvider
*/
public static void registerPeripheralProvider( @Nonnull IPeripheralProvider provider )
{
getInstance().registerPeripheralProvider( provider );
}
/**
* Registers a new turtle turtle for use in ComputerCraft. After calling this,
* users should be able to craft Turtles with your new turtle. It is recommended to call
* this during the load() method of your mod.
*
* @param upgrade The turtle upgrade to register.
* @see ITurtleUpgrade
*/
public static void registerTurtleUpgrade( @Nonnull ITurtleUpgrade upgrade )
{
getInstance().registerTurtleUpgrade( upgrade );
}
/**
* Registers a bundled redstone provider to provide bundled redstone output for blocks.
*
* @param provider The bundled redstone provider to register.
* @see IBundledRedstoneProvider
*/
public static void registerBundledRedstoneProvider( @Nonnull IBundledRedstoneProvider provider )
{
getInstance().registerBundledRedstoneProvider( provider );
}
/**
* If there is a Computer or Turtle at a certain position in the world, get it's bundled redstone output.
*
* @param world The world this block is in.
* @param pos The position this block is at.
* @param side The side to extract the bundled redstone output from.
* @return If there is a block capable of emitting bundled redstone at the location, it's signal (0-65535) will be returned.
* If there is no block capable of emitting bundled redstone at the location, -1 will be returned.
* @see IBundledRedstoneProvider
*/
public static int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side )
{
return getInstance().getBundledRedstoneOutput( world, pos, side );
}
/**
* Registers a media provider to provide {@link IMedia} implementations for Items
*
* @param provider The media provider to register.
* @see IMediaProvider
*/
public static void registerMediaProvider( @Nonnull IMediaProvider provider )
{
getInstance().registerMediaProvider( provider );
}
public static void registerPocketUpgrade( @Nonnull IPocketUpgrade upgrade )
{
getInstance().registerPocketUpgrade( upgrade );
}
/**
* Attempt to get the game-wide wireless network.
*
* @return The global wireless network, or {@code null} if it could not be fetched.
*/
public static IPacketNetwork getWirelessNetwork()
{
return getInstance().getWirelessNetwork();
}
public static void registerAPIFactory( @Nonnull ILuaAPIFactory factory )
{
getInstance().registerAPIFactory( factory );
}
/**
* Construct a new wired node for a given wired element
*
* @param element The element to construct it for
* @return The element's node
* @see IWiredElement#getNode()
*/
@Nonnull
public static IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )
{
return getInstance().createWiredNodeForElement( element );
}
/**
* Get the wired network element for a block in world
*
* @param world The world the block exists in
* @param pos The position the block exists in
* @param side The side to extract the network element from
* @return The element's node
* @see IWiredElement#getNode()
*/
@Nullable
public static IWiredElement getWiredElementAt( @Nonnull BlockView world, @Nonnull BlockPos pos, @Nonnull Direction side )
{
return getInstance().getWiredElementAt( world, pos, side );
}
private static IComputerCraftAPI instance;
@Nonnull
private static IComputerCraftAPI getInstance()
{
if( instance != null ) return instance;
try
{
return instance = (IComputerCraftAPI) Class.forName( "dan200.computercraft.ComputerCraftAPIImpl" )
.getField( "INSTANCE" ).get( null );
}
catch( ReflectiveOperationException e )
{
throw new IllegalStateException( "Cannot find ComputerCraft API", e );
}
}
public interface IComputerCraftAPI
{
@Nonnull
String getInstalledVersion();
int createUniqueNumberedSaveDir( @Nonnull World world, @Nonnull String parentSubPath );
@Nullable
IWritableMount createSaveDirMount( @Nonnull World world, @Nonnull String subPath, long capacity );
@Nullable
IMount createResourceMount( @Nonnull String domain, @Nonnull String subPath );
void registerPeripheralProvider( @Nonnull IPeripheralProvider provider );
void registerTurtleUpgrade( @Nonnull ITurtleUpgrade upgrade );
void registerBundledRedstoneProvider( @Nonnull IBundledRedstoneProvider provider );
int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
void registerMediaProvider( @Nonnull IMediaProvider provider );
void registerPocketUpgrade( @Nonnull IPocketUpgrade upgrade );
@Nonnull
IPacketNetwork getWirelessNetwork();
void registerAPIFactory( @Nonnull ILuaAPIFactory factory );
@Nonnull
IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element );
@Nullable
IWiredElement getWiredElementAt( @Nonnull BlockView world, @Nonnull BlockPos pos, @Nonnull Direction side );
}
}

View File

@ -0,0 +1,44 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.filesystem;
import java.io.IOException;
/**
* Provides a mount of the entire computer's file system.
*
* This exists for use by various APIs - one should not attempt to mount it.
*/
public interface IFileSystem extends IWritableMount
{
/**
* Combine two paths together, reducing them into a normalised form.
*
* @param path The main path.
* @param child The path to append.
* @return The combined, normalised path.
*/
String combine( String path, String child );
/**
* Copy files from one location to another.
*
* @param from The location to copy from.
* @param to The location to copy to. This should not exist.
* @throws IOException If the copy failed.
*/
void copy( String from, String to ) throws IOException;
/**
* Move files from one location to another.
*
* @param from The location to move from.
* @param to The location to move to. This should not exist.
* @throws IOException If the move failed.
*/
void move( String from, String to ) throws IOException;
}

View File

@ -0,0 +1,98 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.filesystem;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IComputerAccess;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.List;
/**
* Represents a read only part of a virtual filesystem that can be mounted onto a computer using
* {@link IComputerAccess#mount(String, IMount)}
*
* Ready made implementations of this interface can be created using
* {@link ComputerCraftAPI#createSaveDirMount(World, String, long)} or
* {@link ComputerCraftAPI#createResourceMount(Class, String, String)}, or you're free to implement it yourselves!
*
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see IComputerAccess#mount(String, IMount)
* @see IWritableMount
*/
public interface IMount
{
/**
* Returns whether a file with a given path exists or not.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram"
* @return If the file exists.
* @throws IOException If an error occurs when checking the existence of the file.
*/
boolean exists( @Nonnull String path ) throws IOException;
/**
* Returns whether a file with a given path is a directory or not.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprograms".
* @return If the file exists and is a directory
* @throws IOException If an error occurs when checking whether the file is a directory.
*/
boolean isDirectory( @Nonnull String path ) throws IOException;
/**
* Returns the file names of all the files in a directory.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprograms".
* @param contents A list of strings. Add all the file names to this list.
* @throws IOException If the file was not a directory, or could not be listed.
*/
void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException;
/**
* Returns the size of a file with a given path, in bytes
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return The size of the file, in bytes.
* @throws IOException If the file does not exist, or its size could not be determined.
*/
long getSize( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link InputStream} representing its contents.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream representing the contents of the file.
* @throws IOException If the file does not exist, or could not be opened.
* @deprecated Use {@link #openChannelForRead(String)} instead
*/
@Nonnull
@Deprecated
InputStream openForRead( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link ReadableByteChannel} representing its contents.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A channel representing the contents of the file. If the channel implements
* {@link java.nio.channels.SeekableByteChannel}, one will be able to seek to arbitrary positions when using binary
* mode.
* @throws IOException If the file does not exist, or could not be opened.
*/
@Nonnull
@SuppressWarnings( "deprecation" )
default ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForRead( path ) );
}
}

View File

@ -0,0 +1,111 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.filesystem;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IComputerAccess;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
/**
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
* or {@link IComputerAccess#mountWritable(String, IWritableMount)}, that can also be written to.
*
* Ready made implementations of this interface can be created using
* {@link ComputerCraftAPI#createSaveDirMount(World, String, long)}, or you're free to implement it yourselves!
*
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see IComputerAccess#mount(String, IMount)
* @see IComputerAccess#mountWritable(String, IWritableMount)
* @see IMount
*/
public interface IWritableMount extends IMount
{
/**
* Creates a directory at a given path inside the virtual file system.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/mynewprograms".
* @throws IOException If the directory already exists or could not be created.
*/
void makeDirectory( @Nonnull String path ) throws IOException;
/**
* Deletes a directory at a given path inside the virtual file system.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myoldprograms".
* @throws IOException If the file does not exist or could not be deleted.
*/
void delete( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to
* @throws IOException If the file could not be opened for writing.
* @deprecated Use {@link #openChannelForWrite(String)} instead.
*/
@Nonnull
@Deprecated
OutputStream openForWrite( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
* will be able to seek to arbitrary positions when using binary mode.
* @throws IOException If the file could not be opened for writing.
*/
@Nonnull
@SuppressWarnings( "deprecation" )
default WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForWrite( path ) );
}
/**
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to.
* @throws IOException If the file could not be opened for writing.
* @deprecated Use {@link #openChannelForAppend(String)} instead.
*/
@Nonnull
@Deprecated
OutputStream openForAppend( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
* will be able to seek to arbitrary positions when using binary mode.
* @throws IOException If the file could not be opened for writing.
*/
@Nonnull
@SuppressWarnings( "deprecation" )
default WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForAppend( path ) );
}
/**
* Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the
* mount, and write operations should fail once it reaches zero.
*
* @return The amount of free space, in bytes.
* @throws IOException If the remaining space could not be computed.
*/
long getRemainingSpace() throws IOException;
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.peripheral.IComputerAccess;
import javax.annotation.Nullable;
/**
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information
* about a computer.
*/
public interface IComputerSystem extends IComputerAccess
{
/**
* Get the file system for this computer.
*
* @return The computer's file system, or {@code null} if it is not initialised.
*/
@Nullable
IFileSystem getFileSystem();
/**
* Get the label for this computer
*
* @return This computer's label, or {@code null} if it is not set.
*/
@Nullable
String getLabel();
}

View File

@ -0,0 +1,53 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.ComputerCraftAPI;
/**
* Represents a {@link ILuaObject} which is stored as a global variable on computer startup.
*
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
* to use peripherals to provide functionality to users.
*
* @see ILuaAPIFactory
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
public interface ILuaAPI extends ILuaObject
{
/**
* Get the globals this API will be assigned to. This will override any other global, so you should
*
* @return A list of globals this API will be assigned to.
*/
String[] getNames();
/**
* Called when the computer is turned on.
*
* One should only interact with the file system.
*/
default void startup()
{
}
/**
* Called every time the computer is ticked. This can be used to process various.
*/
default void update()
{
}
/**
* Called when the computer is turned off or unloaded.
*
* This should reset the state of the object, disposing any remaining file handles, or other resources.
*/
default void shutdown()
{
}
}

View File

@ -0,0 +1,31 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.ComputerCraftAPI;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Construct an {@link ILuaAPI} for a specific computer.
*
* @see ILuaAPI
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
@FunctionalInterface
public interface ILuaAPIFactory
{
/**
* Create a new API instance for a given computer.
*
* @param computer The computer this API is for.
* @return The created API, or {@code null} if one should not be injected.
*/
@Nullable
ILuaAPI create( @Nonnull IComputerSystem computer );
}

View File

@ -0,0 +1,105 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods
* that allow the peripheral call to wait for events before returning, just like in lua. This is very useful if you need
* to signal work to be performed on the main thread, and don't want to return until the work has been completed.
*/
public interface ILuaContext
{
/**
* Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
* equivalent to {@code os.pullEvent()} in lua.
*
* @param filter A specific event to wait for, or null to wait for any event.
* @return An object array containing the name of the event that occurred, and any event parameters.
* @throws LuaException If the user presses CTRL+T to terminate the current program while pullEvent() is
* waiting for an event, a "Terminated" exception will be thrown here.
*
* Do not attempt to catch this exception. You should use {@link #pullEventRaw(String)}
* should you wish to disable termination.
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
* event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
*/
@Nonnull
default Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
return results;
}
/**
* The same as {@link #pullEvent(String)}, except "terminated" events are ignored. Only use this if you want to
* prevent program termination, which is not recommended. This method is exactly equivalent to
* {@code os.pullEventRaw()} in lua.
*
* @param filter A specific event to wait for, or null to wait for any event.
* @return An object array containing the name of the event that occurred, and any event parameters.
* @throws InterruptedException If the user shuts down or reboots the computer while pullEventRaw() is waiting for
* an event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String)
*/
@Nonnull
default Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
/**
* Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to
* {@code coroutine.yield()} in lua. Use {@code pullEvent()} if you wish to wait for events.
*
* @param arguments An object array containing the arguments to pass to coroutine.yield()
* @return An object array containing the return values from coroutine.yield()
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String)
*/
@Nonnull
Object[] yield( @Nullable Object[] arguments ) throws InterruptedException;
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, waiting for it to complete.
* This should be used when you need to interact with the world in a thread-safe manner.
*
* Note that the return values of your task are handled as events, meaning more complex objects such as maps or
* {@link ILuaObject} will not preserve their identities.
*
* @param task The task to execute on the main thread.
* @return The objects returned by {@code task}.
* @throws LuaException If the task could not be queued, or if the task threw an exception.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
*/
@Nullable
Object[] executeMainThreadTask( @Nonnull ILuaTask task ) throws LuaException, InterruptedException;
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
* about the result or you wish to run asynchronously.
*
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
* value and the return values, or an error message if it failed. If you need to wait on this event, it may be
* better to use {@link #executeMainThreadTask(ILuaTask)}.
*
* @param task The task to execute on the main thread.
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
* @throws LuaException If the task could not be queued.
*/
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
}

View File

@ -0,0 +1,56 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An interface for representing custom objects returned by {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
* calls.
*
* Return objects implementing this interface to expose objects with methods to lua.
*/
public interface ILuaObject
{
/**
* Get the names of the methods that this object implements. This works the same as {@link IPeripheral#getMethodNames()}.
* See that method for detailed documentation.
*
* @return The method names this object provides.
* @see IPeripheral#getMethodNames()
*/
@Nonnull
String[] getMethodNames();
/**
* Called when a user calls one of the methods that this object implements. This works the same as
* {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}}. See that method for detailed
* documentation.
*
* @param context The context of the currently running lua thread. This can be used to wait for events
* or otherwise yield.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
* the possible values and conversion rules.
* @return An array of objects, representing the values you wish to return to the Lua program.
* See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} for the valid values and
* conversion rules.
* @throws LuaException If the task could not be queued, or if the task threw an exception.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.w
* @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])
*/
@Nullable
Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
}

View File

@ -0,0 +1,33 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
/**
* A task which can be executed via {@link ILuaContext#executeMainThreadTask(ILuaTask)} or
* {@link ILuaContext#issueMainThreadTask(ILuaTask)}. This will be run on the main thread, at the beginning of the
* next tick.
*
* @see ILuaContext#executeMainThreadTask(ILuaTask)
* @see ILuaContext#issueMainThreadTask(ILuaTask)
*/
@FunctionalInterface
public interface ILuaTask
{
/**
* Execute this task.
*
* @return The arguments to add to the {@code task_completed} event. These will be returned by
* {@link ILuaContext#executeMainThreadTask(ILuaTask)}.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
*/
@Nullable
Object[] execute() throws LuaException;
}

View File

@ -0,0 +1,45 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
/**
* An exception representing an error in Lua, like that raised by the {@code error()} function.
*/
public class LuaException extends Exception
{
private static final long serialVersionUID = -6136063076818512651L;
private final int level;
public LuaException()
{
this( "error", 1 );
}
public LuaException( @Nullable String message )
{
this( message, 1 );
}
public LuaException( @Nullable String message, int level )
{
super( message );
this.level = level;
}
/**
* The level this error is raised at. Level 1 is the function's caller, level 2 is that function's caller, and so
* on.
*
* @return The level to raise the error at.
*/
public int getLevel()
{
return level;
}
}

View File

@ -0,0 +1,90 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.media;
import dan200.computercraft.api.filesystem.IMount;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.sound.SoundEvent;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents an item that can be placed in a disk drive and used by a Computer.
*
* Implement this interface on your {@link Item} class to allow it to be used in the drive. Alternatively, register
* a {@link IMediaProvider}.
*/
public interface IMedia
{
/**
* Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
*
* @param stack The {@link ItemStack} to inspect.
* @return The label. ie: "Dan's Programs".
*/
@Nullable
String getLabel( @Nonnull ItemStack stack );
/**
* Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
*
* @param stack The {@link ItemStack} to modify.
* @param label The string to set the label to.
* @return true if the label was updated, false if the label may not be modified.
*/
default boolean setLabel( @Nonnull ItemStack stack, @Nullable String label )
{
return false;
}
/**
* If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
* "Jonathan Coulton - Still Alive"
*
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default String getAudioTitle( @Nonnull ItemStack stack )
{
return null;
}
/**
* If this disk represents an item with audio (like a record), get the resource name of the audio track to play.
*
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default SoundEvent getAudio( @Nonnull ItemStack stack )
{
return null;
}
/**
* If this disk represents an item with data (like a floppy disk), get a mount representing it's contents. This will
* be mounted onto the filesystem of the computer while the media is in the disk drive.
*
* @param stack The {@link ItemStack} to modify.
* @param world The world in which the item and disk drive reside.
* @return The mount, or null if this item does not represent an item with data. If the mount returned also
* implements {@link dan200.computercraft.api.filesystem.IWritableMount}, it will mounted using mountWritable()
* @see IMount
* @see dan200.computercraft.api.filesystem.IWritableMount
* @see dan200.computercraft.api.ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see dan200.computercraft.api.ComputerCraftAPI#createResourceMount(Class, String, String)
*/
@Nullable
default IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world )
{
return null;
}
}

View File

@ -0,0 +1,31 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.media;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(IMediaProvider)
*/
@FunctionalInterface
public interface IMediaProvider
{
/**
* Produce an IMedia implementation from an ItemStack.
*
* @param stack The stack from which to extract the media information.
* @return An {@link IMedia} implementation, or {@code null} if the item is not something you wish to handle
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(IMediaProvider)
*/
@Nullable
IMedia getMedia( @Nonnull ItemStack stack );
}

View File

@ -0,0 +1,59 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
import javax.annotation.Nonnull;
/**
* A packet network represents a collection of devices which can send and receive packets.
*
* @see Packet
* @see IPacketReceiver
*/
public interface IPacketNetwork
{
/**
* Add a receiver to the network.
*
* @param receiver The receiver to register to the network.
*/
void addReceiver( @Nonnull IPacketReceiver receiver );
/**
* Remove a receiver from the network.
*
* @param receiver The device to remove from the network.
*/
void removeReceiver( @Nonnull IPacketReceiver receiver );
/**
* Determine whether this network is wireless.
*
* @return Whether this network is wireless.
*/
boolean isWireless();
/**
* Submit a packet for transmitting across the network. This will route the packet through the network, sending it
* to all receivers within range (or any interdimensional ones).
*
* @param packet The packet to send.
* @param range The maximum distance this packet will be sent.
* @see #transmitInterdimensional(Packet)
* @see IPacketReceiver#receiveSameDimension(Packet, double)
*/
void transmitSameDimension( @Nonnull Packet packet, double range );
/**
* Submit a packet for transmitting across the network. This will route the packet through the network, sending it
* to all receivers across all dimensions.
*
* @param packet The packet to send.
* @see #transmitSameDimension(Packet, double)
* @see IPacketReceiver#receiveDifferentDimension(Packet)
*/
void transmitInterdimensional( @Nonnull Packet packet );
}

View File

@ -0,0 +1,84 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
/**
* An object on an {@link IPacketNetwork}, capable of receiving packets.
*/
public interface IPacketReceiver
{
/**
* Get the world in which this packet receiver exists.
*
* @return The receivers's world.
*/
@Nonnull
World getWorld();
/**
* Get the position in the world at which this receiver exists.
*
* @return The receiver's position.
*/
@Nonnull
Vec3d getPosition();
/**
* Get the maximum distance this receiver can send and receive messages.
*
* When determining whether a receiver can receive a message, the largest distance of the packet and receiver is
* used - ensuring it is within range. If the packet or receiver is inter-dimensional, then the packet will always
* be received.
*
* @return The maximum distance this device can send and receive messages.
* @see #isInterdimensional()
* @see #receiveSameDimension(Packet packet, double)
* @see IPacketNetwork#transmitInterdimensional(Packet)
*/
double getRange();
/**
* Determine whether this receiver can receive packets from other dimensions.
*
* A device will receive an inter-dimensional packet if either it or the sending device is inter-dimensional.
*
* @return Whether this receiver receives packets from other dimensions.
* @see #getRange()
* @see #receiveDifferentDimension(Packet)
* @see IPacketNetwork#transmitInterdimensional(Packet)
*/
boolean isInterdimensional();
/**
* Receive a network packet from the same dimension.
*
* @param packet The packet to receive. Generally you should check that you are listening on the given channel and,
* if so, queue the appropriate modem event.
* @param distance The distance this packet has travelled from the source.
* @see Packet
* @see #getRange()
* @see IPacketNetwork#transmitSameDimension(Packet, double)
* @see IPacketNetwork#transmitInterdimensional(Packet)
*/
void receiveSameDimension( @Nonnull Packet packet, double distance );
/**
* Receive a network packet from a different dimension.
*
* @param packet The packet to receive. Generally you should check that you are listening on the given channel and,
* if so, queue the appropriate modem event.
* @see Packet
* @see IPacketNetwork#transmitInterdimensional(Packet)
* @see IPacketNetwork#transmitSameDimension(Packet, double)
* @see #isInterdimensional()
*/
void receiveDifferentDimension( @Nonnull Packet packet );
}

View File

@ -0,0 +1,42 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
/**
* An object on a {@link IPacketNetwork}, capable of sending packets.
*/
public interface IPacketSender
{
/**
* Get the world in which this packet sender exists.
*
* @return The sender's world.
*/
@Nonnull
World getWorld();
/**
* Get the position in the world at which this sender exists.
*
* @return The sender's position.
*/
@Nonnull
Vec3d getPosition();
/**
* Get some sort of identification string for this sender. This does not strictly need to be unique, but you
* should be able to extract some identifiable information from it.
*
* @return This device's id.
*/
@Nonnull
String getSenderID();
}

View File

@ -0,0 +1,117 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* Represents a packet which may be sent across a {@link IPacketNetwork}.
*
* @see IPacketSender
* @see IPacketNetwork#transmitSameDimension(Packet, double)
* @see IPacketNetwork#transmitInterdimensional(Packet)
* @see IPacketReceiver#receiveDifferentDimension(Packet)
* @see IPacketReceiver#receiveSameDimension(Packet, double)
*/
public class Packet
{
private final int channel;
private final int replyChannel;
private final Object payload;
private final IPacketSender sender;
/**
* Create a new packet, ready for transmitting across the network.
*
* @param channel The channel to send the packet along. Receiving devices should only process packets from on
* channels they are listening to.
* @param replyChannel The channel to reply on.
* @param payload The contents of this packet. This should be a "valid" Lua object, safe for queuing as an
* event or returning from a peripheral call.
* @param sender The object which sent this packet.
*/
public Packet( int channel, int replyChannel, @Nullable Object payload, @Nonnull IPacketSender sender )
{
Objects.requireNonNull( sender, "sender cannot be null" );
this.channel = channel;
this.replyChannel = replyChannel;
this.payload = payload;
this.sender = sender;
}
/**
* Get the channel this packet is sent along. Receivers should generally only process packets from on channels they
* are listening to.
*
* @return This packet's channel.
*/
public int getChannel()
{
return channel;
}
/**
* The channel to reply on. Objects which will reply should send it along this channel.
*
* @return This channel to reply on.
*/
public int getReplyChannel()
{
return replyChannel;
}
/**
* The actual data of this packet. This should be a "valid" Lua object, safe for queuing as an
* event or returning from a peripheral call.
*
* @return The packet's payload
*/
@Nullable
public Object getPayload()
{
return payload;
}
/**
* The object which sent this message.
*
* @return The sending object.
*/
@Nonnull
public IPacketSender getSender()
{
return sender;
}
@Override
public boolean equals( Object o )
{
if( this == o ) return true;
if( o == null || getClass() != o.getClass() ) return false;
Packet packet = (Packet) o;
if( channel != packet.channel ) return false;
if( replyChannel != packet.replyChannel ) return false;
if( !Objects.equals( payload, packet.payload ) ) return false;
return sender.equals( packet.sender );
}
@Override
public int hashCode()
{
int result;
result = channel;
result = 31 * result + replyChannel;
result = 31 * result + (payload != null ? payload.hashCode() : 0);
result = 31 * result + sender.hashCode();
return result;
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.ComputerCraftAPI;
import javax.annotation.Nonnull;
/**
* An object which may be part of a wired network.
*
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(IWiredElement)}. This acts
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
*
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
* {@link IWiredElement} capability for the appropriate sides.
*/
public interface IWiredElement extends IWiredSender
{
/**
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
* peripherals change.
*
* @param change The change which occurred.
* @see IWiredNetworkChange
*/
default void networkChanged( @Nonnull IWiredNetworkChange change )
{
}
}

View File

@ -0,0 +1,86 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series
* of peripherals.
*
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
* there is some path between two nodes then they must be on the same network. {@link IWiredNetwork} will automatically
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
* change.
*
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
* it is generally preferred to use the methods provided by {@link IWiredNode}.
*
* @see IWiredNode#getNetwork()
*/
public interface IWiredNetwork
{
/**
* Create a connection between two nodes.
*
* This should only be used on the server thread.
*
* @param left The first node to connect
* @param right The second node to connect
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @throws IllegalStateException If neither node is on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#connectTo(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
*/
boolean connect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
/**
* Destroy a connection between this node and another.
*
* This should only be used on the server thread.
*
* @param left The first node in the connection.
* @param right The second node in the connection.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If either node is not on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#disconnectFrom(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
*/
boolean disconnect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to remove
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNode#remove()
*/
boolean remove( @Nonnull IWiredNode node );
/**
* Update the peripherals a node provides.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to attach peripherals for.
* @param peripherals The new peripherals for this node.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNode#updatePeripherals(Map)
*/
void updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map<String, IPeripheral> peripherals );
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Represents a change to the objects on a wired network.
*
* @see IWiredElement#networkChanged(IWiredNetworkChange)
*/
public interface IWiredNetworkChange
{
/**
* A set of peripherals which have been removed. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of removed peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsRemoved();
/**
* A set of peripherals which have been added. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of added peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsAdded();
}

View File

@ -0,0 +1,109 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s.
*
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
* methods may be safely used on any thread.
*
* When sending a packet, the system will attempt to find the shortest path between the two nodes based on their
* element's position. Note that packet senders and receivers can have different locations from their associated
* element: the distance between the two will be added to the total packet's distance.
*
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
* be used on the main server thread.
*/
public interface IWiredNode extends IPacketNetwork
{
/**
* The associated element for this network node.
*
* @return This node's element.
*/
@Nonnull
IWiredElement getElement();
/**
* The network this node is currently connected to. Note that this may change
* after any network operation, so it should not be cached.
*
* This should only be used on the server thread.
*
* @return This node's network.
*/
@Nonnull
IWiredNetwork getNetwork();
/**
* Create a connection from this node to another.
*
* This should only be used on the server thread.
*
* @param node The other node to connect to.
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
* @see IWiredNode#disconnectFrom(IWiredNode)
*/
default boolean connectTo( @Nonnull IWiredNode node )
{
return getNetwork().connect( this, node );
}
/**
* Destroy a connection between this node and another.
*
* This should only be used on the server thread.
*
* @param node The other node to disconnect from.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If {@code node} is not on the same network.
* @see IWiredNetwork#disconnect(IWiredNode, IWiredNode)
* @see IWiredNode#connectTo(IWiredNode)
*/
default boolean disconnectFrom( @Nonnull IWiredNode node )
{
return getNetwork().disconnect( this, node );
}
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNetwork#remove(IWiredNode)
*/
default boolean remove()
{
return getNetwork().remove( this );
}
/**
* Mark this node's peripherals as having changed.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param peripherals The new peripherals for this node.
* @see IWiredNetwork#updatePeripherals(IWiredNode, Map)
*/
default void updatePeripherals( @Nonnull Map<String, IPeripheral> peripherals )
{
getNetwork().updatePeripherals( this, peripherals );
}
}

View File

@ -0,0 +1,31 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketSender;
import javax.annotation.Nonnull;
/**
* An object on a {@link IWiredNetwork} capable of sending packets.
*
* Unlike a regular {@link IPacketSender}, this must be associated with the node you are attempting to
* to send the packet from.
*/
public interface IWiredSender extends IPacketSender
{
/**
* The node in the network representing this object.
*
* This should be used as a proxy for the main network. One should send packets
* and register receivers through this object.
*
* @return The node for this element.
*/
@Nonnull
IWiredNode getNode();
}

View File

@ -0,0 +1,215 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
/**
* The interface passed to peripherals by computers or turtles, providing methods
* that they can call. This should not be implemented by your classes. Do not interact
* with computers except via this interface.
*/
public interface IComputerAccess
{
/**
* Mount a mount onto the computer's file system in a read only mode.
*
* @param desiredLocation The location on the computer's file system where you would like the mount to be mounted.
* @param mount The mount object to mount on the computer.
* @return The location on the computer's file system where you the mount mounted, or {@code null} if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see #mount(String, IMount, String)
* @see #mountWritable(String, IWritableMount)
* @see #unmount(String)
* @see IMount
*/
@Nullable
default String mount( @Nonnull String desiredLocation, @Nonnull IMount mount )
{
return mount( desiredLocation, mount, getAttachmentName() );
}
/**
* Mount a mount onto the computer's file system in a read only mode.
*
* @param desiredLocation The location on the computer's file system where you would like the mount to be mounted.
* @param mount The mount object to mount on the computer.
* @param driveName A custom name to give for this mount location, as returned by {@code fs.getDrive()}.
* @return The location on the computer's file system where you the mount mounted, or {@code null} if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see #mount(String, IMount)
* @see #mountWritable(String, IWritableMount)
* @see #unmount(String)
* @see IMount
*/
@Nullable
String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName );
/**
* Mount a mount onto the computer's file system in a writable mode.
*
* @param desiredLocation The location on the computer's file system where you would like the mount to be mounted.
* @param mount The mount object to mount on the computer.
* @return The location on the computer's file system where you the mount mounted, or null if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see #mount(String, IMount)
* @see #unmount(String)
* @see IMount
*/
@Nullable
default String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount )
{
return mountWritable( desiredLocation, mount, getAttachmentName() );
}
/**
* Mount a mount onto the computer's file system in a writable mode.
*
* @param desiredLocation The location on the computer's file system where you would like the mount to be mounted.
* @param mount The mount object to mount on the computer.
* @param driveName A custom name to give for this mount location, as returned by {@code fs.getDrive()}.
* @return The location on the computer's file system where you the mount mounted, or null if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see #mount(String, IMount)
* @see #unmount(String)
* @see IMount
*/
String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName );
/**
* Unmounts a directory previously mounted onto the computers file system by {@link #mount(String, IMount)}
* or {@link #mountWritable(String, IWritableMount)}.
*
* When a directory is unmounted, it will disappear from the computers file system, and the user will no longer be
* able to access it. All directories mounted by a mount or mountWritable are automatically unmounted when the
* peripheral is attached if they have not been explicitly unmounted.
*
* Note that you cannot unmount another peripheral's mounts.
*
* @param location The desired location in the computers file system of the directory to unmount.
* This must be the location of a directory previously mounted by {@link #mount(String, IMount)} or
* {@link #mountWritable(String, IWritableMount)}, as indicated by their return value.
* @throws RuntimeException If the peripheral has been detached.
* @throws RuntimeException If the mount does not exist, or was mounted by another peripheral.
* @see #mount(String, IMount)
* @see #mountWritable(String, IWritableMount)
*/
void unmount( @Nullable String location );
/**
* Returns the numerical ID of this computer.
*
* This is the same number obtained by calling {@code os.getComputerID()} or running the "id" program from lua,
* and is guaranteed unique. This number will be positive.
*
* @return The identifier.
*/
int getID();
/**
* Causes an event to be raised on this computer, which the computer can respond to by calling
* {@code os.pullEvent()}. This can be used to notify the computer when things happen in the world or to
* this peripheral.
*
* @param event A string identifying the type of event that has occurred, this will be
* returned as the first value from {@code os.pullEvent()}. It is recommended that you
* you choose a name that is unique, and recognisable as originating from your
* peripheral. eg: If your peripheral type is "button", a suitable event would be
* "button_pressed".
* @param arguments In addition to a name, you may pass an array of extra arguments to the event, that will
* be supplied as extra return values to os.pullEvent(). Objects in the array will be converted
* to lua data types in the same fashion as the return values of IPeripheral.callMethod().
*
* You may supply {@code null} to indicate that no arguments are to be supplied.
* @throws RuntimeException If the peripheral has been detached.
* @see IPeripheral#callMethod
*/
void queueEvent( @Nonnull String event, @Nullable Object[] arguments );
/**
* Get a string, unique to the computer, by which the computer refers to this peripheral.
* For directly attached peripherals this will be "left","right","front","back",etc, but
* for peripherals attached remotely it will be different. It is good practice to supply
* this string when raising events to the computer, so that the computer knows from
* which peripheral the event came.
*
* @return A string unique to the computer, but not globally.
* @throws RuntimeException If the peripheral has been detached.
*/
@Nonnull
String getAttachmentName();
/**
* Get a set of peripherals that this computer access can "see", along with their attachment name.
*
* This may include other peripherals on the wired network or peripherals on other sides of the computer.
*
* @return All reachable peripherals
* @see #getAttachmentName()
* @see #getAvailablePeripheral(String)
*/
@Nonnull
default Map<String, IPeripheral> getAvailablePeripherals()
{
return Collections.emptyMap();
}
/**
* Get a reachable peripheral with the given attachment name. This is a equivalent to
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more efficient.
*
* @param name The peripheral's attached name
* @return The reachable peripheral, or {@code null} if none can be found.
* @see #getAvailablePeripherals()
*/
@Nullable
default IPeripheral getAvailablePeripheral( @Nonnull String name )
{
return null;
}
/**
* Get a {@link IWorkMonitor} for tasks your peripheral might execute on the main (server) thread.
*
* This should be used to ensure your peripheral integrates with ComputerCraft's monitoring and limiting of how much
* server time each computer consumes. You should not need to use this if you use
* {@link ILuaContext#issueMainThreadTask(ILuaTask)} - this is intended for mods with their own system for running
* work on the main thread.
*
* Please note that the returned implementation is <em>not</em> thread-safe, and should only be used from the main
* thread.
*
* @return The work monitor for the main thread, or {@code null} if this computer does not have one.
*/
@Nullable
default IWorkMonitor getMainThreadMonitor()
{
return null;
}
}

View File

@ -0,0 +1,142 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The interface that defines a peripheral. See {@link IPeripheralProvider} for how to associate blocks with peripherals.
*/
public interface IPeripheral
{
/**
* Should return a string that uniquely identifies this type of peripheral.
* This can be queried from lua by calling {@code peripheral.getType()}
*
* @return A string identifying the type of peripheral.
*/
@Nonnull
String getType();
/**
* Should return an array of strings that identify the methods that this
* peripheral exposes to Lua. This will be called once before each attachment,
* and should not change when called multiple times.
*
* @return An array of strings representing method names.
* @see #callMethod
*/
@Nonnull
String[] getMethodNames();
/**
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
* one of the methods exposed by {@link #getMethodNames()}.
*
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
* with Minecraft objects.
*
* @param computer The interface to the computer that is making the call. Remember that multiple
* computers can be attached to a peripheral at once.
* @param context The context of the currently running lua thread. This can be used to wait for events
* or otherwise yield.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments An array of objects, representing the arguments passed into {@code peripheral.call()}.<br>
* Lua values of type "string" will be represented by Object type String.<br>
* Lua values of type "number" will be represented by Object type Double.<br>
* Lua values of type "boolean" will be represented by Object type Boolean.<br>
* Lua values of type "table" will be represented by Object type Map.<br>
* Lua values of any other type will be represented by a null object.<br>
* This array will be empty if no arguments are passed.
* @return An array of objects, representing values you wish to return to the lua program. Integers, Doubles, Floats,
* Strings, Booleans, Maps and ILuaObject and null be converted to their corresponding lua type. All other types
* will be converted to nil.
*
* You may return null to indicate no values should be returned.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #getMethodNames
*/
@Nullable
Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
/**
* Is called when when a computer is attaching to the peripheral.
*
* This will occur when a peripheral is placed next to an active computer, when a computer is turned on next to a
* peripheral, when a turtle travels into a square next to a peripheral, or when a wired modem adjacent to this
* peripheral is does any of the above.
*
* Between calls to attach and {@link #detach}, the attached computer can make method calls on the peripheral using
* {@code peripheral.call()}. This method can be used to keep track of which computers are attached to the
* peripheral, or to take action when attachment occurs.
*
* Be aware that will be called from both the server thread and ComputerCraft Lua thread, and so must be thread-safe
* and reentrant.
*
* @param computer The interface to the computer that is being attached. Remember that multiple computers can be
* attached to a peripheral at once.
* @see #detach
*/
default void attach( @Nonnull IComputerAccess computer )
{
}
/**
* Called when a computer is detaching from the peripheral.
*
* This will occur when a computer shuts down, when the peripheral is removed while attached to computers, when a
* turtle moves away from a block attached to a peripheral, or when a wired modem adjacent to this peripheral is
* detached.
*
* This method can be used to keep track of which computers are attached to the peripheral, or to take action when
* detachment occurs.
*
* Be aware that this will be called from both the server and ComputerCraft Lua thread, and must be thread-safe
* and reentrant.
*
* @param computer The interface to the computer that is being detached. Remember that multiple computers can be
* attached to a peripheral at once.
* @see #attach
*/
default void detach( @Nonnull IComputerAccess computer )
{
}
/**
* Get the object that this peripheral provides methods for. This will generally be the tile entity
* or block, but may be an inventory, entity, etc...
*
* @return The object this peripheral targets
*/
@Nonnull
default Object getTarget()
{
return this;
}
/**
* Determine whether this peripheral is equivalent to another one.
*
* The minimal example should at least check whether they are the same object. However, you may wish to check if
* they point to the same block or tile entity.
*
* @param other The peripheral to compare against. This may be {@code null}.
* @return Whether these peripherals are equivalent.
*/
boolean equals( @Nullable IPeripheral other );
}

View File

@ -0,0 +1,39 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This interface is used to create peripheral implementations for blocks.
*
* If you have a {@link BlockEntity} which acts as a peripheral, you may alternatively implement
* {@link IPeripheralTile}.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
*/
@FunctionalInterface
public interface IPeripheralProvider
{
/**
* Produce an peripheral implementation from a block location.
*
* @param world The world the block is in.
* @param pos The position the block is at.
* @param side The side to get the peripheral from.
* @return A peripheral, or {@code null} if there is not a peripheral here you'd like to handle.
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
*/
@Nullable
IPeripheral getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
}

View File

@ -0,0 +1,32 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A {@link net.minecraft.block.entity.BlockEntity} which may act as a peripheral.
*
* If you need more complex capabilities (such as handling TEs not belonging to your mod), you should use
* {@link IPeripheralProvider}.
*/
public interface IPeripheralTile
{
/**
* Get the peripheral on the given {@code side}.
*
* @param side The side to get the peripheral from.
* @return A peripheral, or {@code null} if there is not a peripheral here.
* @see IPeripheralProvider#getPeripheral(World, BlockPos, Direction)
*/
@Nullable
IPeripheral getPeripheral( @Nonnull Direction side );
}

View File

@ -0,0 +1,78 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.peripheral;
import javax.annotation.Nonnull;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* Monitors "work" associated with a computer, keeping track of how much a computer has done, and ensuring every
* computer receives a fair share of any processing time.
*
* This is primarily intended for work done by peripherals on the main thread (such as on a tile entity's tick), but
* could be used for other purposes (such as complex computations done on another thread).
*
* Before running a task, one should call {@link #canWork()} to determine if the computer is currently allowed to
* execute work. If that returns true, you should execute the task and use {@link #trackWork(long, TimeUnit)} to inform
* the monitor how long that task took.
*
* Alternatively, use {@link #runWork(Runnable)} to run and keep track of work.
*
* @see IComputerAccess#getMainThreadMonitor()
*/
public interface IWorkMonitor
{
/**
* If the owning computer is currently allowed to execute work.
*
* @return If we can execute work right now.
*/
boolean canWork();
/**
* If the owning computer is currently allowed to execute work, and has ample time to do so.
*
* This is effectively a more restrictive form of {@link #canWork()}. One should use that in order to determine if
* you may do an initial piece of work, and shouldWork to determine if any additional task may be performed.
*
* @return If we should execute work right now.
*/
boolean shouldWork();
/**
* Inform the monitor how long some piece of work took to execute.
*
* @param time The time some task took to run
* @param unit The unit that {@code time} was measured in.
*/
void trackWork( long time, @Nonnull TimeUnit unit );
/**
* Run a task if possible, and inform the monitor of how long it took.
*
* @param runnable The task to run.
* @return If the task was actually run (namely, {@link #canWork()} returned {@code true}).
*/
default boolean runWork( @Nonnull Runnable runnable )
{
Objects.requireNonNull( runnable, "runnable should not be null" );
if( !canWork() ) return false;
long start = System.nanoTime();
try
{
runnable.run();
}
finally
{
trackWork( System.nanoTime() - start, TimeUnit.NANOSECONDS );
}
return true;
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.pocket;
import net.minecraft.item.ItemConvertible;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import javax.annotation.Nonnull;
/**
* A base class for {@link IPocketUpgrade}s.
*
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractPocketUpgrade implements IPocketUpgrade
{
private final Identifier id;
private final String adjective;
private final ItemStack stack;
protected AbstractPocketUpgrade( Identifier id, String adjective, ItemStack stack )
{
this.id = id;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractPocketUpgrade( Identifier identifier, String adjective, ItemConvertible item )
{
this( identifier, adjective, new ItemStack( item ) );
}
protected AbstractPocketUpgrade( Identifier id, ItemConvertible item )
{
this( id, Util.createTranslationKey( "upgrade", id ) + ".adjective", new ItemStack( item ) );
}
@Nonnull
@Override
public final Identifier getUpgradeID()
{
return id;
}
@Nonnull
@Override
public final String getUnlocalisedAdjective()
{
return adjective;
}
@Nonnull
@Override
public final ItemStack getCraftingItem()
{
return stack;
}
}

View File

@ -0,0 +1,99 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Identifier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Wrapper class for pocket computers
*/
public interface IPocketAccess
{
/**
* Gets the entity holding this item.
*
* This must be called on the server thread.
*
* @return The holding entity, or {@code null} if none exists.
*/
@Nullable
Entity getEntity();
/**
* Get the colour of this pocket computer as a RGB number.
*
* @return The colour this pocket computer is. This will be a RGB colour between {@code 0x000000} and
* {@code 0xFFFFFF} or -1 if it has no colour.
* @see #setColour(int)
*/
int getColour();
/**
* Set the colour of the pocket computer to a RGB number.
*
* @param colour The colour this pocket computer should be changed to. This should be a RGB colour between
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getColour()
*/
void setColour( int colour );
/**
* Get the colour of this pocket computer's light as a RGB number.
*
* @return The colour this light is. This will be a RGB colour between {@code 0x000000} and {@code 0xFFFFFF} or
* -1 if it has no colour.
* @see #setLight(int)
*/
int getLight();
/**
* Set the colour of the pocket computer's light to a RGB number.
*
* @param colour The colour this modem's light will be changed to. This should be a RGB colour between
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getLight()
*/
void setLight( int colour );
/**
* Get the upgrade-specific NBT.
*
* This is persisted between computer reboots and chunk loads.
*
* @return The upgrade's NBT.
* @see #updateUpgradeNBTData()
*/
@Nonnull
CompoundTag getUpgradeNBTData();
/**
* Mark the upgrade-specific NBT as dirty.
*
* @see #getUpgradeNBTData()
*/
void updateUpgradeNBTData();
/**
* Remove the current peripheral and create a new one. You may wish to do this if the methods available change.
*/
void invalidatePeripheral();
/**
* Get a list of all upgrades for the pocket computer.
*
* @return A collection of all upgrade names.
*/
@Nonnull
Map<Identifier, IPeripheral> getUpgrades();
}

View File

@ -0,0 +1,105 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Additional peripherals for pocket computers.
*
* This is similar to {@link ITurtleUpgrade}.
*/
public interface IPocketUpgrade
{
/**
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or
* "my_mod:my_upgrade".
*
* You should use a unique resource domain to ensure this upgrade is uniquely identified. The upgrade will fail
* registration if an already used ID is specified.
*
* @return The upgrade's id.
* @see IPocketUpgrade#getUpgradeID()
* @see ComputerCraftAPI#registerPocketUpgrade(IPocketUpgrade)
*/
@Nonnull
Identifier getUpgradeID();
/**
* Return an unlocalised string to describe the type of pocket computer this upgrade provides.
*
* An example of a built-in adjectives is "Wireless" - this is converted to "Wireless Pocket Computer".
*
* @return The unlocalised adjective.
* @see ITurtleUpgrade#getUnlocalisedAdjective()
*/
@Nonnull
String getUnlocalisedAdjective();
/**
* Return an item stack representing the type of item that a pocket computer must be crafted with to create a
* pocket computer which holds this upgrade. This item stack is also used to determine the upgrade given by
* {@code pocket.equip()}/{@code pocket.unequip()}.
*
* Ideally this should be constant over a session. It is recommended that you cache
* the item too, in order to prevent constructing it every time the method is called.
*
* @return The item stack used for crafting. This can be {@link ItemStack#EMPTY} if crafting is disabled.
*/
@Nonnull
ItemStack getCraftingItem();
/**
* Creates a peripheral for the pocket computer.
*
* The peripheral created will be stored for the lifetime of the upgrade, will be passed an argument to
* {@link #update(IPocketAccess, IPeripheral)} and will be attached, detached and have methods called in the same
* manner as an ordinary peripheral.
*
* @param access The access object for the pocket item stack.
* @return The newly created peripheral.
* @see #update(IPocketAccess, IPeripheral)
*/
@Nullable
IPeripheral createPeripheral( @Nonnull IPocketAccess access );
/**
* Called when the pocket computer item stack updates.
*
* @param access The access object for the pocket item stack.
* @param peripheral The peripheral for this upgrade.
* @see #createPeripheral(IPocketAccess)
*/
default void update( @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral )
{
}
/**
* Called when the pocket computer is right clicked.
*
* @param world The world the computer is in.
* @param access The access object for the pocket item stack.
* @param peripheral The peripheral for this upgrade.
* @return {@code true} to stop the GUI from opening, otherwise false. You should always provide some code path
* which returns {@code false}, such as requiring the player to be sneaking - otherwise they will be unable to
* access the GUI.
* @see #createPeripheral(IPocketAccess)
*/
default boolean onRightClick( @Nonnull World world, @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral )
{
return false;
}
}

View File

@ -0,0 +1,34 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.redstone;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
/**
* This interface is used to provide bundled redstone output for blocks.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerBundledRedstoneProvider(IBundledRedstoneProvider)
*/
@FunctionalInterface
public interface IBundledRedstoneProvider
{
/**
* Produce an bundled redstone output from a block location.
*
* @param world The world this block is in.
* @param pos The position this block is at.
* @param side The side to extract the bundled redstone output from.
* @return A number in the range 0-65535 to indicate this block is providing output, or -1 if you do not wish to
* handle this block.
* @see dan200.computercraft.api.ComputerCraftAPI#registerBundledRedstoneProvider(IBundledRedstoneProvider)
*/
int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
}

View File

@ -0,0 +1,290 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.inventory.Inventory;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The interface passed to turtle by turtles, providing methods that they can call.
*
* This should not be implemented by your classes. Do not interact with turtles except via this interface and
* {@link ITurtleUpgrade}.
*/
public interface ITurtleAccess
{
/**
* Returns the world in which the turtle resides.
*
* @return the world in which the turtle resides.
*/
@Nonnull
World getWorld();
/**
* Returns a vector containing the integer co-ordinates at which the turtle resides.
*
* @return a vector containing the integer co-ordinates at which the turtle resides.
*/
@Nonnull
BlockPos getPosition();
/**
* Attempt to move this turtle to a new position.
*
* This will preserve the turtle's internal state, such as it's inventory, computer and upgrades. It should
* be used before playing a movement animation using {@link #playAnimation(TurtleAnimation)}.
*
* @param world The new world to move it to
* @param pos The new position to move it to.
* @return Whether the movement was successful. It may fail if the block was not loaded or the block placement
* was cancelled.
* @throws UnsupportedOperationException When attempting to teleport on the client side.
*/
boolean teleportTo( @Nonnull World world, @Nonnull BlockPos pos );
/**
* Returns a vector containing the floating point co-ordinates at which the turtle is rendered.
* This will shift when the turtle is moving.
*
* @param f The subframe fraction.
* @return A vector containing the floating point co-ordinates at which the turtle resides.
* @see #getVisualYaw(float)
*/
@Nonnull
Vec3d getVisualPosition( float f );
/**
* Returns the yaw the turtle is facing when it is rendered.
*
* @param f The subframe fraction.
* @return The yaw the turtle is facing.
* @see #getVisualPosition(float)
*/
float getVisualYaw( float f );
/**
* Returns the world direction the turtle is currently facing.
*
* @return The world direction the turtle is currently facing.
* @see #setDirection(Direction)
*/
@Nonnull
Direction getDirection();
/**
* Set the direction the turtle is facing. Note that this will not play a rotation animation, you will also need to
* call {@link #playAnimation(TurtleAnimation)} to do so.
*
* @param dir The new direction to set. This should be on either the x or z axis (so north, south, east or west).
* @see #getDirection()
*/
void setDirection( @Nonnull Direction dir );
/**
* Get the currently selected slot in the turtle's inventory.
*
* @return An integer representing the current slot.
* @see #getInventory()
* @see #setSelectedSlot(int)
*/
int getSelectedSlot();
/**
* Set the currently selected slot in the turtle's inventory.
*
* @param slot The slot to set. This must be greater or equal to 0 and less than the inventory size. Otherwise no
* action will be taken.
* @throws UnsupportedOperationException When attempting to change the slot on the client side.
* @see #getInventory()
* @see #getSelectedSlot()
*/
void setSelectedSlot( int slot );
/**
* Set the colour of the turtle to a RGB number.
*
* @param colour The colour this turtle should be changed to. This should be a RGB colour between {@code 0x000000}
* and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getColour()
*/
void setColour( int colour );
/**
* Get the colour of this turtle as a RGB number.
*
* @return The colour this turtle is. This will be a RGB colour between {@code 0x000000} and {@code 0xFFFFFF} or
* -1 if it has no colour.
* @see #setColour(int)
*/
int getColour();
/**
* Get the player who owns this turtle, namely whoever placed it.
*
* @return This turtle's owner.
*/
@Nonnull
GameProfile getOwningPlayer();
/**
* Get the inventory of this turtle
*
* @return This turtle's inventory
*/
@Nonnull
Inventory getInventory();
/**
* Determine whether this turtle will require fuel when performing actions.
*
* @return Whether this turtle needs fuel.
* @see #getFuelLevel()
* @see #setFuelLevel(int)
*/
boolean isFuelNeeded();
/**
* Get the current fuel level of this turtle.
*
* @return The turtle's current fuel level.
* @see #isFuelNeeded()
* @see #setFuelLevel(int)
*/
int getFuelLevel();
/**
* Set the fuel level to a new value. It is generally preferred to use {@link #consumeFuel(int)}} or {@link #addFuel(int)}
* instead.
*
* @param fuel The new amount of fuel. This must be between 0 and the fuel limit.
* @see #getFuelLevel()
* @see #getFuelLimit()
* @see #addFuel(int)
* @see #consumeFuel(int)
*/
void setFuelLevel( int fuel );
/**
* Get the maximum amount of fuel a turtle can hold.
*
* @return The turtle's fuel limit.
*/
int getFuelLimit();
/**
* Removes some fuel from the turtles fuel supply. Negative numbers can be passed in to INCREASE the fuel level of the turtle.
*
* @param fuel The amount of fuel to consume.
* @return Whether the turtle was able to consume the amount of fuel specified. Will return false if you supply a number
* greater than the current fuel level of the turtle. No fuel will be consumed if {@code false} is returned.
* @throws UnsupportedOperationException When attempting to consume fuel on the client side.
*/
boolean consumeFuel( int fuel );
/**
* Increase the turtle's fuel level by the given amount.
*
* @param fuel The amount to refuel with.
* @throws UnsupportedOperationException When attempting to refuel on the client side.
*/
void addFuel( int fuel );
/**
* Adds a custom command to the turtles command queue. Unlike peripheral methods, these custom commands will be executed
* on the main thread, so are guaranteed to be able to access Minecraft objects safely, and will be queued up
* with the turtles standard movement and tool commands. An issued command will return an unique integer, which will
* be supplied as a parameter to a "turtle_response" event issued to the turtle after the command has completed. Look at the
* lua source code for "rom/apis/turtle" for how to build a lua wrapper around this functionality.
*
* @param context The Lua context to pull events from.
* @param command An object which will execute the custom command when its point in the queue is reached
* @return The objects the command returned when executed. you should probably return these to the player
* unchanged if called from a peripheral method.
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
* @throws LuaException If the user presses CTRL+T to terminate the current program while {@code executeCommand()} is
* waiting for an event, a "Terminated" exception will be thrown here.
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
* event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see ITurtleCommand
* @see ILuaContext#pullEvent(String)
*/
@Nonnull
Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException;
/**
* Start playing a specific animation. This will prevent other turtle commands from executing until
* it is finished.
*
* @param animation The animation to play.
* @throws UnsupportedOperationException When attempting to execute play an animation on the client side.
* @see TurtleAnimation
*/
void playAnimation( @Nonnull TurtleAnimation animation );
/**
* Returns the turtle on the specified side of the turtle, if there is one.
*
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, if there is one.
* @see #setUpgrade(TurtleSide, ITurtleUpgrade)
*/
@Nullable
ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side );
/**
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
*
* @param side The side to set the upgrade on.
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgrade(TurtleSide)
*/
void setUpgrade( @Nonnull TurtleSide side, @Nullable ITurtleUpgrade upgrade );
/**
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
*
* @param side The side to get the peripheral from.
* @return The peripheral created by the upgrade on the specified side of the turtle, {@code null} if none exists.
*/
@Nullable
IPeripheral getPeripheral( @Nonnull TurtleSide side );
/**
* Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
*
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You must
* call {@link #updateUpgradeNBTData(TurtleSide)} after modifying it.
*
* @param side The side to get the upgrade data for.
* @return The upgrade-specific data.
* @see #updateUpgradeNBTData(TurtleSide)
*/
@Nonnull
CompoundTag getUpgradeNBTData( @Nullable TurtleSide side );
/**
* Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
* client and persisted.
*
* @param side The side to mark dirty.
* @see #updateUpgradeNBTData(TurtleSide)
*/
void updateUpgradeNBTData( @Nonnull TurtleSide side );
}

View File

@ -0,0 +1,36 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.lua.ILuaContext;
import javax.annotation.Nonnull;
/**
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)}.
*
* @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
*/
@FunctionalInterface
public interface ITurtleCommand
{
/**
* Will be called by the turtle on the main thread when it is time to execute the custom command.
*
* The handler should either perform the work of the command, and return success, or return
* failure with an error message to indicate the command cannot be executed at this time.
*
* @param turtle Access to the turtle for whom the command was issued.
* @return A result, indicating whether this action succeeded or not.
* @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
* @see TurtleCommandResult#success()
* @see TurtleCommandResult#failure(String)
* @see TurtleCommandResult
*/
@Nonnull
TurtleCommandResult execute( @Nonnull ITurtleAccess turtle );
}

View File

@ -0,0 +1,144 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
/**
* The primary interface for defining an update for Turtles. A turtle update
* can either be a new tool, or a new peripheral.
*
* @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade)
*/
public interface ITurtleUpgrade
{
/**
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or "my_mod:my_upgrade".
* You should use a unique resource domain to ensure this upgrade is uniquely identified.
* The turtle will fail registration if an already used ID is specified.
*
* @return The unique ID for this upgrade.
* @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade)
*/
@Nonnull
Identifier getUpgradeID();
/**
* Return an unlocalised string to describe this type of turtle in turtle item names.
*
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
*
* @return The localisation key for this upgrade's adjective.
*/
@Nonnull
String getUnlocalisedAdjective();
/**
* Return whether this turtle adds a tool or a peripheral to the turtle.
*
* @return The type of upgrade this is.
* @see TurtleUpgradeType for the differences between them.
*/
@Nonnull
TurtleUpgradeType getType();
/**
* Return an item stack representing the type of item that a turtle must be crafted
* with to create a turtle which holds this upgrade. This item stack is also used
* to determine the upgrade given by {@code turtle.equip()}
*
* Ideally this should be constant over a session. It is recommended that you cache
* the item too, in order to prevent constructing it every time the method is called.
*
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
*/
@Nonnull
ItemStack getCraftingItem();
/**
* Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
*
* The peripheral created will be stored for the lifetime of the upgrade and will be passed as an argument to
* {@link #update(ITurtleAccess, TurtleSide)}. It will be attached, detached and have methods called in the same
* manner as a Computer peripheral.
*
* @param turtle Access to the turtle that the peripheral is being created for.
* @param side Which side of the turtle (left or right) that the upgrade resides on.
* @return The newly created peripheral. You may return {@code null} if this upgrade is a Tool
* and this method is not expected to be called.
*/
@Nullable
default IPeripheral createPeripheral( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side )
{
return null;
}
/**
* Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
* by the turtle, and the tool is required to do some work.
*
* Conforming implementations should fire {@code BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig}for digging,
* {@code AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
*
* @param turtle Access to the turtle that the tool resides on.
* @param side Which side of the turtle (left or right) the tool resides on.
* @param verb Which action (dig or attack) the turtle is being called on to perform.
* @param direction Which world direction the action should be performed in, relative to the turtles
* position. This will either be up, down, or the direction the turtle is facing, depending on
* whether dig, digUp or digDown was called.
* @return Whether the turtle was able to perform the action, and hence whether the {@code turtle.dig()}
* or {@code turtle.attack()} lua method should return true. If true is returned, the tool will perform
* a swinging animation. You may return {@code null} if this turtle is a Peripheral and this method is not expected
* to be called.
*/
@Nonnull
default TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction )
{
return TurtleCommandResult.failure();
}
/**
* Called to obtain the model to be used when rendering a turtle peripheral.
*
* This can be obtained from {@link net.minecraft.client.render.item.ItemModels#getModel(ItemStack)},
* {@link net.minecraft.client.render.model.BakedModelManager#getModel(ModelIdentifier)} or any other
* source.
*
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade, and a transformation to apply to it. Returning
* a transformation of {@code null} has the same effect as the identify matrix.
*/
@Nonnull
@Environment( EnvType.CLIENT )
Pair<BakedModel, Matrix4f> getModel( @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side );
/**
* Called once per tick for each turtle which has the upgrade equipped.
*
* @param turtle Access to the turtle that the upgrade resides on.
* @param side Which side of the turtle (left or right) the upgrade resides on.
*/
default void update( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side )
{
}
}

View File

@ -0,0 +1,87 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
/**
* An animation a turtle will play between executing commands.
*
* Each animation takes 8 ticks to complete unless otherwise specified.
*
* @see ITurtleAccess#playAnimation(TurtleAnimation)
*/
public enum TurtleAnimation
{
/**
* An animation which does nothing. This takes no time to complete.
*
* @see #Wait
* @see #ShortWait
*/
None,
/**
* Make the turtle move forward. Note that the animation starts from the block <em>behind</em> it, and
* moves into this one.
*/
MoveForward,
/**
* Make the turtle move backwards. Note that the animation starts from the block <em>in front</em> it, and
* moves into this one.
*/
MoveBack,
/**
* Make the turtle move backwards. Note that the animation starts from the block <em>above</em> it, and
* moves into this one.
*/
MoveUp,
/**
* Make the turtle move backwards. Note that the animation starts from the block <em>below</em> it, and
* moves into this one.
*/
MoveDown,
/**
* Turn the turtle to the left. Note that the animation starts with the turtle facing <em>right</em>, and
* the turtle turns to face in the current direction.
*/
TurnLeft,
/**
* Turn the turtle to the left. Note that the animation starts with the turtle facing <em>right</em>, and
* the turtle turns to face in the current direction.
*/
TurnRight,
/**
* Swing the tool on the left.
*/
SwingLeftTool,
/**
* Swing the tool on the right.
*/
SwingRightTool,
/**
* Wait until the animation has finished, performing no movement.
*
* @see #ShortWait
* @see #None
*/
Wait,
/**
* Wait until the animation has finished, performing no movement. This takes 4 ticks to complete.
*
* @see #Wait
* @see #None
*/
ShortWait,
}

View File

@ -0,0 +1,115 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import net.minecraft.util.math.Direction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Used to indicate the result of executing a turtle command.
*
* @see ITurtleCommand#execute(ITurtleAccess)
* @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
*/
public final class TurtleCommandResult
{
private static final TurtleCommandResult EMPTY_SUCCESS = new TurtleCommandResult( true, null, null );
private static final TurtleCommandResult EMPTY_FAILURE = new TurtleCommandResult( false, null, null );
/**
* Create a successful command result with no result.
*
* @return A successful command result with no values.
*/
@Nonnull
public static TurtleCommandResult success()
{
return EMPTY_SUCCESS;
}
/**
* Create a successful command result with the given result values.
*
* @param results The results of executing this command.
* @return A successful command result with the given values.
*/
@Nonnull
public static TurtleCommandResult success( @Nullable Object[] results )
{
if( results == null || results.length == 0 ) return EMPTY_SUCCESS;
return new TurtleCommandResult( true, null, results );
}
/**
* Create a failed command result with no error message.
*
* @return A failed command result with no message.
*/
@Nonnull
public static TurtleCommandResult failure()
{
return EMPTY_FAILURE;
}
/**
* Create a failed command result with an error message.
*
* @param errorMessage The error message to provide.
* @return A failed command result with a message.
*/
@Nonnull
public static TurtleCommandResult failure( @Nullable String errorMessage )
{
if( errorMessage == null ) return EMPTY_FAILURE;
return new TurtleCommandResult( false, errorMessage, null );
}
private final boolean success;
private final String errorMessage;
private final Object[] results;
private TurtleCommandResult( boolean success, String errorMessage, Object[] results )
{
this.success = success;
this.errorMessage = errorMessage;
this.results = results;
}
/**
* Determine whether the command executed successfully.
*
* @return If the command was successful.
*/
public boolean isSuccess()
{
return success;
}
/**
* Get the error message of this command result.
*
* @return The command's error message, or {@code null} if it was a success.
*/
@Nullable
public String getErrorMessage()
{
return errorMessage;
}
/**
* Get the resulting values of this command result.
*
* @return The command's result, or {@code null} if it was a failure.
*/
@Nullable
public Object[] getResults()
{
return results;
}
}

View File

@ -0,0 +1,23 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
/**
* An enum representing the two sides of the turtle that a turtle turtle might reside.
*/
public enum TurtleSide
{
/**
* The turtle's left side (where the pickaxe usually is on a Wireless Mining Turtle)
*/
Left,
/**
* The turtle's right side (where the modem usually is on a Wireless Mining Turtle)
*/
Right,
}

View File

@ -0,0 +1,44 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
/**
* An enum representing the different types of turtle that an {@link ITurtleUpgrade} implementation can add to a turtle.
*
* @see ITurtleUpgrade#getType()
*/
public enum TurtleUpgradeType
{
/**
* A tool is rendered as an item on the side of the turtle, and responds to the {@code turtle.dig()}
* and {@code turtle.attack()} methods (Such as pickaxe or sword on Mining and Melee turtles).
*/
Tool,
/**
* A peripheral adds a special peripheral which is attached to the side of the turtle,
* and can be interacted with the {@code peripheral} API (Such as the modem on Wireless Turtles).
*/
Peripheral,
/**
* An upgrade which provides both a tool and a peripheral. This can be used when you wish
* your upgrade to also provide methods. For example, a pickaxe could provide methods
* determining whether it can break the given block or not.
*/
Both;
public boolean isTool()
{
return this == Tool || this == Both;
}
public boolean isPeripheral()
{
return this == Peripheral || this == Both;
}
}

View File

@ -0,0 +1,29 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import net.minecraft.util.math.Direction;
/**
* An enum representing the different actions that an {@link ITurtleUpgrade} of type Tool may be called on to perform by
* a turtle.
*
* @see ITurtleUpgrade#getType()
* @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
*/
public enum TurtleVerb
{
/**
* The turtle called {@code turtle.dig()}, {@code turtle.digUp()} or {@code turtle.digDown()}
*/
Dig,
/**
* The turtle called {@code turtle.attack()}, {@code turtle.attackUp()} or {@code turtle.attackDown()}
*/
Attack,
}

View File

@ -0,0 +1,286 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.turtle.event;
import com.mojang.authlib.GameProfile;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.minecraft.block.entity.CommandBlockBlockEntity;
import net.minecraft.block.entity.SignBlockEntity;
import net.minecraft.command.arguments.EntityAnchorArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.passive.HorseBaseEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.MessageType;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.NetworkState;
import net.minecraft.network.Packet;
import net.minecraft.network.packet.c2s.play.RequestCommandCompletionsC2SPacket;
import net.minecraft.network.packet.c2s.play.VehicleMoveC2SPacket;
import net.minecraft.recipe.Recipe;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.network.ServerPlayerInteractionManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.text.Text;
import net.minecraft.util.Hand;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.village.TraderOfferList;
import net.minecraft.world.GameMode;
import net.minecraft.world.dimension.DimensionType;
import javax.annotation.Nullable;
import javax.crypto.SecretKey;
import java.util.Collection;
import java.util.OptionalInt;
/**
* A wrapper for {@link ServerPlayerEntity} which denotes a "fake" player.
*
* Please note that this does not implement any of the traditional fake player behaviour. It simply exists to prevent
* me passing in normal players.
*/
public class FakePlayer extends ServerPlayerEntity
{
public FakePlayer( ServerWorld world, GameProfile gameProfile )
{
super( world.getServer(), world, gameProfile, new ServerPlayerInteractionManager( world ) );
networkHandler = new FakeNetHandler( this );
}
// region Direct networkHandler access
@Override
public void enterCombat() { }
@Override
public void endCombat() { }
@Override
public void tick() { }
@Override
public void playerTick() { }
@Override
public void onDeath( DamageSource damage ) { }
@Nullable
@Override
public Entity changeDimension( DimensionType dimension )
{
return this;
}
@Override
public void wakeUp( boolean resetTimer, boolean notify, boolean setSpawn ) { }
@Override
public boolean startRiding( Entity entity, boolean flag )
{
return false;
}
@Override
public void stopRiding() { }
@Override
public void openEditSignScreen( SignBlockEntity tile ) { }
@Override
public OptionalInt openHandledScreen( @Nullable NamedScreenHandlerFactory container )
{
return OptionalInt.empty();
}
@Override
public void sendTradeOffers( int id, TraderOfferList list, int level, int experience, boolean levelled, boolean refreshable ) { }
@Override
public void openHorseInventory( HorseBaseEntity horse, Inventory inventory ) { }
@Override
public void openEditBookScreen( ItemStack stack, Hand hand ) { }
@Override
public void openCommandBlockScreen( CommandBlockBlockEntity block ) { }
@Override
public void onSlotUpdate( ScreenHandler container, int slot, ItemStack stack ) { }
@Override
public void onHandlerRegistered( ScreenHandler container, DefaultedList<ItemStack> defaultedList ) { }
@Override
public void onPropertyUpdate( ScreenHandler container, int key, int value ) { }
@Override
public void closeHandledScreen() { }
@Override
public void updateCursorStack() { }
@Override
public void sendMessage( Text textComponent, boolean status ) { }
@Override
protected void consumeItem() { }
@Override
public void lookAt( EntityAnchorArgumentType.EntityAnchor anchor, Vec3d vec3d ) { }
@Override
public void method_14222( EntityAnchorArgumentType.EntityAnchor self, Entity entity, EntityAnchorArgumentType.EntityAnchor target ) { }
@Override
protected void onStatusEffectApplied( StatusEffectInstance statusEffectInstance ) { }
@Override
protected void onStatusEffectUpgraded( StatusEffectInstance statusEffectInstance, boolean particles ) { }
@Override
protected void onStatusEffectRemoved( StatusEffectInstance statusEffectInstance ) { }
@Override
public void requestTeleport( double x, double y, double z ) { }
@Override
public void setGameMode( GameMode gameMode ) { }
@Override
public void sendMessage( Text textComponent, MessageType chatMessageType ) { }
@Override
public String getIp()
{
return "[Fake Player]";
}
@Override
public void sendResourcePackUrl( String url, String hash ) { }
@Override
public void onStoppedTracking( Entity entity ) { }
@Override
public void setCameraEntity( Entity entity ) { }
@Override
public void teleport( ServerWorld serverWorld, double x, double y, double z, float pitch, float yaw ) { }
@Override
public void sendInitialChunkPackets( ChunkPos chunkPos, Packet<?> packet, Packet<?> packet2 ) { }
@Override
public void sendUnloadChunkPacket( ChunkPos chunkPos ) { }
@Override
public void playSound( SoundEvent soundEvent, SoundCategory soundCategory, float volume, float pitch ) { }
// endregion
// Indirect
@Override
public int lockRecipes( Collection<Recipe<?>> recipes )
{
return 0;
}
@Override
public int unlockRecipes( Collection<Recipe<?>> recipes )
{
return 0;
}
//
private static class FakeNetHandler extends ServerPlayNetworkHandler
{
FakeNetHandler( ServerPlayerEntity player )
{
super( player.server, new FakeConnection(), player );
}
@Override
public void disconnect( Text message ) { }
@Override
public void onRequestCommandCompletions( RequestCommandCompletionsC2SPacket packet ) { }
@Override
public void sendPacket( Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> listener ) { }
@Override
public void onVehicleMove( VehicleMoveC2SPacket move ) { }
}
private static class FakeConnection extends ClientConnection
{
FakeConnection()
{
super( NetworkSide.CLIENTBOUND );
}
@Override
public void channelActive( ChannelHandlerContext active )
{
}
@Override
public void setState( NetworkState state )
{
}
@Override
public void disconnect( Text message )
{
}
@Override
public void exceptionCaught( ChannelHandlerContext context, Throwable err )
{
}
@Override
protected void channelRead0( ChannelHandlerContext context, Packet<?> packet )
{
}
@Override
public void tick()
{
}
@Override
public void setupEncryption( SecretKey key )
{
}
@Override
public void disableAutoRead()
{
}
@Override
public void setCompressionThreshold( int size )
{
}
@Override
public void send( Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> listener )
{
}
}
}

View File

@ -0,0 +1,84 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
/**
* A basic action that a turtle may perform, as accessed by the {@code turtle} API.
*
* @see TurtleActionEvent
*/
public enum TurtleAction
{
/**
* A turtle moves to a new position.
*
* @see TurtleBlockEvent.Move
*/
MOVE,
/**
* A turtle turns in a specific direction.
*/
TURN,
/**
* A turtle attempts to dig a block.
*
* @see TurtleBlockEvent.Dig
*/
DIG,
/**
* A turtle attempts to place a block or item in the world.
*
* @see TurtleBlockEvent.Place
*/
PLACE,
/**
* A turtle attempts to attack an entity.
*
* @see TurtleActionEvent
*/
ATTACK,
/**
* Drop an item into an inventory/the world.
*
* @see TurtleInventoryEvent.Drop
*/
DROP,
/**
* Suck an item from an inventory or the world.
*
* @see TurtleInventoryEvent.Suck
*/
SUCK,
/**
* Refuel the turtle's fuel levels.
*/
REFUEL,
/**
* Equip or unequip an item.
*/
EQUIP,
/**
* Inspect a block in world
*
* @see TurtleBlockEvent.Inspect
*/
INSPECT,
/**
* Gather metdata about an item in the turtle's inventory.
*/
INSPECT_ITEM,
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleCommandResult;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* An event fired when a turtle is performing a known action.
*/
public class TurtleActionEvent extends TurtleEvent
{
private final TurtleAction action;
private String failureMessage;
private boolean cancelled = false;
public TurtleActionEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action )
{
super( turtle );
Objects.requireNonNull( action, "action cannot be null" );
this.action = action;
}
public TurtleAction getAction()
{
return action;
}
/**
* Sets the cancellation state of this action.
*
* If {@code cancel} is {@code true}, this action will not be carried out.
*
* @param cancel The new canceled value.
* @see TurtleCommandResult#failure()
* @deprecated Use {@link #setCanceled(boolean, String)} instead.
*/
@Deprecated
public void setCanceled( boolean cancel )
{
setCanceled( cancel, null );
}
/**
* Set the cancellation state of this action, setting a failure message if required.
*
* If {@code cancel} is {@code true}, this action will not be carried out.
*
* @param cancel The new canceled value.
* @param failureMessage The message to return to the user explaining the failure.
* @see TurtleCommandResult#failure(String)
*/
public void setCanceled( boolean cancel, @Nullable String failureMessage )
{
this.cancelled = true;
this.failureMessage = cancel ? failureMessage : null;
}
/**
* Get the message with which this will fail.
*
* @return The failure message.
* @see TurtleCommandResult#failure()
* @see #setCanceled(boolean, String)
*/
@Nullable
public String getFailureMessage()
{
return failureMessage;
}
/**
* Determine if this event is cancelled
*
* @return If this event is cancelled
* @see #setCanceled(boolean, String)
*/
public boolean isCancelled()
{
return cancelled;
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleVerb;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.Direction;
import javax.annotation.Nonnull;
import java.util.Objects;
/**
* Fired when a turtle attempts to attack an entity.
*
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)},
* as the base {@code turtle.attack()} command does not fire it.
*
* Note that such commands should also fire {@link net.fabricmc.fabric.api.event.player.AttackEntityCallback}, so you do
* not need to listen to both.
*
* @see TurtleAction#ATTACK
*/
public class TurtleAttackEvent extends TurtlePlayerEvent
{
private final Entity target;
private final ITurtleUpgrade upgrade;
private final TurtleSide side;
public TurtleAttackEvent( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull Entity target, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
{
super( turtle, TurtleAction.ATTACK, player );
Objects.requireNonNull( target, "target cannot be null" );
Objects.requireNonNull( upgrade, "upgrade cannot be null" );
Objects.requireNonNull( side, "side cannot be null" );
this.target = target;
this.upgrade = upgrade;
this.side = side;
}
/**
* Get the entity being attacked by this turtle.
*
* @return The entity being attacked.
*/
@Nonnull
public Entity getTarget()
{
return target;
}
/**
* Get the upgrade responsible for attacking.
*
* @return The upgrade responsible for attacking.
*/
@Nonnull
public ITurtleUpgrade getUpgrade()
{
return upgrade;
}
/**
* Get the side the attacking upgrade is on.
*
* @return The upgrade's side.
*/
@Nonnull
public TurtleSide getSide()
{
return side;
}
}

View File

@ -0,0 +1,234 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleVerb;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.Objects;
/**
* A general event for when a turtle interacts with a block or region.
*
* You should generally listen to one of the sub-events instead, cancelling them where
* appropriate.
*
* Note that you are not guaranteed to receive this event, if it has been cancelled by other
* mechanisms, such as block protection systems.
*
* Be aware that some events (such as {@link TurtleInventoryEvent}) do not necessarily interact
* with a block, simply objects within that block space.
*/
public abstract class TurtleBlockEvent extends TurtlePlayerEvent
{
private final World world;
private final BlockPos pos;
protected TurtleBlockEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
{
super( turtle, action, player );
Objects.requireNonNull( world, "world cannot be null" );
Objects.requireNonNull( pos, "pos cannot be null" );
this.world = world;
this.pos = pos;
}
/**
* Get the world the turtle is interacting in.
*
* @return The world the turtle is interacting in.
*/
public World getWorld()
{
return world;
}
/**
* Get the position the turtle is interacting with. Note that this is different
* to {@link ITurtleAccess#getPosition()}.
*
* @return The position the turtle is interacting with.
*/
public BlockPos getPos()
{
return pos;
}
/**
* Fired when a turtle attempts to dig a block.
*
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)},
* as the base {@code turtle.dig()} command does not fire it.
*
* Note that such commands should also fire {@link net.fabricmc.fabric.api.event.player.AttackBlockCallback}, so you
* do not need to listen to both.
*
* @see TurtleAction#DIG
*/
public static class Dig extends TurtleBlockEvent
{
private final BlockState block;
private final ITurtleUpgrade upgrade;
private final TurtleSide side;
public Dig( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull BlockState block, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
{
super( turtle, TurtleAction.DIG, player, world, pos );
Objects.requireNonNull( block, "block cannot be null" );
Objects.requireNonNull( upgrade, "upgrade cannot be null" );
Objects.requireNonNull( side, "side cannot be null" );
this.block = block;
this.upgrade = upgrade;
this.side = side;
}
/**
* Get the block which is about to be broken.
*
* @return The block which is going to be broken.
*/
@Nonnull
public BlockState getBlock()
{
return block;
}
/**
* Get the upgrade doing the digging
*
* @return The upgrade doing the digging.
*/
@Nonnull
public ITurtleUpgrade getUpgrade()
{
return upgrade;
}
/**
* Get the side the upgrade doing the digging is on.
*
* @return The upgrade's side.
*/
@Nonnull
public TurtleSide getSide()
{
return side;
}
}
/**
* Fired when a turtle attempts to move into a block.
*
* @see TurtleAction#MOVE
*/
public static class Move extends TurtleBlockEvent
{
public Move( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
{
super( turtle, TurtleAction.MOVE, player, world, pos );
}
}
/**
* Fired when a turtle attempts to place a block in the world.
*
* @see TurtleAction#PLACE
*/
public static class Place extends TurtleBlockEvent
{
private final ItemStack stack;
public Place( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull ItemStack stack )
{
super( turtle, TurtleAction.PLACE, player, world, pos );
Objects.requireNonNull( stack, "stack cannot be null" );
this.stack = stack;
}
/**
* Get the item stack that will be placed. This should not be modified.
*
* @return The item stack to be placed.
*/
@Nonnull
public ItemStack getStack()
{
return stack;
}
}
/**
* Fired when a turtle gathers data on a block in world.
*
* You may prevent blocks being inspected, or add additional information to the result.
*
* @see TurtleAction#INSPECT
*/
public static class Inspect extends TurtleBlockEvent
{
private final BlockState state;
private final Map<String, Object> data;
public Inspect( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull BlockState state, @Nonnull Map<String, Object> data )
{
super( turtle, TurtleAction.INSPECT, player, world, pos );
Objects.requireNonNull( state, "state cannot be null" );
Objects.requireNonNull( data, "data cannot be null" );
this.data = data;
this.state = state;
}
/**
* Get the block state which is being inspected.
*
* @return The inspected block state.
*/
@Nonnull
public BlockState getState()
{
return state;
}
/**
* Get the "inspection data" from this block, which will be returned to the user.
*
* @return This block's inspection data.
*/
@Nonnull
public Map<String, Object> getData()
{
return data;
}
/**
* Add new information to the inspection result. Note this will override fields with the same name.
*
* @param newData The data to add. Note all values should be convertible to Lua (see
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
*/
public void addData( @Nonnull Map<String, ?> newData )
{
Objects.requireNonNull( newData, "newData cannot be null" );
data.putAll( newData );
}
}
}

View File

@ -0,0 +1,51 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import com.google.common.eventbus.EventBus;
import dan200.computercraft.api.turtle.ITurtleAccess;
import javax.annotation.Nonnull;
import java.util.Objects;
/**
* A base class for all events concerning a turtle. This will only ever constructed and fired on the server side,
* so sever specific methods on {@link ITurtleAccess} are safe to use.
*
* You should generally not need to subscribe to this event, preferring one of the more specific classes.
*
* @see TurtleActionEvent
*/
public abstract class TurtleEvent
{
public static final EventBus EVENT_BUS = new EventBus();
private final ITurtleAccess turtle;
protected TurtleEvent( @Nonnull ITurtleAccess turtle )
{
Objects.requireNonNull( turtle, "turtle cannot be null" );
this.turtle = turtle;
}
/**
* Get the turtle which is performing this action.
*
* @return The access for this turtle.
*/
@Nonnull
public ITurtleAccess getTurtle()
{
return turtle;
}
public static boolean post( TurtleActionEvent event )
{
EVENT_BUS.post( event );
return event.isCancelled();
}
}

View File

@ -0,0 +1,74 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.Objects;
/**
* Fired when a turtle gathers data on an item in its inventory.
*
* You may prevent items being inspected, or add additional information to the result. Be aware that this is fired on
* the computer thread, and so any operations on it must be thread safe.
*
* @see TurtleAction#INSPECT_ITEM
*/
public class TurtleInspectItemEvent extends TurtleActionEvent
{
private final ItemStack stack;
private final Map<String, Object> data;
public TurtleInspectItemEvent( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack stack, @Nonnull Map<String, Object> data )
{
super( turtle, TurtleAction.INSPECT_ITEM );
Objects.requireNonNull( stack, "stack cannot be null" );
Objects.requireNonNull( data, "data cannot be null" );
this.stack = stack;
this.data = data;
}
/**
* The item which is currently being inspected.
*
* @return The item stack which is being inspected. This should <b>not</b> be modified.
*/
@Nonnull
public ItemStack getStack()
{
return stack;
}
/**
* Get the "inspection data" from this item, which will be returned to the user.
*
* @return This items's inspection data.
*/
@Nonnull
public Map<String, Object> getData()
{
return data;
}
/**
* Add new information to the inspection result. Note this will override fields with the same name.
*
* @param newData The data to add. Note all values should be convertible to Lua (see
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
*/
public void addData( @Nonnull Map<String, ?> newData )
{
Objects.requireNonNull( newData, "newData cannot be null" );
data.putAll( newData );
}
}

View File

@ -0,0 +1,84 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* Fired when a turtle attempts to interact with an inventory.
*/
public abstract class TurtleInventoryEvent extends TurtleBlockEvent
{
private final Inventory handler;
protected TurtleInventoryEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable Inventory handler )
{
super( turtle, action, player, world, pos );
this.handler = handler;
}
/**
* Get the inventory being interacted with
*
* @return The inventory being interacted with, {@code null} if the item will be dropped to/sucked from the world.
*/
@Nullable
public Inventory getItemHandler()
{
return handler;
}
/**
* Fired when a turtle attempts to suck from an inventory.
*
* @see TurtleAction#SUCK
*/
public static class Suck extends TurtleInventoryEvent
{
public Suck( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable Inventory handler )
{
super( turtle, TurtleAction.SUCK, player, world, pos, handler );
}
}
/**
* Fired when a turtle attempts to drop an item into an inventory.
*
* @see TurtleAction#DROP
*/
public static class Drop extends TurtleInventoryEvent
{
private final ItemStack stack;
public Drop( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable Inventory handler, @Nonnull ItemStack stack )
{
super( turtle, TurtleAction.DROP, player, world, pos, handler );
Objects.requireNonNull( stack, "stack cannot be null" );
this.stack = stack;
}
/**
* The item which will be inserted into the inventory/dropped on the ground.
*
* @return The item stack which will be dropped. This should <b>not</b> be modified.
*/
@Nonnull
public ItemStack getStack()
{
return stack;
}
}
}

View File

@ -0,0 +1,43 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.turtle.ITurtleAccess;
import javax.annotation.Nonnull;
import java.util.Objects;
/**
* An action done by a turtle which is normally done by a player.
*
* {@link #getPlayer()} may be used to modify the player's attributes or perform permission checks.
*/
public abstract class TurtlePlayerEvent extends TurtleActionEvent
{
private final FakePlayer player;
protected TurtlePlayerEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player )
{
super( turtle, action );
Objects.requireNonNull( player, "player cannot be null" );
this.player = player;
}
/**
* A fake player, representing this turtle.
*
* This may be used for triggering permission checks.
*
* @return A {@link FakePlayer} representing this turtle.
*/
@Nonnull
public FakePlayer getPlayer()
{
return player;
}
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* Fired when a turtle attempts to refuel from an item.
*
* One may use {@link #setCanceled(boolean, String)} to prevent refueling from this specific item. Additionally, you
* may use {@link #setHandler(Handler)} to register a custom fuel provider.
*/
public class TurtleRefuelEvent extends TurtleActionEvent
{
private final ItemStack stack;
private Handler handler;
public TurtleRefuelEvent( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack stack )
{
super( turtle, TurtleAction.REFUEL );
Objects.requireNonNull( turtle, "turtle cannot be null" );
this.stack = stack;
}
/**
* Get the stack we are attempting to refuel from.
*
* Do not modify the returned stack - all modifications should be done within the {@link Handler}.
*
* @return The stack to refuel from.
*/
public ItemStack getStack()
{
return stack;
}
/**
* Get the refuel handler for this stack.
*
* @return The refuel handler, or {@code null} if none has currently been set.
* @see #setHandler(Handler)
*/
@Nullable
public Handler getHandler()
{
return handler;
}
/**
* Set the refuel handler for this stack.
*
* You should call this if you can actually refuel from this item, and ideally only if there are no existing
* handlers.
*
* @param handler The new refuel handler.
* @see #getHandler()
*/
public void setHandler( @Nullable Handler handler )
{
this.handler = handler;
}
/**
* Handles refuelling a turtle from a specific item.
*/
@FunctionalInterface
public interface Handler
{
/**
* Refuel a turtle using an item.
*
* @param turtle The turtle to refuel.
* @param stack The stack to refuel with.
* @param slot The slot the stack resides within. This may be used to modify the inventory afterwards.
* @param limit The maximum number of refuel operations to perform. This will often correspond to the number of
* items to consume.
* @return The amount of fuel gained.
*/
int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack stack, int slot, int limit );
}
}

View File

@ -0,0 +1,117 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import net.fabricmc.fabric.api.client.render.ColorProviderRegistry;
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.ModelRotation;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import java.util.HashSet;
import java.util.function.Consumer;
/**
* Registers textures and models for items.
*/
public final class ClientRegistry
{
private static final String[] EXTRA_MODELS = new String[] {
"turtle_modem_normal_off_left",
"turtle_modem_normal_on_left",
"turtle_modem_normal_off_right",
"turtle_modem_normal_on_right",
"turtle_modem_advanced_off_left",
"turtle_modem_advanced_on_left",
"turtle_modem_advanced_off_right",
"turtle_modem_advanced_on_right",
"turtle_crafting_table_left",
"turtle_crafting_table_right",
"turtle_speaker_upgrade_left",
"turtle_speaker_upgrade_right",
"turtle_colour",
"turtle_elf_overlay",
};
private static final String[] EXTRA_TEXTURES = new String[] {
// TODO: Gather these automatically from the model. I'm unable to get this working with Forge's current
// model loading code.
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_crafty_face",
"block/turtle_speaker_face",
};
private ClientRegistry() {}
public static void onTextureStitchEvent( SpriteAtlasTexture atlasTexture, ClientSpriteRegistryCallback.Registry registry )
{
for( String extra : EXTRA_TEXTURES )
{
registry.register( new Identifier( ComputerCraft.MOD_ID, extra ) );
}
}
public static void onModelBakeEvent( ResourceManager manager, Consumer<ModelIdentifier> out )
{
for( String model : EXTRA_MODELS )
{
out.accept( new ModelIdentifier( new Identifier( ComputerCraft.MOD_ID, model ), "inventory" ) );
}
}
public static void onItemColours()
{
ColorProviderRegistry.ITEM.register(
( stack, layer ) -> layer == 1 ? ((ItemDisk) stack.getItem()).getColour( stack ) : 0xFFFFFF,
ComputerCraft.Items.disk
);
ColorProviderRegistry.ITEM.register( ( stack, layer ) -> {
switch( layer )
{
case 0:
default:
return 0xFFFFFF;
case 1: // Frame colour
return IColouredItem.getColourBasic( stack );
case 2: // Light colour
{
int light = ItemPocketComputer.getLightState( stack );
return light == -1 ? Colour.Black.getHex() : light;
}
}
}, ComputerCraft.Items.pocketComputerNormal, ComputerCraft.Items.pocketComputerAdvanced );
// Setup turtle colours
ColorProviderRegistry.ITEM.register(
( stack, tintIndex ) -> tintIndex == 0 ? ((IColouredItem) stack.getItem()).getColour( stack ) : 0xFFFFFF,
ComputerCraft.Blocks.turtleNormal, ComputerCraft.Blocks.turtleAdvanced
);
}
private static BakedModel bake( ModelLoader loader, UnbakedModel model )
{
model.getTextureDependencies( loader::getOrLoadModel, new HashSet<>() );
SpriteAtlasTexture sprite = MinecraftClient.getInstance().getSpriteAtlas();
return model.bake( loader, sprite::getSprite, ModelRotation.X0_Y0 );
}
}

View File

@ -0,0 +1,89 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.command.text.TableFormatter;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.hud.ChatHud;
import net.minecraft.client.util.ChatMessages;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.MathHelper;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
import java.util.List;
public class ClientTableFormatter implements TableFormatter
{
public static final ClientTableFormatter INSTANCE = new ClientTableFormatter();
private static Int2IntOpenHashMap lastHeights = new Int2IntOpenHashMap();
private static TextRenderer renderer()
{
return MinecraftClient.getInstance().textRenderer;
}
@Override
@Nullable
public Text getPadding( Text component, int width )
{
int extraWidth = width - getWidth( component );
if( extraWidth <= 0 ) return null;
TextRenderer renderer = renderer();
float spaceWidth = renderer.getCharWidth( ' ' );
int spaces = MathHelper.floor( extraWidth / spaceWidth );
int extra = extraWidth - (int) (spaces * spaceWidth);
return ChatHelpers.coloured( StringUtils.repeat( ' ', spaces ) + StringUtils.repeat( (char) 712, extra ), Formatting.GRAY );
}
@Override
public int getColumnPadding()
{
return 3;
}
@Override
public int getWidth( Text component )
{
return renderer().getWidth( component.asFormattedString() );
}
@Override
public void writeLine( int id, Text component )
{
MinecraftClient mc = MinecraftClient.getInstance();
ChatHud chat = mc.inGameHud.getChatHud();
// Trim the text if it goes over the allowed length
int maxWidth = MathHelper.floor( chat.getWidth() / chat.getChatScale() );
List<Text> list = ChatMessages.breakRenderedChatMessageLines( component, maxWidth, mc.textRenderer, false, false );
if( !list.isEmpty() ) chat.addMessage( list.get( 0 ), id );
}
@Override
public int display( TableBuilder table )
{
ChatHud chat = MinecraftClient.getInstance().inGameHud.getChatHud();
int lastHeight = lastHeights.get( table.getId() );
int height = TableFormatter.super.display( table );
lastHeights.put( table.getId(), height );
for( int i = height; i < lastHeight; i++ ) chat.removeMessage( i + table.getId() );
return height;
}
}

View File

@ -0,0 +1,37 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client;
public final class FrameInfo
{
private static int tick;
private static long renderFrame;
private FrameInfo()
{
}
public static boolean getGlobalCursorBlink()
{
return (tick / 8) % 2 == 0;
}
public static long getRenderFrame()
{
return renderFrame;
}
public static void onTick()
{
tick++;
}
public static void onRenderFrame()
{
renderFrame++;
}
}

View File

@ -0,0 +1,23 @@
package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.GuiConfig;
import io.github.prospector.modmenu.api.ModMenuApi;
import net.minecraft.client.gui.screen.Screen;
import java.util.function.Function;
public class ModMenuIntegration implements ModMenuApi
{
@Override
public Function<Screen, ? extends Screen> getConfigScreenFactory()
{
return GuiConfig::getScreen;
}
@Override
public String getModId()
{
return ComputerCraft.MOD_ID;
}
}

View File

@ -0,0 +1,200 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.texture.TextureManager;
import net.minecraft.util.Identifier;
import org.lwjgl.opengl.GL11;
import java.util.Arrays;
public final class FixedWidthFontRenderer
{
private static final Identifier FONT = new Identifier( "computercraft", "textures/gui/term_font.png" );
public static final Identifier BACKGROUND = new Identifier( "computercraft", "textures/gui/term_background.png" );
public static final int FONT_HEIGHT = 9;
public static final int FONT_WIDTH = 6;
private static FixedWidthFontRenderer instance;
public static FixedWidthFontRenderer instance()
{
if( instance != null ) return instance;
return instance = new FixedWidthFontRenderer();
}
private final TextureManager m_textureManager;
private FixedWidthFontRenderer()
{
m_textureManager = MinecraftClient.getInstance().getTextureManager();
}
private static void greyscaleify( double[] rgb )
{
Arrays.fill( rgb, (rgb[0] + rgb[1] + rgb[2]) / 3.0f );
}
private void drawChar( BufferBuilder renderer, double x, double y, int index, int color, Palette p, boolean greyscale )
{
int column = index % 16;
int row = index / 16;
double[] colour = p.getColour( 15 - color );
if( greyscale )
{
greyscaleify( colour );
}
float r = (float) colour[0];
float g = (float) colour[1];
float b = (float) colour[2];
int xStart = 1 + column * (FONT_WIDTH + 2);
int yStart = 1 + row * (FONT_HEIGHT + 2);
renderer.vertex( x, y, 0.0 ).texture( xStart / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x, y + FONT_HEIGHT, 0.0 ).texture( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x + FONT_WIDTH, y, 0.0 ).texture( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x + FONT_WIDTH, y, 0.0 ).texture( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x, y + FONT_HEIGHT, 0.0 ).texture( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x + FONT_WIDTH, y + FONT_HEIGHT, 0.0 ).texture( (xStart + FONT_WIDTH) / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).next();
}
private void drawQuad( BufferBuilder renderer, double x, double y, int color, double width, Palette p, boolean greyscale )
{
double[] colour = p.getColour( 15 - color );
if( greyscale )
{
greyscaleify( colour );
}
float r = (float) colour[0];
float g = (float) colour[1];
float b = (float) colour[2];
renderer.vertex( x, y, 0.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x + width, y, 0.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x + width, y, 0.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( x + width, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).next();
}
private boolean isGreyScale( int colour )
{
return colour == 0 || colour == 15 || colour == 7 || colour == 8;
}
public void drawStringBackgroundPart( int x, int y, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )
{
// Draw the quads
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
renderer.begin( GL11.GL_TRIANGLES, VertexFormats.POSITION_COLOR );
if( leftMarginSize > 0.0 )
{
int colour1 = "0123456789abcdef".indexOf( backgroundColour.charAt( 0 ) );
if( colour1 < 0 || (greyScale && !isGreyScale( colour1 )) )
{
colour1 = 15;
}
drawQuad( renderer, x - leftMarginSize, y, colour1, leftMarginSize, p, greyScale );
}
if( rightMarginSize > 0.0 )
{
int colour2 = "0123456789abcdef".indexOf( backgroundColour.charAt( backgroundColour.length() - 1 ) );
if( colour2 < 0 || (greyScale && !isGreyScale( colour2 )) )
{
colour2 = 15;
}
drawQuad( renderer, x + backgroundColour.length() * FONT_WIDTH, y, colour2, rightMarginSize, p, greyScale );
}
for( int i = 0; i < backgroundColour.length(); i++ )
{
int colour = "0123456789abcdef".indexOf( backgroundColour.charAt( i ) );
if( colour < 0 || (greyScale && !isGreyScale( colour )) )
{
colour = 15;
}
drawQuad( renderer, x + i * FONT_WIDTH, y, colour, FONT_WIDTH, p, greyScale );
}
GlStateManager.disableTexture();
tessellator.draw();
GlStateManager.enableTexture();
}
public void drawStringTextPart( int x, int y, TextBuffer s, TextBuffer textColour, boolean greyScale, Palette p )
{
// Draw the quads
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
renderer.begin( GL11.GL_TRIANGLES, VertexFormats.POSITION_TEXTURE_COLOR );
for( int i = 0; i < s.length(); i++ )
{
// Switch colour
int colour = "0123456789abcdef".indexOf( textColour.charAt( i ) );
if( colour < 0 || (greyScale && !isGreyScale( colour )) )
{
colour = 0;
}
// Draw char
int index = s.charAt( i );
if( index < 0 || index > 255 )
{
index = '?';
}
drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale );
}
tessellator.draw();
}
public void drawString( TextBuffer s, int x, int y, TextBuffer textColour, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )
{
// Draw background
if( backgroundColour != null )
{
// Bind the background texture
m_textureManager.bindTextureInner( BACKGROUND );
// Draw the quads
drawStringBackgroundPart( x, y, backgroundColour, leftMarginSize, rightMarginSize, greyScale, p );
}
// Draw text
if( s != null && textColour != null )
{
// Bind the font texture
bindFont();
// Draw the quads
drawStringTextPart( x, y, s, textColour, greyScale, p );
}
}
public int getStringWidth( String s )
{
if( s == null )
{
return 0;
}
return s.length() * FONT_WIDTH;
}
public void bindFont()
{
m_textureManager.bindTextureInner( FONT );
GlStateManager.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
}
}

View File

@ -0,0 +1,169 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
import dan200.computercraft.shared.computer.blocks.TileComputer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Identifier;
import org.lwjgl.glfw.GLFW;
public class GuiComputer<T extends ScreenHandler> extends HandledScreen<T>
{
public static final Identifier BACKGROUND_NORMAL = new Identifier( ComputerCraft.MOD_ID, "textures/gui/corners_normal.png" );
public static final Identifier BACKGROUND_ADVANCED = new Identifier( ComputerCraft.MOD_ID, "textures/gui/corners_advanced.png" );
public static final Identifier BACKGROUND_COMMAND = new Identifier( ComputerCraft.MOD_ID, "textures/gui/corners_command.png" );
public static final Identifier BACKGROUND_COLOUR = new Identifier( ComputerCraft.MOD_ID, "textures/gui/corners_colour.png" );
private final ComputerFamily m_family;
private final ClientComputer m_computer;
private final int m_termWidth;
private final int m_termHeight;
private WidgetTerminal terminal;
private WidgetWrapper terminalWrapper;
public GuiComputer( T container, PlayerInventory player, ComputerFamily family, ClientComputer computer, int termWidth, int termHeight )
{
super( container, player, new LiteralText( "" ) );
m_family = family;
m_computer = computer;
m_termWidth = termWidth;
m_termHeight = termHeight;
terminal = null;
}
public static GuiComputer<ContainerComputer> create( int id, TileComputer computer, PlayerInventory player )
{
return new GuiComputer<>(
new ContainerComputer( id, computer ), player,
computer.getFamily(),
computer.createClientComputer(),
ComputerCraft.terminalWidth_computer,
ComputerCraft.terminalHeight_computer
);
}
@Override
protected void init()
{
minecraft.keyboard.enableRepeatEvents( true );
int termPxWidth = m_termWidth * FixedWidthFontRenderer.FONT_WIDTH;
int termPxHeight = m_termHeight * FixedWidthFontRenderer.FONT_HEIGHT;
backgroundWidth = termPxWidth + 4 + 24;
backgroundHeight = termPxHeight + 4 + 24;
super.init();
terminal = new WidgetTerminal( minecraft, () -> m_computer, m_termWidth, m_termHeight, 2, 2, 2, 2 );
terminalWrapper = new WidgetWrapper( terminal, 2 + 12 + x, 2 + 12 + y, termPxWidth, termPxHeight );
children.add( terminalWrapper );
setFocused( terminalWrapper );
}
@Override
public void removed()
{
super.removed();
children.remove( terminal );
terminal = null;
minecraft.keyboard.enableRepeatEvents( false );
}
@Override
public void tick()
{
super.tick();
terminal.update();
}
@Override
public void drawBackground( float partialTicks, int mouseX, int mouseY )
{
// Work out where to draw
int startX = terminalWrapper.getX() - 2;
int startY = terminalWrapper.getY() - 2;
int endX = startX + terminalWrapper.getWidth() + 4;
int endY = startY + terminalWrapper.getHeight() + 4;
// Draw terminal
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
// Draw a border around the terminal
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
switch( m_family )
{
case Normal:
default:
minecraft.getTextureManager().bindTextureInner( BACKGROUND_NORMAL );
break;
case Advanced:
minecraft.getTextureManager().bindTextureInner( BACKGROUND_ADVANCED );
break;
case Command:
minecraft.getTextureManager().bindTextureInner( BACKGROUND_COMMAND );
break;
}
blit( startX - 12, startY - 12, 12, 28, 12, 12 );
blit( startX - 12, endY, 12, 40, 12, 12 );
blit( endX, startY - 12, 24, 28, 12, 12 );
blit( endX, endY, 24, 40, 12, 12 );
blit( startX, startY - 12, 0, 0, endX - startX, 12 );
blit( startX, endY, 0, 12, endX - startX, 12 );
blit( startX - 12, startY, 0, 28, 12, endY - startY );
blit( endX, startY, 36, 28, 12, endY - startY );
}
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
renderBackground( 0 );
super.render( mouseX, mouseY, partialTicks );
drawMouseoverTooltip( mouseX, mouseY );
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
// When pressing tab, send it to the computer first
return (key == GLFW.GLFW_KEY_TAB && getFocused() == terminalWrapper && terminalWrapper.keyPressed( key, scancode, modifiers ))
|| super.keyPressed( key, scancode, modifiers );
}
@Override
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
// Make sure drag events are propagated to children
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );
}
@Override
public boolean mouseReleased( double x, double y, int button )
{
// Make sure release events are propagated to children
return (getFocused() != null && getFocused().mouseReleased( x, y, button ))
|| super.mouseReleased( x, y, button );
}
}

View File

@ -0,0 +1,220 @@
package dan200.computercraft.client.gui;
import dan200.computercraft.core.apis.http.websocket.Websocket;
import dan200.computercraft.shared.util.Config;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.client.gui.screen.Screen;
import java.util.Arrays;
public final class GuiConfig
{
private GuiConfig() {}
public static Screen getScreen( Screen parentScreen )
{
Config config = Config.get();
ConfigBuilder builder = ConfigBuilder.create().setParentScreen( parentScreen ).setTitle( "gui.computercraft.config.title" ).setSavingRunnable( () -> {
Config.save();
Config.sync();
} );
ConfigEntryBuilder entryBuilder = ConfigEntryBuilder.create();
builder.getOrCreateCategory( key( "general" ) )
.addEntry( entryBuilder.startIntField( key( "computer_space_limit" ), config.general.computer_space_limit )
.setSaveConsumer( v -> config.general.computer_space_limit = v )
.setDefaultValue( Config.defaultConfig.general.computer_space_limit )
.build() )
.addEntry( entryBuilder.startIntField( key( "floppy_space_limit" ), config.general.floppy_space_limit )
.setSaveConsumer( v -> config.general.floppy_space_limit = v )
.setDefaultValue( Config.defaultConfig.general.floppy_space_limit )
.build() )
.addEntry( entryBuilder.startIntField( key( "maximum_open_files" ), config.general.maximum_open_files )
.setSaveConsumer( v -> config.general.maximum_open_files = v )
.setDefaultValue( Config.defaultConfig.general.maximum_open_files )
.setMin( 0 )
.build() )
.addEntry( entryBuilder.startBooleanToggle( key( "disable_lua51_features" ), config.general.disable_lua51_features )
.setSaveConsumer( v -> config.general.disable_lua51_features = v )
.setDefaultValue( Config.defaultConfig.general.disable_lua51_features )
.build() )
.addEntry( entryBuilder.startStrField( key( "default_computer_settings" ), config.general.default_computer_settings )
.setSaveConsumer( v -> config.general.default_computer_settings = v )
.setDefaultValue( Config.defaultConfig.general.default_computer_settings )
.build() )
.addEntry( entryBuilder.startBooleanToggle( key( "debug_enabled" ), config.general.debug_enabled )
.setSaveConsumer( v -> config.general.debug_enabled = v )
.setDefaultValue( Config.defaultConfig.general.debug_enabled )
.build() )
.addEntry( entryBuilder.startBooleanToggle( key( "log_computer_errors" ), config.general.log_computer_errors )
.setSaveConsumer( v -> config.general.log_computer_errors = v )
.setDefaultValue( Config.defaultConfig.general.log_computer_errors )
.build() );
builder.getOrCreateCategory( key( "execution" ) )
.addEntry( entryBuilder.startIntField( key( "execution.computer_threads" ), config.execution.computer_threads )
.setSaveConsumer( v -> config.execution.computer_threads = v )
.setDefaultValue( Config.defaultConfig.execution.computer_threads )
.setMin( 1 )
.requireRestart()
.build() )
.addEntry( entryBuilder.startLongField( key( "execution.max_main_global_time" ), config.execution.max_main_global_time )
.setSaveConsumer( v -> config.execution.max_main_global_time = v )
.setDefaultValue( Config.defaultConfig.execution.max_main_global_time )
.setMin( 1 )
.build() )
.addEntry( entryBuilder.startLongField( key( "execution.max_main_computer_time" ), config.execution.max_main_computer_time )
.setSaveConsumer( v -> config.execution.max_main_computer_time = v )
.setDefaultValue( Config.defaultConfig.execution.max_main_computer_time )
.setMin( 1 )
.build() );
builder.getOrCreateCategory( key( "http" ) )
.addEntry( entryBuilder.startBooleanToggle( key( "http.enabled" ), config.http.enabled )
.setSaveConsumer( v -> config.http.enabled = v )
.setDefaultValue( Config.defaultConfig.http.enabled )
.build() )
.addEntry( entryBuilder.startBooleanToggle( key( "http.websocket_enabled" ), config.http.websocket_enabled )
.setSaveConsumer( v -> config.http.websocket_enabled = v )
.setDefaultValue( Config.defaultConfig.http.websocket_enabled )
.build() )
.addEntry( entryBuilder.startStrList( key( "http.whitelist" ), Arrays.asList( config.http.whitelist ) )
.setSaveConsumer( v -> config.http.whitelist = v.toArray( new String[0] ) )
.setDefaultValue( Arrays.asList( Config.defaultConfig.http.whitelist ) )
.build() )
.addEntry( entryBuilder.startStrList( key( "http.blacklist" ), Arrays.asList( config.http.blacklist ) )
.setSaveConsumer( v -> config.http.blacklist = v.toArray( new String[0] ) )
.setDefaultValue( Arrays.asList( Config.defaultConfig.http.blacklist ) )
.build() )
.addEntry( entryBuilder.startIntField( key( "http.timeout" ), config.http.timeout )
.setSaveConsumer( v -> config.http.timeout = v )
.setDefaultValue( Config.defaultConfig.http.timeout )
.setMin( 0 )
.build() )
.addEntry( entryBuilder.startIntField( key( "http.max_requests" ), config.http.max_requests )
.setSaveConsumer( v -> config.http.max_requests = v )
.setDefaultValue( Config.defaultConfig.http.max_requests )
.setMin( 0 )
.build() )
.addEntry( entryBuilder.startLongField( key( "http.max_download" ), config.http.max_download )
.setSaveConsumer( v -> config.http.max_download = v )
.setDefaultValue( Config.defaultConfig.http.max_download )
.setMin( 0 )
.build() )
.addEntry( entryBuilder.startLongField( key( "http.max_upload" ), config.http.max_upload )
.setSaveConsumer( v -> config.http.max_upload = v )
.setDefaultValue( Config.defaultConfig.http.max_upload )
.setMin( 0 )
.build() )
.addEntry( entryBuilder.startIntField( key( "http.max_websockets" ), config.http.max_websockets )
.setSaveConsumer( v -> config.http.max_websockets = v )
.setDefaultValue( Config.defaultConfig.http.max_websockets )
.setMin( 1 )
.build() )
.addEntry( entryBuilder.startIntField( key( "http.max_websocket_message" ), config.http.max_websocket_message )
.setSaveConsumer( v -> config.http.max_websocket_message = v )
.setDefaultValue( Config.defaultConfig.http.max_websocket_message )
.setMin( 0 )
.setMax( Websocket.MAX_MESSAGE_SIZE )
.build() );
builder.getOrCreateCategory( key( "peripheral" ) )
.addEntry( entryBuilder.startBooleanToggle( key( "peripheral.command_block_enabled" ), config.peripheral.command_block_enabled )
.setSaveConsumer( v -> config.peripheral.command_block_enabled = v )
.setDefaultValue( Config.defaultConfig.peripheral.command_block_enabled )
.build() )
.addEntry( entryBuilder.startIntField( key( "peripheral.modem_range" ), config.peripheral.modem_range )
.setSaveConsumer( v -> config.peripheral.modem_range = v )
.setDefaultValue( Config.defaultConfig.peripheral.modem_range )
.setMin( 0 )
.setMax( Config.MODEM_MAX_RANGE )
.build() )
.addEntry( entryBuilder.startIntField( key( "peripheral.modem_high_altitude_range" ), config.peripheral.modem_high_altitude_range )
.setSaveConsumer( v -> config.peripheral.modem_high_altitude_range = v )
.setDefaultValue( Config.defaultConfig.peripheral.modem_high_altitude_range )
.setMin( 0 )
.setMax( Config.MODEM_MAX_RANGE )
.build() )
.addEntry( entryBuilder.startIntField( key( "peripheral.modem_range_during_storm" ), config.peripheral.modem_range_during_storm )
.setSaveConsumer( v -> config.peripheral.modem_range_during_storm = v )
.setDefaultValue( Config.defaultConfig.peripheral.modem_range_during_storm )
.setMin( 0 )
.setMax( Config.MODEM_MAX_RANGE )
.build() )
.addEntry( entryBuilder.startIntField( key( "peripheral.modem_high_altitude_range_during_storm" ), config.peripheral.modem_high_altitude_range_during_storm )
.setSaveConsumer( v -> config.peripheral.modem_high_altitude_range_during_storm = v )
.setDefaultValue( Config.defaultConfig.peripheral.modem_high_altitude_range_during_storm )
.setMin( 0 )
.setMax( Config.MODEM_MAX_RANGE )
.build() )
.addEntry( entryBuilder.startIntField( key( "peripheral.max_notes_per_tick" ), config.peripheral.max_notes_per_tick )
.setSaveConsumer( v -> config.peripheral.max_notes_per_tick = v )
.setDefaultValue( Config.defaultConfig.peripheral.max_notes_per_tick )
.setMin( 1 )
.build() );
builder.getOrCreateCategory( key( "turtle" ) )
.addEntry( entryBuilder.startBooleanToggle( key( "turtle.need_fuel" ), config.turtle.need_fuel )
.setSaveConsumer( v -> config.turtle.need_fuel = v )
.setDefaultValue( Config.defaultConfig.turtle.need_fuel )
.build() )
.addEntry( entryBuilder.startIntField( key( "turtle.normal_fuel_limit" ), config.turtle.normal_fuel_limit )
.setSaveConsumer( v -> config.turtle.normal_fuel_limit = v )
.setDefaultValue( Config.defaultConfig.turtle.normal_fuel_limit )
.setMin( 0 )
.build() )
.addEntry( entryBuilder.startIntField( key( "turtle.advanced_fuel_limit" ), config.turtle.advanced_fuel_limit )
.setSaveConsumer( v -> config.turtle.advanced_fuel_limit = v )
.setDefaultValue( Config.defaultConfig.turtle.advanced_fuel_limit )
.setMin( 0 )
.build() )
.addEntry( entryBuilder.startBooleanToggle( key( "turtle.obey_block_protection" ), config.turtle.obey_block_protection )
.setSaveConsumer( v -> config.turtle.obey_block_protection = v )
.setDefaultValue( Config.defaultConfig.turtle.obey_block_protection )
.build() )
.addEntry( entryBuilder.startStrList( key( "turtle.disabled_actions" ), Arrays.asList( config.turtle.disabled_actions ) )
.setSaveConsumer( v -> config.turtle.disabled_actions = v.toArray( new String[0] ) )
.setDefaultValue( Arrays.asList( Config.defaultConfig.turtle.disabled_actions ) )
.build() );
return builder.build();
}
private static String key( String name )
{
return "gui.computercraft.config." + name;
}
}

View File

@ -0,0 +1,49 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.Identifier;
public class GuiDiskDrive extends HandledScreen<ContainerDiskDrive>
{
private static final Identifier BACKGROUND = new Identifier( "computercraft", "textures/gui/disk_drive.png" );
public GuiDiskDrive( ContainerDiskDrive container, PlayerInventory inventory )
{
super( container, inventory, ComputerCraft.Blocks.diskDrive.getName() );
}
@Override
protected void drawForeground( int par1, int par2 )
{
String title = getTitle().asFormattedString();
font.draw( title, (backgroundWidth - font.getWidth( title )) / 2.0f, 6, 0x404040 );
font.draw( I18n.translate( "container.inventory" ), 8, (backgroundHeight - 96) + 2, 0x404040 );
}
@Override
protected void drawBackground( float partialTicks, int mouseX, int mouseY )
{
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
minecraft.getTextureManager().bindTextureInner( BACKGROUND );
blit( x, y, 0, 0, backgroundWidth, backgroundHeight );
}
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
renderBackground();
super.render( mouseX, mouseY, partialTicks );
drawMouseoverTooltip( mouseX, mouseY );
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
public class GuiPocketComputer extends GuiComputer<ContainerPocketComputer>
{
public GuiPocketComputer( ContainerPocketComputer container, PlayerInventory player )
{
super(
container, player,
getFamily( container.getStack() ),
ItemPocketComputer.createClientComputer( container.getStack() ),
ComputerCraft.terminalWidth_pocketComputer,
ComputerCraft.terminalHeight_pocketComputer
);
}
private static ComputerFamily getFamily( ItemStack stack )
{
Item item = stack.getItem();
return item instanceof ItemPocketComputer ? ((ItemPocketComputer) item).getFamily() : ComputerFamily.Normal;
}
}

View File

@ -0,0 +1,51 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.printer.ContainerPrinter;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.Identifier;
public class GuiPrinter extends HandledScreen<ContainerPrinter>
{
private static final Identifier BACKGROUND = new Identifier( "computercraft", "textures/gui/printer.png" );
public GuiPrinter( ContainerPrinter container, PlayerInventory player )
{
super( container, player, ComputerCraft.Blocks.printer.getName() );
}
@Override
protected void drawForeground( int mouseX, int mouseY )
{
String title = getTitle().asFormattedString();
font.draw( title, (backgroundWidth - font.getWidth( title )) / 2.0f, 6, 0x404040 );
font.draw( I18n.translate( "container.inventory" ), 8, backgroundHeight - 96 + 2, 0x404040 );
}
@Override
protected void drawBackground( float f, int i, int j )
{
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
minecraft.getTextureManager().bindTextureInner( BACKGROUND );
blit( x, y, 0, 0, backgroundWidth, backgroundHeight );
if( handler.isPrinting() ) blit( x + 34, y + 21, 176, 0, 25, 45 );
}
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
renderBackground();
super.render( mouseX, mouseY, partialTicks );
drawMouseoverTooltip( mouseX, mouseY );
}
}

View File

@ -0,0 +1,109 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.common.ContainerHeldItem;
import dan200.computercraft.shared.media.items.ItemPrintout;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.entity.player.PlayerInventory;
import org.lwjgl.glfw.GLFW;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
public class GuiPrintout extends HandledScreen<ContainerHeldItem>
{
private final boolean m_book;
private final int m_pages;
private final TextBuffer[] m_text;
private final TextBuffer[] m_colours;
private int m_page;
public GuiPrintout( ContainerHeldItem container, PlayerInventory player )
{
super( container, player, container.getStack().getName() );
backgroundHeight = Y_SIZE;
String[] text = ItemPrintout.getText( container.getStack() );
m_text = new TextBuffer[text.length];
for( int i = 0; i < m_text.length; i++ ) m_text[i] = new TextBuffer( text[i] );
String[] colours = ItemPrintout.getColours( container.getStack() );
m_colours = new TextBuffer[colours.length];
for( int i = 0; i < m_colours.length; i++ ) m_colours[i] = new TextBuffer( colours[i] );
m_page = 0;
m_pages = Math.max( m_text.length / ItemPrintout.LINES_PER_PAGE, 1 );
m_book = ((ItemPrintout) container.getStack().getItem()).getType() == ItemPrintout.Type.BOOK;
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
if( super.keyPressed( key, scancode, modifiers ) ) return true;
if( key == GLFW.GLFW_KEY_RIGHT )
{
if( m_page < m_pages - 1 ) m_page++;
return true;
}
if( key == GLFW.GLFW_KEY_LEFT )
{
if( m_page > 0 ) m_page--;
return true;
}
return false;
}
@Override
public boolean mouseScrolled( double x, double y, double delta )
{
if( super.mouseScrolled( x, y, delta ) ) return true;
if( delta < 0 )
{
// Scroll up goes to the next page
if( m_page < m_pages - 1 ) m_page++;
return true;
}
if( delta > 0 )
{
// Scroll down goes to the previous page
if( m_page > 0 ) m_page--;
return true;
}
return false;
}
@Override
public void drawBackground( float partialTicks, int mouseX, int mouseY )
{
// Draw the printout
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableDepthTest();
drawBorder( x, y, blitOffset, m_page, m_pages, m_book );
drawText( x + X_TEXT_MARGIN, y + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours );
}
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
// We must take the background further back in order to not overlap with our printed pages.
blitOffset--;
renderBackground();
blitOffset++;
super.render( mouseX, mouseY, partialTicks );
drawMouseoverTooltip( mouseX, mouseY );
}
}

View File

@ -0,0 +1,141 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.Identifier;
import org.lwjgl.glfw.GLFW;
public class GuiTurtle extends HandledScreen<ContainerTurtle>
{
private static final Identifier BACKGROUND_NORMAL = new Identifier( "computercraft", "textures/gui/turtle_normal.png" );
private static final Identifier BACKGROUND_ADVANCED = new Identifier( "computercraft", "textures/gui/turtle_advanced.png" );
private ContainerTurtle m_container;
private final ComputerFamily m_family;
private final ClientComputer m_computer;
private WidgetTerminal terminal;
private WidgetWrapper terminalWrapper;
public GuiTurtle( TileTurtle turtle, ContainerTurtle container, PlayerInventory player )
{
super( container, player, turtle.getDisplayName() );
m_container = container;
m_family = turtle.getFamily();
m_computer = turtle.getClientComputer();
backgroundWidth = 254;
backgroundHeight = 217;
}
@Override
protected void init()
{
super.init();
minecraft.keyboard.enableRepeatEvents( true );
int termPxWidth = ComputerCraft.terminalWidth_turtle * FixedWidthFontRenderer.FONT_WIDTH;
int termPxHeight = ComputerCraft.terminalHeight_turtle * FixedWidthFontRenderer.FONT_HEIGHT;
terminal = new WidgetTerminal(
minecraft, () -> m_computer,
ComputerCraft.terminalWidth_turtle,
ComputerCraft.terminalHeight_turtle,
2, 2, 2, 2
);
terminalWrapper = new WidgetWrapper( terminal, 2 + 8 + x, 2 + 8 + y, termPxWidth, termPxHeight );
children.add( terminalWrapper );
setFocused( terminalWrapper );
}
@Override
public void removed()
{
super.removed();
children.remove( terminal );
terminal = null;
minecraft.keyboard.enableRepeatEvents( false );
}
@Override
public void tick()
{
super.tick();
terminal.update();
}
private void drawSelectionSlot( boolean advanced )
{
// Draw selection slot
int slot = m_container.getSelectedSlot();
if( slot >= 0 )
{
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
int slotX = slot % 4;
int slotY = slot / 4;
minecraft.getTextureManager().bindTextureInner( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
blit( x + m_container.m_turtleInvStartX - 2 + slotX * 18, y + m_container.m_playerInvStartY - 2 + slotY * 18, 0, 217, 24, 24 );
}
}
@Override
protected void drawBackground( float partialTicks, int mouseX, int mouseY )
{
// Draw term
boolean advanced = m_family == ComputerFamily.Advanced;
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
// Draw border/inventory
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
minecraft.getTextureManager().bindTextureInner( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
blit( x, y, 0, 0, backgroundWidth, backgroundHeight );
drawSelectionSlot( advanced );
}
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
renderBackground();
super.render( mouseX, mouseY, partialTicks );
drawMouseoverTooltip( mouseX, mouseY );
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
return (key == GLFW.GLFW_KEY_TAB && getFocused() == terminalWrapper && terminalWrapper.keyPressed( key, scancode, modifiers ))
|| super.keyPressed( key, scancode, modifiers );
}
@Override
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );
}
@Override
public boolean mouseReleased( double x, double y, int button )
{
return (getFocused() != null && getFocused().mouseReleased( x, y, button ))
|| super.mouseReleased( x, y, button );
}
}

View File

@ -0,0 +1,431 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui.widgets;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.SharedConstants;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
import java.util.BitSet;
import java.util.function.Supplier;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.BACKGROUND;
public class WidgetTerminal implements Element
{
private static final float TERMINATE_TIME = 0.5f;
private final MinecraftClient minecraft;
private final Supplier<ClientComputer> computer;
private final int termWidth;
private final int termHeight;
private boolean focused;
private float terminateTimer = -1;
private float rebootTimer = -1;
private float shutdownTimer = -1;
private int lastMouseButton = -1;
private int lastMouseX = -1;
private int lastMouseY = -1;
private final int leftMargin;
private final int rightMargin;
private final int topMargin;
private final int bottomMargin;
private final BitSet keysDown = new BitSet( 256 );
public WidgetTerminal( MinecraftClient minecraft, Supplier<ClientComputer> computer, int termWidth, int termHeight, int leftMargin, int rightMargin, int topMargin, int bottomMargin )
{
this.minecraft = minecraft;
this.computer = computer;
this.termWidth = termWidth;
this.termHeight = termHeight;
this.leftMargin = leftMargin;
this.rightMargin = rightMargin;
this.topMargin = topMargin;
this.bottomMargin = bottomMargin;
}
@Override
public boolean charTyped( char ch, int modifiers )
{
if( ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255 ) // printable chars in byte range
{
// Queue the "char" event
queueEvent( "char", Character.toString( ch ) );
}
return true;
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
if( key == GLFW.GLFW_KEY_ESCAPE ) return false;
if( (modifiers & GLFW.GLFW_MOD_CONTROL) != 0 )
{
switch( key )
{
case GLFW.GLFW_KEY_T:
if( terminateTimer < 0 ) terminateTimer = 0;
return true;
case GLFW.GLFW_KEY_S:
if( shutdownTimer < 0 ) shutdownTimer = 0;
return true;
case GLFW.GLFW_KEY_R:
if( rebootTimer < 0 ) rebootTimer = 0;
return true;
case GLFW.GLFW_KEY_V:
// Ctrl+V for paste
String clipboard = minecraft.keyboard.getClipboard();
if( clipboard != null )
{
// Clip to the first occurrence of \r or \n
int newLineIndex1 = clipboard.indexOf( "\r" );
int newLineIndex2 = clipboard.indexOf( "\n" );
if( newLineIndex1 >= 0 && newLineIndex2 >= 0 )
{
clipboard = clipboard.substring( 0, Math.min( newLineIndex1, newLineIndex2 ) );
}
else if( newLineIndex1 >= 0 )
{
clipboard = clipboard.substring( 0, newLineIndex1 );
}
else if( newLineIndex2 >= 0 )
{
clipboard = clipboard.substring( 0, newLineIndex2 );
}
// Filter the string
clipboard = SharedConstants.stripInvalidChars( clipboard );
if( !clipboard.isEmpty() )
{
// Clip to 512 characters and queue the event
if( clipboard.length() > 512 ) clipboard = clipboard.substring( 0, 512 );
queueEvent( "paste", clipboard );
}
return true;
}
}
}
if( key >= 0 && terminateTimer < 0 && rebootTimer < 0 && shutdownTimer < 0 )
{
// Queue the "key" event and add to the down set
boolean repeat = keysDown.get( key );
keysDown.set( key );
IComputer computer = this.computer.get();
if( computer != null ) computer.keyDown( key, repeat );
}
return true;
}
@Override
public boolean keyReleased( int key, int scancode, int modifiers )
{
// Queue the "key_up" event and remove from the down set
if( key >= 0 && keysDown.get( key ) )
{
keysDown.set( key, false );
IComputer computer = this.computer.get();
if( computer != null ) computer.keyUp( key );
}
switch( key )
{
case GLFW.GLFW_KEY_T:
terminateTimer = -1;
break;
case GLFW.GLFW_KEY_R:
rebootTimer = -1;
break;
case GLFW.GLFW_KEY_S:
shutdownTimer = -1;
break;
case GLFW.GLFW_KEY_LEFT_CONTROL:
case GLFW.GLFW_KEY_RIGHT_CONTROL:
terminateTimer = rebootTimer = shutdownTimer = -1;
break;
}
return true;
}
@Override
public boolean mouseClicked( double mouseX, double mouseY, int button )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
computer.mouseClick( button + 1, charX + 1, charY + 1 );
lastMouseButton = button;
lastMouseX = charX;
lastMouseY = charY;
}
return true;
}
@Override
public boolean mouseReleased( double mouseX, double mouseY, int button )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
if( lastMouseButton == button )
{
computer.mouseUp( lastMouseButton + 1, charX + 1, charY + 1 );
lastMouseButton = -1;
}
lastMouseX = charX;
lastMouseY = charY;
}
return false;
}
@Override
public boolean mouseDragged( double mouseX, double mouseY, int button, double v2, double v3 )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
computer.mouseDrag( button + 1, charX + 1, charY + 1 );
lastMouseX = charX;
lastMouseY = charY;
lastMouseButton = button;
}
return false;
}
@Override
public boolean mouseScrolled( double mouseX, double mouseY, double delta )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || delta == 0 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
computer.mouseScroll( delta < 0 ? 1 : -1, charX + 1, charY + 1 );
lastMouseX = charX;
lastMouseY = charY;
}
return true;
}
public void update()
{
if( terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME )
{
queueEvent( "terminate" );
}
if( shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.shutdown();
}
if( rebootTimer >= 0 && rebootTimer < TERMINATE_TIME && (rebootTimer += 0.05f) > TERMINATE_TIME )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.reboot();
}
}
@Override
public boolean changeFocus( boolean reverse )
{
if( focused )
{
// When blurring, we should make all keys go up
for( int key = 0; key < keysDown.size(); key++ )
{
if( keysDown.get( key ) ) queueEvent( "key_up", key );
}
keysDown.clear();
// When blurring, we should make the last mouse button go up
if( lastMouseButton > 0 )
{
IComputer computer = this.computer.get();
if( computer != null ) computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
lastMouseButton = -1;
}
shutdownTimer = terminateTimer = rebootTimer = -1;
}
focused = !focused;
return true;
}
public void draw( int originX, int originY )
{
synchronized( computer )
{
// Draw the screen contents
ClientComputer computer = this.computer.get();
Terminal terminal = computer != null ? computer.getTerminal() : null;
if( terminal != null )
{
// Draw the terminal
boolean greyscale = !computer.isColour();
Palette palette = terminal.getPalette();
// Get the data from the terminal first
// Unfortunately we have to keep the lock for the whole of drawing, so the text doesn't change under us.
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
boolean tblink = terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink();
int tw = terminal.getWidth();
int th = terminal.getHeight();
int tx = terminal.getCursorX();
int ty = terminal.getCursorY();
// Draw margins
TextBuffer emptyLine = new TextBuffer( ' ', tw );
if( topMargin > 0 )
{
fontRenderer.drawString( emptyLine, originX, originY - topMargin,
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ),
leftMargin, rightMargin, greyscale, palette );
}
if( bottomMargin > 0 )
{
fontRenderer.drawString( emptyLine, originX, originY + bottomMargin + (th - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
terminal.getTextColourLine( th - 1 ), terminal.getBackgroundColourLine( th - 1 ),
leftMargin, rightMargin, greyscale, palette );
}
// Draw lines
int y = originY;
for( int line = 0; line < th; line++ )
{
TextBuffer text = terminal.getLine( line );
TextBuffer colour = terminal.getTextColourLine( line );
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
fontRenderer.drawString( text, originX, y, colour, backgroundColour, leftMargin, rightMargin, greyscale, palette );
y += FixedWidthFontRenderer.FONT_HEIGHT;
}
if( tblink && tx >= 0 && ty >= 0 && tx < tw && ty < th )
{
TextBuffer cursor = new TextBuffer( '_', 1 );
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
cursor,
originX + FixedWidthFontRenderer.FONT_WIDTH * tx,
originY + FixedWidthFontRenderer.FONT_HEIGHT * ty,
cursorColour, null,
0, 0,
greyscale,
palette
);
}
}
else
{
// Draw a black background
Colour black = Colour.Black;
GlStateManager.color4f( black.getR(), black.getG(), black.getB(), 1.0f );
try
{
int x = originX - leftMargin;
int y = originY - rightMargin;
int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin;
int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin;
minecraft.getTextureManager().bindTextureInner( BACKGROUND );
Tessellator tesslector = Tessellator.getInstance();
BufferBuilder buffer = tesslector.getBuffer();
buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_TEXTURE );
buffer.vertex( x, y + height, 0 ).texture( 0 / 256.0, height / 256.0 ).next();
buffer.vertex( x + width, y + height, 0 ).texture( width / 256.0, height / 256.0 ).next();
buffer.vertex( x + width, y, 0 ).texture( width / 256.0, 0 / 256.0 ).next();
buffer.vertex( x, y, 0 ).texture( 0 / 256.0, 0 / 256.0 ).next();
tesslector.draw();
}
finally
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
}
}
}
}
private void queueEvent( String event )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.queueEvent( event );
}
private void queueEvent( String event, Object... args )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.queueEvent( event, args );
}
}

View File

@ -0,0 +1,113 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.gui.widgets;
import net.minecraft.client.gui.Element;
public class WidgetWrapper implements Element
{
private final Element listener;
private final int x;
private final int y;
private final int width;
private final int height;
public WidgetWrapper( Element listener, int x, int y, int width, int height )
{
this.listener = listener;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void mouseMoved( double x, double y )
{
double dx = x - this.x, dy = y - this.y;
if( dx >= 0 && dx < width && dy >= 0 && dy < height ) listener.mouseMoved( dx, dy );
}
@Override
public boolean changeFocus( boolean reverse )
{
return listener.changeFocus( reverse );
}
@Override
public boolean mouseClicked( double x, double y, int button )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseClicked( dx, dy, button );
}
@Override
public boolean mouseReleased( double x, double y, int button )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseReleased( dx, dy, button );
}
@Override
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseDragged( dx, dy, button, deltaX, deltaY );
}
@Override
public boolean mouseScrolled( double x, double y, double delta )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseScrolled( dx, dy, delta );
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
return listener.keyPressed( key, scancode, modifiers );
}
@Override
public boolean keyReleased( int key, int scancode, int modifiers )
{
return listener.keyReleased( key, scancode, modifiers );
}
@Override
public boolean charTyped( char character, int modifiers )
{
return listener.charTyped( character, modifiers );
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
@Override
public boolean isMouseOver( double x, double y )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height;
}
}

View File

@ -0,0 +1,96 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.proxy;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.ClientRegistry;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.TileEntityCableRenderer;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtleModelLoader;
import dan200.computercraft.shared.computer.blocks.TileComputer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.network.container.*;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry;
import net.fabricmc.fabric.api.client.render.BlockEntityRendererRegistry;
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback;
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.screen.ArrayPropertyDelegate;
public final class ComputerCraftProxyClient
{
public static void setup()
{
registerContainers();
// Setup TESRs
BlockEntityRendererRegistry.INSTANCE.register( TileMonitor.class, new TileEntityMonitorRenderer() );
BlockEntityRendererRegistry.INSTANCE.register( TileCable.class, new TileEntityCableRenderer() );
BlockEntityRendererRegistry.INSTANCE.register( TileTurtle.class, new TileEntityTurtleRenderer() );
ClientRegistry.onItemColours();
ClientSpriteRegistryCallback.event( SpriteAtlasTexture.BLOCK_ATLAS_TEX ).register( ClientRegistry::onTextureStitchEvent );
ModelLoadingRegistry.INSTANCE.registerAppender( ClientRegistry::onModelBakeEvent );
ModelLoadingRegistry.INSTANCE.registerResourceProvider( loader -> ( name, context ) ->
TurtleModelLoader.INSTANCE.accepts( name ) ? TurtleModelLoader.INSTANCE.loadModel( name ) : null
);
ClientTickCallback.EVENT.register( client -> FrameInfo.onTick() );
}
private static void registerContainers()
{
ContainerType.registerGui( TileEntityContainerType::computer, ( id, packet, player ) ->
GuiComputer.create( id, (TileComputer) packet.getTileEntity( player ), player.inventory ) );
ContainerType.registerGui( TileEntityContainerType::diskDrive, GuiDiskDrive::new );
ContainerType.registerGui( TileEntityContainerType::printer, GuiPrinter::new );
ContainerType.registerGui( TileEntityContainerType::turtle, ( id, packet, player ) -> {
TileTurtle turtle = (TileTurtle) packet.getTileEntity( player );
return new GuiTurtle( turtle,
new ContainerTurtle( id, player.inventory, new SimpleInventory( TileTurtle.INVENTORY_SIZE ), new ArrayPropertyDelegate( 1 ) ),
player.inventory
);
} );
ContainerType.registerGui( PocketComputerContainerType::new, GuiPocketComputer::new );
ContainerType.registerGui( PrintoutContainerType::new, GuiPrintout::new );
ContainerType.registerGui( ViewComputerContainerType::new, ( id, packet, player ) -> {
ClientComputer computer = ComputerCraft.clientComputerRegistry.get( packet.instanceId );
if( computer == null )
{
ComputerCraft.clientComputerRegistry.add( packet.instanceId, computer = new ClientComputer( packet.instanceId ) );
}
ContainerViewComputer container = new ContainerViewComputer( id, computer );
return new GuiComputer<>( container, player.inventory, packet.family, computer, packet.width, packet.height );
} );
}
/*
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public static final class ForgeHandlers
{
@SubscribeEvent
public static void onWorldUnload( WorldEvent.Unload event )
{
if( event.getWorld().isRemote() )
{
ClientMonitor.destroyAll();
}
}
}
*/
}

View File

@ -0,0 +1,74 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public final class CableHighlightRenderer
{
private CableHighlightRenderer()
{
}
/**
* Draw an outline for a specific part of a cable "Multipart".
*
* @see WorldRenderer#drawHighlightedBlockOutline(Entity, HitResult, int, float)
*/
public static boolean drawHighlight( Camera camera, BlockHitResult hit )
{
MinecraftClient mc = MinecraftClient.getInstance();
BlockPos pos = hit.getBlockPos();
World world = mc.world;
BlockState state = world.getBlockState( pos );
// We only care about instances with both cable and modem.
if( state.getBlock() != ComputerCraft.Blocks.cable || state.get( BlockCable.MODEM ).getFacing() == null || !state.get( BlockCable.CABLE ) )
{
return false;
}
GlStateManager.enableBlend();
GlStateManager.blendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GlStateManager.lineWidth( Math.max( 2.5F, mc.window.getFramebufferWidth() / 1920.0F * 2.5F ) );
GlStateManager.disableTexture();
GlStateManager.depthMask( false );
GlStateManager.matrixMode( GL11.GL_PROJECTION );
GlStateManager.pushMatrix();
GlStateManager.scalef( 1.0F, 1.0F, 0.999F );
VoxelShape shape = WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.getPos().subtract( pos.getX(), pos.getY(), pos.getZ() ) )
? CableShapes.getModemShape( state )
: CableShapes.getCableShape( state );
WorldRenderer.drawShapeOutline( shape, pos.getX() - camera.getPos().getX(), pos.getY() - camera.getPos().getY(), pos.getZ() - camera.getPos().getZ(), 0.0F, 0.0F, 0.0F, 0.4F );
GlStateManager.popMatrix();
GlStateManager.matrixMode( GL11.GL_MODELVIEW );
GlStateManager.depthMask( true );
GlStateManager.enableTexture();
GlStateManager.disableBlend();
return true;
}
}

View File

@ -0,0 +1,118 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.shared.mixed.MixedFirstPersonRenderer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Arm;
import net.minecraft.util.Hand;
import net.minecraft.util.math.MathHelper;
public abstract class ItemMapLikeRenderer
{
/**
* The main rendering method for the item
*
* @param stack The stack to render
* @see FirstPersonRenderer#renderFirstPersonMap(ItemStack)
*/
protected abstract void renderItem( ItemStack stack );
public void renderItemFirstPerson( Hand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack )
{
PlayerEntity player = MinecraftClient.getInstance().player;
GlStateManager.pushMatrix();
if( hand == Hand.MAIN_HAND && player.getOffHandStack().isEmpty() )
{
renderItemFirstPersonCenter( pitch, equipProgress, swingProgress, stack );
}
else
{
renderItemFirstPersonSide(
hand == Hand.MAIN_HAND ? player.getMainArm() : player.getMainArm().getOpposite(),
equipProgress, swingProgress, stack
);
}
GlStateManager.popMatrix();
}
/**
* Renders the item to one side of the player.
*
* @param side The side to render on
* @param equipProgress The equip progress of this item
* @param swingProgress The swing progress of this item
* @param stack The stack to render
* @see FirstPersonRenderer#method_3222(float, Arm, float, ItemStack) // renderMapFirstPersonSide
*/
private void renderItemFirstPersonSide( Arm side, float equipProgress, float swingProgress, ItemStack stack )
{
MinecraftClient minecraft = MinecraftClient.getInstance();
float offset = side == Arm.RIGHT ? 1f : -1f;
GlStateManager.translatef( offset * 0.125f, -0.125f, 0f );
// If the player is not invisible then render a single arm
if( !minecraft.player.isInvisible() )
{
GlStateManager.pushMatrix();
GlStateManager.rotatef( offset * 10f, 0f, 0f, 1f );
((MixedFirstPersonRenderer) minecraft.getHeldItemRenderer()).renderArmFirstPerson_CC( equipProgress, swingProgress, side );
GlStateManager.popMatrix();
}
// Setup the appropriate transformations. This is just copied from the
// corresponding method in ItemRenderer.
GlStateManager.pushMatrix();
GlStateManager.translatef( offset * 0.51f, -0.08f + equipProgress * -1.2f, -0.75f );
float f1 = MathHelper.sqrt( swingProgress );
float f2 = MathHelper.sin( f1 * (float) Math.PI );
float f3 = -0.5f * f2;
float f4 = 0.4f * MathHelper.sin( f1 * ((float) Math.PI * 2f) );
float f5 = -0.3f * MathHelper.sin( swingProgress * (float) Math.PI );
GlStateManager.translatef( offset * f3, f4 - 0.3f * f2, f5 );
GlStateManager.rotatef( f2 * -45f, 1f, 0f, 0f );
GlStateManager.rotatef( offset * f2 * -30f, 0f, 1f, 0f );
renderItem( stack );
GlStateManager.popMatrix();
}
/**
* Render an item in the middle of the screen
*
* @param pitch The pitch of the player
* @param equipProgress The equip progress of this item
* @param swingProgress The swing progress of this item
* @param stack The stack to render
* @see FirstPersonRenderer#renderFirstPersonMap(float, float, float)
*/
private void renderItemFirstPersonCenter( float pitch, float equipProgress, float swingProgress, ItemStack stack )
{
MixedFirstPersonRenderer renderer = (MixedFirstPersonRenderer) MinecraftClient.getInstance().getHeldItemRenderer();
// Setup the appropriate transformations. This is just copied from the
// corresponding method in ItemRenderer.
float swingRt = MathHelper.sqrt( swingProgress );
float tX = -0.2f * MathHelper.sin( swingProgress * (float) Math.PI );
float tZ = -0.4f * MathHelper.sin( swingRt * (float) Math.PI );
GlStateManager.translatef( 0f, -tX / 2f, tZ );
float pitchAngle = renderer.getMapAngleFromPitch_CC( pitch );
GlStateManager.translatef( 0f, 0.04f + equipProgress * -1.2f + pitchAngle * -0.5f, -0.72f );
GlStateManager.rotatef( pitchAngle * -85f, 1f, 0f, 0f );
renderer.renderArms_CC();
float rX = MathHelper.sin( swingRt * (float) Math.PI );
GlStateManager.rotatef( rX * 20f, 1f, 0f, 0f );
GlStateManager.scalef( 2f, 2f, 2f );
renderItem( stack );
}
}

View File

@ -0,0 +1,242 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.item.ItemStack;
import org.lwjgl.opengl.GL11;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
import static dan200.computercraft.client.gui.GuiComputer.*;
/**
* Emulates map rendering for pocket computers
*/
@Environment( EnvType.CLIENT )
public final class ItemPocketRenderer extends ItemMapLikeRenderer
{
private static final int MARGIN = 2;
private static final int FRAME = 12;
private static final int LIGHT_HEIGHT = 8;
public static final ItemPocketRenderer INSTANCE = new ItemPocketRenderer();
private ItemPocketRenderer()
{
}
@Override
protected void renderItem( ItemStack stack )
{
ClientComputer computer = ItemPocketComputer.createClientComputer( stack );
Terminal terminal = computer == null ? null : computer.getTerminal();
int termWidth, termHeight;
if( terminal == null )
{
termWidth = ComputerCraft.terminalWidth_pocketComputer;
termHeight = ComputerCraft.terminalHeight_pocketComputer;
}
else
{
termWidth = terminal.getWidth();
termHeight = terminal.getHeight();
}
int width = termWidth * FONT_WIDTH + MARGIN * 2;
int height = termHeight * FONT_HEIGHT + MARGIN * 2;
// Setup various transformations. Note that these are partially adapted from the corresponding method
// in ItemRenderer
GlStateManager.pushMatrix();
GlStateManager.disableLighting();
GlStateManager.disableDepthTest();
GlStateManager.rotatef( 180f, 0f, 1f, 0f );
GlStateManager.rotatef( 180f, 0f, 0f, 1f );
GlStateManager.scalef( 0.5f, 0.5f, 0.5f );
double scale = 0.75 / Math.max( width + FRAME * 2, height + FRAME * 2 + LIGHT_HEIGHT );
GlStateManager.scaled( scale, scale, 0 );
GlStateManager.translated( -0.5 * width, -0.5 * height, 0 );
// Render the main frame
ItemPocketComputer item = (ItemPocketComputer) stack.getItem();
ComputerFamily family = item.getFamily();
int frameColour = item.getColour( stack );
renderFrame( family, frameColour, width, height );
// Render the light
int lightColour = ItemPocketComputer.getLightState( stack );
if( lightColour == -1 ) lightColour = Colour.Black.getHex();
renderLight( lightColour, width, height );
if( computer != null && terminal != null )
{
// If we've a computer and terminal then attempt to render it.
renderTerminal( terminal, !computer.isColour(), width, height );
}
else
{
// Otherwise render a plain background
MinecraftClient.getInstance().getTextureManager().bindTextureInner( BACKGROUND );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Colour black = Colour.Black;
buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION );
renderTexture( buffer, 0, 0, 0, 0, width, height, black.getR(), black.getG(), black.getB() );
tessellator.draw();
}
GlStateManager.enableDepthTest();
GlStateManager.enableLighting();
GlStateManager.popMatrix();
}
private static void renderFrame( ComputerFamily family, int colour, int width, int height )
{
MinecraftClient.getInstance().getTextureManager().bindTextureInner( colour != -1
? BACKGROUND_COLOUR
: family == ComputerFamily.Normal ? BACKGROUND_NORMAL : BACKGROUND_ADVANCED
);
float r = ((colour >>> 16) & 0xFF) / 255.0f;
float g = ((colour >>> 8) & 0xFF) / 255.0f;
float b = (colour & 0xFF) / 255.0f;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_TEXTURE_COLOR );
// Top left, middle, right
renderTexture( buffer, -FRAME, -FRAME, 12, 28, FRAME, FRAME, r, g, b );
renderTexture( buffer, 0, -FRAME, 0, 0, width, FRAME, r, g, b );
renderTexture( buffer, width, -FRAME, 24, 28, FRAME, FRAME, r, g, b );
// Left and bright border
renderTexture( buffer, -FRAME, 0, 0, 28, FRAME, height, r, g, b );
renderTexture( buffer, width, 0, 36, 28, FRAME, height, r, g, b );
// Bottom left, middle, right. We do this in three portions: the top inner corners, an extended region for
// lights, and then the bottom outer corners.
renderTexture( buffer, -FRAME, height, 12, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, 0, height, 0, 12, width, FRAME / 2, r, g, b );
renderTexture( buffer, width, height, 24, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, -FRAME, height + FRAME / 2, 12, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, 0, height + FRAME / 2, 0, 16, width, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, width, height + FRAME / 2, 24, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, -FRAME, height + LIGHT_HEIGHT + FRAME / 2, 12, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, 0, height + LIGHT_HEIGHT + FRAME / 2, 0, 12 + FRAME / 2, width, FRAME / 2, r, g, b );
renderTexture( buffer, width, height + LIGHT_HEIGHT + FRAME / 2, 24, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
tessellator.draw();
}
private static void renderLight( int colour, int width, int height )
{
GlStateManager.enableBlend();
GlStateManager.disableTexture();
float r = ((colour >>> 16) & 0xFF) / 255.0f;
float g = ((colour >>> 8) & 0xFF) / 255.0f;
float b = (colour & 0xFF) / 255.0f;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_COLOR );
buffer.vertex( width - LIGHT_HEIGHT * 2, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).next();
buffer.vertex( width, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).next();
buffer.vertex( width, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).next();
buffer.vertex( width - LIGHT_HEIGHT * 2, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).next();
tessellator.draw();
GlStateManager.enableTexture();
}
private static void renderTerminal( Terminal terminal, boolean greyscale, int width, int height )
{
synchronized( terminal )
{
int termWidth = terminal.getWidth();
int termHeight = terminal.getHeight();
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
Palette palette = terminal.getPalette();
// Render top/bottom borders
TextBuffer emptyLine = new TextBuffer( ' ', termWidth );
fontRenderer.drawString(
emptyLine, MARGIN, 0,
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ), MARGIN, MARGIN, greyscale, palette
);
fontRenderer.drawString(
emptyLine, MARGIN, 2 * MARGIN + (termHeight - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
terminal.getTextColourLine( termHeight - 1 ), terminal.getBackgroundColourLine( termHeight - 1 ), MARGIN, MARGIN, greyscale, palette
);
// Render the actual text
for( int line = 0; line < termWidth; line++ )
{
TextBuffer text = terminal.getLine( line );
TextBuffer colour = terminal.getTextColourLine( line );
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
fontRenderer.drawString(
text, MARGIN, MARGIN + line * FONT_HEIGHT,
colour, backgroundColour, MARGIN, MARGIN, greyscale, palette
);
}
// And render the cursor;
int tx = terminal.getCursorX(), ty = terminal.getCursorY();
if( terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink() &&
tx >= 0 && ty >= 0 && tx < termWidth && ty < termHeight )
{
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
new TextBuffer( '_', 1 ), MARGIN + FONT_WIDTH * tx, MARGIN + FONT_HEIGHT * ty,
cursorColour, null, 0, 0, greyscale, palette
);
}
}
}
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, float r, float g, float b )
{
renderTexture( builder, x, y, textureX, textureY, width, height, width, height, r, g, b );
}
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, int textureWidth, int textureHeight, float r, float g, float b )
{
float scale = 1 / 255.0f;
builder.vertex( x, y + height, 0 ).texture( textureX * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).next();
builder.vertex( x + width, y + height, 0 ).texture( (textureX + textureWidth) * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).next();
builder.vertex( x + width, y, 0 ).texture( (textureX + textureWidth) * scale, textureY * scale ).color( r, g, b, 1.0f ).next();
builder.vertex( x, y, 0 ).texture( textureX * scale, textureY * scale ).color( r, g, b, 1.0f ).next();
}
}

View File

@ -0,0 +1,112 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.shared.media.items.ItemPrintout;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.entity.decoration.ItemFrameEntity;
import net.minecraft.item.ItemStack;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
import static dan200.computercraft.shared.media.items.ItemPrintout.LINE_MAX_LENGTH;
/**
* Emulates map and item-frame rendering for printouts
*/
@Environment( EnvType.CLIENT )
public final class ItemPrintoutRenderer extends ItemMapLikeRenderer
{
public static final ItemPrintoutRenderer INSTANCE = new ItemPrintoutRenderer();
private ItemPrintoutRenderer()
{
}
/*
@SubscribeEvent
public static void onRenderInHand( RenderSpecificHandEvent event )
{
ItemStack stack = event.getItemStack();
if( !(stack.getItem() instanceof ItemPrintout) ) return;
event.setCanceled( true );
INSTANCE.renderItemFirstPerson( event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack() );
}
*/
@Override
protected void renderItem( ItemStack stack )
{
// Setup various transformations. Note that these are partially adapated from the corresponding method
// in FirstPersonRenderer.renderFirstPersonMap
GlStateManager.disableLighting();
GlStateManager.rotatef( 180f, 0f, 1f, 0f );
GlStateManager.rotatef( 180f, 0f, 0f, 1f );
GlStateManager.scalef( 0.42f, 0.42f, -0.42f );
GlStateManager.translatef( -0.5f, -0.48f, 0.0f );
drawPrintout( stack );
GlStateManager.enableLighting();
}
public void renderInFrame( ItemFrameEntity entity, ItemStack stack )
{
GlStateManager.disableLighting();
int rotation = entity.getRotation();
GlStateManager.rotatef( (float) rotation * 360.0F / 8.0F, 0.0F, 0.0F, 1.0F );
// Move a little bit forward to ensure we're not clipping with the frame
GlStateManager.translatef( 0.0f, 0.0f, -0.001f );
GlStateManager.rotatef( 180f, 0f, 0f, 1f );
GlStateManager.scalef( 0.95f, 0.95f, -0.95f );
GlStateManager.translatef( -0.5f, -0.5f, 0.0f );
drawPrintout( stack );
GlStateManager.enableLighting();
GlStateManager.disableBlend();
}
private static void drawPrintout( ItemStack stack )
{
int pages = ItemPrintout.getPageCount( stack );
boolean book = ((ItemPrintout) stack.getItem()).getType() == ItemPrintout.Type.BOOK;
double width = LINE_MAX_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2;
// Non-books will be left aligned
if( !book ) width += offsetAt( pages );
double visualWidth = width, visualHeight = height;
// Meanwhile books will be centred
if( book )
{
visualWidth += 2 * COVER_SIZE + 2 * offsetAt( pages );
visualHeight += 2 * COVER_SIZE;
}
double max = Math.max( visualHeight, visualWidth );
// Scale the printout to fit correctly.
double scale = 1.0 / max;
GlStateManager.scaled( scale, scale, scale );
GlStateManager.translated( (max - width) / 2.0, (max - height) / 2.0, 0.0 );
drawBorder( 0, 0, -0.01, 0, pages, book );
drawText( X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, ItemPrintout.getText( stack ), ItemPrintout.getColours( stack ) );
}
}

View File

@ -0,0 +1,100 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormatElement;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.model.BakedQuad;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector4f;
import java.util.List;
/**
* Transforms vertices of a model, remaining aware of winding order, and rearranging
* vertices if needed.
*/
public final class ModelTransformer
{
private static final Matrix4f identity;
static
{
identity = new Matrix4f();
identity.setIdentity();
}
private ModelTransformer()
{
}
public static void transformQuadsTo( List<BakedQuad> output, List<BakedQuad> input, Matrix4f transform )
{
transformQuadsTo( VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL, output, input, transform );
}
public static void transformQuadsTo( VertexFormat format, List<BakedQuad> output, List<BakedQuad> input, Matrix4f transform )
{
if( transform == null || transform.equals( identity ) )
{
output.addAll( input );
}
else
{
for( BakedQuad quad : input ) output.add( doTransformQuad( format, quad, transform ) );
}
}
public static BakedQuad transformQuad( VertexFormat format, BakedQuad input, Matrix4f transform )
{
if( transform == null || transform.equals( identity ) ) return input;
return doTransformQuad( format, input, transform );
}
private static BakedQuad doTransformQuad( VertexFormat format, BakedQuad quad, Matrix4f transform )
{
int[] vertexData = quad.getVertexData().clone();
int offset = 0;
BakedQuad copy = new BakedQuad( vertexData, -1, quad.getFace(), quad.getSprite() );
for( int i = 0; i < format.getElementCount(); ++i ) // For each vertex element
{
VertexFormatElement element = format.getElement( i );
if( element.isPosition() &&
element.getFormat() == VertexFormatElement.Format.FLOAT &&
element.getCount() == 3 ) // When we find a position element
{
for( int j = 0; j < 4; ++j ) // For each corner of the quad
{
int start = offset + j * format.getVertexSize();
if( (start % 4) == 0 )
{
start = start / 4;
// Extract the position
Vector4f pos = new Vector4f(
Float.intBitsToFloat( vertexData[start] ),
Float.intBitsToFloat( vertexData[start + 1] ),
Float.intBitsToFloat( vertexData[start + 2] ),
1
);
// Transform the position
transform.transform( pos );
// Insert the position
vertexData[start] = Float.floatToRawIntBits( pos.x );
vertexData[start + 1] = Float.floatToRawIntBits( pos.y );
vertexData[start + 2] = Float.floatToRawIntBits( pos.z );
}
}
}
offset += element.getSize();
}
return copy;
}
}

View File

@ -0,0 +1,107 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import java.util.EnumSet;
import static net.minecraft.util.math.Direction.*;
public final class MonitorHighlightRenderer
{
private static final float EXPAND = 0.002f;
private MonitorHighlightRenderer()
{
}
public static boolean drawHighlight( Camera camera, BlockHitResult hit )
{
MinecraftClient mc = MinecraftClient.getInstance();
if( mc.player.isSneaking() ) return false;
BlockPos pos = hit.getBlockPos();
World world = mc.world;
BlockEntity tile = world.getBlockEntity( pos );
if( !(tile instanceof TileMonitor) ) return false;
TileMonitor monitor = (TileMonitor) tile;
// Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
EnumSet<Direction> faces = EnumSet.allOf( Direction.class );
Direction front = monitor.getFront();
faces.remove( front );
if( monitor.getXIndex() != 0 ) faces.remove( monitor.getRight().getOpposite() );
if( monitor.getXIndex() != monitor.getWidth() - 1 ) faces.remove( monitor.getRight() );
if( monitor.getYIndex() != 0 ) faces.remove( monitor.getDown().getOpposite() );
if( monitor.getYIndex() != monitor.getHeight() - 1 ) faces.remove( monitor.getDown() );
GlStateManager.enableBlend();
GlStateManager.blendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GlStateManager.lineWidth( Math.max( 2.5F, (float) mc.window.getFramebufferWidth() / 1920.0F * 2.5F ) );
GlStateManager.disableTexture();
GlStateManager.depthMask( false );
GlStateManager.pushMatrix();
GlStateManager.translated( pos.getX() - camera.getPos().getX(), pos.getY() - camera.getPos().getY(), pos.getZ() - camera.getPos().getZ() );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_LINES, VertexFormats.POSITION_COLOR );
// I wish I could think of a better way to do this
if( faces.contains( NORTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 1, EAST );
if( faces.contains( NORTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 1, EAST );
if( faces.contains( WEST ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( DOWN ) ) line( buffer, 1, 0, 0, SOUTH );
if( faces.contains( WEST ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( UP ) ) line( buffer, 1, 1, 0, SOUTH );
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.depthMask( true );
GlStateManager.enableTexture();
GlStateManager.disableBlend();
return true;
}
private static void line( BufferBuilder buffer, int x, int y, int z, Direction direction )
{
double minX = x == 0 ? -EXPAND : 1 + EXPAND;
double minY = y == 0 ? -EXPAND : 1 + EXPAND;
double minZ = z == 0 ? -EXPAND : 1 + EXPAND;
buffer.vertex( minX, minY, minZ ).color( 0, 0, 0, 0.4f ).next();
buffer.vertex(
minX + direction.getOffsetX() * (1 + EXPAND * 2),
minY + direction.getOffsetY() * (1 + EXPAND * 2),
minZ + direction.getOffsetZ() * (1 + EXPAND * 2)
).color( 0, 0, 0, 0.4f ).next();
}
}

View File

@ -0,0 +1,180 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.GlStateManager.DestFactor;
import com.mojang.blaze3d.platform.GlStateManager.SourceFactor;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.util.Identifier;
import org.lwjgl.opengl.GL11;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
public final class PrintoutRenderer
{
private static final Identifier BG = new Identifier( "computercraft", "textures/gui/printout.png" );
private static final double BG_SIZE = 256.0;
/**
* Width of a page
*/
public static final int X_SIZE = 172;
/**
* Height of a page
*/
public static final int Y_SIZE = 209;
/**
* Padding between the left and right of a page and the text
*/
public static final int X_TEXT_MARGIN = 13;
/**
* Padding between the top and bottom of a page and the text
*/
public static final int Y_TEXT_MARGIN = 11;
/**
* Width of the extra page texture
*/
private static final int X_FOLD_SIZE = 12;
/**
* Size of the leather cover
*/
public static final int COVER_SIZE = 12;
private static final int COVER_Y = Y_SIZE;
private static final int COVER_X = X_SIZE + 4 * X_FOLD_SIZE;
private PrintoutRenderer() {}
public static void drawText( int x, int y, int start, TextBuffer[] text, TextBuffer[] colours )
{
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{
fontRenderer.drawString( text[start + line], x, y + line * FONT_HEIGHT, colours[start + line], null, 0, 0, false, Palette.DEFAULT );
}
}
public static void drawText( int x, int y, int start, String[] text, String[] colours )
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableBlend();
GlStateManager.enableTexture();
GlStateManager.blendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{
fontRenderer.drawString( new TextBuffer( text[start + line] ), x, y + line * FONT_HEIGHT, new TextBuffer( colours[start + line] ), null, 0, 0, false, Palette.DEFAULT );
}
}
public static void drawBorder( double x, double y, double z, int page, int pages, boolean isBook )
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableBlend();
GlStateManager.enableTexture();
GlStateManager.blendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
MinecraftClient.getInstance().getTextureManager().bindTextureInner( BG );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_TEXTURE );
int leftPages = page;
int rightPages = pages - page - 1;
if( isBook )
{
// Border
double offset = offsetAt( pages );
final double left = x - 4 - offset;
final double right = x + X_SIZE + offset - 4;
// Left and right border
drawTexture( buffer, left - 4, y - 8, z - 0.02, COVER_X, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2 );
drawTexture( buffer, right, y - 8, z - 0.02, COVER_X + COVER_SIZE, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2 );
// Draw centre panel (just stretched texture, sorry).
drawTexture( buffer,
x - offset, y, z - 0.02, X_SIZE + offset * 2, Y_SIZE,
COVER_X + COVER_SIZE / 2.0f, COVER_SIZE, COVER_SIZE, Y_SIZE
);
double borderX = left;
while( borderX < right )
{
double thisWidth = Math.min( right - borderX, X_SIZE );
drawTexture( buffer, borderX, y - 8, z - 0.02, 0, COVER_Y, thisWidth, COVER_SIZE );
drawTexture( buffer, borderX, y + Y_SIZE - 4, z - 0.02, 0, COVER_Y + COVER_SIZE, thisWidth, COVER_SIZE );
borderX += thisWidth;
}
}
// Left half
drawTexture( buffer, x, y, z, X_FOLD_SIZE * 2, 0, X_SIZE / 2.0f, Y_SIZE );
for( int n = 0; n <= leftPages; n++ )
{
drawTexture( buffer,
x - offsetAt( n ), y, z - 1e-3 * n,
// Use the left "bold" fold for the outermost page
n == leftPages ? 0 : X_FOLD_SIZE, 0,
X_FOLD_SIZE, Y_SIZE
);
}
// Right half
drawTexture( buffer, x + X_SIZE / 2.0f, y, z, X_FOLD_SIZE * 2 + X_SIZE / 2.0f, 0, X_SIZE / 2.0f, Y_SIZE );
for( int n = 0; n <= rightPages; n++ )
{
drawTexture( buffer,
x + (X_SIZE - X_FOLD_SIZE) + offsetAt( n ), y, z - 1e-3 * n,
// Two folds, then the main page. Use the right "bold" fold for the outermost page.
X_FOLD_SIZE * 2 + X_SIZE + (n == rightPages ? X_FOLD_SIZE : 0), 0,
X_FOLD_SIZE, Y_SIZE
);
}
tessellator.draw();
}
private static void drawTexture( BufferBuilder buffer, double x, double y, double z, double u, double v, double width, double height )
{
buffer.vertex( x, y + height, z ).texture( u / BG_SIZE, (v + height) / BG_SIZE ).next();
buffer.vertex( x + width, y + height, z ).texture( (u + width) / BG_SIZE, (v + height) / BG_SIZE ).next();
buffer.vertex( x + width, y, z ).texture( (u + width) / BG_SIZE, v / BG_SIZE ).next();
buffer.vertex( x, y, z ).texture( u / BG_SIZE, v / BG_SIZE ).next();
}
private static void drawTexture( BufferBuilder buffer, double x, double y, double z, double width, double height, double u, double v, double tWidth, double tHeight )
{
buffer.vertex( x, y + height, z ).texture( u / BG_SIZE, (v + tHeight) / BG_SIZE ).next();
buffer.vertex( x + width, y + height, z ).texture( (u + tWidth) / BG_SIZE, (v + tHeight) / BG_SIZE ).next();
buffer.vertex( x + width, y, z ).texture( (u + tWidth) / BG_SIZE, v / BG_SIZE ).next();
buffer.vertex( x, y, z ).texture( u / BG_SIZE, v / BG_SIZE ).next();
}
public static double offsetAt( int page )
{
return 32 * (1 - Math.pow( 1.2, -page ));
}
}

View File

@ -0,0 +1,112 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nonnull;
import java.util.Random;
/**
* Render breaking animation only over part of a {@link TileCable}.
*/
public class TileEntityCableRenderer extends BlockEntityRenderer<TileCable>
{
private static final Random random = new Random();
@Override
public void render( @Nonnull TileCable te, double x, double y, double z, float partialTicks, int destroyStage )
{
if( destroyStage < 0 ) return;
BlockPos pos = te.getPos();
MinecraftClient mc = MinecraftClient.getInstance();
HitResult hit = mc.crosshairTarget;
if( !(hit instanceof BlockHitResult) || !((BlockHitResult) hit).getBlockPos().equals( pos ) ) return;
World world = te.getWorld();
BlockState state = te.getCachedState();
Block block = state.getBlock();
if( block != ComputerCraft.Blocks.cable ) return;
state = WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.getPos().subtract( pos.getX(), pos.getY(), pos.getZ() ) )
? block.getDefaultState().with( BlockCable.MODEM, state.get( BlockCable.MODEM ) )
: state.with( BlockCable.MODEM, CableModemVariant.None );
BakedModel model = mc.getBlockRenderManager().getModel( state );
preRenderDamagedBlocks();
BufferBuilder buffer = Tessellator.getInstance().getBuffer();
buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_COLOR_UV_LMAP );
buffer.setOffset( x - pos.getX(), y - pos.getY(), z - pos.getZ() );
buffer.disableColor();
// See BlockRendererDispatcher#renderBlockDamage
Sprite breakingTexture = mc.getSpriteAtlas().getSprite( DESTROY_STAGE_TEXTURES[destroyStage] );
mc.getBlockRenderManager().tesselateDamage( state, pos, breakingTexture, world );
buffer.setOffset( 0, 0, 0 );
Tessellator.getInstance().draw();
postRenderDamagedBlocks();
}
/**
* @see WorldRenderer#preRenderDamagedBlocks()
*/
private void preRenderDamagedBlocks()
{
GlStateManager.disableLighting();
GlStateManager.enableBlend();
GlStateManager.blendFuncSeparate( GlStateManager.SourceFactor.DST_COLOR, GlStateManager.DestFactor.SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GlStateManager.enableBlend();
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 0.5F );
GlStateManager.polygonOffset( -3.0F, -3.0F );
GlStateManager.enablePolygonOffset();
GlStateManager.alphaFunc( 516, 0.1F );
GlStateManager.enableAlphaTest();
GlStateManager.pushMatrix();
}
/**
* @see WorldRenderer#postRenderDamagedBlocks()
*/
private void postRenderDamagedBlocks()
{
GlStateManager.disableAlphaTest();
GlStateManager.polygonOffset( 0.0F, 0.0F );
GlStateManager.disablePolygonOffset();
GlStateManager.disablePolygonOffset();
GlStateManager.depthMask( true );
GlStateManager.popMatrix();
}
}

View File

@ -0,0 +1,292 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GLX;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import org.lwjgl.opengl.GL11;
public class TileEntityMonitorRenderer extends BlockEntityRenderer<TileMonitor>
{
@Override
public void render( TileMonitor tileEntity, double posX, double posY, double posZ, float f, int i )
{
if( tileEntity != null )
{
renderMonitorAt( tileEntity, posX, posY, posZ, f, i );
}
}
private static void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
{
// Render from the origin monitor
ClientMonitor originTerminal = monitor.getClientMonitor();
if( originTerminal == null ) return;
TileMonitor origin = originTerminal.getOrigin();
BlockPos monitorPos = monitor.getPos();
// Ensure each monitor terminal is rendered only once. We allow rendering a specific tile
// multiple times in a single frame to ensure compatibility with shaders which may run a
// pass multiple times.
long renderFrame = FrameInfo.getRenderFrame();
if( originTerminal.lastRenderFrame == renderFrame && !monitorPos.equals( originTerminal.lastRenderPos ) )
{
return;
}
originTerminal.lastRenderFrame = renderFrame;
originTerminal.lastRenderPos = monitorPos;
BlockPos originPos = origin.getPos();
posX += originPos.getX() - monitorPos.getX();
posY += originPos.getY() - monitorPos.getY();
posZ += originPos.getZ() - monitorPos.getZ();
// Determine orientation
Direction dir = origin.getDirection();
Direction front = origin.getFront();
float yaw = dir.asRotation();
float pitch = DirectionUtil.toPitchAngle( front );
GlStateManager.pushMatrix();
try
{
// Setup initial transform
GlStateManager.translated( posX + 0.5, posY + 0.5, posZ + 0.5 );
GlStateManager.rotatef( -yaw, 0.0f, 1.0f, 0.0f );
GlStateManager.rotatef( pitch, 1.0f, 0.0f, 0.0f );
GlStateManager.translated(
-0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN,
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN) + 0,
0.5
);
double xSize = origin.getWidth() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
double ySize = origin.getHeight() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
// Get renderers
MinecraftClient mc = MinecraftClient.getInstance();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
// Get terminal
boolean redraw = originTerminal.pollTerminalChanged();
// Draw the contents
GlStateManager.depthMask( false );
GLX.glMultiTexCoord2f( GLX.GL_TEXTURE1, 0xFFFF, 0xFFFF );
GlStateManager.disableLighting();
mc.gameRenderer.disableLightmap();
try
{
Terminal terminal = originTerminal.getTerminal();
if( terminal != null )
{
Palette palette = terminal.getPalette();
// Allocate display lists
if( originTerminal.renderDisplayLists == null )
{
originTerminal.createLists();
redraw = true;
}
// Draw a terminal
boolean greyscale = !originTerminal.isColour();
int width = terminal.getWidth();
int height = terminal.getHeight();
int cursorX = terminal.getCursorX();
int cursorY = terminal.getCursorY();
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
GlStateManager.pushMatrix();
try
{
double xScale = xSize / (width * FixedWidthFontRenderer.FONT_WIDTH);
double yScale = ySize / (height * FixedWidthFontRenderer.FONT_HEIGHT);
GlStateManager.scaled( xScale, -yScale, 1.0 );
// Draw background
mc.getTextureManager().bindTextureInner( FixedWidthFontRenderer.BACKGROUND );
if( redraw )
{
// Build background display list
GlStateManager.newList( originTerminal.renderDisplayLists[0], GL11.GL_COMPILE );
try
{
double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
double marginYSize = TileMonitor.RENDER_MARGIN / yScale;
double marginSquash = marginYSize / FixedWidthFontRenderer.FONT_HEIGHT;
// Top and bottom margins
GlStateManager.pushMatrix();
try
{
GlStateManager.scaled( 1.0, marginSquash, 1.0 );
GlStateManager.translated( 0.0, -marginYSize / marginSquash, 0.0 );
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( 0 ), marginXSize, marginXSize, greyscale, palette );
GlStateManager.translated( 0.0, (marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT) / marginSquash, 0.0 );
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( height - 1 ), marginXSize, marginXSize, greyscale, palette );
}
finally
{
GlStateManager.popMatrix();
}
// Backgrounds
for( int y = 0; y < height; y++ )
{
fontRenderer.drawStringBackgroundPart(
0, FixedWidthFontRenderer.FONT_HEIGHT * y,
terminal.getBackgroundColourLine( y ),
marginXSize, marginXSize,
greyscale,
palette
);
}
}
finally
{
GlStateManager.endList();
}
}
GlStateManager.callList( originTerminal.renderDisplayLists[0] );
GlStateManager.clearCurrentColor();
// Draw text
fontRenderer.bindFont();
if( redraw )
{
// Build text display list
GlStateManager.newList( originTerminal.renderDisplayLists[1], GL11.GL_COMPILE );
try
{
// Lines
for( int y = 0; y < height; y++ )
{
fontRenderer.drawStringTextPart(
0, FixedWidthFontRenderer.FONT_HEIGHT * y,
terminal.getLine( y ),
terminal.getTextColourLine( y ),
greyscale,
palette
);
}
}
finally
{
GlStateManager.endList();
}
}
GlStateManager.callList( originTerminal.renderDisplayLists[1] );
GlStateManager.clearCurrentColor();
// Draw cursor
fontRenderer.bindFont();
if( redraw )
{
// Build cursor display list
GlStateManager.newList( originTerminal.renderDisplayLists[2], GL11.GL_COMPILE );
try
{
// Cursor
if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height )
{
TextBuffer cursor = new TextBuffer( "_" );
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
cursor,
FixedWidthFontRenderer.FONT_WIDTH * cursorX,
FixedWidthFontRenderer.FONT_HEIGHT * cursorY,
cursorColour, null,
0, 0,
greyscale,
palette
);
}
}
finally
{
GlStateManager.endList();
}
}
if( FrameInfo.getGlobalCursorBlink() )
{
GlStateManager.callList( originTerminal.renderDisplayLists[2] );
GlStateManager.clearCurrentColor();
}
}
finally
{
GlStateManager.popMatrix();
}
}
else
{
// Draw a big black quad
mc.getTextureManager().bindTextureInner( FixedWidthFontRenderer.BACKGROUND );
final Colour colour = Colour.Black;
final float r = colour.getR();
final float g = colour.getG();
final float b = colour.getB();
renderer.begin( GL11.GL_TRIANGLE_STRIP, VertexFormats.POSITION_TEXTURE_COLOR );
renderer.vertex( -TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0D ).texture( 0.0, 0.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( -TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).texture( 0.0, 1.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( xSize + TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0D ).texture( 1.0, 0.0 ).color( r, g, b, 1.0f ).next();
renderer.vertex( xSize + TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).texture( 1.0, 1.0 ).color( r, g, b, 1.0f ).next();
tessellator.draw();
}
}
finally
{
GlStateManager.depthMask( true );
mc.gameRenderer.enableLightmap();
GlStateManager.enableLighting();
}
// Draw the depth blocker
GlStateManager.colorMask( false, false, false, false );
try
{
mc.getTextureManager().bindTextureInner( FixedWidthFontRenderer.BACKGROUND );
renderer.begin( GL11.GL_TRIANGLE_STRIP, VertexFormats.POSITION );
renderer.vertex( -TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0 ).next();
renderer.vertex( -TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).next();
renderer.vertex( xSize + TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0 ).next();
renderer.vertex( xSize + TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).next();
tessellator.draw();
}
finally
{
GlStateManager.colorMask( true, true, true, true );
}
}
finally
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.popMatrix();
}
}
}

View File

@ -0,0 +1,238 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.Holiday;
import dan200.computercraft.shared.util.HolidayUtil;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
import org.apache.commons.lang3.tuple.Pair;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import javax.vecmath.Matrix4f;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.util.List;
import java.util.Random;
public class TileEntityTurtleRenderer extends BlockEntityRenderer<TileTurtle>
{
private static final ModelIdentifier NORMAL_TURTLE_MODEL = new ModelIdentifier( "computercraft:turtle_normal", "inventory" );
private static final ModelIdentifier ADVANCED_TURTLE_MODEL = new ModelIdentifier( "computercraft:turtle_advanced", "inventory" );
private static final ModelIdentifier COLOUR_TURTLE_MODEL = new ModelIdentifier( "computercraft:turtle_colour", "inventory" );
private static final ModelIdentifier ELF_OVERLAY_MODEL = new ModelIdentifier( "computercraft:turtle_elf_overlay", "inventory" );
private static final FloatBuffer matrixBuf = BufferUtils.createFloatBuffer( 16 );
@Override
public void render( TileTurtle tileEntity, double posX, double posY, double posZ, float partialTicks, int breaking )
{
if( tileEntity != null ) renderTurtleAt( tileEntity, posX, posY, posZ, partialTicks );
}
public static ModelIdentifier getTurtleModel( ComputerFamily family, boolean coloured )
{
switch( family )
{
case Normal:
default:
return coloured ? COLOUR_TURTLE_MODEL : NORMAL_TURTLE_MODEL;
case Advanced:
return coloured ? COLOUR_TURTLE_MODEL : ADVANCED_TURTLE_MODEL;
}
}
public static ModelIdentifier getTurtleOverlayModel( Identifier overlay, boolean christmas )
{
if( overlay != null )
{
return new ModelIdentifier( overlay, "inventory" );
}
else if( christmas )
{
return ELF_OVERLAY_MODEL;
}
else
{
return null;
}
}
private void renderTurtleAt( TileTurtle turtle, double posX, double posY, double posZ, float partialTicks )
{
// Render the label
String label = turtle.createProxy().getLabel();
if( label != null && renderManager.crosshairTarget != null && renderManager.crosshairTarget instanceof BlockHitResult && turtle.getPos().equals( ((BlockHitResult) renderManager.crosshairTarget).getBlockPos() ) )
{
disableLightmap( true );
GameRenderer.renderFloatingText(
getFontRenderer(), label,
(float) posX + 0.5F, (float) posY + 1.2F, (float) posZ + 0.5F, 0,
renderManager.camera.getYaw(), renderManager.camera.getPitch(), false
);
disableLightmap( false );
}
GlStateManager.pushMatrix();
try
{
BlockState state = turtle.getCachedState();
// Setup the transform
Vec3d offset = turtle.getRenderOffset( partialTicks );
float yaw = turtle.getRenderYaw( partialTicks );
GlStateManager.translated( posX + offset.x, posY + offset.y, posZ + offset.z );
// Render the turtle
GlStateManager.translatef( 0.5f, 0.5f, 0.5f );
GlStateManager.rotatef( 180.0f - yaw, 0.0f, 1.0f, 0.0f );
if( label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" )) )
{
// Flip the model and swap the cull face as winding order will have changed.
GlStateManager.scalef( 1.0f, -1.0f, 1.0f );
GlStateManager.cullFace( GlStateManager.FaceSides.FRONT );
}
GlStateManager.translatef( -0.5f, -0.5f, -0.5f );
// Render the turtle
int colour = turtle.getColour();
ComputerFamily family = turtle.getFamily();
Identifier overlay = turtle.getOverlay();
renderModel( state, getTurtleModel( family, colour != -1 ), colour == -1 ? null : new int[] { colour } );
// Render the overlay
ModelIdentifier overlayModel = getTurtleOverlayModel(
overlay,
HolidayUtil.getCurrentHoliday() == Holiday.Christmas
);
if( overlayModel != null )
{
GlStateManager.disableCull();
GlStateManager.enableBlend();
GlStateManager.blendFunc( GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA );
try
{
renderModel( state, overlayModel, null );
}
finally
{
GlStateManager.disableBlend();
GlStateManager.enableCull();
}
}
// Render the upgrades
renderUpgrade( state, turtle, TurtleSide.Left, partialTicks );
renderUpgrade( state, turtle, TurtleSide.Right, partialTicks );
}
finally
{
GlStateManager.popMatrix();
GlStateManager.cullFace( GlStateManager.FaceSides.BACK );
}
}
private void renderUpgrade( BlockState state, TileTurtle turtle, TurtleSide side, float f )
{
ITurtleUpgrade upgrade = turtle.getUpgrade( side );
if( upgrade != null )
{
GlStateManager.pushMatrix();
try
{
float toolAngle = turtle.getToolRenderAngle( side, f );
GlStateManager.translatef( 0.0f, 0.5f, 0.5f );
GlStateManager.rotatef( -toolAngle, 1.0f, 0.0f, 0.0f );
GlStateManager.translatef( 0.0f, -0.5f, -0.5f );
Pair<BakedModel, Matrix4f> pair = upgrade.getModel( turtle.getAccess(), side );
if( pair != null )
{
if( pair.getRight() != null )
{
((Buffer) matrixBuf).clear();
float[] t = new float[4];
for( int i = 0; i < 4; i++ )
{
pair.getRight().getColumn( i, t );
matrixBuf.put( t );
}
((Buffer) matrixBuf).flip();
GlStateManager.multMatrix( matrixBuf );
}
if( pair.getLeft() != null )
{
renderModel( state, pair.getLeft(), null );
}
}
}
finally
{
GlStateManager.popMatrix();
}
}
}
private void renderModel( BlockState state, ModelIdentifier modelLocation, int[] tints )
{
MinecraftClient mc = MinecraftClient.getInstance();
BakedModelManager modelManager = mc.getItemRenderer().getModels().getModelManager();
renderModel( state, modelManager.getModel( modelLocation ), tints );
}
private void renderModel( BlockState state, BakedModel model, int[] tints )
{
Random random = new Random( 0 );
Tessellator tessellator = Tessellator.getInstance();
renderManager.textureManager.bindTextureInner( SpriteAtlasTexture.BLOCK_ATLAS_TEX );
renderQuads( tessellator, model.getQuads( state, null, random ), tints );
for( Direction facing : DirectionUtil.FACINGS )
{
renderQuads( tessellator, model.getQuads( state, facing, random ), tints );
}
}
private static void renderQuads( Tessellator tessellator, List<BakedQuad> quads, int[] tints )
{
BufferBuilder buffer = tessellator.getBuffer();
VertexFormat format = VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL;
buffer.begin( GL11.GL_QUADS, format );
for( BakedQuad quad : quads )
{
int colour = 0xFFFFFFFF;
if( quad.hasColor() && tints != null )
{
int index = quad.getColorIndex();
if( index >= 0 && index < tints.length ) colour = tints[index] | 0xFF000000;
}
buffer.putVertexData( quad.getVertexData() );
buffer.setQuadColor( colour );
Vec3i normal = quad.getFace().getVector();
buffer.postNormal( normal.getX(), normal.getY(), normal.getZ() );
}
tessellator.draw();
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import dan200.computercraft.ComputerCraft;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.Identifier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public final class TurtleModelLoader
{
private static final Identifier NORMAL_TURTLE_MODEL = new Identifier( ComputerCraft.MOD_ID, "block/turtle_normal" );
private static final Identifier ADVANCED_TURTLE_MODEL = new Identifier( ComputerCraft.MOD_ID, "block/turtle_advanced" );
private static final Identifier COLOUR_TURTLE_MODEL = new Identifier( ComputerCraft.MOD_ID, "block/turtle_colour" );
public static final TurtleModelLoader INSTANCE = new TurtleModelLoader();
private TurtleModelLoader()
{
}
public boolean accepts( @Nonnull Identifier name )
{
return name.getNamespace().equals( ComputerCraft.MOD_ID )
&& (name.getPath().equals( "item/turtle_normal" ) || name.getPath().equals( "item/turtle_advanced" ));
}
@Nonnull
public UnbakedModel loadModel( @Nonnull Identifier name )
{
if( name.getNamespace().equals( ComputerCraft.MOD_ID ) )
{
switch( name.getPath() )
{
case "item/turtle_normal":
return new TurtleModel( NORMAL_TURTLE_MODEL );
case "item/turtle_advanced":
return new TurtleModel( ADVANCED_TURTLE_MODEL );
}
}
throw new IllegalStateException( "Loader does not accept " + name );
}
private static final class TurtleModel implements UnbakedModel
{
private final Identifier family;
private TurtleModel( Identifier family ) {this.family = family;}
@Nonnull
@Override
public Collection<Identifier> getModelDependencies()
{
return Arrays.asList( family, COLOUR_TURTLE_MODEL );
}
@Nonnull
@Override
public Collection<Identifier> getTextureDependencies( @Nonnull Function<Identifier, UnbakedModel> modelGetter, @Nonnull Set<String> missingTextureErrors )
{
return getModelDependencies().stream()
.flatMap( x -> modelGetter.apply( x ).getTextureDependencies( modelGetter, missingTextureErrors ).stream() )
.collect( Collectors.toSet() );
}
@Nullable
@Override
public BakedModel bake( @Nonnull ModelLoader loader, @Nonnull Function<Identifier, Sprite> spriteGetter, @Nonnull ModelBakeSettings state )
{
return new TurtleSmartItemModel(
loader,
loader.getOrLoadModel( family ).bake( loader, spriteGetter, state ),
loader.getOrLoadModel( COLOUR_TURTLE_MODEL ).bake( loader, spriteGetter, state )
);
}
}
}

View File

@ -0,0 +1,128 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
import javax.annotation.Nonnull;
import javax.vecmath.Matrix4f;
import java.util.*;
public class TurtleMultiModel implements BakedModel
{
private final BakedModel m_baseModel;
private final BakedModel m_overlayModel;
private final Matrix4f m_generalTransform;
private final BakedModel m_leftUpgradeModel;
private final Matrix4f m_leftUpgradeTransform;
private final BakedModel m_rightUpgradeModel;
private final Matrix4f m_rightUpgradeTransform;
private List<BakedQuad> m_generalQuads = null;
private Map<Direction, List<BakedQuad>> m_faceQuads = new EnumMap<>( Direction.class );
public TurtleMultiModel( BakedModel baseModel, BakedModel overlayModel, Matrix4f generalTransform, BakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, BakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform )
{
// Get the models
m_baseModel = baseModel;
m_overlayModel = overlayModel;
m_leftUpgradeModel = leftUpgradeModel;
m_leftUpgradeTransform = leftUpgradeTransform;
m_rightUpgradeModel = rightUpgradeModel;
m_rightUpgradeTransform = rightUpgradeTransform;
m_generalTransform = generalTransform;
}
@Nonnull
@Override
public List<BakedQuad> getQuads( BlockState state, Direction side, @Nonnull Random rand )
{
if( side != null )
{
if( !m_faceQuads.containsKey( side ) ) m_faceQuads.put( side, buildQuads( state, side, rand ) );
return m_faceQuads.get( side );
}
else
{
if( m_generalQuads == null ) m_generalQuads = buildQuads( state, side, rand );
return m_generalQuads;
}
}
private List<BakedQuad> buildQuads( BlockState state, Direction side, Random rand )
{
ArrayList<BakedQuad> quads = new ArrayList<>();
ModelTransformer.transformQuadsTo( quads, m_baseModel.getQuads( state, side, rand ), m_generalTransform );
if( m_overlayModel != null )
{
ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand ), m_generalTransform );
}
if( m_leftUpgradeModel != null )
{
Matrix4f upgradeTransform = m_generalTransform;
if( m_leftUpgradeTransform != null )
{
upgradeTransform = new Matrix4f( m_generalTransform );
upgradeTransform.mul( m_leftUpgradeTransform );
}
ModelTransformer.transformQuadsTo( quads, m_leftUpgradeModel.getQuads( state, side, rand ), upgradeTransform );
}
if( m_rightUpgradeModel != null )
{
Matrix4f upgradeTransform = m_generalTransform;
if( m_rightUpgradeTransform != null )
{
upgradeTransform = new Matrix4f( m_generalTransform );
upgradeTransform.mul( m_rightUpgradeTransform );
}
ModelTransformer.transformQuadsTo( quads, m_rightUpgradeModel.getQuads( state, side, rand ), upgradeTransform );
}
quads.trimToSize();
return quads;
}
@Override
public boolean useAmbientOcclusion()
{
return m_baseModel.useAmbientOcclusion();
}
@Override
public boolean hasDepth()
{
return m_baseModel.hasDepth();
}
@Override
public boolean isBuiltin()
{
return m_baseModel.isBuiltin();
}
@Override
public Sprite getSprite()
{
return m_baseModel.getSprite();
}
@Override
public ModelTransformation getTransformation()
{
return m_baseModel.getTransformation();
}
@Override
public ModelOverrideList getOverrides()
{
return ModelOverrideList.EMPTY;
}
}

View File

@ -0,0 +1,212 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.google.common.base.Objects;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.turtle.items.ItemTurtle;
import dan200.computercraft.shared.util.Holiday;
import dan200.computercraft.shared.util.HolidayUtil;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.json.ModelOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
public class TurtleSmartItemModel implements BakedModel
{
private static final Matrix4f s_identity, s_flip;
static
{
s_identity = new Matrix4f();
s_identity.setIdentity();
s_flip = new Matrix4f();
s_flip.setIdentity();
s_flip.m11 = -1; // Flip on the y axis
s_flip.m13 = 1; // Models go from (0,0,0) to (1,1,1), so push back up.
}
private static class TurtleModelCombination
{
final boolean m_colour;
final ITurtleUpgrade m_leftUpgrade;
final ITurtleUpgrade m_rightUpgrade;
final Identifier m_overlay;
final boolean m_christmas;
final boolean m_flip;
TurtleModelCombination( boolean colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, Identifier overlay, boolean christmas, boolean flip )
{
m_colour = colour;
m_leftUpgrade = leftUpgrade;
m_rightUpgrade = rightUpgrade;
m_overlay = overlay;
m_christmas = christmas;
m_flip = flip;
}
@Override
public boolean equals( Object other )
{
if( other == this ) return true;
if( !(other instanceof TurtleModelCombination) ) return false;
TurtleModelCombination otherCombo = (TurtleModelCombination) other;
return otherCombo.m_colour == m_colour &&
otherCombo.m_leftUpgrade == m_leftUpgrade &&
otherCombo.m_rightUpgrade == m_rightUpgrade &&
Objects.equal( otherCombo.m_overlay, m_overlay ) &&
otherCombo.m_christmas == m_christmas &&
otherCombo.m_flip == m_flip;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 0;
result = prime * result + (m_colour ? 1 : 0);
result = prime * result + (m_leftUpgrade != null ? m_leftUpgrade.hashCode() : 0);
result = prime * result + (m_rightUpgrade != null ? m_rightUpgrade.hashCode() : 0);
result = prime * result + (m_overlay != null ? m_overlay.hashCode() : 0);
result = prime * result + (m_christmas ? 1 : 0);
result = prime * result + (m_flip ? 1 : 0);
return result;
}
}
private final BakedModel familyModel;
private final BakedModel colourModel;
private HashMap<TurtleModelCombination, BakedModel> m_cachedModels;
private ModelOverrideList m_overrides;
public TurtleSmartItemModel( ModelLoader loader, BakedModel familyModel, BakedModel colourModel )
{
this.familyModel = familyModel;
this.colourModel = colourModel;
m_cachedModels = new HashMap<>();
m_overrides = new ModelOverrideList( loader, null, null, Collections.emptyList() )
{
@Nonnull
@Override
public BakedModel apply( @Nonnull BakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable LivingEntity entity )
{
ItemTurtle turtle = (ItemTurtle) stack.getItem();
int colour = turtle.getColour( stack );
ITurtleUpgrade leftUpgrade = turtle.getUpgrade( stack, TurtleSide.Left );
ITurtleUpgrade rightUpgrade = turtle.getUpgrade( stack, TurtleSide.Right );
Identifier overlay = turtle.getOverlay( stack );
boolean christmas = HolidayUtil.getCurrentHoliday() == Holiday.Christmas;
String label = turtle.getLabel( stack );
boolean flip = label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" ));
TurtleModelCombination combo = new TurtleModelCombination( colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip );
BakedModel model = m_cachedModels.get( combo );
if( model == null ) m_cachedModels.put( combo, model = buildModel( combo ) );
return model;
}
};
}
@Nonnull
@Override
public ModelOverrideList getOverrides()
{
return m_overrides;
}
private BakedModel buildModel( TurtleModelCombination combo )
{
MinecraftClient mc = MinecraftClient.getInstance();
BakedModelManager modelManager = mc.getItemRenderer().getModels().getModelManager();
ModelIdentifier overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_overlay, combo.m_christmas );
BakedModel baseModel = combo.m_colour ? colourModel : familyModel;
BakedModel overlayModel = overlayModelLocation != null ? modelManager.getModel( overlayModelLocation ) : null;
Matrix4f transform = combo.m_flip ? s_flip : s_identity;
Pair<BakedModel, Matrix4f> leftModel = combo.m_leftUpgrade != null ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
Pair<BakedModel, Matrix4f> rightModel = combo.m_rightUpgrade != null ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;
if( leftModel != null && rightModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() );
}
else if( leftModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), null, null );
}
else if( rightModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, rightModel.getLeft(), rightModel.getRight() );
}
else
{
return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, null, null );
}
}
@Nonnull
@Override
@Deprecated
public List<BakedQuad> getQuads( BlockState state, Direction facing, Random rand )
{
return familyModel.getQuads( state, facing, rand );
}
@Override
public boolean useAmbientOcclusion()
{
return familyModel.useAmbientOcclusion();
}
@Override
public boolean hasDepth()
{
return familyModel.hasDepth();
}
@Override
public boolean isBuiltin()
{
return familyModel.isBuiltin();
}
@Override
public Sprite getSprite()
{
return null;
}
@Override
public ModelTransformation getTransformation()
{
return familyModel.getTransformation();
}
}

View File

@ -0,0 +1,182 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import com.google.common.net.InetAddresses;
import dan200.computercraft.ComputerCraft;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* Used to determine whether a domain or IP address matches a series of patterns.
*/
public class AddressPredicate
{
private static final class HostRange
{
private final byte[] min;
private final byte[] max;
private HostRange( byte[] min, byte[] max )
{
this.min = min;
this.max = max;
}
public boolean contains( InetAddress address )
{
byte[] entry = address.getAddress();
if( entry.length != min.length ) return false;
for( int i = 0; i < entry.length; i++ )
{
int value = 0xFF & entry[i];
if( value < (0xFF & min[i]) || value > (0xFF & max[i]) ) return false;
}
return true;
}
}
private final List<Pattern> wildcards;
private final List<HostRange> ranges;
public AddressPredicate( String... filters )
{
this( Arrays.asList( filters ) );
}
public AddressPredicate( Iterable<? extends String> filters )
{
List<Pattern> wildcards = this.wildcards = new ArrayList<>();
List<HostRange> ranges = this.ranges = new ArrayList<>();
for( String filter : filters )
{
int cidr = filter.indexOf( '/' );
if( cidr >= 0 )
{
String addressStr = filter.substring( 0, cidr );
String prefixSizeStr = filter.substring( cidr + 1 );
int prefixSize;
try
{
prefixSize = Integer.parseInt( prefixSizeStr );
}
catch( NumberFormatException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract size of CIDR mask from '{}'.",
filter, prefixSizeStr
);
continue;
}
InetAddress address;
try
{
address = InetAddresses.forString( addressStr );
}
catch( IllegalArgumentException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract IP address from '{}'.",
filter, prefixSizeStr
);
continue;
}
// Mask the bytes of the IP address.
byte[] minBytes = address.getAddress(), maxBytes = address.getAddress();
int size = prefixSize;
for( int i = 0; i < minBytes.length; i++ )
{
if( size <= 0 )
{
minBytes[i] &= 0;
maxBytes[i] |= 0xFF;
}
else if( size < 8 )
{
minBytes[i] &= 0xFF << (8 - size);
maxBytes[i] |= ~(0xFF << (8 - size));
}
size -= 8;
}
ranges.add( new HostRange( minBytes, maxBytes ) );
}
else
{
wildcards.add( Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" ) );
}
}
}
/**
* Determine whether a host name matches a series of patterns.
*
* This is intended to allow early exiting, before one has to look up the IP address. You should use
* {@link #matches(InetAddress)} instead of/in addition to this one.
*
* @param domain The domain to match.
* @return Whether the patterns were matched.
*/
public boolean matches( String domain )
{
for( Pattern domainPattern : wildcards )
{
if( domainPattern.matcher( domain ).matches() ) return true;
}
return false;
}
private boolean matchesAddress( InetAddress address )
{
String addressString = address.getHostAddress();
for( Pattern domainPattern : wildcards )
{
if( domainPattern.matcher( addressString ).matches() ) return true;
}
for( HostRange range : ranges )
{
if( range.contains( address ) ) return true;
}
return false;
}
/**
* Determine whether the given address matches a series of patterns
*
* @param address The address to check.
* @return Whether it matches any of these patterns.
*/
public boolean matches( InetAddress address )
{
// Match the host name
String host = address.getHostName();
if( host != null && matches( host ) ) return true;
// Match the normal address
if( matchesAddress( address ) ) return true;
// If we're an IPv4 address in disguise then let's check that.
return address instanceof Inet6Address && InetAddresses.is6to4Address( (Inet6Address) address )
&& matchesAddress( InetAddresses.get6to4IPv4Address( (Inet6Address) address ) );
}
}

View File

@ -0,0 +1,36 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
public final class ApiFactories
{
private ApiFactories()
{
}
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
private static final Collection<ILuaAPIFactory> factoriesView = Collections.unmodifiableCollection( factories );
public static synchronized void register( @Nonnull ILuaAPIFactory factory )
{
Objects.requireNonNull( factory, "provider cannot be null" );
factories.add( factory );
}
public static Iterable<ILuaAPIFactory> getAll()
{
return factoriesView;
}
}

View File

@ -0,0 +1,268 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Various helpers for arguments
*/
public final class ArgumentHelper
{
private ArgumentHelper()
{
throw new IllegalStateException( "Cannot instantiate singleton " + getClass().getName() );
}
@Nonnull
public static String getType( @Nullable Object type )
{
if( type == null ) return "nil";
if( type instanceof String ) return "string";
if( type instanceof Boolean ) return "boolean";
if( type instanceof Number ) return "number";
if( type instanceof Map ) return "table";
Class<?> klass = type.getClass();
if( klass.isArray() )
{
StringBuilder name = new StringBuilder();
while( klass.isArray() )
{
name.append( "[]" );
klass = klass.getComponentType();
}
name.insert( 0, klass.getName() );
return name.toString();
}
else
{
return klass.getName();
}
}
@Nonnull
public static LuaException badArgument( int index, @Nonnull String expected, @Nullable Object actual )
{
return badArgument( index, expected, getType( actual ) );
}
@Nonnull
public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
{
return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" );
}
public static double getNumber( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "number", "nil" );
Object value = args[index];
if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badArgument( index, "number", value );
}
}
public static int getInt( @Nonnull Object[] args, int index ) throws LuaException
{
return (int) getLong( args, index );
}
public static long getLong( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "number", "nil" );
Object value = args[index];
if( value instanceof Number )
{
return checkReal( index, (Number) value ).longValue();
}
else
{
throw badArgument( index, "number", value );
}
}
public static double getReal( @Nonnull Object[] args, int index ) throws LuaException
{
return checkReal( index, getNumber( args, index ) );
}
public static boolean getBoolean( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "boolean", "nil" );
Object value = args[index];
if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badArgument( index, "boolean", value );
}
}
@Nonnull
public static String getString( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "string", "nil" );
Object value = args[index];
if( value instanceof String )
{
return (String) value;
}
else
{
throw badArgument( index, "string", value );
}
}
@SuppressWarnings( "unchecked" )
@Nonnull
public static Map<Object, Object> getTable( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "table", "nil" );
Object value = args[index];
if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badArgument( index, "table", value );
}
}
public static double optNumber( @Nonnull Object[] args, int index, double def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badArgument( index, "number", value );
}
}
public static int optInt( @Nonnull Object[] args, int index, int def ) throws LuaException
{
return (int) optLong( args, index, def );
}
public static long optLong( @Nonnull Object[] args, int index, long def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return checkReal( index, (Number) value ).longValue();
}
else
{
throw badArgument( index, "number", value );
}
}
public static double optReal( @Nonnull Object[] args, int index, double def ) throws LuaException
{
return checkReal( index, optNumber( args, index, def ) );
}
public static boolean optBoolean( @Nonnull Object[] args, int index, boolean def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null )
{
return def;
}
else if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badArgument( index, "boolean", value );
}
}
public static String optString( @Nonnull Object[] args, int index, String def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null )
{
return def;
}
else if( value instanceof String )
{
return (String) value;
}
else
{
throw badArgument( index, "string", value );
}
}
@SuppressWarnings( "unchecked" )
public static Map<Object, Object> optTable( @Nonnull Object[] args, int index, Map<Object, Object> def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null )
{
return def;
}
else if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badArgument( index, "table", value );
}
}
private static Number checkReal( int index, Number value ) throws LuaException
{
checkReal( index, value.doubleValue() );
return value;
}
private static double checkReal( int index, double value ) throws LuaException
{
if( Double.isNaN( value ) )
{
throw badArgument( index, "number", "nan" );
}
else if( value == Double.POSITIVE_INFINITY )
{
throw badArgument( index, "number", "inf" );
}
else if( value == Double.NEGATIVE_INFINITY )
{
throw badArgument( index, "number", "-inf" );
}
else
{
return value;
}
}
}

View File

@ -0,0 +1,149 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public abstract class ComputerAccess implements IComputerAccess
{
private final IAPIEnvironment m_environment;
private final Set<String> m_mounts = new HashSet<>();
protected ComputerAccess( IAPIEnvironment environment )
{
this.m_environment = environment;
}
public void unmountAll()
{
FileSystem fileSystem = m_environment.getFileSystem();
for( String mount : m_mounts )
{
fileSystem.unmount( mount );
}
m_mounts.clear();
}
@Override
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
{
Objects.requireNonNull( desiredLoc, "desiredLocation cannot be null" );
Objects.requireNonNull( mount, "mount cannot be null" );
Objects.requireNonNull( driveName, "driveName cannot be null" );
// Mount the location
String location;
FileSystem fileSystem = m_environment.getFileSystem();
if( fileSystem == null ) throw new IllegalStateException( "File system has not been created" );
synchronized( fileSystem )
{
location = findFreeLocation( desiredLoc );
if( location != null )
{
try
{
fileSystem.mount( driveName, location, mount );
}
catch( FileSystemException ignored )
{
}
}
}
if( location != null ) m_mounts.add( location );
return location;
}
@Override
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
Objects.requireNonNull( desiredLoc, "desiredLocation cannot be null" );
Objects.requireNonNull( mount, "mount cannot be null" );
Objects.requireNonNull( driveName, "driveName cannot be null" );
// Mount the location
String location;
FileSystem fileSystem = m_environment.getFileSystem();
if( fileSystem == null ) throw new IllegalStateException( "File system has not been created" );
synchronized( fileSystem )
{
location = findFreeLocation( desiredLoc );
if( location != null )
{
try
{
fileSystem.mountWritable( driveName, location, mount );
}
catch( FileSystemException ignored )
{
}
}
}
if( location != null ) m_mounts.add( location );
return location;
}
@Override
public void unmount( String location )
{
if( location == null ) return;
if( !m_mounts.contains( location ) ) throw new IllegalStateException( "You didn't mount this location" );
m_environment.getFileSystem().unmount( location );
m_mounts.remove( location );
}
@Override
public int getID()
{
return m_environment.getComputerID();
}
@Override
public void queueEvent( @Nonnull final String event, final Object[] arguments )
{
Objects.requireNonNull( event, "event cannot be null" );
m_environment.queueEvent( event, arguments );
}
@Nullable
@Override
public IWorkMonitor getMainThreadMonitor()
{
return m_environment.getMainThreadMonitor();
}
private String findFreeLocation( String desiredLoc )
{
try
{
FileSystem fileSystem = m_environment.getFileSystem();
if( !fileSystem.exists( desiredLoc ) ) return desiredLoc;
// We used to check foo2, foo3, foo4, etc here but the disk drive does this itself now
return null;
}
catch( FileSystemException e )
{
return null;
}
}
}

View File

@ -0,0 +1,360 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.FileSystemWrapper;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
public class FSAPI implements ILuaAPI
{
private IAPIEnvironment m_env;
private FileSystem m_fileSystem;
public FSAPI( IAPIEnvironment env )
{
m_env = env;
m_fileSystem = null;
}
@Override
public String[] getNames()
{
return new String[] {
"fs"
};
}
@Override
public void startup()
{
m_fileSystem = m_env.getFileSystem();
}
@Override
public void shutdown()
{
m_fileSystem = null;
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"list",
"combine",
"getName",
"getSize",
"exists",
"isDir",
"isReadOnly",
"makeDir",
"move",
"copy",
"delete",
"open",
"getDrive",
"getFreeSpace",
"find",
"getDir",
};
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
{
// list
String path = getString( args, 0 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try
{
String[] results = m_fileSystem.list( path );
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < results.length; i++ )
{
table.put( i + 1, results[i] );
}
return new Object[] { table };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 1:
{
// combine
String pathA = getString( args, 0 );
String pathB = getString( args, 1 );
return new Object[] { m_fileSystem.combine( pathA, pathB ) };
}
case 2:
{
// getName
String path = getString( args, 0 );
return new Object[] { FileSystem.getName( path ) };
}
case 3:
{
// getSize
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.getSize( path ) };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 4:
{
// exists
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.exists( path ) };
}
catch( FileSystemException e )
{
return new Object[] { false };
}
}
case 5:
{
// isDir
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.isDir( path ) };
}
catch( FileSystemException e )
{
return new Object[] { false };
}
}
case 6:
{
// isReadOnly
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.isReadOnly( path ) };
}
catch( FileSystemException e )
{
return new Object[] { false };
}
}
case 7:
{
// makeDir
String path = getString( args, 0 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.makeDir( path );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 8:
{
// move
String path = getString( args, 0 );
String dest = getString( args, 1 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.move( path, dest );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 9:
{
// copy
String path = getString( args, 0 );
String dest = getString( args, 1 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.copy( path, dest );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 10:
{
// delete
String path = getString( args, 0 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.delete( path );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 11:
{
// open
String path = getString( args, 0 );
String mode = getString( args, 1 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try
{
switch( mode )
{
case "r":
{
// Open the file for reading, then create a wrapper around the reader
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
}
case "w":
{
// Open the file for writing, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "a":
{
// Open the file for appending, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "rb":
{
// Open the file for binary reading, then create a wrapper around the reader
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() );
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
}
case "wb":
{
// Open the file for binary writing, then create a wrapper around the writer
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
}
case "ab":
{
// Open the file for binary appending, then create a wrapper around the reader
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
}
default:
throw new LuaException( "Unsupported mode" );
}
}
catch( FileSystemException e )
{
return new Object[] { null, e.getMessage() };
}
}
case 12:
{
// getDrive
String path = getString( args, 0 );
try
{
if( !m_fileSystem.exists( path ) )
{
return null;
}
return new Object[] { m_fileSystem.getMountLabel( path ) };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 13:
{
// getFreeSpace
String path = getString( args, 0 );
try
{
long freeSpace = m_fileSystem.getFreeSpace( path );
if( freeSpace >= 0 )
{
return new Object[] { freeSpace };
}
return new Object[] { "unlimited" };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 14:
{
// find
String path = getString( args, 0 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
String[] results = m_fileSystem.find( path );
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < results.length; i++ )
{
table.put( i + 1, results[i] );
}
return new Object[] { table };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 15:
{
// getDir
String path = getString( args, 0 );
return new Object[] { FileSystem.getDirectory( path ) };
}
default:
assert false;
return null;
}
}
}

View File

@ -0,0 +1,227 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.http.*;
import dan200.computercraft.core.apis.http.request.HttpRequest;
import dan200.computercraft.core.apis.http.websocket.Websocket;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import javax.annotation.Nonnull;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI
{
private final IAPIEnvironment m_apiEnvironment;
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>();
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>( () -> ComputerCraft.httpMaxWebsockets );
public HTTPAPI( IAPIEnvironment environment )
{
m_apiEnvironment = environment;
}
@Override
public String[] getNames()
{
return new String[] {
"http"
};
}
@Override
public void startup()
{
checkUrls.startup();
requests.startup();
websockets.startup();
}
@Override
public void shutdown()
{
checkUrls.shutdown();
requests.shutdown();
websockets.shutdown();
}
@Override
public void update()
{
// It's rather ugly to run this here, but we need to clean up
// resources as often as possible to reduce blocking.
Resource.cleanup();
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"request",
"checkURL",
"websocket",
};
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0: // request
{
String address, postString, requestMethod;
Map<Object, Object> headerTable;
boolean binary, redirect;
if( args.length >= 1 && args[0] instanceof Map )
{
Map<?, ?> options = (Map) args[0];
address = getStringField( options, "url" );
postString = optStringField( options, "body", null );
headerTable = optTableField( options, "headers", Collections.emptyMap() );
binary = optBooleanField( options, "binary", false );
requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
}
else
{
// Get URL and post information
address = getString( args, 0 );
postString = optString( args, 1, null );
headerTable = optTable( args, 2, Collections.emptyMap() );
binary = optBoolean( args, 3, false );
requestMethod = null;
redirect = true;
}
HttpHeaders headers = getHeaders( headerTable );
HttpMethod httpMethod;
if( requestMethod == null )
{
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
}
else
{
httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
{
throw new LuaException( "Unsupported HTTP method" );
}
}
try
{
URI uri = HttpRequest.checkUri( address );
HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
long requestBody = request.body().readableBytes() + HttpRequest.getHeaderSize( headers );
if( ComputerCraft.httpMaxUpload != 0 && requestBody > ComputerCraft.httpMaxUpload )
{
throw new HTTPRequestException( "Request body is too large" );
}
// Make the request
request.queue( r -> r.request( uri, httpMethod ) );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
case 1: // checkURL
{
String address = getString( args, 0 );
// Check URL
try
{
URI uri = HttpRequest.checkUri( address );
new CheckUrl( checkUrls, m_apiEnvironment, address, uri ).queue( CheckUrl::run );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
case 2: // websocket
{
String address = getString( args, 0 );
Map<Object, Object> headerTbl = optTable( args, 1, Collections.emptyMap() );
if( !ComputerCraft.http_websocket_enable )
{
throw new LuaException( "Websocket connections are disabled" );
}
HttpHeaders headers = getHeaders( headerTbl );
try
{
URI uri = Websocket.checkUri( address );
if( !new Websocket( websockets, m_apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
{
throw new LuaException( "Too many websockets already open" );
}
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
default:
return null;
}
}
@Nonnull
private static HttpHeaders getHeaders( @Nonnull Map<?, ?> headerTable ) throws LuaException
{
HttpHeaders headers = new DefaultHttpHeaders();
for( Map.Entry<?, ?> entry : headerTable.entrySet() )
{
Object value = entry.getValue();
if( entry.getKey() instanceof String && value instanceof String )
{
try
{
headers.add( (String) entry.getKey(), value );
}
catch( IllegalArgumentException e )
{
throw new LuaException( e.getMessage() );
}
}
}
return headers;
}
}

View File

@ -0,0 +1,74 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public interface IAPIEnvironment
{
@FunctionalInterface
interface IPeripheralChangeListener
{
void onPeripheralChanged( ComputerSide side, @Nullable IPeripheral newPeripheral );
}
int getComputerID();
@Nonnull
IComputerEnvironment getComputerEnvironment();
@Nonnull
IWorkMonitor getMainThreadMonitor();
@Nonnull
Terminal getTerminal();
FileSystem getFileSystem();
void shutdown();
void reboot();
void queueEvent( String event, Object[] args );
void setOutput( ComputerSide side, int output );
int getOutput( ComputerSide side );
int getInput( ComputerSide side );
void setBundledOutput( ComputerSide side, int output );
int getBundledOutput( ComputerSide side );
int getBundledInput( ComputerSide side );
void setPeripheralChangeListener( @Nullable IPeripheralChangeListener listener );
@Nullable
IPeripheral getPeripheral( ComputerSide side );
String getLabel();
void setLabel( @Nullable String label );
void addTrackingChange( @Nonnull TrackingField field, long change );
default void addTrackingChange( @Nonnull TrackingField field )
{
addTrackingChange( field, 1 );
}
}

View File

@ -0,0 +1,24 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
/**
* This exists purely to ensure binary compatibility.
*
* @see dan200.computercraft.api.lua.ILuaAPI
*/
@Deprecated
public interface ILuaAPI extends dan200.computercraft.api.lua.ILuaAPI
{
void advance( double v );
@Override
default void update()
{
advance( 0.05 );
}
}

View File

@ -0,0 +1,281 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.TextStyle;
import java.time.temporal.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.LongUnaryOperator;
final class LuaDateTime
{
private LuaDateTime()
{
}
static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffset offset ) throws LuaException
{
for( int i = 0; i < format.length(); )
{
char c;
switch( c = format.charAt( i++ ) )
{
case '\n':
formatter.appendLiteral( '\n' );
break;
default:
formatter.appendLiteral( c );
break;
case '%':
if( i >= format.length() ) break;
switch( c = format.charAt( i++ ) )
{
default:
throw new LuaException( "bad argument #1: invalid conversion specifier '%" + c + "'" );
case '%':
formatter.appendLiteral( '%' );
break;
case 'a':
formatter.appendText( ChronoField.DAY_OF_WEEK, TextStyle.SHORT );
break;
case 'A':
formatter.appendText( ChronoField.DAY_OF_WEEK, TextStyle.FULL );
break;
case 'b':
case 'h':
formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.SHORT );
break;
case 'B':
formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.FULL );
break;
case 'c':
format( formatter, "%a %b %e %H:%M:%S %Y", offset );
break;
case 'C':
formatter.appendValueReduced( CENTURY, 2, 2, 0 );
break;
case 'd':
formatter.appendValue( ChronoField.DAY_OF_MONTH, 2 );
break;
case 'D':
case 'x':
format( formatter, "%m/%d/%y", offset );
break;
case 'e':
formatter.padNext( 2 ).appendValue( ChronoField.DAY_OF_MONTH );
break;
case 'F':
format( formatter, "%Y-%m-%d", offset );
break;
case 'g':
formatter.appendValueReduced( IsoFields.WEEK_BASED_YEAR, 2, 2, 0 );
break;
case 'G':
formatter.appendValue( IsoFields.WEEK_BASED_YEAR );
break;
case 'H':
formatter.appendValue( ChronoField.HOUR_OF_DAY, 2 );
break;
case 'I':
formatter.appendValue( ChronoField.HOUR_OF_AMPM );
break;
case 'j':
formatter.appendValue( ChronoField.DAY_OF_YEAR, 3 );
break;
case 'm':
formatter.appendValue( ChronoField.MONTH_OF_YEAR, 2 );
break;
case 'M':
formatter.appendValue( ChronoField.MINUTE_OF_HOUR, 2 );
break;
case 'n':
formatter.appendLiteral( '\n' );
break;
case 'p':
formatter.appendText( ChronoField.AMPM_OF_DAY );
break;
case 'r':
format( formatter, "%I:%M:%S %p", offset );
break;
case 'R':
format( formatter, "%H:%M", offset );
break;
case 'S':
formatter.appendValue( ChronoField.SECOND_OF_MINUTE, 2 );
break;
case 't':
formatter.appendLiteral( '\t' );
break;
case 'T':
case 'X':
format( formatter, "%H:%M:%S", offset );
break;
case 'u':
formatter.appendValue( ChronoField.DAY_OF_WEEK );
break;
case 'U':
formatter.appendValue( ChronoField.ALIGNED_WEEK_OF_YEAR, 2 );
break;
case 'V':
formatter.appendValue( IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2 );
break;
case 'w':
formatter.appendValue( ZERO_WEEK );
break;
case 'W':
formatter.appendValue( WeekFields.ISO.weekOfYear(), 2 );
break;
case 'y':
formatter.appendValueReduced( ChronoField.YEAR, 2, 2, 0 );
break;
case 'Y':
formatter.appendValue( ChronoField.YEAR );
break;
case 'z':
formatter.appendOffset( "+HHMM", "+0000" );
break;
case 'Z':
formatter.appendChronologyId();
break;
}
}
}
}
static long fromTable( Map<?, ?> table ) throws LuaException
{
int year = getField( table, "year", -1 );
int month = getField( table, "month", -1 );
int day = getField( table, "day", -1 );
int hour = getField( table, "hour", 12 );
int minute = getField( table, "min", 12 );
int second = getField( table, "sec", 12 );
LocalDateTime time = LocalDateTime.of( year, month, day, hour, minute, second );
Boolean isDst = getBoolField( table, "isdst" );
if( isDst != null )
{
boolean requireDst = isDst;
for( ZoneOffset possibleOffset : ZoneOffset.systemDefault().getRules().getValidOffsets( time ) )
{
Instant instant = time.toInstant( possibleOffset );
if( possibleOffset.getRules().getDaylightSavings( instant ).isZero() == requireDst )
{
return instant.getEpochSecond();
}
}
}
ZoneOffset offset = ZoneOffset.systemDefault().getRules().getOffset( time );
return time.toInstant( offset ).getEpochSecond();
}
static Map<String, ?> toTable( TemporalAccessor date, ZoneId offset, Instant instant )
{
HashMap<String, Object> table = new HashMap<>( 9 );
table.put( "year", date.getLong( ChronoField.YEAR ) );
table.put( "month", date.getLong( ChronoField.MONTH_OF_YEAR ) );
table.put( "day", date.getLong( ChronoField.DAY_OF_MONTH ) );
table.put( "hour", date.getLong( ChronoField.HOUR_OF_DAY ) );
table.put( "min", date.getLong( ChronoField.MINUTE_OF_HOUR ) );
table.put( "sec", date.getLong( ChronoField.SECOND_OF_MINUTE ) );
table.put( "wday", date.getLong( WeekFields.SUNDAY_START.dayOfWeek() ) );
table.put( "yday", date.getLong( ChronoField.DAY_OF_YEAR ) );
table.put( "isdst", offset.getRules().isDaylightSavings( instant ) );
return table;
}
private static int getField( Map<?, ?> table, String field, int def ) throws LuaException
{
Object value = table.get( field );
if( value instanceof Number ) return ((Number) value).intValue();
if( def < 0 ) throw new LuaException( "field \"" + field + "\" missing in date table" );
return def;
}
private static Boolean getBoolField( Map<?, ?> table, String field ) throws LuaException
{
Object value = table.get( field );
if( value instanceof Boolean || value == null ) return (Boolean) value;
throw new LuaException( "field \"" + field + "\" missing in date table" );
}
private static final TemporalField CENTURY = map( ChronoField.YEAR, ValueRange.of( 0, 6 ), x -> (x / 100) % 100 );
private static final TemporalField ZERO_WEEK = map( WeekFields.SUNDAY_START.dayOfWeek(), ValueRange.of( 0, 6 ), x -> x - 1 );
private static TemporalField map( TemporalField field, ValueRange range, LongUnaryOperator convert )
{
return new TemporalField()
{
private final ValueRange range = ValueRange.of( 0, 99 );
@Override
public TemporalUnit getBaseUnit()
{
return field.getBaseUnit();
}
@Override
public TemporalUnit getRangeUnit()
{
return field.getRangeUnit();
}
@Override
public ValueRange range()
{
return range;
}
@Override
public boolean isDateBased()
{
return field.isDateBased();
}
@Override
public boolean isTimeBased()
{
return field.isTimeBased();
}
@Override
public boolean isSupportedBy( TemporalAccessor temporal )
{
return field.isSupportedBy( temporal );
}
@Override
public ValueRange rangeRefinedBy( TemporalAccessor temporal )
{
return range;
}
@Override
public long getFrom( TemporalAccessor temporal )
{
return convert.applyAsLong( temporal.getLong( field ) );
}
@Override
@SuppressWarnings( "unchecked" )
public <R extends Temporal> R adjustInto( R temporal, long newValue )
{
return (R) temporal.with( field, newValue );
}
};
}
}

View File

@ -0,0 +1,445 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatterBuilder;
import java.util.*;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
public class OSAPI implements ILuaAPI
{
private IAPIEnvironment m_apiEnvironment;
private final Map<Integer, Timer> m_timers;
private final Map<Integer, Alarm> m_alarms;
private int m_clock;
private double m_time;
private int m_day;
private int m_nextTimerToken;
private int m_nextAlarmToken;
private static class Timer
{
public int m_ticksLeft;
public Timer( int ticksLeft )
{
m_ticksLeft = ticksLeft;
}
}
private static class Alarm implements Comparable<Alarm>
{
public final double m_time;
public final int m_day;
public Alarm( double time, int day )
{
m_time = time;
m_day = day;
}
@Override
public int compareTo( @Nonnull Alarm o )
{
double t = m_day * 24.0 + m_time;
double ot = m_day * 24.0 + m_time;
return Double.compare( t, ot );
}
}
public OSAPI( IAPIEnvironment environment )
{
m_apiEnvironment = environment;
m_nextTimerToken = 0;
m_nextAlarmToken = 0;
m_timers = new HashMap<>();
m_alarms = new HashMap<>();
}
// ILuaAPI implementation
@Override
public String[] getNames()
{
return new String[] {
"os"
};
}
@Override
public void startup()
{
m_time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
m_day = m_apiEnvironment.getComputerEnvironment().getDay();
m_clock = 0;
synchronized( m_timers )
{
m_timers.clear();
}
synchronized( m_alarms )
{
m_alarms.clear();
}
}
@Override
public void update()
{
synchronized( m_timers )
{
// Update the clock
m_clock++;
// Countdown all of our active timers
Iterator<Map.Entry<Integer, Timer>> it = m_timers.entrySet().iterator();
while( it.hasNext() )
{
Map.Entry<Integer, Timer> entry = it.next();
Timer timer = entry.getValue();
timer.m_ticksLeft--;
if( timer.m_ticksLeft <= 0 )
{
// Queue the "timer" event
queueLuaEvent( "timer", new Object[] { entry.getKey() } );
it.remove();
}
}
}
// Wait for all of our alarms
synchronized( m_alarms )
{
double previousTime = m_time;
int previousDay = m_day;
double time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
int day = m_apiEnvironment.getComputerEnvironment().getDay();
if( time > previousTime || day > previousDay )
{
double now = m_day * 24.0 + m_time;
Iterator<Map.Entry<Integer, Alarm>> it = m_alarms.entrySet().iterator();
while( it.hasNext() )
{
Map.Entry<Integer, Alarm> entry = it.next();
Alarm alarm = entry.getValue();
double t = alarm.m_day * 24.0 + alarm.m_time;
if( now >= t )
{
queueLuaEvent( "alarm", new Object[] { entry.getKey() } );
it.remove();
}
}
}
m_time = time;
m_day = day;
}
}
@Override
public void shutdown()
{
synchronized( m_timers )
{
m_timers.clear();
}
synchronized( m_alarms )
{
m_alarms.clear();
}
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"queueEvent",
"startTimer",
"setAlarm",
"shutdown",
"reboot",
"computerID",
"getComputerID",
"setComputerLabel",
"computerLabel",
"getComputerLabel",
"clock",
"time",
"day",
"cancelTimer",
"cancelAlarm",
"epoch",
"date",
};
}
private static float getTimeForCalendar( Calendar c )
{
float time = c.get( Calendar.HOUR_OF_DAY );
time += c.get( Calendar.MINUTE ) / 60.0f;
time += c.get( Calendar.SECOND ) / (60.0f * 60.0f);
return time;
}
private static int getDayForCalendar( Calendar c )
{
GregorianCalendar g = c instanceof GregorianCalendar ? (GregorianCalendar) c : new GregorianCalendar();
int year = c.get( Calendar.YEAR );
int day = 0;
for( int y = 1970; y < year; y++ )
{
day += g.isLeapYear( y ) ? 366 : 365;
}
day += c.get( Calendar.DAY_OF_YEAR );
return day;
}
private static long getEpochForCalendar( Calendar c )
{
return c.getTime().getTime();
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0: // queueEvent
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
return null;
case 1:
{
// startTimer
double timer = getReal( args, 0 );
synchronized( m_timers )
{
m_timers.put( m_nextTimerToken, new Timer( (int) Math.round( timer / 0.05 ) ) );
return new Object[] { m_nextTimerToken++ };
}
}
case 2:
{
// setAlarm
double time = getReal( args, 0 );
if( time < 0.0 || time >= 24.0 )
{
throw new LuaException( "Number out of range" );
}
synchronized( m_alarms )
{
int day = time > m_time ? m_day : m_day + 1;
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
return new Object[] { m_nextAlarmToken++ };
}
}
case 3: // shutdown
m_apiEnvironment.shutdown();
return null;
case 4: // reboot
m_apiEnvironment.reboot();
return null;
case 5:
case 6: // computerID/getComputerID
return new Object[] { getComputerID() };
case 7:
{
// setComputerLabel
String label = optString( args, 0, null );
m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
return null;
}
case 8:
case 9:
{
// computerLabel/getComputerLabel
String label = m_apiEnvironment.getLabel();
if( label != null )
{
return new Object[] { label };
}
return null;
}
case 10: // clock
synchronized( m_timers )
{
return new Object[] { m_clock * 0.05 };
}
case 11:
{
// time
Object value = args.length > 0 ? args[0] : null;
if( value instanceof Map ) return new Object[] { LuaDateTime.fromTable( (Map<?, ?>) value ) };
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
{
case "utc":
{
// Get Hour of day (UTC)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getTimeForCalendar( c ) };
}
case "local":
{
// Get Hour of day (local time)
Calendar c = Calendar.getInstance();
return new Object[] { getTimeForCalendar( c ) };
}
case "ingame":
// Get ingame hour
synchronized( m_alarms )
{
return new Object[] { m_time };
}
default:
throw new LuaException( "Unsupported operation" );
}
}
case 12:
{
// day
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
{
case "utc":
{
// Get numbers of days since 1970-01-01 (utc)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getDayForCalendar( c ) };
}
case "local":
{
// Get numbers of days since 1970-01-01 (local time)
Calendar c = Calendar.getInstance();
return new Object[] { getDayForCalendar( c ) };
}
case "ingame":
// Get game day
synchronized( m_alarms )
{
return new Object[] { m_day };
}
default:
throw new LuaException( "Unsupported operation" );
}
}
case 13:
{
// cancelTimer
int token = getInt( args, 0 );
synchronized( m_timers )
{
m_timers.remove( token );
}
return null;
}
case 14:
{
// cancelAlarm
int token = getInt( args, 0 );
synchronized( m_alarms )
{
m_alarms.remove( token );
}
return null;
}
case 15: // epoch
{
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
{
case "utc":
{
// Get utc epoch
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getEpochForCalendar( c ) };
}
case "local":
{
// Get local epoch
Calendar c = Calendar.getInstance();
return new Object[] { getEpochForCalendar( c ) };
}
case "ingame":
// Get in-game epoch
synchronized( m_alarms )
{
return new Object[] {
m_day * 86400000 + (int) (m_time * 3600000.0f)
};
}
default:
throw new LuaException( "Unsupported operation" );
}
}
case 16: // date
{
String format = optString( args, 0, "%c" );
long time = optLong( args, 1, Instant.now().getEpochSecond() );
Instant instant = Instant.ofEpochSecond( time );
ZonedDateTime date;
ZoneOffset offset;
boolean isDst;
if( format.startsWith( "!" ) )
{
offset = ZoneOffset.UTC;
date = ZonedDateTime.ofInstant( instant, offset );
format = format.substring( 1 );
}
else
{
ZoneId id = ZoneId.systemDefault();
offset = id.getRules().getOffset( instant );
date = ZonedDateTime.ofInstant( instant, id );
}
if( format.equals( "*t" ) ) return new Object[] { LuaDateTime.toTable( date, offset, instant ) };
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
LuaDateTime.format( formatter, format, offset );
return new Object[] { formatter.toFormatter( Locale.ROOT ).format( date ) };
}
default:
return null;
}
}
// Private methods
private void queueLuaEvent( String event, Object[] args )
{
m_apiEnvironment.queueEvent( event, args );
}
private Object[] trimArray( Object[] array, int skip )
{
return Arrays.copyOfRange( array, skip, array.length );
}
private int getComputerID()
{
return m_apiEnvironment.getComputerID();
}
}

View File

@ -0,0 +1,420 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener
{
private class PeripheralWrapper extends ComputerAccess
{
private final String m_side;
private final IPeripheral m_peripheral;
private String m_type;
private String[] m_methods;
private Map<String, Integer> m_methodMap;
private boolean m_attached;
public PeripheralWrapper( IPeripheral peripheral, String side )
{
super( m_environment );
m_side = side;
m_peripheral = peripheral;
m_attached = false;
m_type = peripheral.getType();
m_methods = peripheral.getMethodNames();
assert m_type != null;
assert m_methods != null;
m_methodMap = new HashMap<>();
for( int i = 0; i < m_methods.length; i++ )
{
if( m_methods[i] != null )
{
m_methodMap.put( m_methods[i], i );
}
}
}
public IPeripheral getPeripheral()
{
return m_peripheral;
}
public String getType()
{
return m_type;
}
public String[] getMethods()
{
return m_methods;
}
public synchronized boolean isAttached()
{
return m_attached;
}
public synchronized void attach()
{
m_attached = true;
m_peripheral.attach( this );
}
public void detach()
{
// Call detach
m_peripheral.detach( this );
synchronized( this )
{
// Unmount everything the detach function forgot to do
unmountAll();
}
m_attached = false;
}
public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
{
int method = -1;
synchronized( this )
{
if( m_methodMap.containsKey( methodName ) )
{
method = m_methodMap.get( methodName );
}
}
if( method >= 0 )
{
m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
return m_peripheral.callMethod( this, context, method, arguments );
}
else
{
throw new LuaException( "No such method " + methodName );
}
}
// IComputerAccess implementation
@Override
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
return super.mount( desiredLoc, mount, driveName );
}
@Override
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
return super.mountWritable( desiredLoc, mount, driveName );
}
@Override
public synchronized void unmount( String location )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
super.unmount( location );
}
@Override
public int getID()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
return super.getID();
}
@Override
public void queueEvent( @Nonnull final String event, final Object[] arguments )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
super.queueEvent( event, arguments );
}
@Nonnull
@Override
public String getAttachmentName()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
return m_side;
}
@Nonnull
@Override
public Map<String, IPeripheral> getAvailablePeripherals()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
Map<String, IPeripheral> peripherals = new HashMap<>();
for( PeripheralWrapper wrapper : m_peripherals )
{
if( wrapper != null && wrapper.isAttached() )
{
peripherals.put( wrapper.getAttachmentName(), wrapper.getPeripheral() );
}
}
return Collections.unmodifiableMap( peripherals );
}
@Nullable
@Override
public IPeripheral getAvailablePeripheral( @Nonnull String name )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
for( PeripheralWrapper wrapper : m_peripherals )
{
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
{
return wrapper.getPeripheral();
}
}
return null;
}
}
private final IAPIEnvironment m_environment;
private final PeripheralWrapper[] m_peripherals;
private boolean m_running;
public PeripheralAPI( IAPIEnvironment environment )
{
m_environment = environment;
m_environment.setPeripheralChangeListener( this );
m_peripherals = new PeripheralWrapper[6];
for( int i = 0; i < 6; i++ )
{
m_peripherals[i] = null;
}
m_running = false;
}
// IPeripheralChangeListener
@Override
public void onPeripheralChanged( ComputerSide side, IPeripheral newPeripheral )
{
synchronized( m_peripherals )
{
int index = side.ordinal();
if( m_peripherals[index] != null )
{
// Queue a detachment
final PeripheralWrapper wrapper = m_peripherals[index];
if( wrapper.isAttached() ) wrapper.detach();
// Queue a detachment event
m_environment.queueEvent( "peripheral_detach", new Object[] { side.getName() } );
}
// Assign the new peripheral
m_peripherals[index] = newPeripheral == null ? null
: new PeripheralWrapper( newPeripheral, side.getName() );
if( m_peripherals[index] != null )
{
// Queue an attachment
final PeripheralWrapper wrapper = m_peripherals[index];
if( m_running && !wrapper.isAttached() ) wrapper.attach();
// Queue an attachment event
m_environment.queueEvent( "peripheral", new Object[] { side.getName() } );
}
}
}
// ILuaAPI implementation
@Override
public String[] getNames()
{
return new String[] {
"peripheral"
};
}
@Override
public void startup()
{
synchronized( m_peripherals )
{
m_running = true;
for( int i = 0; i < 6; i++ )
{
PeripheralWrapper wrapper = m_peripherals[i];
if( wrapper != null && !wrapper.isAttached() ) wrapper.attach();
}
}
}
@Override
public void shutdown()
{
synchronized( m_peripherals )
{
m_running = false;
for( int i = 0; i < 6; i++ )
{
PeripheralWrapper wrapper = m_peripherals[i];
if( wrapper != null && wrapper.isAttached() )
{
wrapper.detach();
}
}
}
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"isPresent",
"getType",
"getMethods",
"call"
};
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
{
switch( method )
{
case 0:
{
// isPresent
boolean present = false;
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
{
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null ) present = true;
}
}
return new Object[] { present };
}
case 1:
{
// getType
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
{
String type = null;
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null ) return new Object[] { p.getType() };
}
}
return null;
}
case 2:
{
// getMethods
String[] methods = null;
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
{
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null )
{
methods = p.getMethods();
}
}
}
if( methods != null )
{
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < methods.length; i++ )
{
table.put( i + 1, methods[i] );
}
return new Object[] { table };
}
return null;
}
case 3:
{
// call
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
String methodName = getString( args, 1 );
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
if( side != null )
{
PeripheralWrapper p;
synchronized( m_peripherals )
{
p = m_peripherals[side.ordinal()];
}
if( p != null )
{
return p.call( context, methodName, methodArgs );
}
}
throw new LuaException( "No peripheral attached" );
}
default:
return null;
}
}
}

View File

@ -0,0 +1,136 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.ComputerSide;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
public class RedstoneAPI implements ILuaAPI
{
private IAPIEnvironment m_environment;
public RedstoneAPI( IAPIEnvironment environment )
{
m_environment = environment;
}
@Override
public String[] getNames()
{
return new String[] {
"rs", "redstone"
};
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"getSides",
"setOutput",
"getOutput",
"getInput",
"setBundledOutput",
"getBundledOutput",
"getBundledInput",
"testBundledInput",
"setAnalogOutput",
"setAnalogueOutput",
"getAnalogOutput",
"getAnalogueOutput",
"getAnalogInput",
"getAnalogueInput",
};
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
{
// getSides
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < ComputerSide.NAMES.length; i++ )
{
table.put( i + 1, ComputerSide.NAMES[i] );
}
return new Object[] { table };
}
case 1:
{
// setOutput
ComputerSide side = parseSide( args );
boolean output = getBoolean( args, 1 );
m_environment.setOutput( side, output ? 15 : 0 );
return null;
}
case 2: // getOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) > 0 };
case 3: // getInput
return new Object[] { m_environment.getInput( parseSide( args ) ) > 0 };
case 4:
{
// setBundledOutput
ComputerSide side = parseSide( args );
int output = getInt( args, 1 );
m_environment.setBundledOutput( side, output );
return null;
}
case 5: // getBundledOutput
return new Object[] { m_environment.getBundledOutput( parseSide( args ) ) };
case 6: // getBundledInput
return new Object[] { m_environment.getBundledInput( parseSide( args ) ) };
case 7:
{
// testBundledInput
ComputerSide side = parseSide( args );
int mask = getInt( args, 1 );
int input = m_environment.getBundledInput( side );
return new Object[] { (input & mask) == mask };
}
case 8:
case 9:
{
// setAnalogOutput/setAnalogueOutput
ComputerSide side = parseSide( args );
int output = getInt( args, 1 );
if( output < 0 || output > 15 )
{
throw new LuaException( "Expected number in range 0-15" );
}
m_environment.setOutput( side, output );
return null;
}
case 10:
case 11: // getAnalogOutput/getAnalogueOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) };
case 12:
case 13: // getAnalogInput/getAnalogueInput
return new Object[] { m_environment.getInput( parseSide( args ) ) };
default:
return null;
}
}
private static ComputerSide parseSide( Object[] args ) throws LuaException
{
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side == null ) throw new LuaException( "Invalid side." );
return side;
}
}

View File

@ -0,0 +1,220 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Various helpers for tables
*/
public final class TableHelper
{
private TableHelper()
{
throw new IllegalStateException( "Cannot instantiate singleton " + getClass().getName() );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
{
return badKey( key, expected, ArgumentHelper.getType( actual ) );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nonnull String actual )
{
return new LuaException( "bad field '" + key + "' (" + expected + " expected, got " + actual + ")" );
}
public static double getNumberField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int getIntField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double getRealField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
return checkReal( key, getNumberField( table, key ) );
}
public static boolean getBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
@Nonnull
public static String getStringField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
@Nonnull
public static Map<Object, Object> getTableField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
public static double optNumberField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int optIntField( @Nonnull Map<?, ?> table, @Nonnull String key, int def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double optRealField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
return checkReal( key, optNumberField( table, key, def ) );
}
public static boolean optBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key, boolean def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
public static String optStringField( @Nonnull Map<?, ?> table, @Nonnull String key, String def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
public static Map<Object, Object> optTableField( @Nonnull Map<?, ?> table, @Nonnull String key, Map<Object, Object> def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
private static double checkReal( @Nonnull String key, double value ) throws LuaException
{
if( Double.isNaN( value ) )
{
throw badKey( key, "number", "nan" );
}
else if( value == Double.POSITIVE_INFINITY )
{
throw badKey( key, "number", "inf" );
}
else if( value == Double.NEGATIVE_INFINITY )
{
throw badKey( key, "number", "-inf" );
}
else
{
return value;
}
}
}

View File

@ -0,0 +1,301 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
public class TermAPI implements ILuaAPI
{
private final Terminal m_terminal;
private final IComputerEnvironment m_environment;
public TermAPI( IAPIEnvironment environment )
{
m_terminal = environment.getTerminal();
m_environment = environment.getComputerEnvironment();
}
@Override
public String[] getNames()
{
return new String[] {
"term"
};
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"write",
"scroll",
"setCursorPos",
"setCursorBlink",
"getCursorPos",
"getSize",
"clear",
"clearLine",
"setTextColour",
"setTextColor",
"setBackgroundColour",
"setBackgroundColor",
"isColour",
"isColor",
"getTextColour",
"getTextColor",
"getBackgroundColour",
"getBackgroundColor",
"blit",
"setPaletteColour",
"setPaletteColor",
"getPaletteColour",
"getPaletteColor",
"nativePaletteColour",
"nativePaletteColor",
"getCursorBlink",
};
}
public static int parseColour( Object[] args ) throws LuaException
{
int colour = getInt( args, 0 );
if( colour <= 0 )
{
throw new LuaException( "Colour out of range" );
}
colour = getHighestBit( colour ) - 1;
if( colour < 0 || colour > 15 )
{
throw new LuaException( "Colour out of range" );
}
return colour;
}
public static Object[] encodeColour( int colour ) throws LuaException
{
return new Object[] {
1 << colour
};
}
public static void setColour( Terminal terminal, int colour, double r, double g, double b )
{
if( terminal.getPalette() != null )
{
terminal.getPalette().setColour( colour, r, g, b );
terminal.setChanged();
}
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
{
// write
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
synchronized( m_terminal )
{
m_terminal.write( text );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
}
return null;
}
case 1:
{
// scroll
int y = getInt( args, 0 );
synchronized( m_terminal )
{
m_terminal.scroll( y );
}
return null;
}
case 2:
{
// setCursorPos
int x = getInt( args, 0 ) - 1;
int y = getInt( args, 1 ) - 1;
synchronized( m_terminal )
{
m_terminal.setCursorPos( x, y );
}
return null;
}
case 3:
{
// setCursorBlink
boolean b = getBoolean( args, 0 );
synchronized( m_terminal )
{
m_terminal.setCursorBlink( b );
}
return null;
}
case 4:
{
// getCursorPos
int x, y;
synchronized( m_terminal )
{
x = m_terminal.getCursorX();
y = m_terminal.getCursorY();
}
return new Object[] { x + 1, y + 1 };
}
case 5:
{
// getSize
int width, height;
synchronized( m_terminal )
{
width = m_terminal.getWidth();
height = m_terminal.getHeight();
}
return new Object[] { width, height };
}
case 6: // clear
synchronized( m_terminal )
{
m_terminal.clear();
}
return null;
case 7: // clearLine
synchronized( m_terminal )
{
m_terminal.clearLine();
}
return null;
case 8:
case 9:
{
// setTextColour/setTextColor
int colour = parseColour( args );
synchronized( m_terminal )
{
m_terminal.setTextColour( colour );
}
return null;
}
case 10:
case 11:
{
// setBackgroundColour/setBackgroundColor
int colour = parseColour( args );
synchronized( m_terminal )
{
m_terminal.setBackgroundColour( colour );
}
return null;
}
case 12:
case 13: // isColour/isColor
return new Object[] { m_environment.isColour() };
case 14:
case 15: // getTextColour/getTextColor
return encodeColour( m_terminal.getTextColour() );
case 16:
case 17: // getBackgroundColour/getBackgroundColor
return encodeColour( m_terminal.getBackgroundColour() );
case 18:
{
// blit
String text = getString( args, 0 );
String textColour = getString( args, 1 );
String backgroundColour = getString( args, 2 );
if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
{
throw new LuaException( "Arguments must be the same length" );
}
synchronized( m_terminal )
{
m_terminal.blit( text, textColour, backgroundColour );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
}
return null;
}
case 19:
case 20:
{
// setPaletteColour/setPaletteColor
int colour = 15 - parseColour( args );
if( args.length == 2 )
{
int hex = getInt( args, 1 );
double[] rgb = Palette.decodeRGB8( hex );
setColour( m_terminal, colour, rgb[0], rgb[1], rgb[2] );
}
else
{
double r = getReal( args, 1 );
double g = getReal( args, 2 );
double b = getReal( args, 3 );
setColour( m_terminal, colour, r, g, b );
}
return null;
}
case 21:
case 22:
{
// getPaletteColour/getPaletteColor
int colour = 15 - parseColour( args );
synchronized( m_terminal )
{
if( m_terminal.getPalette() != null )
{
return ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) );
}
}
return null;
}
case 23:
case 24:
{
// nativePaletteColour/nativePaletteColor
int colour = 15 - parseColour( args );
Colour c = Colour.fromInt( colour );
float[] rgb = c.getRGB();
Object[] rgbObj = new Object[rgb.length];
for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
return rgbObj;
}
case 25:
// getCursorBlink
return new Object[] { m_terminal.getCursorBlink() };
default:
return null;
}
}
private static int getHighestBit( int group )
{
int bit = 0;
while( group > 0 )
{
group >>= 1;
bit++;
}
return bit;
}
}

View File

@ -0,0 +1,95 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import java.util.Objects;
/**
* A seekable, readable byte channel which is backed by a simple byte array.
*/
public class ArrayByteChannel implements SeekableByteChannel
{
private boolean closed = false;
private int position = 0;
private final byte[] backing;
public ArrayByteChannel( byte[] backing )
{
this.backing = backing;
}
@Override
public int read( ByteBuffer destination ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
Objects.requireNonNull( destination, "destination" );
if( position >= backing.length ) return -1;
int remaining = Math.min( backing.length - position, destination.remaining() );
destination.put( backing, position, remaining );
position += remaining;
return remaining;
}
@Override
public int write( ByteBuffer src ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();
}
@Override
public long position() throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
return position;
}
@Override
public SeekableByteChannel position( long newPosition ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
if( newPosition < 0 || newPosition > Integer.MAX_VALUE )
{
throw new IllegalArgumentException( "Position out of bounds" );
}
position = (int) newPosition;
return this;
}
@Override
public long size() throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
return backing.length;
}
@Override
public SeekableByteChannel truncate( long size ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();
}
@Override
public boolean isOpen()
{
return !closed;
}
@Override
public void close()
{
closed = true;
}
}

View File

@ -0,0 +1,225 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import com.google.common.collect.ObjectArrays;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
public class BinaryReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private static final String[] METHOD_NAMES = new String[] { "read", "readAll", "readLine", "close" };
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
private final ReadableByteChannel m_reader;
private final SeekableByteChannel m_seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
public BinaryReadableHandle( ReadableByteChannel channel, Closeable closeable )
{
super( closeable );
m_reader = channel;
m_seekable = asSeekable( channel );
}
public BinaryReadableHandle( ReadableByteChannel channel )
{
this( channel, channel );
}
@Nonnull
@Override
public String[] getMethodNames()
{
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0: // read
checkOpen();
try
{
if( args.length > 0 && args[0] != null )
{
int count = getInt( args, 0 );
if( count < 0 )
{
throw new LuaException( "Cannot read a negative number of bytes" );
}
else if( count == 0 && m_seekable != null )
{
return m_seekable.position() >= m_seekable.size() ? null : new Object[] { "" };
}
if( count <= BUFFER_SIZE )
{
ByteBuffer buffer = ByteBuffer.allocate( count );
int read = m_reader.read( buffer );
if( read < 0 ) return null;
return new Object[] { read < count ? Arrays.copyOf( buffer.array(), read ) : buffer.array() };
}
else
{
// Read the initial set of characters, failing if none are read.
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
int read = m_reader.read( buffer );
if( read < 0 ) return null;
// If we failed to read "enough" here, let's just abort
if( read >= count || read < BUFFER_SIZE )
{
return new Object[] { Arrays.copyOf( buffer.array(), read ) };
}
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
// than doubling up the buffer each time.
int totalRead = read;
List<ByteBuffer> parts = new ArrayList<>( 4 );
parts.add( buffer );
while( read >= BUFFER_SIZE && totalRead < count )
{
buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
read = m_reader.read( buffer );
if( read < 0 ) break;
totalRead += read;
parts.add( buffer );
}
// Now just copy all the bytes across!
byte[] bytes = new byte[totalRead];
int pos = 0;
for( ByteBuffer part : parts )
{
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
pos += part.position();
}
return new Object[] { bytes };
}
}
else
{
((Buffer) single).clear();
int b = m_reader.read( single );
return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
}
}
catch( IOException e )
{
return null;
}
case 1: // readAll
checkOpen();
try
{
int expected = 32;
if( m_seekable != null )
{
expected = Math.max( expected, (int) (m_seekable.size() - m_seekable.position()) );
}
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
ByteBuffer buf = ByteBuffer.allocate( 8192 );
boolean readAnything = false;
while( true )
{
((Buffer) buf).clear();
int r = m_reader.read( buf );
if( r == -1 ) break;
readAnything = true;
stream.write( buf.array(), 0, r );
}
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
catch( IOException e )
{
return null;
}
case 2: // readLine
{
checkOpen();
boolean withTrailing = optBoolean( args, 0, false );
try
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean readAnything = false, readRc = false;
while( true )
{
((Buffer) single).clear();
int read = m_reader.read( single );
if( read <= 0 )
{
// Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
// back.
if( readRc ) stream.write( '\r' );
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
readAnything = true;
byte chr = single.get( 0 );
if( chr == '\n' )
{
if( withTrailing )
{
if( readRc ) stream.write( '\r' );
stream.write( chr );
}
return new Object[] { stream.toByteArray() };
}
else
{
// We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
// Note, this behaviour is non-standard compliant (strictly speaking we should have no
// special logic for \r), but we preserve compatibility with EncodedReadableHandle and
// previous behaviour of the io library.
if( readRc ) stream.write( '\r' );
readRc = chr == '\r';
if( !readRc ) stream.write( chr );
}
}
}
catch( IOException e )
{
return null;
}
}
case 3: // close
close();
return null;
case 4: // seek
checkOpen();
return handleSeek( m_seekable, args );
default:
return null;
}
}
}

View File

@ -0,0 +1,108 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import com.google.common.collect.ObjectArrays;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ArgumentHelper;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
public class BinaryWritableHandle extends HandleGeneric
{
private static final String[] METHOD_NAMES = new String[] { "write", "flush", "close" };
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
private final WritableByteChannel m_writer;
private final SeekableByteChannel m_seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
{
super( closeable );
m_writer = channel;
m_seekable = asSeekable( channel );
}
public BinaryWritableHandle( WritableByteChannel channel )
{
this( channel, channel );
}
@Nonnull
@Override
public String[] getMethodNames()
{
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0: // write
checkOpen();
try
{
if( args.length > 0 && args[0] instanceof Number )
{
int number = ((Number) args[0]).intValue();
((Buffer) single).clear();
single.put( (byte) number );
((Buffer) single).flip();
m_writer.write( single );
}
else if( args.length > 0 && args[0] instanceof String )
{
String value = (String) args[0];
m_writer.write( ByteBuffer.wrap( StringUtil.encodeString( value ) ) );
}
else
{
throw ArgumentHelper.badArgument( 0, "string or number", args.length > 0 ? args[0] : null );
}
return null;
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
case 1: // flush
checkOpen();
try
{
// Technically this is not needed
if( m_writer instanceof FileChannel ) ((FileChannel) m_writer).force( false );
return null;
}
catch( IOException e )
{
return null;
}
case 2: // close
close();
return null;
case 3: // seek
checkOpen();
return handleSeek( m_seekable, args );
default:
return null;
}
}
}

Some files were not shown because too many files have changed in this diff Show More